From b6959b5ec4317bdf7b71b3aff7fd3476f1afc076 Mon Sep 17 00:00:00 2001 From: suyuan168 <175338101@qq.com> Date: Fri, 29 Apr 2022 23:15:16 +0800 Subject: [PATCH] add ipq60xx --- root/include/kernel-version.mk | 4 +- root/include/target.mk | 8 + .../package/boot/uboot-envtools/files/ipq60xx | 39 + root/package/kernel/linux/modules/usb.mk | 1717 ++ root/package/kernel/mac80211.ipq60xx.tar.gz | Bin 0 -> 319565 bytes root/package/kernel/mac80211.orig.tar.gz | Bin 0 -> 418659 bytes .../package/link4all/ath11k-firmware/Makefile | 66 + .../ath11k-firmware/files/board-2.bin.IPQ6018 | Bin 0 -> 787208 bytes root/package/link4all/ch342/Makefile | 45 + root/package/link4all/ch342/src/Makefile | 3 + root/package/link4all/ch342/src/ch343.c | 1901 ++ root/package/link4all/ch342/src/ch343.h | 230 + root/package/link4all/ch9344/Makefile | 45 + root/package/link4all/ch9344/src/Makefile | 3 + root/package/link4all/ch9344/src/ch9344.c | 2248 ++ root/package/link4all/ch9344/src/ch9344.h | 199 + root/package/link4all/gobinet/Makefile | 54 + .../Quectel_Linux_GobiNet_SR01A02V16.zip | Bin 0 -> 1496246 bytes root/package/link4all/gobinet/gobinet.tar.gz | Bin 0 -> 1116160 bytes .../gobinet/patches/001-atomic_read.patch | 14 + .../link4all/gobinet/src.ec25/GobiUSBNet.c | 1443 + .../link4all/gobinet/src.ec25/Makefile | 30 + root/package/link4all/gobinet/src.ec25/QMI.c | 1386 + root/package/link4all/gobinet/src.ec25/QMI.h | 328 + .../link4all/gobinet/src.ec25/QMIDevice.c | 4088 +++ .../link4all/gobinet/src.ec25/QMIDevice.h | 345 + .../link4all/gobinet/src.ec25/Readme.txt | 78 + .../link4all/gobinet/src.ec25/Structs.h | 439 + .../package/link4all/gobinet/src/GobiUSBNet.c | 1577 + root/package/link4all/gobinet/src/Makefile | 30 + root/package/link4all/gobinet/src/QMI.c | 1343 + root/package/link4all/gobinet/src/QMI.h | 314 + root/package/link4all/gobinet/src/QMIDevice.c | 4046 +++ root/package/link4all/gobinet/src/QMIDevice.h | 345 + root/package/link4all/gobinet/src/Structs.h | 423 + root/package/link4all/gobinet_srm815/Makefile | 54 + .../patches00/gobinet-4.19.patch | 14 + .../gobinet_srm815/src.old/GobiUSBNet.c | 2171 ++ .../link4all/gobinet_srm815/src.old/Makefile | 30 + .../link4all/gobinet_srm815/src.old/QMI.c | 1446 + .../link4all/gobinet_srm815/src.old/QMI.h | 337 + .../gobinet_srm815/src.old/QMIDevice.c | 3906 +++ .../gobinet_srm815/src.old/QMIDevice.h | 368 + .../gobinet_srm815/src.old/Readme.txt | 15 + .../link4all/gobinet_srm815/src.old/Structs.h | 455 + .../link4all/gobinet_srm815/src/GobiUSBNet.c | 2171 ++ .../package/link4all/gobinet_srm815/src/QMI.c | 1446 + .../package/link4all/gobinet_srm815/src/QMI.h | 337 + .../link4all/gobinet_srm815/src/QMIDevice.c | 3906 +++ .../link4all/gobinet_srm815/src/QMIDevice.h | 368 + .../link4all/gobinet_srm815/src/Readme.txt | 15 + .../link4all/gobinet_srm815/src/Structs.h | 455 + root/package/link4all/quectel-CM/Makefile | 84 + .../link4all/quectel-CM/quectel-CM.tar.gz | Bin 0 -> 160619 bytes .../quectel1.6.0.15_src/GobiNetCM.c | 242 + .../quectel-CM/quectel1.6.0.15_src/MPQCTL.h | 377 + .../quectel-CM/quectel1.6.0.15_src/MPQMI.h | 302 + .../quectel-CM/quectel1.6.0.15_src/MPQMUX.c | 446 + .../quectel-CM/quectel1.6.0.15_src/MPQMUX.h | 3485 +++ .../quectel-CM/quectel1.6.0.15_src/Makefile | 24 + .../quectel-CM/quectel1.6.0.15_src/NOTICE | 7 + .../quectel1.6.0.15_src/QMIThread.c | 2204 ++ .../quectel1.6.0.15_src/QMIThread.h | 310 + .../quectel1.6.0.15_src/QmiWwanCM.c | 389 + .../quectel1.6.0.15_src/ReleaseNote.txt | 69 + .../quectel1.6.0.15_src/default.script | 63 + .../quectel-CM/quectel1.6.0.15_src/device.c | 500 + .../quectel1.6.0.15_src/ethtool-copy.h | 1100 + .../quectel1.6.0.15_src/libmnl/README | 28 + .../quectel1.6.0.15_src/libmnl/attr.c | 722 + .../quectel1.6.0.15_src/libmnl/callback.c | 167 + .../quectel1.6.0.15_src/libmnl/dhcp/dhcp.h | 5 + .../libmnl/dhcp/dhcpclient.c | 515 + .../quectel1.6.0.15_src/libmnl/dhcp/dhcpmsg.c | 100 + .../quectel1.6.0.15_src/libmnl/dhcp/dhcpmsg.h | 106 + .../quectel1.6.0.15_src/libmnl/dhcp/packet.c | 247 + .../quectel1.6.0.15_src/libmnl/dhcp/packet.h | 25 + .../quectel1.6.0.15_src/libmnl/ifutils.c | 742 + .../quectel1.6.0.15_src/libmnl/ifutils.h | 53 + .../quectel1.6.0.15_src/libmnl/libmnl.h | 202 + .../quectel1.6.0.15_src/libmnl/nlmsg.c | 556 + .../quectel1.6.0.15_src/libmnl/socket.c | 351 + .../quectel-CM/quectel1.6.0.15_src/main.c | 947 + .../quectel-CM/quectel1.6.0.15_src/mbim-cm.c | 2268 ++ .../quectel1.6.0.15_src/qmap_bridge_mode.c | 409 + .../quectel1.6.0.15_src/quectel-qmi-proxy.c | 1546 + .../quectel-CM/quectel1.6.0.15_src/udhcpc.c | 675 + .../quectel1.6.0.15_src/udhcpc_netlink.c | 179 + .../quectel-CM/quectel1.6.0.15_src/util.c | 301 + .../quectel-CM/quectel1.6.0.15_src/util.h | 52 + .../link4all/quectel-CM/simcom-cm/GobiNetCM.c | 217 + .../link4all/quectel-CM/simcom-cm/MPQCTL.h | 376 + .../link4all/quectel-CM/simcom-cm/MPQMI.h | 221 + .../link4all/quectel-CM/simcom-cm/MPQMUX.c | 424 + .../link4all/quectel-CM/simcom-cm/MPQMUX.h | 3300 +++ .../link4all/quectel-CM/simcom-cm/Makefile | 24 + .../link4all/quectel-CM/simcom-cm/QMIThread.c | 2020 ++ .../link4all/quectel-CM/simcom-cm/QMIThread.h | 174 + .../link4all/quectel-CM/simcom-cm/QmiWwanCM.c | 281 + .../quectel-CM/simcom-cm/default.script | 63 + .../quectel-CM/simcom-cm/dhcpclient.c | 90 + .../link4all/quectel-CM/simcom-cm/main.c | 965 + .../link4all/quectel-CM/simcom-cm/udhcpc.c | 222 + .../link4all/quectel-CM/simcom-cm/util.c | 158 + .../link4all/quectel-CM/simcom-cm/util.h | 54 + .../link4all/quectel-CM/src.all/GobiNetCM.c | 213 + .../link4all/quectel-CM/src.all/MPQCTL.h | 376 + .../link4all/quectel-CM/src.all/MPQMI.h | 220 + .../link4all/quectel-CM/src.all/MPQMUX.c | 422 + .../link4all/quectel-CM/src.all/MPQMUX.h | 3244 +++ .../link4all/quectel-CM/src.all/Makefile | 6 + .../link4all/quectel-CM/src.all/QMIThread.c | 1739 ++ .../link4all/quectel-CM/src.all/QMIThread.h | 164 + .../link4all/quectel-CM/src.all/QmiWwanCM.c | 274 + .../link4all/quectel-CM/src.all/dhcpclient.c | 90 + .../link4all/quectel-CM/src.all/main.c | 681 + .../link4all/quectel-CM/src.all/udhcpc.c | 372 + .../link4all/quectel-CM/src.all/util.c | 158 + .../link4all/quectel-CM/src.all/util.h | 54 + .../quectel-CM/src/20201022_104550_0000.qmdl2 | Bin 0 -> 175 bytes .../link4all/quectel-CM/src/Android.mk | 8 + .../link4all/quectel-CM/src/GobiNetCM.c | 243 + root/package/link4all/quectel-CM/src/MPQCTL.h | 377 + root/package/link4all/quectel-CM/src/MPQMI.h | 302 + root/package/link4all/quectel-CM/src/MPQMUX.c | 446 + root/package/link4all/quectel-CM/src/MPQMUX.h | 3485 +++ root/package/link4all/quectel-CM/src/Makefile | 37 + root/package/link4all/quectel-CM/src/NOTICE | 7 + .../link4all/quectel-CM/src/QMIThread.c | 2196 ++ .../link4all/quectel-CM/src/QMIThread.h | 331 + .../link4all/quectel-CM/src/QmiWwanCM.c | 430 + .../link4all/quectel-CM/src/ReleaseNote.txt | 69 + .../link4all/quectel-CM/src/default.script | 63 + .../link4all/quectel-CM/src/default.script_ip | 61 + .../link4all/quectel-CM/src/device.bak.c | 558 + root/package/link4all/quectel-CM/src/device.c | 558 + .../link4all/quectel-CM/src/device.new.c | 569 + .../link4all/quectel-CM/src/dhcpclient.c | 90 + .../link4all/quectel-CM/src/ethtool-copy.h | 1100 + .../link4all/quectel-CM/src/libmnl/README | 28 + .../link4all/quectel-CM/src/libmnl/attr.c | 722 + .../link4all/quectel-CM/src/libmnl/callback.c | 171 + .../quectel-CM/src/libmnl/dhcp/dhcp.h | 5 + .../quectel-CM/src/libmnl/dhcp/dhcpclient.c | 515 + .../quectel-CM/src/libmnl/dhcp/dhcpmsg.c | 100 + .../quectel-CM/src/libmnl/dhcp/dhcpmsg.h | 106 + .../quectel-CM/src/libmnl/dhcp/packet.c | 247 + .../quectel-CM/src/libmnl/dhcp/packet.h | 25 + .../link4all/quectel-CM/src/libmnl/ifutils.c | 744 + .../link4all/quectel-CM/src/libmnl/ifutils.h | 53 + .../link4all/quectel-CM/src/libmnl/libmnl.h | 202 + .../link4all/quectel-CM/src/libmnl/nlmsg.c | 556 + .../link4all/quectel-CM/src/libmnl/socket.c | 351 + root/package/link4all/quectel-CM/src/main.c | 953 + .../package/link4all/quectel-CM/src/mbim-cm.c | 2351 ++ .../link4all/quectel-CM/src/ql-ifconfig.c | 0 .../quectel-CM/src/qmap_bridge_mode.c | 409 + .../quectel-CM/src/quectel-mbim-proxy.c | 437 + .../quectel-CM/src/quectel-qmi-proxy.c | 1602 + .../link4all/quectel-CM/src/udhcpc.bak.c | 716 + root/package/link4all/quectel-CM/src/udhcpc.c | 842 + .../link4all/quectel-CM/src/udhcpc.orig.c | 688 + .../link4all/quectel-CM/src/udhcpc_netlink.c | 179 + .../link4all/quectel-CM/src/udhcpc_script.c | 132 + root/package/link4all/quectel-CM/src/util.c | 362 + root/package/link4all/quectel-CM/src/util.h | 52 + .../quectel-CM/src_fangge/GobiNetCM.c | 212 + .../link4all/quectel-CM/src_fangge/MPQCTL.h | 376 + .../link4all/quectel-CM/src_fangge/MPQMI.h | 220 + .../link4all/quectel-CM/src_fangge/MPQMUX.c | 413 + .../link4all/quectel-CM/src_fangge/MPQMUX.h | 1067 + .../link4all/quectel-CM/src_fangge/Makefile | 6 + .../quectel-CM/src_fangge/QMIThread.c | 1705 ++ .../quectel-CM/src_fangge/QMIThread.h | 164 + .../quectel-CM/src_fangge/QmiWwanCM.c | 274 + .../quectel-CM/src_fangge/dhcpclient.c | 90 + .../link4all/quectel-CM/src_fangge/main.c | 825 + .../link4all/quectel-CM/src_fangge/udhcpc.c | 415 + .../link4all/quectel-CM/src_fangge/util.c | 160 + .../link4all/quectel-CM/src_fangge/util.h | 54 + .../quectel-CM/src_quectel/GobiNetCM.c | 213 + .../link4all/quectel-CM/src_quectel/MPQCTL.h | 376 + .../link4all/quectel-CM/src_quectel/MPQMI.h | 220 + .../link4all/quectel-CM/src_quectel/MPQMUX.c | 422 + .../link4all/quectel-CM/src_quectel/MPQMUX.h | 3244 +++ .../link4all/quectel-CM/src_quectel/Makefile | 6 + .../quectel-CM/src_quectel/QMIThread.c | 1739 ++ .../quectel-CM/src_quectel/QMIThread.h | 164 + .../quectel-CM/src_quectel/QmiWwanCM.c | 274 + .../quectel-CM/src_quectel/dhcpclient.c | 90 + .../link4all/quectel-CM/src_quectel/main.c | 677 + .../link4all/quectel-CM/src_quectel/udhcpc.c | 372 + .../link4all/quectel-CM/src_quectel/util.c | 158 + .../link4all/quectel-CM/src_quectel/util.h | 54 + root/package/link4all/rm500q/Makefile | 55 + .../link4all/rm500q/log/how_to_use_bridge.txt | 68 + .../rm500q/log/how_to_use_bridge_and_QMAP.txt | 234 + root/package/link4all/rm500q/src/Makefile | 1 + root/package/link4all/rm500q/src/rm500q.c | 2434 ++ root/package/link4all/rm500q/src/rmnet_nss.c | 424 + root/package/qca/nss-firmware/Makefile | 73 + root/package/qca/qca-nss-cfi/Makefile | 71 + .../patches/100-remove-noise-logs.patch | 30 + root/package/qca/qca-nss-clients/Makefile | 115 + .../qca/qca-nss-clients/files/qca-nss-ipsec | 92 + .../qca-nss-clients/files/qca-nss-mirred.init | 28 + .../qca-nss-clients/files/qca-nss-ovpn.init | 69 + root/package/qca/qca-nss-crypto/Makefile | 62 + root/package/qca/qca-nss-dp/Makefile | 54 + root/package/qca/qca-nss-drv/Makefile | 109 + .../qca/qca-nss-drv/files/qca-nss-drv.conf | 6 + .../qca/qca-nss-drv/files/qca-nss-drv.debug | 26 + .../qca/qca-nss-drv/files/qca-nss-drv.hotplug | 70 + .../qca/qca-nss-drv/files/qca-nss-drv.init | 50 + .../qca/qca-nss-drv/files/qca-nss-drv.sysctl | 3 + root/package/qca/qca-nss-ecm/Makefile | 90 + .../package/qca/qca-nss-ecm/files/ecm_dump.sh | 95 + .../qca/qca-nss-ecm/files/on-demand-down | 6 + .../qca-nss-ecm/files/qca-nss-ecm.defaults | 28 + .../qca-nss-ecm/files/qca-nss-ecm.firewall | 18 + .../qca/qca-nss-ecm/files/qca-nss-ecm.init | 139 + .../qca/qca-nss-ecm/files/qca-nss-ecm.sysctl | 2 + .../qca/qca-nss-ecm/files/qca-nss-ecm.uci | 2 + .../patches/200-resolve-high-load.patch | 61 + root/package/qca/qca-nss-fw-eip/Makefile | 25 + .../qca/qca-nss-fw-eip/files/ipq60xx/ifpp.bin | Bin 0 -> 12272 bytes .../qca/qca-nss-fw-eip/files/ipq60xx/ipue.bin | Bin 0 -> 6116 bytes .../qca/qca-nss-fw-eip/files/ipq60xx/ofpp.bin | Bin 0 -> 6128 bytes .../qca/qca-nss-fw-eip/files/ipq60xx/opue.bin | Bin 0 -> 4068 bytes .../qca/qca-nss-fw-eip/files/ipq807x/ifpp.bin | Bin 0 -> 12272 bytes .../qca/qca-nss-fw-eip/files/ipq807x/ipue.bin | Bin 0 -> 6116 bytes .../qca/qca-nss-fw-eip/files/ipq807x/ofpp.bin | Bin 0 -> 6128 bytes .../qca/qca-nss-fw-eip/files/ipq807x/opue.bin | Bin 0 -> 4068 bytes root/package/qca/qca-ssdk-shell/Makefile | 48 + root/package/qca/qca-ssdk/Makefile | 98 + root/package/qca/qca-ssdk/files/qca-ssdk | 206 + root/target/linux/ipq60xx/Makefile | 22 + .../ipq60xx/base-files/etc/board.d/01_leds | 23 + .../ipq60xx/base-files/etc/board.d/02_network | 52 + .../etc/hotplug.d/firmware/11-ath11k-caldata | 20 + .../linux/ipq60xx/base-files/etc/inittab | 5 + .../base-files/lib/upgrade/platform.sh | 18 + root/target/linux/ipq60xx/config-5.4 | 743 + root/target/linux/ipq60xx/config-5.4.bak | 655 + root/target/linux/ipq60xx/config-5.4.ok | 674 + .../arch/arm64/boot/dts/qcom/ipq6018-q60.dts | 380 + .../arch/arm64/boot/dts/qcom/ipq6018-x5.dts | 614 + .../arch/arm64/boot/dts/qcom/ipq6018-x511.dts | 469 + .../arch/arm64/boot/dts/qcom/ipq6018-x8.dts | 631 + root/target/linux/ipq60xx/generic/target.mk | 1 + root/target/linux/ipq60xx/image/Makefile | 83 + root/target/linux/ipq60xx/modules.mk | 19 + ...lags-if-controller-driver-configures.patch | 83 + ...ore-Add-support-for-forced-PM-resume.patch | 105 + .../patches-5.4/003-axp2402support.patch | 1657 ++ .../ipq60xx/patches-5.4/004-flashid.patch | 37 + .../patches-5.4/005-cursor_blink.patch | 73 + .../ipq60xx/patches-5.4/006-modems.patch | 31 + .../linux/ipq60xx/patches-5.4/691-mptcp.patch | 24123 ++++++++++++++++ .../patches-5.4/692-tcp_nanqinlang.patch | 1037 + .../ipq60xx/patches-5.4/693-tcp_bbr2.patch | 3368 +++ .../901-arm64-boot-add-dts-files.patch | 13 + .../patches-5.4/902-add-gpio-export.patch | 162 + ...9-display-model-name-in-proc-cpuinfo.patch | 11 + root/target/linux/ipq60xx/profiles/default.mk | 9 + 265 files changed, 154161 insertions(+), 2 deletions(-) create mode 100644 root/package/boot/uboot-envtools/files/ipq60xx create mode 100644 root/package/kernel/linux/modules/usb.mk create mode 100644 root/package/kernel/mac80211.ipq60xx.tar.gz create mode 100644 root/package/kernel/mac80211.orig.tar.gz create mode 100644 root/package/link4all/ath11k-firmware/Makefile create mode 100644 root/package/link4all/ath11k-firmware/files/board-2.bin.IPQ6018 create mode 100644 root/package/link4all/ch342/Makefile create mode 100644 root/package/link4all/ch342/src/Makefile create mode 100644 root/package/link4all/ch342/src/ch343.c create mode 100644 root/package/link4all/ch342/src/ch343.h create mode 100644 root/package/link4all/ch9344/Makefile create mode 100644 root/package/link4all/ch9344/src/Makefile create mode 100644 root/package/link4all/ch9344/src/ch9344.c create mode 100644 root/package/link4all/ch9344/src/ch9344.h create mode 100755 root/package/link4all/gobinet/Makefile create mode 100644 root/package/link4all/gobinet/Quectel_Linux_GobiNet_SR01A02V16.zip create mode 100755 root/package/link4all/gobinet/gobinet.tar.gz create mode 100644 root/package/link4all/gobinet/patches/001-atomic_read.patch create mode 100644 root/package/link4all/gobinet/src.ec25/GobiUSBNet.c create mode 100644 root/package/link4all/gobinet/src.ec25/Makefile create mode 100644 root/package/link4all/gobinet/src.ec25/QMI.c create mode 100644 root/package/link4all/gobinet/src.ec25/QMI.h create mode 100644 root/package/link4all/gobinet/src.ec25/QMIDevice.c create mode 100644 root/package/link4all/gobinet/src.ec25/QMIDevice.h create mode 100644 root/package/link4all/gobinet/src.ec25/Readme.txt create mode 100644 root/package/link4all/gobinet/src.ec25/Structs.h create mode 100755 root/package/link4all/gobinet/src/GobiUSBNet.c create mode 100755 root/package/link4all/gobinet/src/Makefile create mode 100755 root/package/link4all/gobinet/src/QMI.c create mode 100755 root/package/link4all/gobinet/src/QMI.h create mode 100755 root/package/link4all/gobinet/src/QMIDevice.c create mode 100755 root/package/link4all/gobinet/src/QMIDevice.h create mode 100755 root/package/link4all/gobinet/src/Structs.h create mode 100755 root/package/link4all/gobinet_srm815/Makefile create mode 100644 root/package/link4all/gobinet_srm815/patches00/gobinet-4.19.patch create mode 100755 root/package/link4all/gobinet_srm815/src.old/GobiUSBNet.c create mode 100755 root/package/link4all/gobinet_srm815/src.old/Makefile create mode 100755 root/package/link4all/gobinet_srm815/src.old/QMI.c create mode 100755 root/package/link4all/gobinet_srm815/src.old/QMI.h create mode 100755 root/package/link4all/gobinet_srm815/src.old/QMIDevice.c create mode 100755 root/package/link4all/gobinet_srm815/src.old/QMIDevice.h create mode 100644 root/package/link4all/gobinet_srm815/src.old/Readme.txt create mode 100755 root/package/link4all/gobinet_srm815/src.old/Structs.h create mode 100755 root/package/link4all/gobinet_srm815/src/GobiUSBNet.c create mode 100755 root/package/link4all/gobinet_srm815/src/QMI.c create mode 100755 root/package/link4all/gobinet_srm815/src/QMI.h create mode 100755 root/package/link4all/gobinet_srm815/src/QMIDevice.c create mode 100755 root/package/link4all/gobinet_srm815/src/QMIDevice.h create mode 100644 root/package/link4all/gobinet_srm815/src/Readme.txt create mode 100755 root/package/link4all/gobinet_srm815/src/Structs.h create mode 100755 root/package/link4all/quectel-CM/Makefile create mode 100644 root/package/link4all/quectel-CM/quectel-CM.tar.gz create mode 100644 root/package/link4all/quectel-CM/quectel1.6.0.15_src/GobiNetCM.c create mode 100644 root/package/link4all/quectel-CM/quectel1.6.0.15_src/MPQCTL.h create mode 100644 root/package/link4all/quectel-CM/quectel1.6.0.15_src/MPQMI.h create mode 100644 root/package/link4all/quectel-CM/quectel1.6.0.15_src/MPQMUX.c create mode 100644 root/package/link4all/quectel-CM/quectel1.6.0.15_src/MPQMUX.h create mode 100644 root/package/link4all/quectel-CM/quectel1.6.0.15_src/Makefile create mode 100644 root/package/link4all/quectel-CM/quectel1.6.0.15_src/NOTICE create mode 100644 root/package/link4all/quectel-CM/quectel1.6.0.15_src/QMIThread.c create mode 100644 root/package/link4all/quectel-CM/quectel1.6.0.15_src/QMIThread.h create mode 100644 root/package/link4all/quectel-CM/quectel1.6.0.15_src/QmiWwanCM.c create mode 100644 root/package/link4all/quectel-CM/quectel1.6.0.15_src/ReleaseNote.txt create mode 100644 root/package/link4all/quectel-CM/quectel1.6.0.15_src/default.script create mode 100644 root/package/link4all/quectel-CM/quectel1.6.0.15_src/device.c create mode 100644 root/package/link4all/quectel-CM/quectel1.6.0.15_src/ethtool-copy.h create mode 100644 root/package/link4all/quectel-CM/quectel1.6.0.15_src/libmnl/README create mode 100644 root/package/link4all/quectel-CM/quectel1.6.0.15_src/libmnl/attr.c create mode 100644 root/package/link4all/quectel-CM/quectel1.6.0.15_src/libmnl/callback.c create mode 100644 root/package/link4all/quectel-CM/quectel1.6.0.15_src/libmnl/dhcp/dhcp.h create mode 100644 root/package/link4all/quectel-CM/quectel1.6.0.15_src/libmnl/dhcp/dhcpclient.c create mode 100644 root/package/link4all/quectel-CM/quectel1.6.0.15_src/libmnl/dhcp/dhcpmsg.c create mode 100644 root/package/link4all/quectel-CM/quectel1.6.0.15_src/libmnl/dhcp/dhcpmsg.h create mode 100644 root/package/link4all/quectel-CM/quectel1.6.0.15_src/libmnl/dhcp/packet.c create mode 100644 root/package/link4all/quectel-CM/quectel1.6.0.15_src/libmnl/dhcp/packet.h create mode 100644 root/package/link4all/quectel-CM/quectel1.6.0.15_src/libmnl/ifutils.c create mode 100644 root/package/link4all/quectel-CM/quectel1.6.0.15_src/libmnl/ifutils.h create mode 100644 root/package/link4all/quectel-CM/quectel1.6.0.15_src/libmnl/libmnl.h create mode 100644 root/package/link4all/quectel-CM/quectel1.6.0.15_src/libmnl/nlmsg.c create mode 100644 root/package/link4all/quectel-CM/quectel1.6.0.15_src/libmnl/socket.c create mode 100644 root/package/link4all/quectel-CM/quectel1.6.0.15_src/main.c create mode 100644 root/package/link4all/quectel-CM/quectel1.6.0.15_src/mbim-cm.c create mode 100644 root/package/link4all/quectel-CM/quectel1.6.0.15_src/qmap_bridge_mode.c create mode 100644 root/package/link4all/quectel-CM/quectel1.6.0.15_src/quectel-qmi-proxy.c create mode 100644 root/package/link4all/quectel-CM/quectel1.6.0.15_src/udhcpc.c create mode 100644 root/package/link4all/quectel-CM/quectel1.6.0.15_src/udhcpc_netlink.c create mode 100644 root/package/link4all/quectel-CM/quectel1.6.0.15_src/util.c create mode 100644 root/package/link4all/quectel-CM/quectel1.6.0.15_src/util.h create mode 100644 root/package/link4all/quectel-CM/simcom-cm/GobiNetCM.c create mode 100644 root/package/link4all/quectel-CM/simcom-cm/MPQCTL.h create mode 100644 root/package/link4all/quectel-CM/simcom-cm/MPQMI.h create mode 100644 root/package/link4all/quectel-CM/simcom-cm/MPQMUX.c create mode 100644 root/package/link4all/quectel-CM/simcom-cm/MPQMUX.h create mode 100644 root/package/link4all/quectel-CM/simcom-cm/Makefile create mode 100644 root/package/link4all/quectel-CM/simcom-cm/QMIThread.c create mode 100644 root/package/link4all/quectel-CM/simcom-cm/QMIThread.h create mode 100644 root/package/link4all/quectel-CM/simcom-cm/QmiWwanCM.c create mode 100644 root/package/link4all/quectel-CM/simcom-cm/default.script create mode 100644 root/package/link4all/quectel-CM/simcom-cm/dhcpclient.c create mode 100644 root/package/link4all/quectel-CM/simcom-cm/main.c create mode 100644 root/package/link4all/quectel-CM/simcom-cm/udhcpc.c create mode 100644 root/package/link4all/quectel-CM/simcom-cm/util.c create mode 100644 root/package/link4all/quectel-CM/simcom-cm/util.h create mode 100755 root/package/link4all/quectel-CM/src.all/GobiNetCM.c create mode 100755 root/package/link4all/quectel-CM/src.all/MPQCTL.h create mode 100755 root/package/link4all/quectel-CM/src.all/MPQMI.h create mode 100755 root/package/link4all/quectel-CM/src.all/MPQMUX.c create mode 100755 root/package/link4all/quectel-CM/src.all/MPQMUX.h create mode 100755 root/package/link4all/quectel-CM/src.all/Makefile create mode 100755 root/package/link4all/quectel-CM/src.all/QMIThread.c create mode 100755 root/package/link4all/quectel-CM/src.all/QMIThread.h create mode 100755 root/package/link4all/quectel-CM/src.all/QmiWwanCM.c create mode 100755 root/package/link4all/quectel-CM/src.all/dhcpclient.c create mode 100755 root/package/link4all/quectel-CM/src.all/main.c create mode 100755 root/package/link4all/quectel-CM/src.all/udhcpc.c create mode 100755 root/package/link4all/quectel-CM/src.all/util.c create mode 100755 root/package/link4all/quectel-CM/src.all/util.h create mode 100644 root/package/link4all/quectel-CM/src/20201022_104550_0000.qmdl2 create mode 100644 root/package/link4all/quectel-CM/src/Android.mk create mode 100644 root/package/link4all/quectel-CM/src/GobiNetCM.c create mode 100644 root/package/link4all/quectel-CM/src/MPQCTL.h create mode 100644 root/package/link4all/quectel-CM/src/MPQMI.h create mode 100644 root/package/link4all/quectel-CM/src/MPQMUX.c create mode 100644 root/package/link4all/quectel-CM/src/MPQMUX.h create mode 100644 root/package/link4all/quectel-CM/src/Makefile create mode 100644 root/package/link4all/quectel-CM/src/NOTICE create mode 100644 root/package/link4all/quectel-CM/src/QMIThread.c create mode 100644 root/package/link4all/quectel-CM/src/QMIThread.h create mode 100644 root/package/link4all/quectel-CM/src/QmiWwanCM.c create mode 100644 root/package/link4all/quectel-CM/src/ReleaseNote.txt create mode 100644 root/package/link4all/quectel-CM/src/default.script create mode 100644 root/package/link4all/quectel-CM/src/default.script_ip create mode 100644 root/package/link4all/quectel-CM/src/device.bak.c create mode 100644 root/package/link4all/quectel-CM/src/device.c create mode 100644 root/package/link4all/quectel-CM/src/device.new.c create mode 100644 root/package/link4all/quectel-CM/src/dhcpclient.c create mode 100644 root/package/link4all/quectel-CM/src/ethtool-copy.h create mode 100644 root/package/link4all/quectel-CM/src/libmnl/README create mode 100644 root/package/link4all/quectel-CM/src/libmnl/attr.c create mode 100644 root/package/link4all/quectel-CM/src/libmnl/callback.c create mode 100644 root/package/link4all/quectel-CM/src/libmnl/dhcp/dhcp.h create mode 100644 root/package/link4all/quectel-CM/src/libmnl/dhcp/dhcpclient.c create mode 100644 root/package/link4all/quectel-CM/src/libmnl/dhcp/dhcpmsg.c create mode 100644 root/package/link4all/quectel-CM/src/libmnl/dhcp/dhcpmsg.h create mode 100644 root/package/link4all/quectel-CM/src/libmnl/dhcp/packet.c create mode 100644 root/package/link4all/quectel-CM/src/libmnl/dhcp/packet.h create mode 100644 root/package/link4all/quectel-CM/src/libmnl/ifutils.c create mode 100644 root/package/link4all/quectel-CM/src/libmnl/ifutils.h create mode 100644 root/package/link4all/quectel-CM/src/libmnl/libmnl.h create mode 100644 root/package/link4all/quectel-CM/src/libmnl/nlmsg.c create mode 100644 root/package/link4all/quectel-CM/src/libmnl/socket.c create mode 100644 root/package/link4all/quectel-CM/src/main.c create mode 100644 root/package/link4all/quectel-CM/src/mbim-cm.c create mode 100644 root/package/link4all/quectel-CM/src/ql-ifconfig.c create mode 100644 root/package/link4all/quectel-CM/src/qmap_bridge_mode.c create mode 100644 root/package/link4all/quectel-CM/src/quectel-mbim-proxy.c create mode 100644 root/package/link4all/quectel-CM/src/quectel-qmi-proxy.c create mode 100644 root/package/link4all/quectel-CM/src/udhcpc.bak.c create mode 100644 root/package/link4all/quectel-CM/src/udhcpc.c create mode 100644 root/package/link4all/quectel-CM/src/udhcpc.orig.c create mode 100644 root/package/link4all/quectel-CM/src/udhcpc_netlink.c create mode 100644 root/package/link4all/quectel-CM/src/udhcpc_script.c create mode 100644 root/package/link4all/quectel-CM/src/util.c create mode 100644 root/package/link4all/quectel-CM/src/util.h create mode 100755 root/package/link4all/quectel-CM/src_fangge/GobiNetCM.c create mode 100755 root/package/link4all/quectel-CM/src_fangge/MPQCTL.h create mode 100755 root/package/link4all/quectel-CM/src_fangge/MPQMI.h create mode 100755 root/package/link4all/quectel-CM/src_fangge/MPQMUX.c create mode 100755 root/package/link4all/quectel-CM/src_fangge/MPQMUX.h create mode 100755 root/package/link4all/quectel-CM/src_fangge/Makefile create mode 100755 root/package/link4all/quectel-CM/src_fangge/QMIThread.c create mode 100755 root/package/link4all/quectel-CM/src_fangge/QMIThread.h create mode 100755 root/package/link4all/quectel-CM/src_fangge/QmiWwanCM.c create mode 100755 root/package/link4all/quectel-CM/src_fangge/dhcpclient.c create mode 100755 root/package/link4all/quectel-CM/src_fangge/main.c create mode 100755 root/package/link4all/quectel-CM/src_fangge/udhcpc.c create mode 100755 root/package/link4all/quectel-CM/src_fangge/util.c create mode 100755 root/package/link4all/quectel-CM/src_fangge/util.h create mode 100755 root/package/link4all/quectel-CM/src_quectel/GobiNetCM.c create mode 100755 root/package/link4all/quectel-CM/src_quectel/MPQCTL.h create mode 100755 root/package/link4all/quectel-CM/src_quectel/MPQMI.h create mode 100755 root/package/link4all/quectel-CM/src_quectel/MPQMUX.c create mode 100755 root/package/link4all/quectel-CM/src_quectel/MPQMUX.h create mode 100755 root/package/link4all/quectel-CM/src_quectel/Makefile create mode 100755 root/package/link4all/quectel-CM/src_quectel/QMIThread.c create mode 100755 root/package/link4all/quectel-CM/src_quectel/QMIThread.h create mode 100755 root/package/link4all/quectel-CM/src_quectel/QmiWwanCM.c create mode 100755 root/package/link4all/quectel-CM/src_quectel/dhcpclient.c create mode 100755 root/package/link4all/quectel-CM/src_quectel/main.c create mode 100755 root/package/link4all/quectel-CM/src_quectel/udhcpc.c create mode 100755 root/package/link4all/quectel-CM/src_quectel/util.c create mode 100755 root/package/link4all/quectel-CM/src_quectel/util.h create mode 100755 root/package/link4all/rm500q/Makefile create mode 100644 root/package/link4all/rm500q/log/how_to_use_bridge.txt create mode 100644 root/package/link4all/rm500q/log/how_to_use_bridge_and_QMAP.txt create mode 100644 root/package/link4all/rm500q/src/Makefile create mode 100644 root/package/link4all/rm500q/src/rm500q.c create mode 100644 root/package/link4all/rm500q/src/rmnet_nss.c create mode 100644 root/package/qca/nss-firmware/Makefile create mode 100644 root/package/qca/qca-nss-cfi/Makefile create mode 100644 root/package/qca/qca-nss-cfi/patches/100-remove-noise-logs.patch create mode 100644 root/package/qca/qca-nss-clients/Makefile create mode 100644 root/package/qca/qca-nss-clients/files/qca-nss-ipsec create mode 100644 root/package/qca/qca-nss-clients/files/qca-nss-mirred.init create mode 100644 root/package/qca/qca-nss-clients/files/qca-nss-ovpn.init create mode 100644 root/package/qca/qca-nss-crypto/Makefile create mode 100644 root/package/qca/qca-nss-dp/Makefile create mode 100644 root/package/qca/qca-nss-drv/Makefile create mode 100644 root/package/qca/qca-nss-drv/files/qca-nss-drv.conf create mode 100644 root/package/qca/qca-nss-drv/files/qca-nss-drv.debug create mode 100644 root/package/qca/qca-nss-drv/files/qca-nss-drv.hotplug create mode 100644 root/package/qca/qca-nss-drv/files/qca-nss-drv.init create mode 100644 root/package/qca/qca-nss-drv/files/qca-nss-drv.sysctl create mode 100644 root/package/qca/qca-nss-ecm/Makefile create mode 100755 root/package/qca/qca-nss-ecm/files/ecm_dump.sh create mode 100644 root/package/qca/qca-nss-ecm/files/on-demand-down create mode 100644 root/package/qca/qca-nss-ecm/files/qca-nss-ecm.defaults create mode 100644 root/package/qca/qca-nss-ecm/files/qca-nss-ecm.firewall create mode 100644 root/package/qca/qca-nss-ecm/files/qca-nss-ecm.init create mode 100644 root/package/qca/qca-nss-ecm/files/qca-nss-ecm.sysctl create mode 100644 root/package/qca/qca-nss-ecm/files/qca-nss-ecm.uci create mode 100644 root/package/qca/qca-nss-ecm/patches/200-resolve-high-load.patch create mode 100644 root/package/qca/qca-nss-fw-eip/Makefile create mode 100644 root/package/qca/qca-nss-fw-eip/files/ipq60xx/ifpp.bin create mode 100644 root/package/qca/qca-nss-fw-eip/files/ipq60xx/ipue.bin create mode 100644 root/package/qca/qca-nss-fw-eip/files/ipq60xx/ofpp.bin create mode 100644 root/package/qca/qca-nss-fw-eip/files/ipq60xx/opue.bin create mode 100644 root/package/qca/qca-nss-fw-eip/files/ipq807x/ifpp.bin create mode 100644 root/package/qca/qca-nss-fw-eip/files/ipq807x/ipue.bin create mode 100644 root/package/qca/qca-nss-fw-eip/files/ipq807x/ofpp.bin create mode 100644 root/package/qca/qca-nss-fw-eip/files/ipq807x/opue.bin create mode 100644 root/package/qca/qca-ssdk-shell/Makefile create mode 100644 root/package/qca/qca-ssdk/Makefile create mode 100755 root/package/qca/qca-ssdk/files/qca-ssdk create mode 100644 root/target/linux/ipq60xx/Makefile create mode 100755 root/target/linux/ipq60xx/base-files/etc/board.d/01_leds create mode 100755 root/target/linux/ipq60xx/base-files/etc/board.d/02_network create mode 100644 root/target/linux/ipq60xx/base-files/etc/hotplug.d/firmware/11-ath11k-caldata create mode 100644 root/target/linux/ipq60xx/base-files/etc/inittab create mode 100644 root/target/linux/ipq60xx/base-files/lib/upgrade/platform.sh create mode 100644 root/target/linux/ipq60xx/config-5.4 create mode 100644 root/target/linux/ipq60xx/config-5.4.bak create mode 100644 root/target/linux/ipq60xx/config-5.4.ok create mode 100644 root/target/linux/ipq60xx/files-5.4/arch/arm64/boot/dts/qcom/ipq6018-q60.dts create mode 100644 root/target/linux/ipq60xx/files-5.4/arch/arm64/boot/dts/qcom/ipq6018-x5.dts create mode 100644 root/target/linux/ipq60xx/files-5.4/arch/arm64/boot/dts/qcom/ipq6018-x511.dts create mode 100644 root/target/linux/ipq60xx/files-5.4/arch/arm64/boot/dts/qcom/ipq6018-x8.dts create mode 100644 root/target/linux/ipq60xx/generic/target.mk create mode 100644 root/target/linux/ipq60xx/image/Makefile create mode 100644 root/target/linux/ipq60xx/modules.mk create mode 100644 root/target/linux/ipq60xx/patches-5.4/001-mhi-use-irq_flags-if-controller-driver-configures.patch create mode 100644 root/target/linux/ipq60xx/patches-5.4/002-bus-mhi-core-Add-support-for-forced-PM-resume.patch create mode 100644 root/target/linux/ipq60xx/patches-5.4/003-axp2402support.patch create mode 100644 root/target/linux/ipq60xx/patches-5.4/004-flashid.patch create mode 100644 root/target/linux/ipq60xx/patches-5.4/005-cursor_blink.patch create mode 100644 root/target/linux/ipq60xx/patches-5.4/006-modems.patch create mode 100644 root/target/linux/ipq60xx/patches-5.4/691-mptcp.patch create mode 100644 root/target/linux/ipq60xx/patches-5.4/692-tcp_nanqinlang.patch create mode 100644 root/target/linux/ipq60xx/patches-5.4/693-tcp_bbr2.patch create mode 100644 root/target/linux/ipq60xx/patches-5.4/901-arm64-boot-add-dts-files.patch create mode 100644 root/target/linux/ipq60xx/patches-5.4/902-add-gpio-export.patch create mode 100644 root/target/linux/ipq60xx/patches-5.4/999-display-model-name-in-proc-cpuinfo.patch create mode 100644 root/target/linux/ipq60xx/profiles/default.mk diff --git a/root/include/kernel-version.mk b/root/include/kernel-version.mk index b7bca712..103f5139 100755 --- a/root/include/kernel-version.mk +++ b/root/include/kernel-version.mk @@ -6,12 +6,12 @@ ifdef CONFIG_TESTING_KERNEL KERNEL_PATCHVER:=$(KERNEL_TESTING_PATCHVER) endif -LINUX_VERSION-5.4 = .182 +LINUX_VERSION-5.4 = .164 LINUX_VERSION-5.10 = .64 LINUX_VERSION-5.14 = .6 LINUX_VERSION-5.15 = .36 -LINUX_KERNEL_HASH-5.4.132 = 8466adbfb3579e751ede683496df7bb20f258b5f882250f3dd82be63736d00ef +LINUX_KERNEL_HASH-5.4.164 = 2d692089dfd02e238d2164f8a599db21e04dab8d4e291c3ccbd863bcd5b00e6a LINUX_KERNEL_HASH-5.4.182 = b2f1201f64f010e9e3c85d6f303a559a7944a80a0244a86b8f5035bd23f1f40d LINUX_KERNEL_HASH-5.10.64 = 3eb84bd24a2de2b4749314e34597c02401c5d6831b055ed5224adb405c35e30a LINUX_KERNEL_HASH-5.14.6 = 54848c1268771ee3515e4c33e29abc3f1fa90d8144894cce6d0ebc3b158bccec diff --git a/root/include/target.mk b/root/include/target.mk index 948bc50f..90dccc18 100644 --- a/root/include/target.mk +++ b/root/include/target.mk @@ -148,10 +148,17 @@ ifneq ($(TARGET_BUILD)$(if $(DUMP),,1),) endif GENERIC_PLATFORM_DIR := $(TOPDIR)/target/linux/generic +ifeq ($(CONFIG_TARGET_ipq60xx),y) +GENERIC_BACKPORT_DIR := +GENERIC_PATCH_DIR := +GENERIC_HACK_DIR := +GENERIC_FILES_DIR := +else GENERIC_BACKPORT_DIR := $(GENERIC_PLATFORM_DIR)/backport$(if $(wildcard $(GENERIC_PLATFORM_DIR)/backport-$(KERNEL_PATCHVER)),-$(KERNEL_PATCHVER)) GENERIC_PATCH_DIR := $(GENERIC_PLATFORM_DIR)/pending$(if $(wildcard $(GENERIC_PLATFORM_DIR)/pending-$(KERNEL_PATCHVER)),-$(KERNEL_PATCHVER)) GENERIC_HACK_DIR := $(GENERIC_PLATFORM_DIR)/hack$(if $(wildcard $(GENERIC_PLATFORM_DIR)/hack-$(KERNEL_PATCHVER)),-$(KERNEL_PATCHVER)) GENERIC_FILES_DIR := $(foreach dir,$(wildcard $(GENERIC_PLATFORM_DIR)/files $(GENERIC_PLATFORM_DIR)/files-$(KERNEL_PATCHVER)),"$(dir)") +endif __config_name_list = $(1)/config-$(KERNEL_PATCHVER) $(1)/config-default __config_list = $(firstword $(wildcard $(call __config_name_list,$(1)))) @@ -243,6 +250,7 @@ ifeq ($(DUMP),1) endif ifeq ($(ARCH),aarch64) CPU_TYPE ?= generic + CPU_CFLAGS += -march=armv8-a+crypto CPU_CFLAGS_generic = -mcpu=generic CPU_CFLAGS_cortex-a53 = -mcpu=cortex-a53 endif diff --git a/root/package/boot/uboot-envtools/files/ipq60xx b/root/package/boot/uboot-envtools/files/ipq60xx new file mode 100644 index 00000000..b389619c --- /dev/null +++ b/root/package/boot/uboot-envtools/files/ipq60xx @@ -0,0 +1,39 @@ +#!/bin/sh + +[ -e /etc/config/ubootenv ] && exit 0 + +touch /etc/config/ubootenv + +. /lib/uboot-envtools.sh +. /lib/functions.sh + +board=$(board_name) + +ubootenv_mtdinfo () { + UBOOTENV_PART=$(cat /proc/mtd | grep APPSBLENV) + mtd_dev=$(echo $UBOOTENV_PART | awk '{print $1}' | sed 's/:$//') + mtd_size=$(echo $UBOOTENV_PART | awk '{print "0x"$2}') + mtd_erase=$(echo $UBOOTENV_PART | awk '{print "0x"$3}') + nor_flash=$(find /sys/bus/spi/devices/*/mtd -name ${mtd_dev}) + + if [ -n "$nor_flash" ]; then + ubootenv_size=$mtd_size + else + # size is fixed to 0x40000 in u-boot + ubootenv_size=0x40000 + fi + + sectors=$(( $ubootenv_size / $mtd_erase )) + echo /dev/$mtd_dev 0x0 $ubootenv_size $mtd_erase $sectors +} + +case "$board" in +*) + ubootenv_add_uci_config $(ubootenv_mtdinfo) + ;; +esac + +config_load ubootenv +config_foreach ubootenv_add_app_config ubootenv + +exit 0 diff --git a/root/package/kernel/linux/modules/usb.mk b/root/package/kernel/linux/modules/usb.mk new file mode 100644 index 00000000..2e24efed --- /dev/null +++ b/root/package/kernel/linux/modules/usb.mk @@ -0,0 +1,1717 @@ +# +# Copyright (C) 2006-2014 OpenWrt.org +# +# This is free software, licensed under the GNU General Public License v2. +# See /LICENSE for more information. +# + +USB_MENU:=USB Support + +USBNET_DIR:=net/usb +USBHID_DIR?=hid/usbhid +USBINPUT_DIR?=input/misc + +define KernelPackage/usb-core + SUBMENU:=$(USB_MENU) + TITLE:=Support for USB + DEPENDS:=@USB_SUPPORT + KCONFIG:=CONFIG_USB CONFIG_XPS_USB_HCD_XILINX=n CONFIG_USB_FHCI_HCD=n + FILES:= \ + $(LINUX_DIR)/drivers/usb/core/usbcore.ko \ + $(LINUX_DIR)/drivers/usb/common/usb-common.ko + AUTOLOAD:=$(call AutoLoad,20,usb-common usbcore,1) + $(call AddDepends/nls) +endef + +define KernelPackage/usb-core/description + Kernel support for USB +endef + +$(eval $(call KernelPackage,usb-core)) + + +define AddDepends/usb + SUBMENU:=$(USB_MENU) + DEPENDS+=+kmod-usb-core $(1) +endef + + +define KernelPackage/usb-ledtrig-usbport + TITLE:=LED trigger for USB ports + KCONFIG:=CONFIG_USB_LEDS_TRIGGER_USBPORT + FILES:=$(LINUX_DIR)/drivers/usb/core/ledtrig-usbport.ko + AUTOLOAD:=$(call AutoLoad,50,ledtrig-usbport) + $(call AddDepends/usb) +endef + +define KernelPackage/usb-ledtrig-usbport/description + This driver allows LEDs to be controlled by USB events. Enabling this + trigger allows specifying list of USB ports that should turn on LED + when some USB device gets connected. + If possible it should be prefered over similar ledtrig-usbdev. +endef + +$(eval $(call KernelPackage,usb-ledtrig-usbport)) + + +define KernelPackage/usb-phy-nop + TITLE:=Support for USB NOP transceiver + KCONFIG:=CONFIG_NOP_USB_XCEIV + HIDDEN:=1 + FILES:=$(LINUX_DIR)/drivers/usb/phy/phy-generic.ko + AUTOLOAD:=$(call AutoLoad,21,phy-generic,1) + $(call AddDepends/usb) +endef + +define KernelPackage/usb-phy-nop/description + Support for USB NOP transceiver +endef + +$(eval $(call KernelPackage,usb-phy-nop)) + + +define KernelPackage/phy-ath79-usb + TITLE:=Support for ATH79 USB PHY + KCONFIG:=CONFIG_PHY_AR7100_USB \ + CONFIG_PHY_AR7200_USB + DEPENDS:=@TARGET_ath79 + HIDDEN:=1 + FILES:=$(LINUX_DIR)/drivers/phy/phy-ar7100-usb.ko \ + $(LINUX_DIR)/drivers/phy/phy-ar7200-usb.ko + AUTOLOAD:=$(call AutoLoad,21,phy-ar7100-usb phy-ar7200-usb,1) + $(call AddDepends/usb) +endef + +define KernelPackage/phy-ath79-usb/description + Support for ATH79 USB transceiver +endef + +$(eval $(call KernelPackage,phy-ath79-usb)) + + +define KernelPackage/usb-gadget + TITLE:=USB Gadget support + KCONFIG:=CONFIG_USB_GADGET + HIDDEN:=1 + FILES:=\ + $(LINUX_DIR)/drivers/usb/gadget/udc/udc-core.ko + AUTOLOAD:=$(call AutoLoad,21,udc-core,1) + DEPENDS:=@USB_GADGET_SUPPORT + $(call AddDepends/usb) +endef + +define KernelPackage/usb-gadget/description + Kernel support for USB Gadget mode +endef + +$(eval $(call KernelPackage,usb-gadget)) + +define KernelPackage/usb-lib-composite + TITLE:=USB lib composite + KCONFIG:=CONFIG_USB_LIBCOMPOSITE + DEPENDS:=+kmod-usb-gadget +kmod-fs-configfs + HIDDEN:=1 + FILES:=$(LINUX_DIR)/drivers/usb/gadget/libcomposite.ko + AUTOLOAD:=$(call AutoLoad,50,libcomposite) + $(call AddDepends/usb) +endef + +define KernelPackage/usb-lib-composite/description + Lib Composite +endef + +$(eval $(call KernelPackage,usb-lib-composite)) + +define KernelPackage/usb-gadget-hid + TITLE:=USB HID Gadget Support + KCONFIG:=CONFIG_USB_G_HID + DEPENDS:=+kmod-usb-gadget +kmod-usb-lib-composite + FILES:= \ + $(LINUX_DIR)/drivers/usb/gadget/legacy/g_hid.ko \ + $(LINUX_DIR)/drivers/usb/gadget/function/usb_f_hid.ko + AUTOLOAD:=$(call AutoLoad,52,usb_f_hid) + $(call AddDepends/usb) +endef + +define KernelPackage/usb-gadget-hid/description + Kernel support for USB HID Gadget. +endef + +$(eval $(call KernelPackage,usb-gadget-hid)) + +define KernelPackage/usb-gadget-ehci-debug + TITLE:=USB EHCI debug port Gadget support + KCONFIG:=\ + CONFIG_USB_G_DBGP \ + CONFIG_USB_G_DBGP_SERIAL=y \ + CONFIG_USB_G_DBGP_PRINTK=n + DEPENDS:=+kmod-usb-gadget +kmod-usb-lib-composite +kmod-usb-gadget-serial + FILES:=$(LINUX_DIR)/drivers/usb/gadget/legacy/g_dbgp.ko + $(call AddDepends/usb) +endef + +define KernelPackage/usb-gadget-ehci-debug/description + Kernel support for USB EHCI debug port Gadget. +endef + +$(eval $(call KernelPackage,usb-gadget-ehci-debug)) + +define KernelPackage/usb-gadget-eth + TITLE:=USB Ethernet Gadget support + KCONFIG:= \ + CONFIG_USB_ETH \ + CONFIG_USB_ETH_RNDIS=y \ + CONFIG_USB_ETH_EEM=n + DEPENDS:=+kmod-usb-gadget +kmod-usb-lib-composite + FILES:= \ + $(LINUX_DIR)/drivers/usb/gadget/function/u_ether.ko \ + $(LINUX_DIR)/drivers/usb/gadget/function/usb_f_ecm.ko \ + $(LINUX_DIR)/drivers/usb/gadget/function/usb_f_ecm_subset.ko \ + $(LINUX_DIR)/drivers/usb/gadget/function/usb_f_rndis.ko \ + $(LINUX_DIR)/drivers/usb/gadget/legacy/g_ether.ko + AUTOLOAD:=$(call AutoLoad,52,usb_f_ecm) + $(call AddDepends/usb) +endef + +define KernelPackage/usb-gadget-eth/description + Kernel support for USB Ethernet Gadget +endef + +$(eval $(call KernelPackage,usb-gadget-eth)) + + +define KernelPackage/usb-gadget-serial + TITLE:=USB Serial Gadget support + KCONFIG:=CONFIG_USB_G_SERIAL + DEPENDS:=+kmod-usb-gadget +kmod-usb-lib-composite + FILES:= \ + $(LINUX_DIR)/drivers/usb/gadget/function/u_serial.ko \ + $(LINUX_DIR)/drivers/usb/gadget/function/usb_f_acm.ko \ + $(LINUX_DIR)/drivers/usb/gadget/function/usb_f_obex.ko \ + $(LINUX_DIR)/drivers/usb/gadget/function/usb_f_serial.ko \ + $(LINUX_DIR)/drivers/usb/gadget/legacy/g_serial.ko + AUTOLOAD:=$(call AutoLoad,52,usb_f_acm) + $(call AddDepends/usb) +endef + +define KernelPackage/usb-gadget-serial/description + Kernel support for USB Serial Gadget. +endef + +$(eval $(call KernelPackage,usb-gadget-serial)) + +define KernelPackage/usb-gadget-mass-storage + TITLE:=USB Mass Storage support + KCONFIG:=CONFIG_USB_MASS_STORAGE + DEPENDS:=+kmod-usb-gadget +kmod-usb-lib-composite + FILES:= \ + $(LINUX_DIR)/drivers/usb/gadget/function/usb_f_mass_storage.ko \ + $(LINUX_DIR)/drivers/usb/gadget/legacy/g_mass_storage.ko + AUTOLOAD:=$(call AutoLoad,52,usb_f_mass_storage) + $(call AddDepends/usb) +endef + +define KernelPackage/usb-gadget-mass-storage/description + Kernel support for USB Gadget Mass Storage +endef + +$(eval $(call KernelPackage,usb-gadget-mass-storage)) + +define KernelPackage/usb-gadget-cdc-composite + TITLE:= USB CDC Composite (Ethernet + ACM) + KCONFIG:=CONFIG_USB_CDC_COMPOSITE + DEPENDS:=+kmod-usb-gadget +kmod-usb-lib-composite \ + +kmod-usb-gadget-eth +kmod-usb-gadget-serial + FILES:= $(LINUX_DIR)/drivers/usb/gadget/legacy/g_cdc.ko + $(call AddDepends/usb) +endef + +define KernelPackage/usb-gadget-cdc-composite/description + Kernel support for the USB CDC Composite gadget. + This appears as an ethernet + ACM serial gadget. +endef + +$(eval $(call KernelPackage,usb-gadget-cdc-composite)) + + +define KernelPackage/usb-uhci + TITLE:=Support for UHCI controllers + KCONFIG:= \ + CONFIG_USB_PCI=y \ + CONFIG_USB_UHCI_ALT \ + CONFIG_USB_UHCI_HCD + FILES:=$(LINUX_DIR)/drivers/usb/host/uhci-hcd.ko + AUTOLOAD:=$(call AutoLoad,50,uhci-hcd,1) + $(call AddDepends/usb) +endef + +define KernelPackage/usb-uhci/description + Kernel support for USB UHCI controllers +endef + +$(eval $(call KernelPackage,usb-uhci,1)) + + +define KernelPackage/usb-ohci + TITLE:=Support for OHCI controllers + DEPENDS:= \ + +TARGET_bcm53xx:kmod-usb-bcma \ + +TARGET_bcm47xx:kmod-usb-bcma \ + +TARGET_bcm47xx:kmod-usb-ssb + KCONFIG:= \ + CONFIG_USB_OHCI \ + CONFIG_USB_OHCI_HCD \ + CONFIG_USB_OHCI_ATH79=y \ + CONFIG_USB_OHCI_HCD_AT91=y \ + CONFIG_USB_OHCI_BCM63XX=y \ + CONFIG_USB_OCTEON_OHCI=y \ + CONFIG_USB_OHCI_HCD_OMAP3=y \ + CONFIG_USB_OHCI_HCD_PLATFORM=y + FILES:= \ + $(LINUX_DIR)/drivers/usb/host/ohci-hcd.ko \ + $(LINUX_DIR)/drivers/usb/host/ohci-platform.ko + ifneq ($(wildcard $(LINUX_DIR)/drivers/usb/host/ohci-at91.ko),) + FILES+=$(LINUX_DIR)/drivers/usb/host/ohci-at91.ko + endif + AUTOLOAD:=$(call AutoLoad,50,ohci-hcd ohci-platform ohci-at91,1) + $(call AddDepends/usb) +endef + +define KernelPackage/usb-ohci/description + Kernel support for USB OHCI controllers +endef + +$(eval $(call KernelPackage,usb-ohci,1)) + + +define KernelPackage/usb-ohci-pci + TITLE:=Support for PCI OHCI controllers + DEPENDS:=@PCI_SUPPORT +kmod-usb-ohci + KCONFIG:= \ + CONFIG_USB_PCI=y \ + CONFIG_USB_OHCI_HCD_PCI + FILES:=$(LINUX_DIR)/drivers/usb/host/ohci-pci.ko + AUTOLOAD:=$(call AutoLoad,51,ohci-pci,1) + $(call AddDepends/usb) +endef + +define KernelPackage/usb-ohci-pci/description + Kernel support for PCI OHCI controllers +endef + +$(eval $(call KernelPackage,usb-ohci-pci)) + + +define KernelPackage/usb-bcma + TITLE:=Support for BCMA USB controllers + DEPENDS:=@USB_SUPPORT @TARGET_bcm47xx||TARGET_bcm53xx + HIDDEN:=1 + KCONFIG:=CONFIG_USB_HCD_BCMA + FILES:= \ + $(if $(CONFIG_USB_HCD_BCMA),$(LINUX_DIR)/drivers/usb/host/bcma-hcd.ko) + AUTOLOAD:=$(call AutoLoad,19,$(if $(CONFIG_USB_HCD_BCMA),bcma-hcd),1) + $(call AddDepends/usb) +endef +$(eval $(call KernelPackage,usb-bcma)) + +define KernelPackage/usb-fotg210 + TITLE:=Support for FOTG210 USB host controllers + DEPENDS:=@USB_SUPPORT @TARGET_gemini + KCONFIG:=CONFIG_USB_FOTG210_HCD + FILES:= \ + $(if $(CONFIG_USB_FOTG210_HCD),$(LINUX_DIR)/drivers/usb/host/fotg210-hcd.ko) + AUTOLOAD:=$(call AutoLoad,50,fotg210-hcd,1) + $(call AddDepends/usb) +endef +$(eval $(call KernelPackage,usb-fotg210)) + +define KernelPackage/usb-ssb + TITLE:=Support for SSB USB controllers + DEPENDS:=@USB_SUPPORT @TARGET_bcm47xx + HIDDEN:=1 + KCONFIG:=CONFIG_USB_HCD_SSB + FILES:= \ + $(if $(CONFIG_USB_HCD_SSB),$(LINUX_DIR)/drivers/usb/host/ssb-hcd.ko) + AUTOLOAD:=$(call AutoLoad,19,$(if $(CONFIG_USB_HCD_SSB),ssb-hcd),1) + $(call AddDepends/usb) +endef +$(eval $(call KernelPackage,usb-ssb)) + +define KernelPackage/usb-ehci + TITLE:=EHCI controller support + HIDDEN:=1 + KCONFIG:= \ + CONFIG_USB_EHCI_HCD + FILES:= \ + $(LINUX_DIR)/drivers/usb/host/ehci-hcd.ko + AUTOLOAD:=$(call AutoLoad,35,ehci-hcd,1) + $(call AddDepends/usb) +endef +$(eval $(call KernelPackage,usb-ehci)) + +define KernelPackage/usb2 + TITLE:=Support for USB2 controllers + DEPENDS:=\ + +TARGET_bcm47xx:kmod-usb-bcma \ + +TARGET_bcm47xx:kmod-usb-ssb \ + +TARGET_bcm53xx:kmod-usb-bcma \ + +TARGET_bcm53xx:kmod-phy-bcm-ns-usb2 \ + +TARGET_ath79:kmod-phy-ath79-usb \ + +kmod-usb-ehci + KCONFIG:=\ + CONFIG_USB_EHCI_HCD_PLATFORM \ + CONFIG_USB_EHCI_BCM63XX=y \ + CONFIG_USB_IMX21_HCD=y \ + CONFIG_USB_EHCI_MXC=y \ + CONFIG_USB_OCTEON_EHCI=y \ + CONFIG_USB_EHCI_HCD_ORION=y \ + CONFIG_USB_EHCI_HCD_AT91=y \ + CONFIG_USB_EHCI_FSL + FILES:= \ + $(LINUX_DIR)/drivers/usb/host/ehci-platform.ko + ifneq ($(wildcard $(LINUX_DIR)/drivers/usb/host/ehci-orion.ko),) + FILES+=$(LINUX_DIR)/drivers/usb/host/ehci-orion.ko + endif + ifneq ($(wildcard $(LINUX_DIR)/drivers/usb/host/ehci-atmel.ko),) + FILES+=$(LINUX_DIR)/drivers/usb/host/ehci-atmel.ko + endif + ifneq ($(wildcard $(LINUX_DIR)/drivers/usb/host/ehci-fsl.ko),) + FILES+=$(LINUX_DIR)/drivers/usb/host/ehci-fsl.ko + endif + ifneq ($(wildcard $(LINUX_DIR)/drivers/usb/host/fsl-mph-dr-of.ko),) + FILES+=$(LINUX_DIR)/drivers/usb/host/fsl-mph-dr-of.ko + endif + AUTOLOAD:=$(call AutoLoad,40,ehci-hcd ehci-platform ehci-orion ehci-atmel ehci-fsl fsl-mph-dr-of,1) + $(call AddDepends/usb) +endef + +define KernelPackage/usb2/description + Kernel support for USB2 (EHCI) controllers +endef + +$(eval $(call KernelPackage,usb2)) + + +define KernelPackage/usb2-pci + TITLE:=Support for PCI USB2 controllers + DEPENDS:=@PCI_SUPPORT +kmod-usb2 + KCONFIG:= \ + CONFIG_USB_PCI=y \ + CONFIG_USB_EHCI_PCI + FILES:=$(LINUX_DIR)/drivers/usb/host/ehci-pci.ko + AUTOLOAD:=$(call AutoLoad,42,ehci-pci,1) + $(call AddDepends/usb) +endef + +define KernelPackage/usb2-pci/description + Kernel support for PCI USB2 (EHCI) controllers +endef + +$(eval $(call KernelPackage,usb2-pci)) + + +define KernelPackage/usb-dwc2 + TITLE:=DWC2 USB controller driver + DEPENDS:=+USB_GADGET_SUPPORT:kmod-usb-gadget + KCONFIG:= \ + CONFIG_USB_PCI=y \ + CONFIG_USB_DWC2 \ + CONFIG_USB_DWC2_PCI \ + CONFIG_USB_DWC2_PLATFORM \ + CONFIG_USB_DWC2_DEBUG=n \ + CONFIG_USB_DWC2_VERBOSE=n \ + CONFIG_USB_DWC2_TRACK_MISSED_SOFS=n \ + CONFIG_USB_DWC2_DEBUG_PERIODIC=n + FILES:= \ + $(LINUX_DIR)/drivers/usb/dwc2/dwc2.ko + AUTOLOAD:=$(call AutoLoad,54,dwc2,1) + $(call AddDepends/usb) +endef + +define KernelPackage/usb-dwc2/description + This driver provides USB Device Controller support for the + Synopsys DesignWare USB OTG Core +endef + +$(eval $(call KernelPackage,usb-dwc2)) + + +define KernelPackage/usb-dwc3 + TITLE:=DWC3 USB controller driver + KCONFIG:= \ + CONFIG_USB_DWC3 \ + CONFIG_USB_DWC3_HOST=y \ + CONFIG_USB_DWC3_GADGET=n \ + CONFIG_USB_DWC3_DUAL_ROLE=n \ + CONFIG_USB_DWC3_DEBUG=n \ + CONFIG_USB_DWC3_VERBOSE=n + FILES:= $(LINUX_DIR)/drivers/usb/dwc3/dwc3.ko + AUTOLOAD:=$(call AutoLoad,54,dwc3,1) + $(call AddDepends/usb) +endef + +define KernelPackage/usb-dwc3/description + This driver provides support for the Dual Role SuperSpeed + USB Controller based on the Synopsys DesignWare USB3 IP Core +endef + +$(eval $(call KernelPackage,usb-dwc3)) + + +define KernelPackage/usb-dwc3-qcom + TITLE:=DWC3 Qualcomm USB driver + DEPENDS:=@(TARGET_ipq40xx||TARGET_ipq806x||TARGET_ipq60xx) +kmod-usb-dwc3 + KCONFIG:= CONFIG_USB_DWC3_QCOM + FILES:= $(LINUX_DIR)/drivers/usb/dwc3/dwc3-qcom.ko + AUTOLOAD:=$(call AutoLoad,53,dwc3-qcom,1) + $(call AddDepends/usb) +endef + +define KernelPackage/usb-dwc3-qcom/description + Some Qualcomm SoCs use DesignWare Core IP for USB2/3 functionality. + This driver also handles Qscratch wrapper which is needed for + peripheral mode support. +endef + + +$(eval $(call KernelPackage,usb-dwc3-qcom)) + + +define KernelPackage/usb-acm + TITLE:=Support for modems/isdn controllers + KCONFIG:=CONFIG_USB_ACM + FILES:=$(LINUX_DIR)/drivers/usb/class/cdc-acm.ko + AUTOLOAD:=$(call AutoProbe,cdc-acm) +$(call AddDepends/usb) +endef + +define KernelPackage/usb-acm/description + Kernel support for USB ACM devices (modems/isdn controllers) +endef + +$(eval $(call KernelPackage,usb-acm)) + + +define KernelPackage/usb-wdm + TITLE:=USB Wireless Device Management + KCONFIG:=CONFIG_USB_WDM + FILES:=$(LINUX_DIR)/drivers/usb/class/cdc-wdm.ko + AUTOLOAD:=$(call AutoProbe,cdc-wdm) +$(call AddDepends/usb) +$(call AddDepends/usb-net) +endef + +define KernelPackage/usb-wdm/description + USB Wireless Device Management support +endef + +$(eval $(call KernelPackage,usb-wdm)) + + +define KernelPackage/usb-audio + TITLE:=Support for USB audio devices + KCONFIG:= \ + CONFIG_USB_AUDIO \ + CONFIG_SND_USB=y \ + CONFIG_SND_USB_AUDIO + $(call AddDepends/usb) + $(call AddDepends/sound) + FILES:= \ + $(LINUX_DIR)/sound/usb/snd-usbmidi-lib.ko \ + $(LINUX_DIR)/sound/usb/snd-usb-audio.ko + AUTOLOAD:=$(call AutoProbe,snd-usbmidi-lib snd-usb-audio) +endef + +define KernelPackage/usb-audio/description + Kernel support for USB audio devices +endef + +$(eval $(call KernelPackage,usb-audio)) + + +define KernelPackage/usb-printer + TITLE:=Support for printers + KCONFIG:=CONFIG_USB_PRINTER + FILES:=$(LINUX_DIR)/drivers/usb/class/usblp.ko + AUTOLOAD:=$(call AutoProbe,usblp) + $(call AddDepends/usb) +endef + +define KernelPackage/usb-printer/description + Kernel support for USB printers +endef + +$(eval $(call KernelPackage,usb-printer)) + + +define KernelPackage/usb-serial + TITLE:=Support for USB-to-Serial converters + KCONFIG:=CONFIG_USB_SERIAL + FILES:=$(LINUX_DIR)/drivers/usb/serial/usbserial.ko + AUTOLOAD:=$(call AutoProbe,usbserial) + $(call AddDepends/usb) +endef + +define KernelPackage/usb-serial/description + Kernel support for USB-to-Serial converters +endef + +$(eval $(call KernelPackage,usb-serial)) + + +define AddDepends/usb-serial + SUBMENU:=$(USB_MENU) + DEPENDS+=+kmod-usb-serial $(1) +endef + + +define KernelPackage/usb-serial-belkin + TITLE:=Support for Belkin devices + KCONFIG:=CONFIG_USB_SERIAL_BELKIN + FILES:=$(LINUX_DIR)/drivers/usb/serial/belkin_sa.ko + AUTOLOAD:=$(call AutoProbe,belkin_sa) + $(call AddDepends/usb-serial) +endef + +define KernelPackage/usb-serial-belkin/description + Kernel support for Belkin USB-to-Serial converters +endef + +$(eval $(call KernelPackage,usb-serial-belkin)) + + +define KernelPackage/usb-serial-ch341 + TITLE:=Support for CH341 devices + KCONFIG:=CONFIG_USB_SERIAL_CH341 + FILES:=$(LINUX_DIR)/drivers/usb/serial/ch341.ko + AUTOLOAD:=$(call AutoProbe,ch341) + $(call AddDepends/usb-serial) +endef + +define KernelPackage/usb-serial-ch341/description + Kernel support for Winchiphead CH341 USB-to-Serial converters +endef + +$(eval $(call KernelPackage,usb-serial-ch341)) + + +define KernelPackage/usb-serial-edgeport + TITLE:=Support for Digi Edgeport devices + KCONFIG:=CONFIG_USB_SERIAL_EDGEPORT + FILES:=$(LINUX_DIR)/drivers/usb/serial/io_edgeport.ko + AUTOLOAD:=$(call AutoProbe,io_edgeport) + $(call AddDepends/usb-serial) + DEPENDS+=+edgeport-firmware +endef + +define KernelPackage/usb-serial-edgeport/description + Kernel support for Inside Out Networks (Digi) + Edgeport/4 + Rapidport/4 + Edgeport/4t + Edgeport/2 + Edgeport/4i + Edgeport/2i + Edgeport/421 + Edgeport/21 + Edgeport/8 + Edgeport/8 Dual + Edgeport/2D8 + Edgeport/4D8 + Edgeport/8i + Edgeport/2 DIN + Edgeport/4 DIN + Edgeport/16 Dual +endef + +$(eval $(call KernelPackage,usb-serial-edgeport)) + + +define KernelPackage/usb-serial-ftdi + TITLE:=Support for FTDI devices + KCONFIG:=CONFIG_USB_SERIAL_FTDI_SIO + FILES:=$(LINUX_DIR)/drivers/usb/serial/ftdi_sio.ko + AUTOLOAD:=$(call AutoProbe,ftdi_sio) + $(call AddDepends/usb-serial) +endef + +define KernelPackage/usb-serial-ftdi/description + Kernel support for FTDI USB-to-Serial converters +endef + +$(eval $(call KernelPackage,usb-serial-ftdi)) + + +define KernelPackage/usb-serial-garmin + TITLE:=Support for Garmin GPS devices + KCONFIG:=CONFIG_USB_SERIAL_GARMIN + FILES:=$(LINUX_DIR)/drivers/usb/serial/garmin_gps.ko + AUTOLOAD:=$(call AutoProbe,garmin_gps) + $(call AddDepends/usb-serial) +endef + +define KernelPackage/usb-serial-garmin/description + Should work with most Garmin GPS devices which have a native USB port. +endef + +$(eval $(call KernelPackage,usb-serial-garmin)) + + +define KernelPackage/usb-serial-simple + TITLE:=USB Serial Simple (Motorola phone) + KCONFIG:=CONFIG_USB_SERIAL_SIMPLE + FILES:=$(LINUX_DIR)/drivers/usb/serial/usb-serial-simple.ko + AUTOLOAD:=$(call AutoProbe,usb-serial-simple) + $(call AddDepends/usb-serial) +endef + +define KernelPackage/usb-serial-simple/description + Kernel support for "very simple devices". + +Specifically, it supports: + - Suunto ANT+ USB device. + - Medtronic CareLink USB device (3.18) + - Fundamental Software dongle. + - Google USB serial devices (3.19) + - HP4x calculators + - a number of Motorola phones + - Novatel Wireless GPS receivers (3.18) + - Siemens USB/MPI adapter. + - ViVOtech ViVOpay USB device. + - Infineon Modem Flashloader USB interface + - ZIO Motherboard USB serial interface +endef + +$(eval $(call KernelPackage,usb-serial-simple)) + + +define KernelPackage/usb-serial-ti-usb + TITLE:=Support for TI USB 3410/5052 + KCONFIG:=CONFIG_USB_SERIAL_TI + FILES:=$(LINUX_DIR)/drivers/usb/serial/ti_usb_3410_5052.ko + AUTOLOAD:=$(call AutoProbe,ti_usb_3410_5052) + $(call AddDepends/usb-serial) +endef + +define KernelPackage/usb-serial-ti-usb/description + Kernel support for TI USB 3410/5052 devices +endef + +$(eval $(call KernelPackage,usb-serial-ti-usb)) + + +define KernelPackage/usb-serial-ipw + TITLE:=Support for IPWireless 3G devices + KCONFIG:=CONFIG_USB_SERIAL_IPW + FILES:=$(LINUX_DIR)/drivers/usb/serial/ipw.ko + AUTOLOAD:=$(call AutoProbe,ipw) + $(call AddDepends/usb-serial,+kmod-usb-serial-wwan) +endef + +$(eval $(call KernelPackage,usb-serial-ipw)) + + +define KernelPackage/usb-serial-mct + TITLE:=Support for Magic Control Tech. devices + KCONFIG:=CONFIG_USB_SERIAL_MCT_U232 + FILES:=$(LINUX_DIR)/drivers/usb/serial/mct_u232.ko + AUTOLOAD:=$(call AutoProbe,mct_u232) + $(call AddDepends/usb-serial) +endef + +define KernelPackage/usb-serial-mct/description + Kernel support for Magic Control Technology USB-to-Serial converters +endef + +$(eval $(call KernelPackage,usb-serial-mct)) + + +define KernelPackage/usb-serial-mos7720 + TITLE:=Support for Moschip MOS7720 devices + KCONFIG:=CONFIG_USB_SERIAL_MOS7720 + FILES:=$(LINUX_DIR)/drivers/usb/serial/mos7720.ko + AUTOLOAD:=$(call AutoProbe,mos7720) + $(call AddDepends/usb-serial) +endef + +define KernelPackage/usb-serial-mos7720/description + Kernel support for Moschip MOS7720 USB-to-Serial converters +endef + +$(eval $(call KernelPackage,usb-serial-mos7720)) + + +define KernelPackage/usb-serial-mos7840 + TITLE:=Support for Moschip MOS7840 devices + KCONFIG:=CONFIG_USB_SERIAL_MOS7840 + FILES:=$(LINUX_DIR)/drivers/usb/serial/mos7840.ko + AUTOLOAD:=$(call AutoProbe,mos7840) + $(call AddDepends/usb-serial) +endef + +define KernelPackage/usb-serial-mos7840/description + Kernel support for Moschip MOS7840 USB-to-Serial converters +endef + +$(eval $(call KernelPackage,usb-serial-mos7840)) + + +define KernelPackage/usb-serial-pl2303 + TITLE:=Support for Prolific PL2303 devices + KCONFIG:=CONFIG_USB_SERIAL_PL2303 + FILES:=$(LINUX_DIR)/drivers/usb/serial/pl2303.ko + AUTOLOAD:=$(call AutoProbe,pl2303) + $(call AddDepends/usb-serial) +endef + +define KernelPackage/usb-serial-pl2303/description + Kernel support for Prolific PL2303 USB-to-Serial converters +endef + +$(eval $(call KernelPackage,usb-serial-pl2303)) + + +define KernelPackage/usb-serial-cp210x + TITLE:=Support for Silicon Labs cp210x devices + KCONFIG:=CONFIG_USB_SERIAL_CP210X + FILES:=$(LINUX_DIR)/drivers/usb/serial/cp210x.ko + AUTOLOAD:=$(call AutoProbe,cp210x) + $(call AddDepends/usb-serial) +endef + +define KernelPackage/usb-serial-cp210x/description + Kernel support for Silicon Labs cp210x USB-to-Serial converters +endef + +$(eval $(call KernelPackage,usb-serial-cp210x)) + + +define KernelPackage/usb-serial-ark3116 + TITLE:=Support for ArkMicroChips ARK3116 devices + KCONFIG:=CONFIG_USB_SERIAL_ARK3116 + FILES:=$(LINUX_DIR)/drivers/usb/serial/ark3116.ko + AUTOLOAD:=$(call AutoProbe,ark3116) + $(call AddDepends/usb-serial) +endef + +define KernelPackage/usb-serial-ark3116/description + Kernel support for ArkMicroChips ARK3116 USB-to-Serial converters +endef + +$(eval $(call KernelPackage,usb-serial-ark3116)) + + +define KernelPackage/usb-serial-oti6858 + TITLE:=Support for Ours Technology OTI6858 devices + KCONFIG:=CONFIG_USB_SERIAL_OTI6858 + FILES:=$(LINUX_DIR)/drivers/usb/serial/oti6858.ko + AUTOLOAD:=$(call AutoProbe,oti6858) + $(call AddDepends/usb-serial) +endef + +define KernelPackage/usb-serial-oti6858/description + Kernel support for Ours Technology OTI6858 USB-to-Serial converters +endef + +$(eval $(call KernelPackage,usb-serial-oti6858)) + + +define KernelPackage/usb-serial-sierrawireless + TITLE:=Support for Sierra Wireless devices + KCONFIG:=CONFIG_USB_SERIAL_SIERRAWIRELESS + FILES:=$(LINUX_DIR)/drivers/usb/serial/sierra.ko + AUTOLOAD:=$(call AutoProbe,sierra) + $(call AddDepends/usb-serial) +endef + +define KernelPackage/usb-serial-sierrawireless/description + Kernel support for Sierra Wireless devices +endef + +$(eval $(call KernelPackage,usb-serial-sierrawireless)) + + +define KernelPackage/usb-serial-visor + TITLE:=Support for Handspring Visor devices + KCONFIG:=CONFIG_USB_SERIAL_VISOR + FILES:=$(LINUX_DIR)/drivers/usb/serial/visor.ko + AUTOLOAD:=$(call AutoProbe,visor) + $(call AddDepends/usb-serial) +endef + +define KernelPackage/usb-serial-visor/description + Kernel support for Handspring Visor PDAs +endef + +$(eval $(call KernelPackage,usb-serial-visor)) + + +define KernelPackage/usb-serial-cypress-m8 + TITLE:=Support for CypressM8 USB-Serial + KCONFIG:=CONFIG_USB_SERIAL_CYPRESS_M8 + FILES:=$(LINUX_DIR)/drivers/usb/serial/cypress_m8.ko + AUTOLOAD:=$(call AutoProbe,cypress_m8) + $(call AddDepends/usb-serial) +endef + +define KernelPackage/usb-serial-cypress-m8/description + Kernel support for devices with Cypress M8 USB to Serial chip + (for example, the Delorme Earthmate LT-20 GPS) + Supported microcontrollers in the CY4601 family are: + CY7C63741 CY7C63742 CY7C63743 CY7C64013 +endef + +$(eval $(call KernelPackage,usb-serial-cypress-m8)) + + +define KernelPackage/usb-serial-keyspan + TITLE:=Support for Keyspan USB-to-Serial devices + KCONFIG:= \ + CONFIG_USB_SERIAL_KEYSPAN \ + CONFIG_USB_SERIAL_KEYSPAN_USA28 \ + CONFIG_USB_SERIAL_KEYSPAN_USA28X \ + CONFIG_USB_SERIAL_KEYSPAN_USA28XA \ + CONFIG_USB_SERIAL_KEYSPAN_USA28XB \ + CONFIG_USB_SERIAL_KEYSPAN_USA19 \ + CONFIG_USB_SERIAL_KEYSPAN_USA18X \ + CONFIG_USB_SERIAL_KEYSPAN_USA19W \ + CONFIG_USB_SERIAL_KEYSPAN_USA19QW \ + CONFIG_USB_SERIAL_KEYSPAN_USA19QI \ + CONFIG_USB_SERIAL_KEYSPAN_MPR \ + CONFIG_USB_SERIAL_KEYSPAN_USA49W \ + CONFIG_USB_SERIAL_KEYSPAN_USA49WLC + FILES:= \ + $(LINUX_DIR)/drivers/usb/serial/keyspan.ko \ + $(wildcard $(LINUX_DIR)/drivers/usb/misc/ezusb.ko) + AUTOLOAD:=$(call AutoProbe,ezusb keyspan) + $(call AddDepends/usb-serial) +endef + +define KernelPackage/usb-serial-keyspan/description + Kernel support for Keyspan USB-to-Serial devices +endef + +$(eval $(call KernelPackage,usb-serial-keyspan)) + + +define KernelPackage/usb-serial-wwan + TITLE:=Support for GSM and CDMA modems + KCONFIG:=CONFIG_USB_SERIAL_WWAN + FILES:=$(LINUX_DIR)/drivers/usb/serial/usb_wwan.ko + HIDDEN:=1 + AUTOLOAD:=$(call AutoProbe,usb_wwan) + $(call AddDepends/usb-serial) +endef + +define KernelPackage/usb-serial-wwan/description + Kernel support for USB GSM and CDMA modems +endef + +$(eval $(call KernelPackage,usb-serial-wwan)) + + +define KernelPackage/usb-serial-option + TITLE:=Support for Option HSDPA modems + KCONFIG:=CONFIG_USB_SERIAL_OPTION + FILES:=$(LINUX_DIR)/drivers/usb/serial/option.ko + AUTOLOAD:=$(call AutoProbe,option) + $(call AddDepends/usb-serial,+kmod-usb-serial-wwan) +endef + +define KernelPackage/usb-serial-option/description + Kernel support for Option HSDPA modems +endef + +$(eval $(call KernelPackage,usb-serial-option)) + + +define KernelPackage/usb-serial-qualcomm + TITLE:=Support for Qualcomm USB serial + KCONFIG:=CONFIG_USB_SERIAL_QUALCOMM + FILES:=$(LINUX_DIR)/drivers/usb/serial/qcserial.ko + AUTOLOAD:=$(call AutoProbe,qcserial) + $(call AddDepends/usb-serial,+kmod-usb-serial-wwan) +endef + +define KernelPackage/usb-serial-qualcomm/description + Kernel support for Qualcomm USB Serial devices (Gobi) +endef + +$(eval $(call KernelPackage,usb-serial-qualcomm)) + + +define KernelPackage/usb-storage + TITLE:=USB Storage support + DEPENDS:= +kmod-scsi-core + KCONFIG:=CONFIG_USB_STORAGE + FILES:=$(LINUX_DIR)/drivers/usb/storage/usb-storage.ko + AUTOLOAD:=$(call AutoProbe,usb-storage,1) + $(call AddDepends/usb) +endef + +define KernelPackage/usb-storage/description + Kernel support for USB Mass Storage devices +endef + +$(eval $(call KernelPackage,usb-storage)) + + +define KernelPackage/usb-storage-extras + SUBMENU:=$(USB_MENU) + TITLE:=Extra drivers for usb-storage + DEPENDS:=+kmod-usb-storage + KCONFIG:= \ + CONFIG_USB_STORAGE_ALAUDA \ + CONFIG_USB_STORAGE_CYPRESS_ATACB \ + CONFIG_USB_STORAGE_DATAFAB \ + CONFIG_USB_STORAGE_FREECOM \ + CONFIG_USB_STORAGE_ISD200 \ + CONFIG_USB_STORAGE_JUMPSHOT \ + CONFIG_USB_STORAGE_KARMA \ + CONFIG_USB_STORAGE_SDDR09 \ + CONFIG_USB_STORAGE_SDDR55 \ + CONFIG_USB_STORAGE_USBAT + FILES:= \ + $(LINUX_DIR)/drivers/usb/storage/ums-alauda.ko \ + $(LINUX_DIR)/drivers/usb/storage/ums-cypress.ko \ + $(LINUX_DIR)/drivers/usb/storage/ums-datafab.ko \ + $(LINUX_DIR)/drivers/usb/storage/ums-freecom.ko \ + $(LINUX_DIR)/drivers/usb/storage/ums-isd200.ko \ + $(LINUX_DIR)/drivers/usb/storage/ums-jumpshot.ko \ + $(LINUX_DIR)/drivers/usb/storage/ums-karma.ko \ + $(LINUX_DIR)/drivers/usb/storage/ums-sddr09.ko \ + $(LINUX_DIR)/drivers/usb/storage/ums-sddr55.ko \ + $(LINUX_DIR)/drivers/usb/storage/ums-usbat.ko + AUTOLOAD:=$(call AutoProbe,ums-alauda ums-cypress ums-datafab \ + ums-freecom ums-isd200 ums-jumpshot \ + ums-karma ums-sddr09 ums-sddr55 ums-usbat) +endef + +define KernelPackage/usb-storage-extras/description + Say Y here if you want to have some more drivers, + such as for SmartMedia card readers +endef + +$(eval $(call KernelPackage,usb-storage-extras)) + + +define KernelPackage/usb-storage-uas + SUBMENU:=$(USB_MENU) + TITLE:=USB Attached SCSI (UASP) support + DEPENDS:=+kmod-usb-storage + KCONFIG:=CONFIG_USB_UAS + FILES:=$(LINUX_DIR)/drivers/usb/storage/uas.ko + AUTOLOAD:=$(call AutoProbe,uas,1) +endef + +define KernelPackage/usb-storage-uas/description + Say Y here if you want to include support for + USB Attached SCSI (UAS/UASP), a higher + performance protocol available on many + newer USB 3.0 storage devices +endef + +$(eval $(call KernelPackage,usb-storage-uas)) + + +define KernelPackage/usb-atm + TITLE:=Support for ATM on USB bus + DEPENDS:=+kmod-atm + KCONFIG:=CONFIG_USB_ATM + FILES:=$(LINUX_DIR)/drivers/usb/atm/usbatm.ko + AUTOLOAD:=$(call AutoProbe,usbatm) + $(call AddDepends/usb) +endef + +define KernelPackage/usb-atm/description + Kernel support for USB DSL modems +endef + +$(eval $(call KernelPackage,usb-atm)) + + +define AddDepends/usb-atm + SUBMENU:=$(USB_MENU) + DEPENDS+=kmod-usb-atm $(1) +endef + + +define KernelPackage/usb-atm-speedtouch + TITLE:=SpeedTouch USB ADSL modems support + KCONFIG:=CONFIG_USB_SPEEDTOUCH + FILES:=$(LINUX_DIR)/drivers/usb/atm/speedtch.ko + AUTOLOAD:=$(call AutoProbe,speedtch) + $(call AddDepends/usb-atm) +endef + +define KernelPackage/usb-atm-speedtouch/description + Kernel support for SpeedTouch USB ADSL modems +endef + +$(eval $(call KernelPackage,usb-atm-speedtouch)) + + +define KernelPackage/usb-atm-ueagle + TITLE:=Eagle 8051 based USB ADSL modems support + FILES:=$(LINUX_DIR)/drivers/usb/atm/ueagle-atm.ko + KCONFIG:=CONFIG_USB_UEAGLEATM + AUTOLOAD:=$(call AutoProbe,ueagle-atm) + $(call AddDepends/usb-atm) +endef + +define KernelPackage/usb-atm-ueagle/description + Kernel support for Eagle 8051 based USB ADSL modems +endef + +$(eval $(call KernelPackage,usb-atm-ueagle)) + + +define KernelPackage/usb-atm-cxacru + TITLE:=cxacru + FILES:=$(LINUX_DIR)/drivers/usb/atm/cxacru.ko + KCONFIG:=CONFIG_USB_CXACRU + AUTOLOAD:=$(call AutoProbe,cxacru) + $(call AddDepends/usb-atm) +endef + +define KernelPackage/usb-atm-cxacru/description + Kernel support for cxacru based USB ADSL modems +endef + +$(eval $(call KernelPackage,usb-atm-cxacru)) + + +define KernelPackage/usb-net + TITLE:=Kernel modules for USB-to-Ethernet convertors + DEPENDS:=+kmod-mii + KCONFIG:=CONFIG_USB_USBNET \ + CONFIG_USB_NET_DRIVERS + AUTOLOAD:=$(call AutoProbe,usbnet) + FILES:=$(LINUX_DIR)/drivers/$(USBNET_DIR)/usbnet.ko + $(call AddDepends/usb) +endef + +define KernelPackage/usb-net/description + Kernel modules for USB-to-Ethernet convertors +endef + +$(eval $(call KernelPackage,usb-net)) + + +define AddDepends/usb-net + SUBMENU:=$(USB_MENU) + DEPENDS+=+kmod-usb-net $(1) +endef + + +define KernelPackage/usb-net-aqc111 + TITLE:=Support for USB-to-Ethernet Aquantia AQtion 5/2.5GbE + KCONFIG:=CONFIG_USB_NET_AQC111 + FILES:=$(LINUX_DIR)/drivers/$(USBNET_DIR)/aqc111.ko + AUTOLOAD:=$(call AutoProbe,aqc111) + $(call AddDepends/usb-net) +endef + +define KernelPackage/usb-net-aqc111/description + Support for USB-to-Ethernet Aquantia AQtion 5/2.5GbE +endef + +$(eval $(call KernelPackage,usb-net-aqc111)) + + +define KernelPackage/usb-net-asix + TITLE:=Kernel module for USB-to-Ethernet Asix convertors + DEPENDS:=+kmod-libphy + KCONFIG:=CONFIG_USB_NET_AX8817X + FILES:=$(LINUX_DIR)/drivers/$(USBNET_DIR)/asix.ko + AUTOLOAD:=$(call AutoProbe,asix) + $(call AddDepends/usb-net) +endef + +define KernelPackage/usb-net-asix/description + Kernel module for USB-to-Ethernet Asix convertors +endef + +$(eval $(call KernelPackage,usb-net-asix)) + + +define KernelPackage/usb-net-asix-ax88179 + TITLE:=Kernel module for USB-to-Gigabit-Ethernet Asix convertors + DEPENDS:=+kmod-libphy + KCONFIG:=CONFIG_USB_NET_AX88179_178A + FILES:=$(LINUX_DIR)/drivers/$(USBNET_DIR)/ax88179_178a.ko + AUTOLOAD:=$(call AutoProbe,ax88179_178a) + $(call AddDepends/usb-net) +endef + +define KernelPackage/usb-net-asix-ax88179/description + Kernel module for USB-to-Ethernet ASIX AX88179 based USB 3.0/2.0 + to Gigabit Ethernet adapters. +endef + +$(eval $(call KernelPackage,usb-net-asix-ax88179)) + + +define KernelPackage/usb-net-hso + TITLE:=Kernel module for Option USB High Speed Mobile Devices + KCONFIG:=CONFIG_USB_HSO + FILES:= \ + $(LINUX_DIR)/drivers/$(USBNET_DIR)/hso.ko + AUTOLOAD:=$(call AutoProbe,hso) + $(call AddDepends/usb-net) + $(call AddDepends/rfkill) +endef + +define KernelPackage/usb-net-hso/description + Kernel module for Option USB High Speed Mobile Devices +endef + +$(eval $(call KernelPackage,usb-net-hso)) + + +define KernelPackage/usb-net-kaweth + TITLE:=Kernel module for USB-to-Ethernet Kaweth convertors + KCONFIG:=CONFIG_USB_KAWETH + FILES:=$(LINUX_DIR)/drivers/$(USBNET_DIR)/kaweth.ko + AUTOLOAD:=$(call AutoProbe,kaweth) + $(call AddDepends/usb-net) +endef + +define KernelPackage/usb-net-kaweth/description + Kernel module for USB-to-Ethernet Kaweth convertors +endef + +$(eval $(call KernelPackage,usb-net-kaweth)) + + +define KernelPackage/usb-net-pegasus + TITLE:=Kernel module for USB-to-Ethernet Pegasus convertors + KCONFIG:=CONFIG_USB_PEGASUS + FILES:=$(LINUX_DIR)/drivers/$(USBNET_DIR)/pegasus.ko + AUTOLOAD:=$(call AutoProbe,pegasus) + $(call AddDepends/usb-net) +endef + +define KernelPackage/usb-net-pegasus/description + Kernel module for USB-to-Ethernet Pegasus convertors +endef + +$(eval $(call KernelPackage,usb-net-pegasus)) + + +define KernelPackage/usb-net-mcs7830 + TITLE:=Kernel module for USB-to-Ethernet MCS7830 convertors + KCONFIG:=CONFIG_USB_NET_MCS7830 + FILES:=$(LINUX_DIR)/drivers/$(USBNET_DIR)/mcs7830.ko + AUTOLOAD:=$(call AutoProbe,mcs7830) + $(call AddDepends/usb-net) +endef + +define KernelPackage/usb-net-mcs7830/description + Kernel module for USB-to-Ethernet MCS7830 convertors +endef + +$(eval $(call KernelPackage,usb-net-mcs7830)) + + +define KernelPackage/usb-net-smsc95xx + TITLE:=SMSC LAN95XX based USB 2.0 10/100 ethernet devices + KCONFIG:=CONFIG_USB_NET_SMSC95XX + FILES:=$(LINUX_DIR)/drivers/$(USBNET_DIR)/smsc95xx.ko + AUTOLOAD:=$(call AutoProbe,smsc95xx) + $(call AddDepends/usb-net, +kmod-lib-crc16) +endef + +define KernelPackage/usb-net-smsc95xx/description + Kernel module for SMSC LAN95XX based devices +endef + +$(eval $(call KernelPackage,usb-net-smsc95xx)) + + +define KernelPackage/usb-net-dm9601-ether + TITLE:=Support for DM9601 ethernet connections + KCONFIG:=CONFIG_USB_NET_DM9601 + FILES:=$(LINUX_DIR)/drivers/$(USBNET_DIR)/dm9601.ko + AUTOLOAD:=$(call AutoProbe,dm9601) + $(call AddDepends/usb-net) +endef + +define KernelPackage/usb-net-dm9601-ether/description + Kernel support for USB DM9601 devices +endef + +$(eval $(call KernelPackage,usb-net-dm9601-ether)) + +define KernelPackage/usb-net-cdc-ether + TITLE:=Support for cdc ethernet connections + KCONFIG:=CONFIG_USB_NET_CDCETHER + FILES:=$(LINUX_DIR)/drivers/$(USBNET_DIR)/cdc_ether.ko + AUTOLOAD:=$(call AutoProbe,cdc_ether) + $(call AddDepends/usb-net) +endef + +define KernelPackage/usb-net-cdc-ether/description + Kernel support for USB CDC Ethernet devices +endef + +$(eval $(call KernelPackage,usb-net-cdc-ether)) + + +define KernelPackage/usb-net-cdc-eem + TITLE:=Support for CDC EEM connections + KCONFIG:=CONFIG_USB_NET_CDC_EEM + FILES:=$(LINUX_DIR)/drivers/$(USBNET_DIR)/cdc_eem.ko + AUTOLOAD:=$(call AutoProbe,cdc_eem) + $(call AddDepends/usb-net) +endef + +define KernelPackage/usb-net-cdc-eem/description + Kernel support for USB CDC EEM +endef + +$(eval $(call KernelPackage,usb-net-cdc-eem)) + + +define KernelPackage/usb-net-cdc-subset + TITLE:=Support for CDC Ethernet subset connections + KCONFIG:= \ + CONFIG_USB_NET_CDC_SUBSET \ + CONFIG_USB_ARMLINUX + FILES:=$(LINUX_DIR)/drivers/$(USBNET_DIR)/cdc_subset.ko + AUTOLOAD:=$(call AutoProbe,cdc_subset) + $(call AddDepends/usb-net) +endef + +define KernelPackage/usb-net-cdc-subset/description + Kernel support for Simple USB Network Links (CDC Ethernet subset) +endef + +$(eval $(call KernelPackage,usb-net-cdc-subset)) + + +define KernelPackage/usb-net-qmi-wwan + TITLE:=QMI WWAN driver + KCONFIG:=CONFIG_USB_NET_QMI_WWAN + FILES:= $(LINUX_DIR)/drivers/$(USBNET_DIR)/qmi_wwan.ko + AUTOLOAD:=$(call AutoProbe,qmi_wwan) + $(call AddDepends/usb-net,+kmod-usb-wdm) +endef + +define KernelPackage/usb-net-qmi-wwan/description + QMI WWAN driver for Qualcomm MSM based 3G and LTE modems +endef + +$(eval $(call KernelPackage,usb-net-qmi-wwan)) + + +define KernelPackage/usb-net-rtl8150 + TITLE:=Kernel module for USB-to-Ethernet Realtek convertors + KCONFIG:=CONFIG_USB_RTL8150 + FILES:=$(LINUX_DIR)/drivers/$(USBNET_DIR)/rtl8150.ko + AUTOLOAD:=$(call AutoProbe,rtl8150) + $(call AddDepends/usb-net) +endef + +define KernelPackage/usb-net-rtl8150/description + Kernel module for USB-to-Ethernet Realtek 8150 convertors +endef + +$(eval $(call KernelPackage,usb-net-rtl8150)) + + +define KernelPackage/usb-net-rtl8152 + TITLE:=Kernel module for USB-to-Ethernet Realtek convertors + KCONFIG:=CONFIG_USB_RTL8152 + FILES:=$(LINUX_DIR)/drivers/$(USBNET_DIR)/r8152.ko + AUTOLOAD:=$(call AutoProbe,r8152) + $(call AddDepends/usb-net) +endef + +define KernelPackage/usb-net-rtl8152/description + Kernel module for USB-to-Ethernet Realtek 8152 USB2.0/3.0 convertors +endef + +$(eval $(call KernelPackage,usb-net-rtl8152)) + + +define KernelPackage/usb-net-sr9700 + TITLE:=Support for CoreChip SR9700 ethernet devices + KCONFIG:=CONFIG_USB_NET_SR9700 + FILES:=$(LINUX_DIR)/drivers/$(USBNET_DIR)/sr9700.ko + AUTOLOAD:=$(call AutoProbe,sr9700) + $(call AddDepends/usb-net) +endef + +define KernelPackage/usb-net-sr9700/description + Kernel module for CoreChip-sz SR9700 based USB 1.1 10/100 ethernet devices +endef + +$(eval $(call KernelPackage,usb-net-sr9700)) + + +define KernelPackage/usb-net-rndis + TITLE:=Support for RNDIS connections + KCONFIG:=CONFIG_USB_NET_RNDIS_HOST + FILES:= $(LINUX_DIR)/drivers/$(USBNET_DIR)/rndis_host.ko + AUTOLOAD:=$(call AutoProbe,rndis_host) + $(call AddDepends/usb-net,+kmod-usb-net-cdc-ether) +endef + +define KernelPackage/usb-net-rndis/description + Kernel support for RNDIS connections +endef + +$(eval $(call KernelPackage,usb-net-rndis)) + + +define KernelPackage/usb-net-cdc-mbim + SUBMENU:=$(USB_MENU) + TITLE:=Kernel module for MBIM Devices + KCONFIG:=CONFIG_USB_NET_CDC_MBIM + FILES:= \ + $(LINUX_DIR)/drivers/$(USBNET_DIR)/cdc_mbim.ko + AUTOLOAD:=$(call AutoProbe,cdc_mbim) + $(call AddDepends/usb-net,+kmod-usb-wdm +kmod-usb-net-cdc-ncm) +endef + +define KernelPackage/usb-net-cdc-mbim/description + Kernel module for CDC MBIM (Mobile Broadband Interface Model) devices +endef + +$(eval $(call KernelPackage,usb-net-cdc-mbim)) + + +define KernelPackage/usb-net-cdc-ncm + TITLE:=Support for CDC NCM connections + KCONFIG:=CONFIG_USB_NET_CDC_NCM + FILES:= $(LINUX_DIR)/drivers/$(USBNET_DIR)/cdc_ncm.ko + AUTOLOAD:=$(call AutoProbe,cdc_ncm) + $(call AddDepends/usb-net) +endef + +define KernelPackage/usb-net-cdc-ncm/description + Kernel support for CDC NCM connections +endef + +$(eval $(call KernelPackage,usb-net-cdc-ncm)) + + +define KernelPackage/usb-net-huawei-cdc-ncm + TITLE:=Support for Huawei CDC NCM connections + KCONFIG:=CONFIG_USB_NET_HUAWEI_CDC_NCM + FILES:= $(LINUX_DIR)/drivers/$(USBNET_DIR)/huawei_cdc_ncm.ko + AUTOLOAD:=$(call AutoProbe,huawei_cdc_ncm) + $(call AddDepends/usb-net,+kmod-usb-net-cdc-ncm +kmod-usb-wdm) +endef + +define KernelPackage/usb-net-huawei-cdc-ncm/description + Kernel support for Huawei CDC NCM connections +endef + +$(eval $(call KernelPackage,usb-net-huawei-cdc-ncm)) + + +define KernelPackage/usb-net-sierrawireless + TITLE:=Support for Sierra Wireless devices + KCONFIG:=CONFIG_USB_SIERRA_NET + FILES:=$(LINUX_DIR)/drivers/net/usb/sierra_net.ko + AUTOLOAD:=$(call AutoProbe,sierra_net) + $(call AddDepends/usb-net) +endef + +define KernelPackage/usb-net-sierrawireless/description + Kernel support for Sierra Wireless devices +endef + +$(eval $(call KernelPackage,usb-net-sierrawireless)) + + +define KernelPackage/usb-net-ipheth + TITLE:=Apple iPhone USB Ethernet driver + KCONFIG:=CONFIG_USB_IPHETH + FILES:=$(LINUX_DIR)/drivers/net/usb/ipheth.ko + AUTOLOAD:=$(call AutoProbe,ipheth) + $(call AddDepends/usb-net) +endef + +define KernelPackage/usb-net-ipheth/description + Kernel support for Apple iPhone USB Ethernet driver +endef + +$(eval $(call KernelPackage,usb-net-ipheth)) + + +define KernelPackage/usb-net-kalmia + TITLE:=Samsung Kalmia based LTE USB modem + KCONFIG:=CONFIG_USB_NET_KALMIA + FILES:=$(LINUX_DIR)/drivers/net/usb/kalmia.ko + AUTOLOAD:=$(call AutoProbe,kalmia) + $(call AddDepends/usb-net) +endef + +define KernelPackage/usb-net-kalmia/description + Kernel support for Samsung Kalmia based LTE USB modem +endef + +$(eval $(call KernelPackage,usb-net-kalmia)) + +define KernelPackage/usb-net-pl + TITLE:=Prolific PL-2301/2302/25A1 based cables + KCONFIG:=CONFIG_USB_NET_PLUSB + FILES:=$(LINUX_DIR)/drivers/net/usb/plusb.ko + AUTOLOAD:=$(call AutoProbe,plusb) + $(call AddDepends/usb-net) +endef + +define KernelPackage/usb-net-pl/description + Kernel support for Prolific PL-2301/2302/25A1 based cables +endef + +$(eval $(call KernelPackage,usb-net-pl)) + +define KernelPackage/usb-hid + TITLE:=Support for USB Human Input Devices + KCONFIG:=CONFIG_HID_SUPPORT=y CONFIG_USB_HID CONFIG_USB_HIDDEV=y + DEPENDS:=+kmod-hid +kmod-hid-generic +kmod-input-evdev + FILES:=$(LINUX_DIR)/drivers/$(USBHID_DIR)/usbhid.ko + AUTOLOAD:=$(call AutoProbe,usbhid) + $(call AddDepends/usb) +endef + +define KernelPackage/usb-hid/description + Kernel support for USB HID devices such as keyboards and mice +endef + +$(eval $(call KernelPackage,usb-hid)) + + +define KernelPackage/usb-hid-cp2112 + SUBMENU:=$(USB_MENU) + TITLE:=Silicon Labs CP2112 HID USB to SMBus Master Bridge + KCONFIG:=CONFIG_GPIOLIB=y CONFIG_HID_CP2112 + DEPENDS:=+kmod-usb-hid +kmod-i2c-core + FILES:=$(LINUX_DIR)/drivers/hid/hid-cp2112.ko + AUTOLOAD:=$(call AutoProbe,hid-cp2112) +endef + +define KernelPackage/usb-hid-cp2112/description + HID device driver which registers as an i2c adapter and gpiochip to expose + these functions of the CP2112. +endef + +$(eval $(call KernelPackage,usb-hid-cp2112)) + + +define KernelPackage/usb-yealink + TITLE:=USB Yealink VOIP phone + DEPENDS:=+kmod-input-evdev + KCONFIG:=CONFIG_USB_YEALINK CONFIG_INPUT_YEALINK CONFIG_INPUT=m CONFIG_INPUT_MISC=y + FILES:=$(LINUX_DIR)/drivers/$(USBINPUT_DIR)/yealink.ko + AUTOLOAD:=$(call AutoProbe,yealink) + $(call AddDepends/usb) +endef + +define KernelPackage/usb-yealink/description + Kernel support for Yealink VOIP phone +endef + +$(eval $(call KernelPackage,usb-yealink)) + + +define KernelPackage/usb-cm109 + TITLE:=Support for CM109 device + DEPENDS:=+kmod-input-evdev + KCONFIG:=CONFIG_USB_CM109 CONFIG_INPUT_CM109 CONFIG_INPUT=m CONFIG_INPUT_MISC=y + FILES:=$(LINUX_DIR)/drivers/$(USBINPUT_DIR)/cm109.ko + AUTOLOAD:=$(call AutoProbe,cm109) + $(call AddDepends/usb) +endef + +define KernelPackage/usb-cm109/description + Kernel support for CM109 VOIP phone +endef + +$(eval $(call KernelPackage,usb-cm109)) + + +define KernelPackage/usb-test + TITLE:=USB Testing Driver + DEPENDS:=@DEVEL + KCONFIG:=CONFIG_USB_TEST + FILES:=$(LINUX_DIR)/drivers/usb/misc/usbtest.ko + $(call AddDepends/usb) +endef + +define KernelPackage/usb-test/description + Kernel support for testing USB Host Controller software +endef + +$(eval $(call KernelPackage,usb-test)) + + +define KernelPackage/usbip + TITLE := USB-over-IP kernel support + KCONFIG:= \ + CONFIG_USBIP_CORE \ + CONFIG_USBIP_DEBUG=n + FILES:=$(LINUX_DIR)/drivers/usb/usbip/usbip-core.ko + AUTOLOAD:=$(call AutoProbe,usbip-core) + $(call AddDepends/usb) +endef + +$(eval $(call KernelPackage,usbip)) + + +define KernelPackage/usbip-client + TITLE := USB-over-IP client driver + DEPENDS := +kmod-usbip + KCONFIG := CONFIG_USBIP_VHCI_HCD + FILES :=$(LINUX_DIR)/drivers/usb/usbip/vhci-hcd.ko + AUTOLOAD := $(call AutoProbe,vhci-hcd) + $(call AddDepends/usb) +endef + +$(eval $(call KernelPackage,usbip-client)) + + +define KernelPackage/usbip-server +$(call KernelPackage/usbip/Default) + TITLE := USB-over-IP host driver + DEPENDS := +kmod-usbip + KCONFIG := CONFIG_USBIP_HOST + FILES :=$(LINUX_DIR)/drivers/usb/usbip/usbip-host.ko + AUTOLOAD := $(call AutoProbe,usbip-host) + $(call AddDepends/usb) +endef + +$(eval $(call KernelPackage,usbip-server)) + + +define KernelPackage/usb-chipidea + TITLE:=Host and device support for Chipidea controllers + DEPENDS:=+USB_GADGET_SUPPORT:kmod-usb-gadget @TARGET_ath79 +kmod-usb-ehci +kmod-usb-phy-nop + KCONFIG:= \ + CONFIG_EXTCON \ + CONFIG_USB_CHIPIDEA \ + CONFIG_USB_CHIPIDEA_HOST=y \ + CONFIG_USB_CHIPIDEA_UDC=y \ + CONFIG_USB_CHIPIDEA_DEBUG=y + FILES:= \ + $(LINUX_DIR)/drivers/extcon/extcon-core.ko \ + $(LINUX_DIR)/drivers/usb/chipidea/ci_hdrc.ko \ + $(LINUX_DIR)/drivers/usb/common/ulpi.ko \ + $(LINUX_DIR)/drivers/usb/roles/roles.ko + AUTOLOAD:=$(call AutoLoad,39,ci_hdrc,1) + $(call AddDepends/usb) +endef + +define KernelPackage/usb-chipidea/description + Kernel support for USB Chipidea controllers +endef + +$(eval $(call KernelPackage,usb-chipidea)) + + +define KernelPackage/usb-chipidea2 + TITLE:=Host and device support for Chipidea2 controllers + DEPENDS:=+kmod-usb-chipidea + KCONFIG:= \ + CONFIG_EXTCON \ + CONFIG_USB_CHIPIDEA \ + CONFIG_USB_CHIPIDEA_HOST=y \ + CONFIG_USB_CHIPIDEA_UDC=y \ + CONFIG_USB_CHIPIDEA_DEBUG=y + FILES:= \ + $(LINUX_DIR)/drivers/extcon/extcon-core.ko \ + $(LINUX_DIR)/drivers/usb/chipidea/ci_hdrc_usb2.ko + AUTOLOAD:=$(call AutoLoad,39,ci_hdrc_usb2,1) + $(call AddDepends/usb) +endef + +define KernelPackage/usb-chipidea2/description + Kernel support for USB Chipidea controllers +endef + +$(eval $(call KernelPackage,usb-chipidea2)) + + +define KernelPackage/usbmon + TITLE:=USB traffic monitor + KCONFIG:=CONFIG_USB_MON + $(call AddDepends/usb) + FILES:=$(LINUX_DIR)/drivers/usb/mon/usbmon.ko + AUTOLOAD:=$(call AutoProbe,usbmon) +endef + +define KernelPackage/usbmon/description + Kernel support for USB traffic monitoring +endef + +$(eval $(call KernelPackage,usbmon)) + +XHCI_MODULES := xhci-hcd xhci-pci xhci-plat-hcd +ifdef CONFIG_TARGET_ramips_mt7621 + XHCI_MODULES += xhci-mtk +endif +XHCI_FILES := $(wildcard $(patsubst %,$(LINUX_DIR)/drivers/usb/host/%.ko,$(XHCI_MODULES))) +XHCI_AUTOLOAD := $(patsubst $(LINUX_DIR)/drivers/usb/host/%.ko,%,$(XHCI_FILES)) + +define KernelPackage/usb3 + TITLE:=Support for USB3 controllers + DEPENDS:= \ + +TARGET_bcm53xx:kmod-usb-bcma \ + +TARGET_bcm53xx:kmod-phy-bcm-ns-usb3 + KCONFIG:= \ + CONFIG_USB_PCI=y \ + CONFIG_USB_XHCI_HCD \ + CONFIG_USB_XHCI_PCI \ + CONFIG_USB_XHCI_PLATFORM \ + CONFIG_USB_XHCI_MTK \ + CONFIG_USB_XHCI_HCD_DEBUGGING=n + FILES:= \ + $(XHCI_FILES) + AUTOLOAD:=$(call AutoLoad,54,$(XHCI_AUTOLOAD),1) + $(call AddDepends/usb) +endef + +define KernelPackage/usb3/description + Kernel support for USB3 (XHCI) controllers +endef + +$(eval $(call KernelPackage,usb3)) + + +define KernelPackage/usb-net2280 + TITLE:=Support for NetChip 228x PCI USB peripheral controller + KCONFIG:= \ + CONFIG_USB_PCI=y \ + CONFIG_USB_NET2280 + DEPENDS:=@PCI_SUPPORT +kmod-usb-gadget + FILES:=$(LINUX_DIR)/drivers/usb/gadget/udc/net2280.ko + AUTOLOAD:=$(call AutoLoad,46,net2280) + $(call AddDepends/usb) +endef + +define KernelPackage/usb-net2280/description + Kernel support for NetChip 228x / PLX USB338x PCI USB peripheral controller. +endef + +$(eval $(call KernelPackage,usb-net2280)) + +define KernelPackage/chaoskey + SUBMENU:=$(USB_MENU) + TITLE:=Chaoskey hardware RNG support + DEPENDS:=+kmod-random-core + KCONFIG:=CONFIG_USB_CHAOSKEY + FILES:=$(LINUX_DIR)/drivers/usb/misc/chaoskey.ko + AUTOLOAD:=$(call AutoProbe,chaoskey) + $(call AddDepends/usb) +endef + +define KernelPackage/chaoskey/description + Kernel module for chaoskey, USB attached true random number generator +endef + +$(eval $(call KernelPackage,chaoskey)) + diff --git a/root/package/kernel/mac80211.ipq60xx.tar.gz b/root/package/kernel/mac80211.ipq60xx.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..1691febd7c4f77deb8997c99bb3f3cb81bfb5e57 GIT binary patch literal 319565 zcmV(+K;6F|iwFS4oH=3u1MEEgciP6X{T2R-Ev2Spotd2#o$5xTtSU?Ji2fpFZJInLKmcSeE!AGi5G@R+wu|vGjL}jNJKAGW@ z5wNGaS{Zoksi7)5mBa7u?y!H}Xk@d7V-KxSF~4uDy^}4(6R9&2(~)|ronF3scX_cL0-6}w*=mrN7}VQtkeL|N-foZ)B*?qjJcyZR zI0)X3V>6vzUcBkP)vk_De>r~J(WJE7os*ll3Hh^@Ele8iKb-9S`}ZM%_wh#U=Az%} z^*ik?FtUXfqJ+1TUIJ&YfBd$4v6(hlk^;+bjRyY55Y8$mwM8Py_|+G zKndt9Yto@*Q}PRSZ92K)vz7Q-y(_ipP!A@4mJmTz5-f~t0@UipDcrV0DNF^iPWTxVK5su*{NqDc~&r8e;TD8uD-j4sKekeK;r z5WUvBxw^W%?n?nc^S=UbkEybyL(*-nX;)Lia@3c;lzSI z`L2OqJGgZK>+wzh^8E6+jfffH$ARx$xz0d3Tn1a%g86#(Z?Fz-O|mY;4UP|7m`hhB zQ6~VsBT3ZdeM>3SH%b7#c8M>bY=+-Ss$0osG6Tn%keoDeNh;X&AQ+7-dz1qhlg?m9 zk3eLWXqbG&Q~)uilNsCy>FM|6U$2NYB==50=DO_@-{IJZyD|wE(m{3`hMuu-MOMM5 zBQxFglbYX;S^Z)WvxCCYx+5=t=Gl*?C>ODH@^`I(DN5YA_RqTQcITqm+PK7JO0ZM0 z5R?S*g4xV*AMp%ADv@HI%m21MfUp0jN-4~Lo*}D~K9Py55KoH7-jP5;dZVX}!ijRc zCyNKX+>VV1R?r$FTJK|bZ&QGIE1Saf_9;S(r?$@!T$t6C!UR$ZQ^<~hK!OR~#?Z*$ z78B!UnXM=KBA%=7P8N*@q;?yZ8QWKeEs6c(%aEwz&~m49oyG9o$STJ1b^)91W3~~Y zw*wLOq&X?Qtwk74pCYVh$;ykdwymtZ?PO){KvwP0+}cl-mAQMvGGn}LD=c$6VHv>= z*5*yPHgC2N7FKmOtj%EeYcq(RwqBdTW)ZS&`!hwzHv3x$E-H|%u|7u3ZbipXMo(Lb zj+ZhviVkZKe?xLeqigFvFhJr`#DYbt5I1KQ;okWf5@YOy4_nEJ6=Aj#Q^};B;aW4_ zVtGY>Y~+_Hl^ z5^iZcALuw>6NcDf+FRS1v!Qdx#VwmZCOWZ$=>WQ&p{)NL-@Jm6*TOHn5@nIbwR}8M zuvNFs$c=<06Uk#VX8=-iv7b(y*_6WG5l4=75CWN4Q_H6&8B*O3nB9BSKOkM3F>4vY zLay$4)T8 zxk2PQ@ur|y>cYS&ojUG4h~M#Nu7$f>K+44W33Lof5hlcD7*H@IYlssLU1!R`ESvCh z(252Cs_PR2&`A%#q%ILM4AMYh2yV_xq9~^asD`vo5P85AkZ#-D3*hKM;PXAZ1vYF` z5H%>@aBSaoCS*RgjIqc^1(Y{NfQh4!#)F6F1`YLuCn>Dl zSrV(R-C1A1c&ub`d>XmPM++Grvze#*MqrlzWu@5x1`+anG&{j!?1mWQ`B*@A<_?HK zG|$F@q4C*BwyWDjRzc!gA#tf}j*;x};h77H9a)fGyL$;vMN}crk1xnY_f*EAec?V6 zl?$<^rpDT2Vyz69T=f71$?uy5C(c{8lT0#wNb|Qc8p`vFm@ovfuK)(zDg<0~oa_*H z=s;A?V(Ax~6o^VAaJ60AhX{7+OlN^lNe|-h?JEXwde*&S;RfRBJ#nDP6~M}+_y4M@eEt6Oi(D!CXH~>q&hd2pSCm@y ztNy>lwNC$GBH%rr?G5q2u2d__@&B-r+Z{2;B;Y6>^-| zBnPXM0soKzxrjJMTSSxSCkx`cWbe56KYL_PFaEiQ_yu|a5~CEi>L^Dn8>hiWTr61a*0KYd<=rv#kgQ>`!6*682>(zLJ=F6G%Y!pgTnzNP?_QK1P#_s8 zfQ;$b6y?Ccva%iIGPG<{lU5NKL#RiP3ZO-Mi7OMCjVW?`k0YcxB~7WQqY35K+K^xc z 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%0DwY`DJX_q(+k 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

-?Xhti*qXuj;e>_eriq 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 N{{ow1@e2U37y#WG_iz9J literal 0 HcmV?d00001 diff --git a/root/package/kernel/mac80211.orig.tar.gz b/root/package/kernel/mac80211.orig.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..3264037ef855814e9ebc41e87faaceb29baec02a GIT binary patch literal 418659 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.Fd4?A;gTxrxz!SDN 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 z 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 z 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

5QR5-C~ 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 bp@e!-IqdhJ|L0Q){z@zUJ}8JpN{#eC;yOYg literal 0 HcmV?d00001 diff --git a/root/package/link4all/ath11k-firmware/Makefile b/root/package/link4all/ath11k-firmware/Makefile new file mode 100644 index 00000000..5c9d33d1 --- /dev/null +++ b/root/package/link4all/ath11k-firmware/Makefile @@ -0,0 +1,66 @@ +# +# Copyright (C) 2021 OpenWrt.org +# +# This is free software, licensed under the GNU General Public License v2. +# See /LICENSE for more information. +# + +include $(TOPDIR)/rules.mk + +PKG_NAME:=ath11k-firmware +PKG_SOURCE_DATE:=2021-07-20 +PKG_SOURCE_VERSION:=d4003c1921810adcc455d46f17776a3392b29436 +PKG_MIRROR_HASH:=6839081bafbc56d3b8e41d30790b20813c349cbc1732e887347ef5ed3ea717e4 +PKG_RELEASE:=1 + +PKG_SOURCE_PROTO:=git +PKG_SOURCE_URL:=https://github.com/kvalo/ath11k-firmware.git + +PKG_MAINTAINER:=Robert Marko + +include $(INCLUDE_DIR)/package.mk + +define Package/ath11k-firmware-default + SECTION:=firmware + CATEGORY:=LINK4ALL + URL:=$(PKG_SOURCE_URL) + DEPENDS:= +endef + +define Package/ath11k-firmware-ipq6018 +$(Package/ath11k-firmware-default) + TITLE:=ath11k firmware for IPQ6018 devices +endef + +define Package/ath11k-firmware-ipq8074 +$(Package/ath11k-firmware-default) + TITLE:=ath11k firmware for IPQ8074 devices +endef + +define Build/Compile + true +endef + +define Package/ath11k-firmware-ipq6018/install + $(INSTALL_DIR) $(1)/lib/firmware/ath11k/IPQ6018/hw1.0 + $(INSTALL_DIR) $(1)/lib/firmware/IPQ6018 + $(INSTALL_DATA) \ + $(PKG_BUILD_DIR)/IPQ6018/hw1.0/2.5.0.1/WLAN.HK.2.5.0.1-01100-QCAHKSWPL_SILICONZ-1/* \ + $(1)/lib/firmware/IPQ6018/ + $(INSTALL_DATA) ./files/board-2.bin.IPQ6018 \ + $(1)/lib/firmware/ath11k/IPQ6018/hw1.0/board-2.bin +endef + +define Package/ath11k-firmware-ipq8074/install + $(INSTALL_DIR) $(1)/lib/firmware/ath11k/IPQ8074/hw2.0 + $(INSTALL_DIR) $(1)/lib/firmware/IPQ8074 + $(INSTALL_DATA) \ + $(PKG_BUILD_DIR)/IPQ8074/hw2.0/board-2.bin \ + $(1)/lib/firmware/ath11k/IPQ8074/hw2.0/ + $(INSTALL_DATA) \ + $(PKG_BUILD_DIR)/IPQ8074/hw2.0/2.5.0.1/WLAN.HK.2.5.0.1-01100-QCAHKSWPL_SILICONZ-1/* \ + $(1)/lib/firmware/IPQ8074/ +endef + +$(eval $(call BuildPackage,ath11k-firmware-ipq6018)) +$(eval $(call BuildPackage,ath11k-firmware-ipq8074)) diff --git a/root/package/link4all/ath11k-firmware/files/board-2.bin.IPQ6018 b/root/package/link4all/ath11k-firmware/files/board-2.bin.IPQ6018 new file mode 100644 index 0000000000000000000000000000000000000000..7f111a11a4ecc6c0c3d4c6427159c596f852d992 GIT binary patch literal 787208 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 + * + * USB driver for USB to serial chip ch342, ch343, ch9102, etc + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * System required: + * Kernel version beyond 3.4.x + * + * Version: V1.00 + * + * Update Log: + * V1.00 - initial version + */ + +#define DEBUG +#define VERBOSE_DEBUG + +#undef DEBUG +#undef VERBOSE_DEBUG + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0)) +#include +#endif + +#include "ch343.h" + +#define DRIVER_AUTHOR "TECH39" +#define DRIVER_DESC "USB driver for USB to serial chip ch342, ch343, ch9102, etc." +#define VERSION_DESC "V1.00 On 2020.07.29" + +#define IOCTL_MAGIC 'W' +#define IOCTL_CMD_GPIOENABLE _IOW(IOCTL_MAGIC, 0x80, u16) +#define IOCTL_CMD_GPIOSET _IOW(IOCTL_MAGIC, 0x81, u16) +#define IOCTL_CMD_GPIOGET _IOWR(IOCTL_MAGIC, 0x82, u16) + +static struct usb_driver ch34x_driver; +static struct tty_driver *ch34x_tty_driver; + +static DEFINE_IDR(ch34x_minors); +static DEFINE_MUTEX(ch34x_minors_lock); + +static void ch34x_tty_set_termios(struct tty_struct *tty, + struct ktermios *termios_old); + +/* + * Look up an ch34x structure by minor. If found and not disconnected, increment + * its refcount and return it with its mutex held. + */ +static struct ch34x *ch34x_get_by_minor(unsigned int minor) +{ + struct ch34x *ch34x; + + mutex_lock(&ch34x_minors_lock); + ch34x = idr_find(&ch34x_minors, minor); + if (ch34x) { + mutex_lock(&ch34x->mutex); + if (ch34x->disconnected) { + mutex_unlock(&ch34x->mutex); + ch34x = NULL; + } else { + tty_port_get(&ch34x->port); + mutex_unlock(&ch34x->mutex); + } + } + mutex_unlock(&ch34x_minors_lock); + return ch34x; +} + +/* + * Try to find an available minor number and if found, associate it with 'ch34x'. + */ +static int ch34x_alloc_minor(struct ch34x *ch34x) +{ + int minor; + + mutex_lock(&ch34x_minors_lock); + minor = idr_alloc(&ch34x_minors, ch34x, 0, CH34X_TTY_MINORS, GFP_KERNEL); + mutex_unlock(&ch34x_minors_lock); + + return minor; +} + +/* Release the minor number associated with 'ch34x'. */ +static void ch34x_release_minor(struct ch34x *ch34x) +{ + mutex_lock(&ch34x_minors_lock); + idr_remove(&ch34x_minors, ch34x->minor); + mutex_unlock(&ch34x_minors_lock); +} + +/* + * Functions for CH34X control messages. + */ +static int ch34x_control_out(struct ch34x *ch34x, u8 request, + u16 value, u16 index) +{ + int retval; + + retval = usb_autopm_get_interface(ch34x->control); + if (retval) + return retval; + + retval = usb_control_msg(ch34x->dev, usb_sndctrlpipe(ch34x->dev, 0), + request, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT, + value, index, NULL, 0, DEFAULT_TIMEOUT); + + dev_vdbg(&ch34x->control->dev, + "ch34x_control_out(%02x,%02x,%04x,%04x)\n", + USB_DIR_OUT|0x40, request, value, index); + + usb_autopm_put_interface(ch34x->control); + + return retval < 0 ? retval : 0; +} + +static int ch34x_control_in(struct ch34x *ch34x, + u8 request, u16 value, u16 index, + char *buf, unsigned bufsize) +{ + int retval; + int i; + + retval = usb_autopm_get_interface(ch34x->control); + if (retval) + return retval; + + retval = usb_control_msg(ch34x->dev, usb_rcvctrlpipe(ch34x->dev, 0), request, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, + value, index, buf, bufsize, DEFAULT_TIMEOUT); + + dev_vdbg(&ch34x->control->dev, + "ch34x_control_in(%02x,%02x,%04x,%04x,%p,%u)\n", + USB_DIR_IN | 0x40, (u8)request, (u16)value, (u16)index, buf, + (int)bufsize); + + dev_vdbg(&ch34x->control->dev, + "ch34x_control_in result:"); + for (i = 0; i < retval; i++) { + dev_vdbg(&ch34x->control->dev, + "0x%.2x ", (u8)buf[i]); + } + + usb_autopm_put_interface(ch34x->control); + + return retval < 0 ? retval : 0; +} + +static inline int ch34x_set_control(struct ch34x *ch34x, int control) +{ + return ch34x_control_out(ch34x, VENDOR_MODEM_OUT + ch34x->iface, + ~control, 0x0000); +} + +static inline int ch34x_set_line(struct ch34x *ch34x, struct usb_cdc_line_coding *line) +{ + return 0; +} + +static int ch34x_get_status(struct ch34x *ch34x) +{ + char *buffer; + int retval; + const unsigned size = 2; + unsigned long flags; + + buffer = kmalloc(size, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + retval = ch34x_control_in(ch34x, VENDOR_READ, VENDOR_MODEM_IN + ch34x->iface, + 0, buffer, size); + if (retval < 0) + goto out; + + /* setup the private status if available */ + spin_lock_irqsave(&ch34x->read_lock, flags); + ch34x->ctrlin = (~(*buffer)) & CH34X_MODEM_STAT; + spin_unlock_irqrestore(&ch34x->read_lock, flags); + +out: + kfree(buffer); + return retval; +} + +/* -------------------------------------------------------------------------- */ + +static int ch34x_configure(struct ch34x *ch34x) +{ + char *buffer; + int r; + const unsigned size = 2; + + buffer = kmalloc(size, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + r = ch34x_control_in(ch34x, VENDOR_VERSION, 0, 0, buffer, size); + if (r < 0) + goto out; + + r = ch34x_get_status(ch34x); + if (r < 0) + goto out; + + switch (buffer[1]) { + case 0x48: + ch34x->chiptype = CHIP_CH342F; + break; + case 0x41: + ch34x->chiptype = CHIP_CH342GJK; + break; + case 0x08: + if (ch34x->idProduct == 0x55D3) + ch34x->chiptype = CHIP_CH343G; + if (ch34x->idProduct == 0x55D4) + ch34x->chiptype = CHIP_CH9102F; + break; + case 0x18: + ch34x->chiptype = CHIP_CH343G_AUTOBAUD; + break; + case 0x01: + ch34x->chiptype = CHIP_CH343K; + break; + case 0x02: + ch34x->chiptype = CHIP_CH343J; + break; + case 0x09: + ch34x->chiptype = CHIP_CH9102X; + break; + default: + break; + } + + dev_info(&ch34x->data->dev, + "%s - chip hver : 0x%2x, sver : 0x%2x, chip : %d\n", + __func__, buffer[0], buffer[1], ch34x->chiptype); +out: + kfree(buffer); + return r < 0 ? r : 0; +} + +/* + * Write buffer management. + * All of these assume proper locks taken by the caller. + */ +static int ch34x_wb_alloc(struct ch34x *ch34x) +{ + int i, wbn; + struct ch34x_wb *wb; + + wbn = 0; + i = 0; + for (;;) { + wb = &ch34x->wb[wbn]; + if (!wb->use) { + wb->use = 1; + return wbn; + } + wbn = (wbn + 1) % CH34X_NW; + if (++i >= CH34X_NW) + return -1; + } +} + +static int ch34x_wb_is_avail(struct ch34x *ch34x) +{ + int i, n; + unsigned long flags; + + n = CH34X_NW; + spin_lock_irqsave(&ch34x->write_lock, flags); + for (i = 0; i < CH34X_NW; i++) + n -= ch34x->wb[i].use; + spin_unlock_irqrestore(&ch34x->write_lock, flags); + return n; +} + +/* + * Finish write. Caller must hold ch34x->write_lock + */ +static void ch34x_write_done(struct ch34x *ch34x, struct ch34x_wb *wb) +{ + wb->use = 0; + ch34x->transmitting--; + usb_autopm_put_interface_async(ch34x->control); +} + +/* + * Poke write. + * + * the caller is responsible for locking + */ +static int ch34x_start_wb(struct ch34x *ch34x, struct ch34x_wb *wb) +{ + int rc; + + ch34x->transmitting++; + + wb->urb->transfer_buffer = wb->buf; + wb->urb->transfer_dma = wb->dmah; + wb->urb->transfer_buffer_length = wb->len; + wb->urb->dev = ch34x->dev; + + rc = usb_submit_urb(wb->urb, GFP_ATOMIC); + if (rc < 0) { + dev_err(&ch34x->data->dev, + "%s - usb_submit_urb(write bulk) failed: %d\n", + __func__, rc); + ch34x_write_done(ch34x, wb); + } + return rc; +} + +static void ch34x_update_status(struct ch34x *ch34x, + unsigned char *data, size_t len) +{ + unsigned long flags; + u8 status; + u8 difference; + u8 type = data[0]; + + if (len < 4) + return; + + switch (type) { + case CH34X_CTRL_TYPE_MODEM: + status = ~data[len - 1] & CH34X_MODEM_STAT; + + if (!ch34x->clocal && (ch34x->ctrlin & status & CH34X_CTRL_DCD)) { + dev_dbg(&ch34x->data->dev, "%s - calling hangup\n", + __func__); + tty_port_tty_hangup(&ch34x->port, false); + } + + spin_lock_irqsave(&ch34x->read_lock, flags); + difference = status ^ ch34x->ctrlin; + ch34x->ctrlin = status; + ch34x->oldcount = ch34x->iocount; + + if (!difference) { + spin_unlock_irqrestore(&ch34x->read_lock, flags); + return; + } + if (difference & CH34X_CTRL_CTS) { + dev_vdbg(&ch34x->data->dev, "%s - cts change\n", __func__); + ch34x->iocount.cts++; + } + if (difference & CH34X_CTRL_DSR) { + dev_vdbg(&ch34x->data->dev, "%s - dsr change\n", __func__); + ch34x->iocount.dsr++; + } + if (difference & CH34X_CTRL_RI) { + dev_vdbg(&ch34x->data->dev, "%s - rng change\n", __func__); + ch34x->iocount.rng++; + } + if (difference & CH34X_CTRL_DCD) { + dev_vdbg(&ch34x->data->dev, "%s - dcd change\n", __func__); + ch34x->iocount.dcd++; + } + spin_unlock_irqrestore(&ch34x->read_lock, flags); + + wake_up_interruptible(&ch34x->wioctl); + break; + case CH34X_CTRL_TYPE_OVERRUN: + spin_lock_irqsave(&ch34x->read_lock, flags); + ch34x->oldcount = ch34x->iocount; + dev_err(&ch34x->data->dev, "%s - overrun error\n", __func__); + ch34x->iocount.overrun++; + spin_unlock_irqrestore(&ch34x->read_lock, flags); + break; + case CH34X_CTRL_TYPE_PARITY: + spin_lock_irqsave(&ch34x->read_lock, flags); + ch34x->oldcount = ch34x->iocount; + dev_err(&ch34x->data->dev, "%s - parity error\n", __func__); + ch34x->iocount.parity++; + spin_unlock_irqrestore(&ch34x->read_lock, flags); + break; + case CH34X_CTRL_TYPE_FRAMING: + spin_lock_irqsave(&ch34x->read_lock, flags); + ch34x->oldcount = ch34x->iocount; + dev_err(&ch34x->data->dev, "%s - frame error\n", __func__); + ch34x->iocount.frame++; + spin_unlock_irqrestore(&ch34x->read_lock, flags); + break; + default: + dev_err(&ch34x->control->dev, + "%s - unknown status received:" + "len:%d, data0:0x%x, data1:0x%x\n", + __func__, + (int)len, data[0], data[1]); + break; + } +} + +/* Reports status changes with "interrupt" transfers */ +static void ch34x_ctrl_irq(struct urb *urb) +{ + struct ch34x *ch34x = urb->context; + unsigned char *data = urb->transfer_buffer; + unsigned int len = urb->actual_length; + int status = urb->status; + int retval; + + switch (status) { + case 0: + /* success */ + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* this urb is terminated, clean up */ + dev_dbg(&ch34x->control->dev, + "%s - urb shutting down with status: %d\n", + __func__, status); + return; + default: + dev_dbg(&ch34x->control->dev, + "%s - nonzero urb status received: %d\n", + __func__, status); + goto exit; + } + + usb_mark_last_busy(ch34x->dev); + ch34x_update_status(ch34x, data, len); +exit: + retval = usb_submit_urb(urb, GFP_ATOMIC); + if (retval && retval != -EPERM) + dev_err(&ch34x->control->dev, "%s - usb_submit_urb failed: %d\n", + __func__, retval); +} + +static int ch34x_submit_read_urb(struct ch34x *ch34x, int index, gfp_t mem_flags) +{ + int res; + + if (!test_and_clear_bit(index, &ch34x->read_urbs_free)) + return 0; + + dev_vdbg(&ch34x->data->dev, "%s - urb %d\n", __func__, index); + + res = usb_submit_urb(ch34x->read_urbs[index], mem_flags); + if (res) { + if (res != -EPERM) { + dev_err(&ch34x->data->dev, + "%s - usb_submit_urb failed: %d\n", + __func__, res); + } + set_bit(index, &ch34x->read_urbs_free); + return res; + } + + return 0; +} + +static int ch34x_submit_read_urbs(struct ch34x *ch34x, gfp_t mem_flags) +{ + int res; + int i; + + for (i = 0; i < ch34x->rx_buflimit; ++i) { + res = ch34x_submit_read_urb(ch34x, i, mem_flags); + if (res) + return res; + } + + return 0; +} + +static void ch34x_process_read_urb(struct ch34x *ch34x, struct urb *urb) +{ + if (!urb->actual_length) + return; + + tty_insert_flip_string(&ch34x->port, urb->transfer_buffer, + urb->actual_length); + tty_flip_buffer_push(&ch34x->port); +} + +static void ch34x_read_bulk_callback(struct urb *urb) +{ + struct ch34x_rb *rb = urb->context; + struct ch34x *ch34x = rb->instance; + unsigned long flags; + int status = urb->status; + + dev_vdbg(&ch34x->data->dev, "%s - urb %d, len %d\n", __func__, + rb->index, urb->actual_length); + + if (!ch34x->dev) { + set_bit(rb->index, &ch34x->read_urbs_free); + dev_dbg(&ch34x->data->dev, "%s - disconnected\n", __func__); + return; + } + + if (status) { + set_bit(rb->index, &ch34x->read_urbs_free); + dev_dbg(&ch34x->data->dev, "%s - non-zero urb status: %d\n", + __func__, status); + return; + } + + usb_mark_last_busy(ch34x->dev); + + ch34x_process_read_urb(ch34x, urb); + /* + * Unthrottle may run on another CPU which needs to see events + * in the same order. Submission has an implict barrier + */ +// smp_mb__before_atomic(); + set_bit(rb->index, &ch34x->read_urbs_free); + + /* throttle device if requested by tty */ + spin_lock_irqsave(&ch34x->read_lock, flags); + ch34x->throttled = ch34x->throttle_req; + if (!ch34x->throttled) { + spin_unlock_irqrestore(&ch34x->read_lock, flags); + ch34x_submit_read_urb(ch34x, rb->index, GFP_ATOMIC); + } else { + spin_unlock_irqrestore(&ch34x->read_lock, flags); + } +} + +/* data interface wrote those outgoing bytes */ +static void ch34x_write_bulk(struct urb *urb) +{ + struct ch34x_wb *wb = urb->context; + struct ch34x *ch34x = wb->instance; + unsigned long flags; + int status = urb->status; + + dev_vdbg(&ch34x->data->dev, "%s, len %d\n", __func__, urb->actual_length); + if (status || (urb->actual_length != urb->transfer_buffer_length)) + dev_vdbg(&ch34x->data->dev, "%s - len %d/%d, status %d\n", + __func__, + urb->actual_length, + urb->transfer_buffer_length, + status); + + spin_lock_irqsave(&ch34x->write_lock, flags); + ch34x_write_done(ch34x, wb); + spin_unlock_irqrestore(&ch34x->write_lock, flags); + schedule_work(&ch34x->work); +} + +static void ch34x_softint(struct work_struct *work) +{ + struct ch34x *ch34x = container_of(work, struct ch34x, work); + + dev_dbg(&ch34x->data->dev, "%s\n", __func__); + + tty_port_tty_wakeup(&ch34x->port); +} + +/* + * TTY handlers + */ +static int ch34x_tty_install(struct tty_driver *driver, struct tty_struct *tty) +{ + struct ch34x *ch34x; + int retval; + + dev_dbg(tty->dev, "%s\n", __func__); + + ch34x = ch34x_get_by_minor(tty->index); + if (!ch34x) + return -ENODEV; + + retval = tty_standard_install(driver, tty); + if (retval) + goto error_init_termios; + + tty->driver_data = ch34x; + + return 0; + +error_init_termios: + tty_port_put(&ch34x->port); + return retval; +} + +static int ch34x_tty_open(struct tty_struct *tty, struct file *filp) +{ + struct ch34x *ch34x = tty->driver_data; + + dev_dbg(tty->dev, "%s\n", __func__); + + return tty_port_open(&ch34x->port, tty, filp); +} + +static void ch34x_port_dtr_rts(struct tty_port *port, int raise) +{ + struct ch34x *ch34x = container_of(port, struct ch34x, port); + int res; + + dev_dbg(&ch34x->data->dev, "%s, raise:%d\n", __func__, raise); + + if (raise) + ch34x->ctrlout |= CH34X_CTRL_DTR | CH34X_CTRL_RTS; + else + ch34x->ctrlout &= ~(CH34X_CTRL_DTR | CH34X_CTRL_RTS); + + res = ch34x_set_control(ch34x, ch34x->ctrlout); + if (res) + dev_err(&ch34x->control->dev, "failed to set dtr/rts\n"); +} + +static int ch34x_port_activate(struct tty_port *port, struct tty_struct *tty) +{ + struct ch34x *ch34x = container_of(port, struct ch34x, port); + int retval = -ENODEV; + + dev_dbg(&ch34x->control->dev, "%s\n", __func__); + + mutex_lock(&ch34x->mutex); + if (ch34x->disconnected) + goto disconnected; + + retval = usb_autopm_get_interface(ch34x->control); + if (retval) + goto error_get_interface; + + /* + * FIXME: Why do we need this? Allocating 64K of physically contiguous + * memory is really nasty... + */ + set_bit(TTY_NO_WRITE_SPLIT, &tty->flags); + ch34x->control->needs_remote_wakeup = 1; + + retval = ch34x_configure(ch34x); + if (retval) + goto error_configure; + + ch34x_tty_set_termios(tty, NULL); + + /* + * Unthrottle device in case the TTY was closed while throttled. + */ + spin_lock_irq(&ch34x->read_lock); + ch34x->throttled = 0; + ch34x->throttle_req = 0; + spin_unlock_irq(&ch34x->read_lock); + + usb_autopm_put_interface(ch34x->control); + + mutex_unlock(&ch34x->mutex); + + return 0; + +error_configure: + usb_autopm_put_interface(ch34x->control); +error_get_interface: +disconnected: + mutex_unlock(&ch34x->mutex); + + return usb_translate_errors(retval); +} + +static void ch34x_port_destruct(struct tty_port *port) +{ + struct ch34x *ch34x = container_of(port, struct ch34x, port); + + dev_dbg(&ch34x->control->dev, "%s\n", __func__); + + ch34x_release_minor(ch34x); + usb_put_intf(ch34x->control); + kfree(ch34x); +} + +static void ch34x_port_shutdown(struct tty_port *port) +{ + struct ch34x *ch34x = container_of(port, struct ch34x, port); + + dev_dbg(&ch34x->control->dev, "%s\n", __func__); + +} + +static void ch34x_tty_cleanup(struct tty_struct *tty) +{ + struct ch34x *ch34x = tty->driver_data; + dev_dbg(&ch34x->control->dev, "%s\n", __func__); + tty_port_put(&ch34x->port); +} + +static void ch34x_tty_hangup(struct tty_struct *tty) +{ + struct ch34x *ch34x = tty->driver_data; + dev_dbg(&ch34x->control->dev, "%s\n", __func__); + tty_port_hangup(&ch34x->port); +} + +static void ch34x_tty_close(struct tty_struct *tty, struct file *filp) +{ + struct ch34x *ch34x = tty->driver_data; + dev_dbg(&ch34x->control->dev, "%s\n", __func__); + tty_port_close(&ch34x->port, tty, filp); +} + +static int ch34x_tty_write(struct tty_struct *tty, + const unsigned char *buf, int count) +{ + struct ch34x *ch34x = tty->driver_data; + int stat; + unsigned long flags; + int wbn; + struct ch34x_wb *wb; + + if (!count) + return 0; + + dev_vdbg(&ch34x->data->dev, "%s - count %d\n", __func__, count); + + spin_lock_irqsave(&ch34x->write_lock, flags); + wbn = ch34x_wb_alloc(ch34x); + if (wbn < 0) { + spin_unlock_irqrestore(&ch34x->write_lock, flags); + return 0; + } + wb = &ch34x->wb[wbn]; + + if (!ch34x->dev) { + wb->use = 0; + spin_unlock_irqrestore(&ch34x->write_lock, flags); + return -ENODEV; + } + + count = (count > ch34x->writesize) ? ch34x->writesize : count; + + memcpy(wb->buf, buf, count); + wb->len = count; + + stat = usb_autopm_get_interface_async(ch34x->control); + if (stat) { + wb->use = 0; + spin_unlock_irqrestore(&ch34x->write_lock, flags); + return stat; + } + + if (ch34x->susp_count) { + usb_anchor_urb(wb->urb, &ch34x->delayed); + spin_unlock_irqrestore(&ch34x->write_lock, flags); + return count; + } + + stat = ch34x_start_wb(ch34x, wb); + spin_unlock_irqrestore(&ch34x->write_lock, flags); + + if (stat < 0) + return stat; + return count; +} + +static int ch34x_tty_write_room(struct tty_struct *tty) +{ + struct ch34x *ch34x = tty->driver_data; + /* + * Do not let the line discipline to know that we have a reserve, + * or it might get too enthusiastic. + */ + return ch34x_wb_is_avail(ch34x) ? ch34x->writesize : 0; +} + +static int ch34x_tty_chars_in_buffer(struct tty_struct *tty) +{ + struct ch34x *ch34x = tty->driver_data; + /* + * if the device was unplugged then any remaining characters fell out + * of the connector ;) + */ + if (ch34x->disconnected) + return 0; + /* + * This is inaccurate (overcounts), but it works. + */ + return (CH34X_NW - ch34x_wb_is_avail(ch34x)) * ch34x->writesize; +} + +static void ch34x_tty_throttle(struct tty_struct *tty) +{ + struct ch34x *ch34x = tty->driver_data; + + dev_err(&ch34x->data->dev, "%s\n", __func__); + spin_lock_irq(&ch34x->read_lock); + ch34x->throttle_req = 1; + spin_unlock_irq(&ch34x->read_lock); +} + +static void ch34x_tty_unthrottle(struct tty_struct *tty) +{ + struct ch34x *ch34x = tty->driver_data; + unsigned int was_throttled; + + dev_err(&ch34x->data->dev, "%s\n", __func__); + spin_lock_irq(&ch34x->read_lock); + was_throttled = ch34x->throttled; + ch34x->throttled = 0; + ch34x->throttle_req = 0; + spin_unlock_irq(&ch34x->read_lock); + + if (was_throttled) + ch34x_submit_read_urbs(ch34x, GFP_KERNEL); +} + +static int ch34x_tty_break_ctl(struct tty_struct *tty, int state) +{ + struct ch34x *ch34x = tty->driver_data; + int retval; + uint16_t reg_contents; + uint8_t *break_reg; + + dev_dbg(&ch34x->control->dev, "%s\n", __func__); + + break_reg = kmalloc(2, GFP_KERNEL); + if (!break_reg) + return -1; + + if (state != 0) { + dev_dbg(&ch34x->control->dev, "%s - Enter break state requested\n", __func__); + break_reg[0] = CH34X_NBREAK_BITS; + break_reg[1] = 0x00; + } else { + dev_dbg(&ch34x->control->dev, "%s - Leave break state requested\n", __func__); + break_reg[0] = CH34X_NBREAK_BITS | CH34X_NBREAK_AUTOBITS; + break_reg[1] = 0x00; + } + dev_dbg(&ch34x->control->dev, "%s - New ch341 break register contents - reg1: %x, reg2: %x\n", + __func__, break_reg[0], break_reg[1]); + reg_contents = get_unaligned_le16(break_reg); + + /* uartB use index value */ + if (ch34x->iface) + retval = ch34x_control_out(ch34x, VENDOR_GPIO, 0x00, + reg_contents); + else + retval = ch34x_control_out(ch34x, VENDOR_GPIO, reg_contents, + 0x00); + + if (retval < 0) + dev_err(&ch34x->control->dev, "%s - USB control write error (%d)\n", + __func__, retval); + + kfree(break_reg); + + return retval; +} + +static int ch34x_tty_tiocmget(struct tty_struct *tty) +{ + struct ch34x *ch34x = tty->driver_data; + unsigned long flags; + unsigned int result; + + dev_dbg(&ch34x->control->dev, "%s\n", __func__); + + spin_lock_irqsave(&ch34x->read_lock, flags); + result = (ch34x->ctrlout & CH34X_CTRL_DTR ? TIOCM_DTR : 0) | + (ch34x->ctrlout & CH34X_CTRL_RTS ? TIOCM_RTS : 0) | + (ch34x->ctrlin & CH34X_CTRL_CTS ? TIOCM_CTS : 0) | + (ch34x->ctrlin & CH34X_CTRL_DSR ? TIOCM_DSR : 0) | + (ch34x->ctrlin & CH34X_CTRL_RI ? TIOCM_RI : 0) | + (ch34x->ctrlin & CH34X_CTRL_DCD ? TIOCM_CD : 0); + spin_unlock_irqrestore(&ch34x->read_lock, flags); + dev_dbg(&ch34x->control->dev, "%s - result = %x\n", __func__, result); + + return result; +} + +static int ch34x_tty_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) +{ + struct ch34x *ch34x = tty->driver_data; + unsigned int newctrl; + + dev_dbg(&ch34x->control->dev, "%s\n", __func__); + dev_dbg(&ch34x->control->dev, "TIOCM_DTR:0x%4x, TIOCM_RTS:0x%4x\n", TIOCM_DTR, TIOCM_RTS); + dev_dbg(&ch34x->control->dev, "set:0x%4x, clear:0x%4x\n", set, clear); + + newctrl = ch34x->ctrlout; + set = (set & TIOCM_DTR ? CH34X_CTRL_DTR : 0) | + (set & TIOCM_RTS ? CH34X_CTRL_RTS : 0); + clear = (clear & TIOCM_DTR ? CH34X_CTRL_DTR : 0) | + (clear & TIOCM_RTS ? CH34X_CTRL_RTS : 0); + + newctrl = (newctrl & ~clear) | set; + + if (ch34x->ctrlout == newctrl) + return 0; + return ch34x_set_control(ch34x, ch34x->ctrlout = newctrl); +} + +static int ch34x_tty_ioctl(struct tty_struct *tty, + unsigned int cmd, unsigned long arg) +{ + struct ch34x *ch34x = tty->driver_data; + int rv = 0; + u16 inarg; + u8 inargH, inargL; + u16 __user *argval = (u16 __user *)arg; + u8 gpiobits, gpioval; + u8 gpioenable, gpiodir; + u16 value, index; + u8 *buffer; + + dev_dbg(&ch34x->control->dev, "%s\n", __func__); + + buffer = kmalloc(8, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + switch (cmd) { + case TIOCGSERIAL: /* gets serial port data */ + break; + case TIOCSSERIAL: + break; + case TIOCMIWAIT: + break; + case TIOCGICOUNT: + break; + case IOCTL_CMD_GPIOENABLE: + if (get_user(inarg, argval)) { + rv = -EFAULT; + goto out; + } + rv = ch34x_control_in(ch34x, VENDOR_GPIO_GET, 0x00, + 0x00, buffer, 0x08); + if (rv < 0) + goto out; + /* keep A status */ + value = buffer[6] + ((u16)buffer[0] << 8); + inargH = inarg >> 8; + inargL = inarg; + gpioenable = 0x00; + gpiodir = 0x00; + if (ch34x->chiptype == CHIP_CH9102X) { + if (inargH & BIT(0)) { + gpioenable |= BIT(3); + if (inargL & BIT(0)) + gpiodir |= BIT(3); + } + if (inargH & BIT(1)) { + gpioenable |= BIT(5); + if (inargL & BIT(1)) + gpiodir |= BIT(5); + } + if (inargH & BIT(2)) { + gpioenable |= BIT(1); + if (inargL & BIT(2)) + gpiodir |= BIT(1); + } + if (inargH & BIT(3)) { + gpioenable |= BIT(7); + if (inargL & BIT(3)) + gpiodir |= BIT(7); + } + if (inargH & BIT(6)) { + gpioenable |= BIT(2); + if (inargL & BIT(6)) + gpiodir |= BIT(2); + } + } else if (ch34x->chiptype == CHIP_CH9102F) { + if (inargH & BIT(0)) { + gpioenable |= BIT(1); + if (inargL & BIT(0)) + gpiodir |= BIT(1); + } + if (inargH & BIT(1)) { + gpioenable |= BIT(7); + if (inargL & BIT(1)) + gpiodir |= BIT(7); + } + if (inargH & BIT(2)) { + gpioenable |= BIT(4); + if (inargL & BIT(2)) + gpiodir |= BIT(4); + } + if (inargH & BIT(3)) { + gpioenable |= BIT(6); + if (inargL & BIT(3)) + gpiodir |= BIT(6); + } + if (inargH & BIT(4)) { + gpioenable |= BIT(3); + if (inargL & BIT(4)) + gpiodir |= BIT(3); + } + } + index = gpioenable + ((u16)gpiodir << 8); + rv = ch34x_control_out(ch34x, VENDOR_GPIO_EN1, value, index); + if (rv < 0) + goto out; + if (ch34x->chiptype == CHIP_CH9102X) { + if ((inargH & BIT(5)) && (inargL & BIT(5))) + gpiodir = BIT(0); + else + gpiodir = 0x00; + value = gpiodir; + ch34x->gpio5dir = gpiodir; + rv = ch34x_control_out(ch34x, VENDOR_GPIO_EN2, value, 0x0000); + if (rv < 0) + goto out; + } + break; + case IOCTL_CMD_GPIOSET: + if (get_user(inarg, argval)) { + rv = -EFAULT; + goto out; + } + rv = ch34x_control_in(ch34x, VENDOR_GPIO_GET, 0x00, + 0x00, buffer, 0x08); + if (rv < 0) + goto out; + value = (u16)buffer[3] << 8; + inargH = inarg >> 8; + inargL = inarg; + gpiobits = 0x00; + gpioval = 0x00; + if (ch34x->chiptype == CHIP_CH9102X) { + if (inargH & BIT(0)) { + gpiobits |= BIT(3); + if (inargL & BIT(0)) + gpioval |= BIT(3); + } + if (inargH & BIT(1)) { + gpiobits |= BIT(5); + if (inargL & BIT(1)) + gpioval |= BIT(5); + } + if (inargH & BIT(2)) { + gpiobits |= BIT(1); + if (inargL & BIT(2)) + gpioval |= BIT(1); + } + if (inargH & BIT(3)) { + gpiobits |= BIT(7); + if (inargL & BIT(3)) + gpioval |= BIT(7); + } + if (inargH & BIT(6)) { + gpiobits |= BIT(2); + if (inargL & BIT(6)) + gpioval |= BIT(2); + } + } else if (ch34x->chiptype == CHIP_CH9102F) { + if (inargH & BIT(0)) { + gpiobits |= BIT(1); + if (inargL & BIT(0)) + gpioval |= BIT(1); + } + if (inargH & BIT(1)) { + gpiobits |= BIT(7); + if (inargL & BIT(1)) + gpioval |= BIT(7); + } + if (inargH & BIT(2)) { + gpiobits |= BIT(4); + if (inargL & BIT(2)) + gpioval |= BIT(4); + } + if (inargH & BIT(3)) { + gpiobits |= BIT(6); + if (inargL & BIT(3)) + gpioval |= BIT(6); + } + if (inargH & BIT(4)) { + gpiobits |= BIT(3); + if (inargL & BIT(4)) + gpioval |= BIT(3); + } + } + index = ((u16)gpioval << 8) + gpiobits; + rv = ch34x_control_out(ch34x, VENDOR_GPIO_SET, value, index); + if (rv < 0) + goto out; + if (ch34x->chiptype == CHIP_CH9102X && ch34x->gpio5dir) { + if ((inargH & BIT(5)) && (inargL & BIT(5))) + gpioval = BIT(0); + else + gpioval = 0x00; + value = ch34x->gpio5dir + ((u16)gpioval << 8); + rv = ch34x_control_out(ch34x, VENDOR_GPIO_EN2, value, 0x0000); + if (rv < 0) + goto out; + } + break; + case IOCTL_CMD_GPIOGET: + if (get_user(inarg, argval)) { + rv = -EFAULT; + goto out; + } + rv = ch34x_control_in(ch34x, VENDOR_GPIO_GET, 0x00, + 0x00, buffer, 0x08); + if (rv < 0) + goto out; + if (ch34x->chiptype == CHIP_CH9102X) { + if (buffer[4] & BIT(3)) + gpioval |= BIT(0); + if (buffer[4] & BIT(5)) + gpioval |= BIT(1); + if (buffer[4] & BIT(1)) + gpioval |= BIT(2); + if (buffer[4] & BIT(7)) + gpioval |= BIT(3); + if (buffer[4] & BIT(2)) + gpioval |= BIT(6); + } else if (ch34x->chiptype == CHIP_CH9102F) { + if (buffer[4] & BIT(1)) + gpioval |= BIT(0); + if (buffer[4] & BIT(7)) + gpioval |= BIT(1); + if (buffer[4] & BIT(4)) + gpioval |= BIT(2); + if (buffer[4] & BIT(6)) + gpioval |= BIT(3); + if (buffer[4] & BIT(3)) + gpioval |= BIT(4); + } + if (buffer[5] & BIT(0)) + gpioval |= BIT(5); + if (put_user(gpioval, argval)) { + rv = -EFAULT; + goto out; + } + break; + default: + rv = -ENOIOCTLCMD; + break; + } + +out: + kfree(buffer); + return rv; +} + +static int ch34x_get_baud_rate(unsigned int baud_rate, + unsigned char *factor, unsigned char *divisor) +{ + unsigned char a; + unsigned char b; + unsigned long c; + + switch (baud_rate) { + case 6000000: + case 4000000: + case 2400000: + case 921600: + case 307200: + case 256000: + b = 7; + c = 12000000; + break; + default: + if (baud_rate > 6000000/255) { + b = 3; + c = 6000000; + } else if (baud_rate > 750000/255) { + b = 2; + c = 750000; + } else if (baud_rate > 93750/255) { + b = 1; + c = 93750; + } else { + b = 0; + c = 11719; + } + break; + } + a = (unsigned char)(c / baud_rate); + if (a == 0 || a == 0xFF) + return -EINVAL; + if ((c / a - baud_rate) > (baud_rate - c / (a + 1))) + a ++; + a = 256 - a; + + *factor = a; + *divisor = b; + + return 0; +} + +static void ch34x_tty_set_termios(struct tty_struct *tty, + struct ktermios *termios_old) +{ + struct ch34x *ch34x = tty->driver_data; + struct ktermios *termios = &tty->termios; + struct usb_ch34x_line_coding newline; + int newctrl = ch34x->ctrlout; + + unsigned char divisor = 0; + unsigned char reg_count = 0; + unsigned char factor = 0; + unsigned char reg_value = 0; + unsigned short value = 0; + unsigned short index = 0; + + dev_dbg(tty->dev, "%s\n", __func__); + + if (termios_old && + !tty_termios_hw_change(&tty->termios, termios_old)) { + dev_dbg(tty->dev, "%s - nothing to change\n", __func__); + return; + } + + newline.dwDTERate = tty_get_baud_rate(tty); + + if (newline.dwDTERate == 0) + newline.dwDTERate = 9600; + ch34x_get_baud_rate(newline.dwDTERate, &factor, &divisor); + + newline.bCharFormat = termios->c_cflag & CSTOPB ? 2 : 1; + if (newline.bCharFormat == 2) + reg_value |= CH34X_LCR_STOP_BITS_2; + + newline.bParityType = termios->c_cflag & PARENB ? + (termios->c_cflag & PARODD ? 1 : 2) + + (termios->c_cflag & CMSPAR ? 2 : 0) : 0; + + switch (newline.bParityType) { + case 0x01: + reg_value |= CH34X_LCR_PAR_ODD; + dev_dbg(&ch34x->control->dev, "parity = odd\n"); + break; + case 0x02: + reg_value |= CH34X_LCR_PAR_EVEN; + dev_dbg(&ch34x->control->dev, "parity = even\n"); + break; + case 0x03: + reg_value |= CH34X_LCR_PAR_MARK; + dev_dbg(&ch34x->control->dev, "parity = mark\n"); + break; + case 0x04: + reg_value |= CH34X_LCR_PAR_SPACE; + dev_dbg(&ch34x->control->dev, "parity = space\n"); + break; + default: + dev_dbg(&ch34x->data->dev, "parity = none\n"); + break; + } + + switch (termios->c_cflag & CSIZE) { + case CS5: + newline.bDataBits = 5; + reg_value |= CH34X_LCR_CS5; + break; + case CS6: + newline.bDataBits = 6; + reg_value |= CH34X_LCR_CS6; + break; + case CS7: + newline.bDataBits = 7; + reg_value |= CH34X_LCR_CS7; + break; + case CS8: + default: + newline.bDataBits = 8; + reg_value |= CH34X_LCR_CS8; + break; + } + + /* FIXME: Needs to clear unsupported bits in the termios */ + ch34x->clocal = ((termios->c_cflag & CLOCAL) != 0); + + if (C_BAUD(tty) == B0) { + newline.dwDTERate = ch34x->line.dwDTERate; + newctrl &= ~CH34X_CTRL_DTR; + } else if (termios_old && (termios_old->c_cflag & CBAUD) == B0) { + newctrl |= CH34X_CTRL_DTR; + } + + //enable SFR_UART RX and TX + reg_value |= CH34X_LCR_ENABLE_RX | CH34X_LCR_ENABLE_TX; + //enable SFR_UART Control register and timer + reg_count |= CH34X_LCR_REG_CTRL | CH34X_LCR_REG_CLK | CH34X_LCR_REG_TIMER; + + value |= reg_count; + value |= (unsigned short)reg_value << 8; + + index |= 0x00 | divisor; + index |= (unsigned short)factor << 8; + ch34x_control_out(ch34x, VENDOR_SERIAL_INIT + ch34x->iface, value, index); + + if (memcmp(&ch34x->line, &newline, sizeof newline)) { + memcpy(&ch34x->line, &newline, sizeof newline); + dev_dbg(&ch34x->control->dev, "%s - set line: %d %d %d %d\n", + __func__, + newline.dwDTERate, + newline.bCharFormat, newline.bParityType, + newline.bDataBits); + } + + if (C_CRTSCTS(tty)) { + dev_dbg(&ch34x->control->dev, "%s - hardflow enable!\n", __func__); + newctrl |= CH34X_CTRL_AFE | CH34X_CTRL_RTS; + } else + newctrl &= ~CH34X_CTRL_AFE; + + if (newctrl != ch34x->ctrlout) + ch34x_set_control(ch34x, ch34x->ctrlout = newctrl); +} + +static const struct tty_port_operations ch34x_port_ops = { + .dtr_rts = ch34x_port_dtr_rts, + .shutdown = ch34x_port_shutdown, + .activate = ch34x_port_activate, + .destruct = ch34x_port_destruct, +}; + +/* Little helpers: write/read buffers free */ +static void ch34x_write_buffers_free(struct ch34x *ch34x) +{ + int i; + struct ch34x_wb *wb; + struct usb_device *usb_dev = interface_to_usbdev(ch34x->control); + + for (wb = &ch34x->wb[0], i = 0; i < CH34X_NW; i++, wb++) + usb_free_coherent(usb_dev, ch34x->writesize, wb->buf, wb->dmah); +} + +static void ch34x_read_buffers_free(struct ch34x *ch34x) +{ + struct usb_device *usb_dev = interface_to_usbdev(ch34x->control); + int i; + + for (i = 0; i < ch34x->rx_buflimit; i++) + usb_free_coherent(usb_dev, ch34x->readsize, + ch34x->read_buffers[i].base, ch34x->read_buffers[i].dma); +} + +/* Little helper: write buffers allocate */ +static int ch34x_write_buffers_alloc(struct ch34x *ch34x) +{ + int i; + struct ch34x_wb *wb; + + for (wb = &ch34x->wb[0], i = 0; i < CH34X_NW; i++, wb++) { + wb->buf = usb_alloc_coherent(ch34x->dev, ch34x->writesize, GFP_KERNEL, + &wb->dmah); + if (!wb->buf) { + while (i != 0) { + --i; + --wb; + usb_free_coherent(ch34x->dev, ch34x->writesize, + wb->buf, wb->dmah); + } + return -ENOMEM; + } + } + return 0; +} + +/* + * USB probe and disconnect routines. + */ +static int ch34x_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_cdc_union_desc *union_header = NULL; + unsigned char *buffer = intf->altsetting->extra; + int buflen = intf->altsetting->extralen; + struct usb_interface *control_interface; + struct usb_interface *data_interface; + struct usb_endpoint_descriptor *epctrl = NULL; + struct usb_endpoint_descriptor *epread = NULL; + struct usb_endpoint_descriptor *epwrite = NULL; + struct usb_device *usb_dev = interface_to_usbdev(intf); + struct ch34x *ch34x; + int minor; + int ctrlsize, readsize; + u8 *buf; + unsigned long quirks; + int num_rx_buf = CH34X_NR; + int i; + unsigned int elength = 0; + struct device *tty_dev; + int rv = -ENOMEM; + + /* normal quirks */ + quirks = (unsigned long)id->driver_info; + if (!buffer) { + dev_err(&intf->dev, "Weird descriptor references\n"); + return -EINVAL; + } + + while (buflen > 0) { + elength = buffer[0]; + if (!elength) { + dev_err(&intf->dev, "skipping garbage byte\n"); + elength = 1; + goto next_desc; + } + if (buffer[1] != USB_DT_CS_INTERFACE) { + dev_err(&intf->dev, "skipping garbage\n"); + goto next_desc; + } + + switch (buffer[2]) { + case USB_CDC_UNION_TYPE: /* we've found it */ + if (elength < sizeof(struct usb_cdc_union_desc)) + goto next_desc; + if (union_header) { + dev_err(&intf->dev, "More than one " + "union descriptor, skipping ...\n"); + goto next_desc; + } + union_header = (struct usb_cdc_union_desc *)buffer; + break; + default: + /* + * there are LOTS more CDC descriptors that + * could legitimately be found here. + */ + break; + } +next_desc: + buflen -= elength; + buffer += elength; + } + + control_interface = usb_ifnum_to_if(usb_dev, union_header->bMasterInterface0); + data_interface = usb_ifnum_to_if(usb_dev, union_header->bSlaveInterface0); + + if (intf != control_interface) + return -ENODEV; + + if (usb_interface_claimed(data_interface)) { + dev_dbg(&intf->dev, "The data interface isn't available\n"); + return -EBUSY; + } + + if (data_interface->cur_altsetting->desc.bNumEndpoints < 2 || + control_interface->cur_altsetting->desc.bNumEndpoints == 0) + return -EINVAL; + + epctrl = &control_interface->cur_altsetting->endpoint[0].desc; + epwrite = &data_interface->cur_altsetting->endpoint[0].desc; + epread = &data_interface->cur_altsetting->endpoint[1].desc; + + /* workaround for switched endpoints */ + if (!usb_endpoint_dir_in(epread)) { + /* descriptors are swapped */ + dev_dbg(&intf->dev, + "The data interface has switched endpoints\n"); + swap(epread, epwrite); + } + + ch34x = kzalloc(sizeof(struct ch34x), GFP_KERNEL); + if (ch34x == NULL) + goto alloc_fail; + + ch34x->idVendor = id->idVendor; + ch34x->idProduct = id->idProduct; + ch34x->iface = control_interface->cur_altsetting->desc.bInterfaceNumber / 2; + + dev_dbg(&intf->dev, "interface %d is valid\n", ch34x->iface); + + minor = ch34x_alloc_minor(ch34x); + if (minor < 0) { + dev_err(&intf->dev, "no more free ch34x devices\n"); + kfree(ch34x); + return -ENODEV; + } + + ctrlsize = usb_endpoint_maxp(epctrl); + readsize = usb_endpoint_maxp(epread) * + (quirks == SINGLE_RX_URB ? 1 : 2); + ch34x->writesize = usb_endpoint_maxp(epwrite) * 20; + ch34x->control = control_interface; + ch34x->data = data_interface; + ch34x->minor = minor; + ch34x->dev = usb_dev; + ch34x->ctrlsize = ctrlsize; + ch34x->readsize = readsize; + ch34x->rx_buflimit = num_rx_buf; + + dev_dbg(&intf->dev, "ep%d ctrl: %d, ep%d read: %d, ep%d write: %d\n", + usb_endpoint_num(epctrl), usb_endpoint_maxp(epctrl), + usb_endpoint_num(epread), usb_endpoint_maxp(epread), + usb_endpoint_num(epwrite), usb_endpoint_maxp(epwrite)); + + INIT_WORK(&ch34x->work, ch34x_softint); + init_waitqueue_head(&ch34x->wioctl); + spin_lock_init(&ch34x->write_lock); + spin_lock_init(&ch34x->read_lock); + mutex_init(&ch34x->mutex); + ch34x->rx_endpoint = usb_rcvbulkpipe(usb_dev, epread->bEndpointAddress); + tty_port_init(&ch34x->port); + ch34x->port.ops = &ch34x_port_ops; + init_usb_anchor(&ch34x->delayed); + ch34x->quirks = quirks; + + buf = usb_alloc_coherent(usb_dev, ctrlsize, GFP_KERNEL, &ch34x->ctrl_dma); + if (!buf) + goto alloc_fail2; + ch34x->ctrl_buffer = buf; + + if (ch34x_write_buffers_alloc(ch34x) < 0) + goto alloc_fail4; + + ch34x->ctrlurb = usb_alloc_urb(0, GFP_KERNEL); + if (!ch34x->ctrlurb) + goto alloc_fail5; + + for (i = 0; i < num_rx_buf; i++) { + struct ch34x_rb *rb = &(ch34x->read_buffers[i]); + struct urb *urb; + + rb->base = usb_alloc_coherent(ch34x->dev, readsize, GFP_KERNEL, + &rb->dma); + if (!rb->base) + goto alloc_fail6; + rb->index = i; + rb->instance = ch34x; + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) + goto alloc_fail6; + + urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + urb->transfer_dma = rb->dma; + usb_fill_bulk_urb(urb, ch34x->dev, + ch34x->rx_endpoint, + rb->base, + ch34x->readsize, + ch34x_read_bulk_callback, rb); + + ch34x->read_urbs[i] = urb; + __set_bit(i, &ch34x->read_urbs_free); + } + for (i = 0; i < CH34X_NW; i++) { + struct ch34x_wb *snd = &(ch34x->wb[i]); + + snd->urb = usb_alloc_urb(0, GFP_KERNEL); + if (snd->urb == NULL) + goto alloc_fail7; + + usb_fill_bulk_urb(snd->urb, usb_dev, + usb_sndbulkpipe(usb_dev, epwrite->bEndpointAddress), + NULL, ch34x->writesize, ch34x_write_bulk, snd); + snd->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + snd->instance = ch34x; + } + + usb_set_intfdata(intf, ch34x); + + usb_fill_int_urb(ch34x->ctrlurb, usb_dev, + usb_rcvintpipe(usb_dev, epctrl->bEndpointAddress), + ch34x->ctrl_buffer, ctrlsize, ch34x_ctrl_irq, ch34x, + epctrl->bInterval ? epctrl->bInterval : 16); + ch34x->ctrlurb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + ch34x->ctrlurb->transfer_dma = ch34x->ctrl_dma; + + dev_info(&intf->dev, "ttyUSB%d: usb to uart device\n", minor); + +// ch34x->line.dwDTERate = cpu_to_le32(9600); +// ch34x->line.bDataBits = 8; +// ch34x_set_line(ch34x, &ch34x->line); + + usb_driver_claim_interface(&ch34x_driver, data_interface, ch34x); + usb_set_intfdata(data_interface, ch34x); + + usb_get_intf(control_interface); + tty_dev = tty_port_register_device(&ch34x->port, ch34x_tty_driver, minor, + &control_interface->dev); + if (IS_ERR(tty_dev)) { + rv = PTR_ERR(tty_dev); + goto alloc_fail7; + } + + if (quirks & CLEAR_HALT_CONDITIONS) { + usb_clear_halt(usb_dev, usb_rcvbulkpipe(usb_dev, epread->bEndpointAddress)); + usb_clear_halt(usb_dev, usb_sndbulkpipe(usb_dev, epwrite->bEndpointAddress)); + } + + // deal with urb when usb plugged in + rv = usb_submit_urb(ch34x->ctrlurb, GFP_KERNEL); + if (rv) { + dev_err(&ch34x->control->dev, + "%s - usb_submit_urb(ctrl cmd) failed\n", __func__); + goto error_submit_urb; + } + + rv = ch34x_submit_read_urbs(ch34x, GFP_KERNEL); + if (rv) + goto error_submit_read_urbs; + + dev_dbg(&intf->dev, "ch34x_probe finished!\n"); + + return 0; + +error_submit_read_urbs: + for (i = 0; i < ch34x->rx_buflimit; i++) + usb_kill_urb(ch34x->read_urbs[i]); +error_submit_urb: + usb_kill_urb(ch34x->ctrlurb); +alloc_fail7: + usb_set_intfdata(intf, NULL); + for (i = 0; i < CH34X_NW; i++) + usb_free_urb(ch34x->wb[i].urb); +alloc_fail6: + for (i = 0; i < num_rx_buf; i++) + usb_free_urb(ch34x->read_urbs[i]); + ch34x_read_buffers_free(ch34x); + usb_free_urb(ch34x->ctrlurb); +alloc_fail5: + ch34x_write_buffers_free(ch34x); +alloc_fail4: + usb_free_coherent(usb_dev, ctrlsize, ch34x->ctrl_buffer, ch34x->ctrl_dma); +alloc_fail2: + ch34x_release_minor(ch34x); + kfree(ch34x); +alloc_fail: + return rv; +} + +static void stop_data_traffic(struct ch34x *ch34x) +{ + int i; + struct urb *urb; + struct ch34x_wb *wb; + + dev_dbg(&ch34x->control->dev, "%s\n", __func__); + + usb_autopm_get_interface_no_resume(ch34x->control); + ch34x->control->needs_remote_wakeup = 0; + usb_autopm_put_interface(ch34x->control); + + for (;;) { + urb = usb_get_from_anchor(&ch34x->delayed); + if (!urb) + break; + wb = urb->context; + wb->use = 0; + usb_autopm_put_interface_async(ch34x->control); + } + + usb_kill_urb(ch34x->ctrlurb); + for (i = 0; i < CH34X_NW; i++) + usb_kill_urb(ch34x->wb[i].urb); + for (i = 0; i < ch34x->rx_buflimit; i++) + usb_kill_urb(ch34x->read_urbs[i]); + cancel_work_sync(&ch34x->work); +} + +static void ch34x_disconnect(struct usb_interface *intf) +{ + struct ch34x *ch34x = usb_get_intfdata(intf); + struct usb_device *usb_dev = interface_to_usbdev(intf); + struct tty_struct *tty; + int i; + + dev_dbg(&intf->dev, "%s\n", __func__); + + /* sibling interface is already cleaning up */ + if (!ch34x) + return; + + mutex_lock(&ch34x->mutex); + ch34x->disconnected = true; + wake_up_all(&ch34x->wioctl); + usb_set_intfdata(ch34x->control, NULL); + usb_set_intfdata(ch34x->data, NULL); + mutex_unlock(&ch34x->mutex); + + tty = tty_port_tty_get(&ch34x->port); + if (tty) { + tty_vhangup(tty); + tty_kref_put(tty); + } + + stop_data_traffic(ch34x); + + tty_unregister_device(ch34x_tty_driver, ch34x->minor); + + usb_free_urb(ch34x->ctrlurb); + for (i = 0; i < CH34X_NW; i++) + usb_free_urb(ch34x->wb[i].urb); + for (i = 0; i < ch34x->rx_buflimit; i++) + usb_free_urb(ch34x->read_urbs[i]); + ch34x_write_buffers_free(ch34x); + usb_free_coherent(usb_dev, ch34x->ctrlsize, ch34x->ctrl_buffer, ch34x->ctrl_dma); + ch34x_read_buffers_free(ch34x); + + usb_driver_release_interface(&ch34x_driver, intf == ch34x->control ? + ch34x->data : ch34x->control); + + tty_port_put(&ch34x->port); + dev_info(&intf->dev, "%s\n", "ch34x usb device disconnect."); +} + +#ifdef CONFIG_PM +static int ch34x_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct ch34x *ch34x = usb_get_intfdata(intf); + int cnt; + + dev_dbg(&intf->dev, "%s\n", __func__); + spin_lock_irq(&ch34x->write_lock); + if (PMSG_IS_AUTO(message)) { + if (ch34x->transmitting) { + spin_unlock_irq(&ch34x->write_lock); + return -EBUSY; + } + } + cnt = ch34x->susp_count++; + spin_unlock_irq(&ch34x->write_lock); + + if (cnt) + return 0; + + stop_data_traffic(ch34x); + + return 0; +} + +static int ch34x_resume(struct usb_interface *intf) +{ + struct ch34x *ch34x = usb_get_intfdata(intf); + struct urb *urb; + int rv = 0; + + dev_dbg(&intf->dev, "%s\n", __func__); + spin_lock_irq(&ch34x->write_lock); + + if (--ch34x->susp_count) + goto out; + + if (test_bit(ASYNCB_INITIALIZED, &ch34x->port.flags)) { + rv = usb_submit_urb(ch34x->ctrlurb, GFP_ATOMIC); + + for (;;) { + urb = usb_get_from_anchor(&ch34x->delayed); + if (!urb) + break; + + ch34x_start_wb(ch34x, urb->context); + } + + /* + * delayed error checking because we must + * do the write path at all cost + */ + if (rv < 0) + goto out; + + rv = ch34x_submit_read_urbs(ch34x, GFP_ATOMIC); + } +out: + spin_unlock_irq(&ch34x->write_lock); + + return rv; +} + +static int ch34x_reset_resume(struct usb_interface *intf) +{ + struct ch34x *ch34x = usb_get_intfdata(intf); + + dev_dbg(&intf->dev, "%s\n", __func__); + if (test_bit(ASYNCB_INITIALIZED, &ch34x->port.flags)) + tty_port_tty_hangup(&ch34x->port, false); + + return ch34x_resume(intf); +} + +#endif /* CONFIG_PM */ + +/* + * USB driver structure. + */ + +static const struct usb_device_id ch34x_ids[] = { + { USB_DEVICE_INTERFACE_PROTOCOL(0x1a86, 0x55D2, /* ch342 chip */ + USB_CDC_ACM_PROTO_AT_V25TER) }, + + { USB_DEVICE_INTERFACE_PROTOCOL(0x1a86, 0x55D3, /* ch343 chip */ + USB_CDC_ACM_PROTO_AT_V25TER) }, + + { USB_DEVICE_INTERFACE_PROTOCOL(0x1a86, 0x55D4, /* ch9102 chip */ + USB_CDC_ACM_PROTO_AT_V25TER) }, + + { } +}; + +MODULE_DEVICE_TABLE(usb, ch34x_ids); + +static struct usb_driver ch34x_driver = { + .name = "usb_ch343", + .probe = ch34x_probe, + .disconnect = ch34x_disconnect, +#ifdef CONFIG_PM + .suspend = ch34x_suspend, + .resume = ch34x_resume, + .reset_resume = ch34x_reset_resume, +#endif + .id_table = ch34x_ids, +#ifdef CONFIG_PM + .supports_autosuspend = 1, +#endif + .disable_hub_initiated_lpm = 1, +}; + +/* + * TTY driver structures. + */ +static const struct tty_operations ch34x_ops = { + .install = ch34x_tty_install, + .open = ch34x_tty_open, + .close = ch34x_tty_close, + .cleanup = ch34x_tty_cleanup, + .hangup = ch34x_tty_hangup, + .write = ch34x_tty_write, + .write_room = ch34x_tty_write_room, + .ioctl = ch34x_tty_ioctl, + .throttle = ch34x_tty_throttle, + .unthrottle = ch34x_tty_unthrottle, + .chars_in_buffer = ch34x_tty_chars_in_buffer, + .break_ctl = ch34x_tty_break_ctl, + .set_termios = ch34x_tty_set_termios, + .tiocmget = ch34x_tty_tiocmget, + .tiocmset = ch34x_tty_tiocmset, +}; + +/* + * Init / exit. + */ +static int __init ch34x_init(void) +{ + int retval; + ch34x_tty_driver = alloc_tty_driver(CH34X_TTY_MINORS); + if (!ch34x_tty_driver) + return -ENOMEM; + ch34x_tty_driver->driver_name = "usbch343", + ch34x_tty_driver->name = "ttyUSB", + ch34x_tty_driver->major = CH34X_TTY_MAJOR, + ch34x_tty_driver->minor_start = 0, + ch34x_tty_driver->type = TTY_DRIVER_TYPE_SERIAL, + ch34x_tty_driver->subtype = SERIAL_TYPE_NORMAL, + ch34x_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV; + ch34x_tty_driver->init_termios = tty_std_termios; + ch34x_tty_driver->init_termios.c_cflag = B9600 | CS8 | CREAD | + HUPCL | CLOCAL; + tty_set_operations(ch34x_tty_driver, &ch34x_ops); + + retval = tty_register_driver(ch34x_tty_driver); + if (retval) { + put_tty_driver(ch34x_tty_driver); + return retval; + } + + retval = usb_register(&ch34x_driver); + if (retval) { + tty_unregister_driver(ch34x_tty_driver); + put_tty_driver(ch34x_tty_driver); + return retval; + } + + printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_DESC "\n"); + printk(KERN_INFO KBUILD_MODNAME ": " VERSION_DESC "\n"); + + return 0; +} + +static void __exit ch34x_exit(void) +{ + usb_deregister(&ch34x_driver); + tty_unregister_driver(ch34x_tty_driver); + put_tty_driver(ch34x_tty_driver); + idr_destroy(&ch34x_minors); + printk(KERN_INFO KBUILD_MODNAME ": " "ch34x driver exit.\n"); +} + +module_init(ch34x_init); +module_exit(ch34x_exit); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_CHARDEV_MAJOR(CH34X_TTY_MAJOR); + diff --git a/root/package/link4all/ch342/src/ch343.h b/root/package/link4all/ch342/src/ch343.h new file mode 100644 index 00000000..90be1702 --- /dev/null +++ b/root/package/link4all/ch342/src/ch343.h @@ -0,0 +1,230 @@ +/* + * + * Includes for ch343.c + * + */ + +/* + * Baud rate and default timeout + */ +#define DEFAULT_BAUD_RATE 9600 +#define DEFAULT_TIMEOUT 2000 + +/* + * CMSPAR, some architectures can't have space and mark parity. + */ + +#ifndef CMSPAR +#define CMSPAR 0 +#endif + +/* + * Major and minor numbers. + */ + +#define CH34X_TTY_MAJOR 168 +#define CH34X_TTY_MINORS 256 + +/* + * Requests. + */ + +#define USB_RT_CH34X (USB_TYPE_CLASS | USB_RECIP_INTERFACE) + +//Vendor define +#define VENDOR_WRITE_TYPE 0x40 +#define VENDOR_READ_TYPE 0xC0 + +#define VENDOR_READ 0x95 +#define VENDOR_WRITE 0x9A +#define VENDOR_SERIAL_INIT 0xA1 +#define VENDOR_MODEM_OUT 0xA4 +#define VENDOR_MODEM_IN 0x05 +#define VENDOR_GPIO 0xA8 +#define VENDOR_DELAY_MS 0x5E +#define VENDOR_VERSION 0x5F +#define VENDOR_GPIO_EN1 0xAA +#define VENDOR_GPIO_EN2 0xAC +#define VENDOR_GPIO_SET 0xAB +#define VENDOR_GPIO_GET 0xA9 + + +/* + * Output control lines. + */ +#define CH34X_CTRL_OUT 0x10 +#define CH34X_CTRL_DTR 0x20 +#define CH34X_CTRL_RTS 0x40 +#define CH34X_CTRL_AFE 0x80 + +/******************************/ +/* interrupt pipe definitions */ +/******************************/ +/* first irq byte indicates type */ +/* second irq byte indicates modemA */ +/* third irq byte indicates modemB */ + +/* + * status returned in third interrupt answer byte, + * inverted in data from irq + */ +#define CH34X_CTRL_CTS 0x01 +#define CH34X_CTRL_DSR 0x02 +#define CH34X_CTRL_RI 0x04 +#define CH34X_CTRL_DCD 0x08 +#define CH34X_MODEM_STAT 0x0f + +#define CH34X_CTRL_TYPE_MODEM 0x08 +#define CH34X_CTRL_TYPE_FRAMING 0x44 +#define CH34X_CTRL_TYPE_PARITY 0x04 +#define CH34X_CTRL_TYPE_OVERRUN 0x02 + +/* + * Input line errors. + */ +#define CH34X_LSR_STATE 0x00 +#define CH34X_LSR_OVERRUN 0x02 +#define CH34X_LSR_RERR 0x04 +#define CH34X_LSR_BREAK +#define CH34X_LSR_PARITY 0x00 +#define CH34X_LSR_FRAME 0x40 +#define CH34X_LSR_MODEM 0x08 + +/* + * LCR defines. + */ +#define CH34X_LCR_REG_CTRL 0x80 +#define CH34X_LCR_REG_CLK 0x04 +#define CH34X_LCR_REG_TIMER 0x08 + +#define CH34X_LCR_ENABLE_RX 0x80 +#define CH34X_LCR_ENABLE_TX 0x40 +#define CH34X_LCR_PAR_SPACE 0x38 +#define CH34X_LCR_PAR_MARK 0x28 +#define CH34X_LCR_PAR_EVEN 0x18 +#define CH34X_LCR_PAR_ODD 0x08 +#define CH34X_LCR_STOP_BITS_2 0x04 +#define CH34X_LCR_CS8 0x03 +#define CH34X_LCR_CS7 0x02 +#define CH34X_LCR_CS6 0x01 +#define CH34X_LCR_CS5 0x00 + +#define CH34X_NBREAK_BITS 0x80 +#define CH34X_NBREAK_AUTOBITS 0x10 + +/* + * Internal driver structures. + */ + +/* + * The only reason to have several buffers is to accommodate assumptions + * in line disciplines. They ask for empty space amount, receive our URB size, + * and proceed to issue several 1-character writes, assuming they will fit. + * The very first write takes a complete URB. Fortunately, this only happens + * when processing onlcr, so we only need 2 buffers. These values must be + * powers of 2. + */ +#define CH34X_NW 16 +#define CH34X_NR 16 + +struct ch34x_wb { + unsigned char *buf; + dma_addr_t dmah; + int len; + int use; + struct urb *urb; + struct ch34x *instance; +}; + +struct ch34x_rb { + int size; + unsigned char *base; + dma_addr_t dma; + int index; + struct ch34x *instance; +}; + +struct usb_ch34x_line_coding { + __u32 dwDTERate; + __u8 bCharFormat; +#define USB_CH34X_1_STOP_BITS 0 +#define USB_CH34X_1_5_STOP_BITS 1 +#define USB_CH34X_2_STOP_BITS 2 + + __u8 bParityType; +#define USB_CH34X_NO_PARITY 0 +#define USB_CH34X_ODD_PARITY 1 +#define USB_CH34X_EVEN_PARITY 2 +#define USB_CH34X_MARK_PARITY 3 +#define USB_CH34X_SPACE_PARITY 4 + + __u8 bDataBits; +} __attribute__ ((packed)); + +typedef enum { + CHIP_CH342F = 0x00, + CHIP_CH342GJK, + CHIP_CH343G, + CHIP_CH343G_AUTOBAUD, + CHIP_CH343K, + CHIP_CH343J, + CHIP_CH9102F, + CHIP_CH9102X, +} CHIPTYPE; + +struct ch34x { + struct usb_device *dev; /* the corresponding usb device */ + struct usb_interface *control; /* control interface */ + struct usb_interface *data; /* data interface */ + struct tty_port port; /* our tty port data */ + struct urb *ctrlurb; /* urbs */ + u8 *ctrl_buffer; /* buffers of urbs */ + dma_addr_t ctrl_dma; /* dma handles of buffers */ + struct ch34x_wb wb[CH34X_NW]; + unsigned long read_urbs_free; + struct urb *read_urbs[CH34X_NR]; + struct ch34x_rb read_buffers[CH34X_NR]; + int rx_buflimit; + int rx_endpoint; + spinlock_t read_lock; + int write_used; /* number of non-empty write buffers */ + int transmitting; + spinlock_t write_lock; + struct mutex mutex; + bool disconnected; + struct usb_ch34x_line_coding line; /* bits, stop, parity */ + struct work_struct work; /* work queue entry for line discipline waking up */ + unsigned int ctrlin; /* input control lines (DCD, DSR, RI, break, overruns) */ + unsigned int ctrlout; /* output control lines (DTR, RTS) */ + struct async_icount iocount; /* counters for control line changes */ + struct async_icount oldcount; /* for comparison of counter */ + wait_queue_head_t wioctl; /* for ioctl */ + unsigned int writesize; /* max packet size for the output bulk endpoint */ + unsigned int readsize,ctrlsize; /* buffer sizes for freeing */ + unsigned int minor; /* ch34x minor number */ + unsigned char clocal; /* termios CLOCAL */ + unsigned int susp_count; /* number of suspended interfaces */ + unsigned int throttled:1; /* actually throttled */ + unsigned int throttle_req:1; /* throttle requested */ + u8 bInterval; + struct usb_anchor delayed; /* writes queued for a device about to be woken */ + unsigned long quirks; + u8 iface; + CHIPTYPE chiptype; + u16 idVendor; + u16 idProduct; + u8 gpio5dir; +}; + +#define CDC_DATA_INTERFACE_TYPE 0x0a + +/* constants describing various quirks and errors */ +#define NO_UNION_NORMAL BIT(0) +#define SINGLE_RX_URB BIT(1) +#define NO_CAP_LINE BIT(2) +#define NO_DATA_INTERFACE BIT(4) +#define IGNORE_DEVICE BIT(5) +#define QUIRK_CONTROL_LINE_STATE BIT(6) +#define CLEAR_HALT_CONDITIONS BIT(7) + + diff --git a/root/package/link4all/ch9344/Makefile b/root/package/link4all/ch9344/Makefile new file mode 100644 index 00000000..a0b801c9 --- /dev/null +++ b/root/package/link4all/ch9344/Makefile @@ -0,0 +1,45 @@ +# +# Copyright (c) 2014 The Linux Foundation. All rights reserved. +# + +include $(TOPDIR)/rules.mk +include $(INCLUDE_DIR)/kernel.mk + +PKG_NAME:=usb-serial-ch9344 +PKG_VERSION:=2022-03-01-1026 +PKG_RELEASE:=1 + + +PKG_BUILD_DIR:=$(KERNEL_BUILD_DIR)/$(PKG_NAME) + +include $(INCLUDE_DIR)/package.mk + +define KernelPackage/usb-serial-ch9344 + SUBMENU:=USB Support + DEPENDS:=+kmod-usb-serial + TITLE:=Kernel USB driver for ch342/3 + FILES:= $(PKG_BUILD_DIR)/ch9344.ko + AUTOLOAD:=$(call AutoLoad,81,ch9344) +endef + +define KernelPackage/usb-serial-ch9344/Description +This package contains a USB driver for ch9344 +endef + +define Build/Prepare + $(CP) src/* $(PKG_BUILD_DIR) + $(call Build/Prepare/Default) +endef + +MAKE_OPTS:= \ + $(KERNEL_MAKE_FLAGS) \ + M="$(PKG_BUILD_DIR)" + +define Build/Compile + $(MAKE) -C "$(LINUX_DIR)" \ + $(MAKE_OPTS) \ + modules +endef + +$(eval $(call KernelPackage,usb-serial-ch9344)) + diff --git a/root/package/link4all/ch9344/src/Makefile b/root/package/link4all/ch9344/src/Makefile new file mode 100644 index 00000000..25af1c97 --- /dev/null +++ b/root/package/link4all/ch9344/src/Makefile @@ -0,0 +1,3 @@ +obj-m += ch9344.o + + diff --git a/root/package/link4all/ch9344/src/ch9344.c b/root/package/link4all/ch9344/src/ch9344.c new file mode 100644 index 00000000..bc972ea4 --- /dev/null +++ b/root/package/link4all/ch9344/src/ch9344.c @@ -0,0 +1,2248 @@ +/* + * USB serial driver for USB to Quad UARTs chip ch9344 and USB to Octal UARTs chip ch348. + * + * Copyright (C) 2021 Nanjing Qinheng Microelectronics Co., Ltd. + * Web: http://wch.cn + * Author: WCH@TECH39 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * System required: + * Kernel version beyond 3.4.x + * Update Log: + * V1.0 - initial version + * V1.1 - added supports for high baudrates 2M, 4M and 6M + * V1.2 - added supports for modem signal, flow control + * V1.3 - added long packet for tty_write + * V1.4 - added ioctl for rs485 mode + * V1.5 - fixed baud rate 0 bugs + * V1.6 - fixed modem out bugs, added gpio function, + * - removed rs485 control(hardware auto supports) + * - added supports for baudrates 250k, 500k, 1M, 1.5M, 3M, 12M + * V1.7 - added supports for break function + * V1.8 - added supports for ch348 + */ + +#define DEBUG +#define VERBOSE_DEBUG + +#undef DEBUG +#undef VERBOSE_DEBUG + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0)) +#include +#endif + +#include "ch9344.h" + +#define DRIVER_AUTHOR "WCH@TECH39" +#define DRIVER_DESC "USB serial driver for ch9344/ch348." +#define VERSION_DESC "V1.8 On 2021.08" + +#define IOCTL_MAGIC 'W' +#define IOCTL_CMD_GPIOENABLE _IOW(IOCTL_MAGIC, 0x80, u16) +#define IOCTL_CMD_GPIODIR _IOW(IOCTL_MAGIC, 0x81, u16) +#define IOCTL_CMD_GPIOSET _IOW(IOCTL_MAGIC, 0x82, u16) +#define IOCTL_CMD_GPIOGET _IOWR(IOCTL_MAGIC, 0x83, u16) +#define IOCTL_CMD_CTRLIN _IOWR(IOCTL_MAGIC, 0x90, u16) +#define IOCTL_CMD_CTRLOUT _IOW(IOCTL_MAGIC, 0x91, u16) + +static struct usb_driver ch9344_driver; +static struct tty_driver *ch9344_tty_driver; + +static DEFINE_IDR(ch9344_minors); +static DEFINE_MUTEX(ch9344_minors_lock); + + +static void ch9344_tty_set_termios(struct tty_struct *tty, + struct ktermios *termios_old); + +static int ch9344_get_portnum(int index); + +static int ch9344_set_uartmode(struct ch9344 *ch9344, int portnum, u8 index, u8 mode); + +/* + * Look up an CH9344 structure by minor. If found and not disconnected, increment + * its refcount and return it with its mutex held. + */ +static struct ch9344 *ch9344_get_by_index(unsigned int index) +{ + struct ch9344 *ch9344; + + mutex_lock(&ch9344_minors_lock); + ch9344 = idr_find(&ch9344_minors, index / NUMSTEP); + if (ch9344) { + mutex_lock(&ch9344->mutex); + if (ch9344->disconnected) { + mutex_unlock(&ch9344->mutex); + ch9344 = NULL; + } else { + tty_port_get(&ch9344->ttyport[ch9344_get_portnum(index)].port); + mutex_unlock(&ch9344->mutex); + } + } + mutex_unlock(&ch9344_minors_lock); + + return ch9344; +} + +/* + * Try to find an available minor number and if found, associate it with 'ch9344'. + */ +static int ch9344_alloc_minor(struct ch9344 *ch9344) +{ + int minor; + + mutex_lock(&ch9344_minors_lock); + minor = idr_alloc(&ch9344_minors, ch9344, 0, CH9344_TTY_MINORS, GFP_KERNEL); + mutex_unlock(&ch9344_minors_lock); + + return minor; +} + +/* Release the minor number associated with 'ch9344'. */ +static void ch9344_release_minor(struct ch9344 *ch9344) +{ + mutex_lock(&ch9344_minors_lock); + idr_remove(&ch9344_minors, ch9344->minor); + mutex_unlock(&ch9344_minors_lock); +} + +static int ch9344_get_portnum(int index) +{ + return index % NUMSTEP; +} + +/* + * Functions for control messages. + */ +static int ch9344_control_out(struct ch9344 *ch9344, u8 request, + u16 value, u16 index) +{ + int retval; + + retval = usb_autopm_get_interface(ch9344->data); + if (retval) + return retval; + + retval = usb_control_msg(ch9344->dev, usb_sndctrlpipe(ch9344->dev, 0), + request, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT, + value, index, NULL, 0, DEFAULT_TIMEOUT); + + usb_autopm_put_interface(ch9344->data); + + return retval < 0 ? retval : 0; +} + +static int ch9344_control_in(struct ch9344 *ch9344, + u8 request, u16 value, u16 index, + char *buf, unsigned bufsize) +{ + int retval; + + retval = usb_autopm_get_interface(ch9344->data); + if (retval) + return retval; + + retval = usb_control_msg(ch9344->dev, usb_rcvctrlpipe(ch9344->dev, 0), request, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, + value, index, buf, bufsize, DEFAULT_TIMEOUT); + + usb_autopm_put_interface(ch9344->data); + + return retval; +} + +/* + * Functions for CH9344 cmd messages. + */ +static int ch9344_cmd_out(struct ch9344 *ch9344, u8 *buf, + int count) +{ + int retval, actual_len; + + retval = usb_bulk_msg(ch9344->dev, ch9344->cmdtx_endpoint, + buf, + min((unsigned int)count, ch9344->cmdsize), + &actual_len, DEFAULT_TIMEOUT); + + if (retval) { + dev_err(&ch9344->data->dev, + "usb_bulk_msg(send) failed, err %i\n", retval); + return retval; + } + + if (actual_len != count) { + dev_err(&ch9344->data->dev, "only wrote %d of %d bytes\n", + actual_len, count); + return -1; + } + + return actual_len; +} + +static inline int ch9344_set_control(struct ch9344 *ch9344, + int portnum, int control) +{ + char *buffer; + int retval; + const unsigned size = 3; + u8 rgadd = 0; + + if (ch9344->chiptype == CHIP_CH9344) + rgadd = 0x10 * portnum + 0x08; + else if (ch9344->chiptype == CHIP_CH348) { + if (portnum < 4) + rgadd = 0x10 * portnum; + else + rgadd = 0x10 * (portnum - 4) + 0x08; + } + + buffer = kzalloc(size, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + if (ch9344->quirks & QUIRK_CONTROL_LINE_STATE) + return -EOPNOTSUPP; + + buffer[0] = CMD_W_BR; + buffer[1] = rgadd + R_C4; + buffer[2] = control; + + retval = ch9344_cmd_out(ch9344, buffer, size); + + kfree(buffer); + + return retval < 0 ? retval : 0; +} + +static int ch9344_configure(struct ch9344 *ch9344, int portnum) +{ + char *buffer; + int ret; + u8 request; + u8 rgadd = 0; + + buffer = kzalloc(ch9344->cmdsize, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + request = CMD_W_R; + if (ch9344->chiptype == CHIP_CH9344) + rgadd = 0x10 * portnum + 0x08; + else if (ch9344->chiptype == CHIP_CH348) { + if (portnum < 4) + rgadd = 0x10 * portnum; + else + rgadd = 0x10 * (portnum - 4) + 0x08; + } + + buffer[0] = request; + buffer[1] = rgadd + R_C2; + buffer[2] = 0x87; + ret = ch9344_cmd_out(ch9344, buffer, 0x03); + if (ret < 0) + goto out; + + if (ch9344->chiptype == CHIP_CH9344) { + buffer[1] = rgadd + R_C3; + buffer[2] = 0x03; + ret = ch9344_cmd_out(ch9344, buffer, 0x03); + if (ret < 0) + goto out; + } + + buffer[1] = rgadd + R_C4; + buffer[2] = 0x08; + ret = ch9344_cmd_out(ch9344, buffer, 0x03); + if (ret < 0) + goto out; +out: + kfree(buffer); + return ret < 0 ? ret : 0; +} + +/* + * Write buffer management. + * All of these assume proper locks taken by the caller. + */ +static int ch9344_wb_alloc(struct ch9344 *ch9344) +{ + int i, wbn; + struct ch9344_wb *wb; + + wbn = 0; + i = 0; + for (;;) { + wb = &ch9344->wb[wbn]; + if (!wb->use) { + wb->use = 1; + return wbn; + } + wbn = (wbn + 1) % CH9344_NW; + if (++i >= CH9344_NW) + return -1; + } +} + +static int ch9344_wb_is_avail(struct ch9344 *ch9344) +{ + int i, n; + unsigned long flags; + + n = CH9344_NW; + spin_lock_irqsave(&ch9344->write_lock, flags); + for (i = 0; i < CH9344_NW; i++) + n -= ch9344->wb[i].use; + spin_unlock_irqrestore(&ch9344->write_lock, flags); + return n; +} + +/* + * Finish write. Caller must hold ch9344->write_lock + */ +static void ch9344_write_done(struct ch9344 *ch9344, struct ch9344_wb *wb) +{ + wb->use = 0; + ch9344->transmitting--; + usb_autopm_put_interface_async(ch9344->data); +} + +/* + * Poke write. + * + * the caller is responsible for locking + */ +static int ch9344_start_wb(struct ch9344 *ch9344, struct ch9344_wb *wb) +{ + int rc; + + ch9344->transmitting++; + + wb->urb->transfer_buffer = wb->buf; + wb->urb->transfer_dma = wb->dmah; + wb->urb->transfer_buffer_length = wb->len; + wb->urb->dev = ch9344->dev; + + rc = usb_submit_urb(wb->urb, GFP_ATOMIC); + if (rc < 0) { + dev_err(&ch9344->data->dev, + "%s - usb_submit_urb(write bulk) failed: %d\n", + __func__, rc); + ch9344_write_done(ch9344, wb); + } + return rc; +} + +/* + * Status handlers for device responses + */ +/* bulk interface reports status changes with "bulk" transfers */ +static void ch9344_cmd_irq(struct urb *urb) +{ + struct ch9344 *ch9344 = urb->context; + unsigned char *data = urb->transfer_buffer; + int len = urb->actual_length; + int retval; + int status = urb->status; + int i; + int portnum; + u8 reg_iir; + int ctrlval; + int newctrl; + int difference; + unsigned long flags; + int left = 0, right = 0; + + if (ch9344->chiptype == CHIP_CH9344) { + left = 4; + right = 8; + } else if (ch9344->chiptype == CHIP_CH348) { + left = 0; + right = 8; + } + + switch (status) { + case 0: + /* success */ + for (i = 0; i < len;) { + reg_iir = *(data + i + 1); + portnum = *(data + i) & 0x0f; + if (reg_iir == R_INIT) { + i += 12; + continue; + } + if (reg_iir >= R_MOD && reg_iir <= R_IO_I) { + if ((portnum >= left) && (portnum < right)) { + portnum -= ch9344->port_offset; + spin_lock_irqsave(&ch9344->write_lock, flags); + if (reg_iir == R_IO_I) { + ch9344->gpio_recv = true; + ch9344->gpiovalin = (*(data + i + 3) << 8) + *(data + i + 2); + } + wake_up_interruptible(&ch9344->wgpioioctl); + spin_unlock_irqrestore(&ch9344->write_lock, flags); + } else + break; + i += 4; + continue; + } else if ((reg_iir & 0x0f) == R_II_B2) { + if ((portnum >= left) && (portnum < right)) { + portnum -= ch9344->port_offset; + spin_lock_irqsave(&ch9344->write_lock, flags); + ch9344->ttyport[portnum].write_empty = true; + wake_up_interruptible(&ch9344->ttyport[portnum].wioctl); + spin_unlock_irqrestore(&ch9344->write_lock, flags); + } else + break; + } else if ((reg_iir & 0x0f) == R_II_B3) { + if ((portnum >= left) && (portnum < right)) { + portnum -= ch9344->port_offset; + + spin_lock(&ch9344->read_lock); + newctrl = ch9344->ttyport[portnum].ctrlin; + /* modem signal */ + ctrlval = *(data + i + 2); + if (ctrlval & CH9344_CTI_C) { + if (ctrlval & (CH9344_CTI_C << 4)) { + newctrl |= CH9344_CTI_C; + } else { + newctrl &= ~CH9344_CTI_C; + } + } + if (ctrlval & CH9344_CTI_DS) { + if (ctrlval & (CH9344_CTI_DS << 4)) { + newctrl |= CH9344_CTI_DS; + } else { + newctrl &= ~CH9344_CTI_DS; + } + } + if (ctrlval & CH9344_CTI_R) { + if (ctrlval & (CH9344_CTI_R << 4)) { + newctrl |= CH9344_CTI_R; + } else { + newctrl &= ~CH9344_CTI_R; + } + } + if (ctrlval & CH9344_CTI_DC) { + if (ctrlval & (CH9344_CTI_DC << 4)) { + newctrl |= CH9344_CTI_DC; + } else { + newctrl &= ~CH9344_CTI_DC; + } + } + + if (!ch9344->ttyport[portnum].clocal && + (ch9344->ttyport[portnum].ctrlin & ~newctrl & CH9344_CTI_DC)) { + spin_unlock(&ch9344->read_lock); + tty_port_tty_hangup(&ch9344->ttyport[portnum].port, false); + spin_lock(&ch9344->read_lock); + } + + difference = ch9344->ttyport[portnum].ctrlin ^ newctrl; + ch9344->ttyport[portnum].ctrlin = newctrl; + ch9344->ttyport[portnum].oldcount = ch9344->ttyport[portnum].iocount; + + if (difference & CH9344_CTI_C) + ch9344->ttyport[portnum].iocount.cts++; + if (difference & CH9344_CTI_DS) + ch9344->ttyport[portnum].iocount.dsr++; + if (difference & CH9344_CTI_R) + ch9344->ttyport[portnum].iocount.rng++; + if (difference & CH9344_CTI_DC) + ch9344->ttyport[portnum].iocount.dcd++; + spin_unlock(&ch9344->read_lock); + + if (difference) + wake_up_interruptible(&ch9344->ttyport[portnum].wmodemioctl); + } else + break; + } else if ((reg_iir & 0x0f) == R_II_B1) { + if ((portnum >= left) && (portnum < right)) { + portnum -= ch9344->port_offset; + + spin_lock(&ch9344->read_lock); + newctrl = ch9344->ttyport[portnum].ctrlin; + /* lsr signal */ + ctrlval = *(data + i + 2); + if (ctrlval & (CH9344_LO >> 3)) { + if (ctrlval & (CH9344_LO >> 3)) { + newctrl |= CH9344_LO; + } else { + newctrl &= ~CH9344_LO; + } + } + if (ctrlval & (CH9344_LP >> 3)) { + if (ctrlval & (CH9344_LP >> 3)) { + newctrl |= CH9344_LP; + } else { + newctrl &= ~CH9344_LP; + } + } + if (ctrlval & (CH9344_LF >> 3)) { + if (ctrlval & (CH9344_LF >> 3)) { + newctrl |= CH9344_LF; + } else { + newctrl &= ~CH9344_LF; + } + } + if (ctrlval & (CH9344_LB >> 3)) { + if (ctrlval & (CH9344_LB >> 3)) { + newctrl |= CH9344_LB; + } else { + newctrl &= ~CH9344_LB; + } + } + difference = ch9344->ttyport[portnum].ctrlin ^ newctrl; + ch9344->ttyport[portnum].ctrlin = newctrl; + ch9344->ttyport[portnum].oldcount = ch9344->ttyport[portnum].iocount; + + if (difference & CH9344_LO) + ch9344->ttyport[portnum].iocount.overrun++; + if (difference & CH9344_LP) + ch9344->ttyport[portnum].iocount.parity++; + if (difference & CH9344_LF) + ch9344->ttyport[portnum].iocount.frame++; + if (difference & CH9344_LB) + ch9344->ttyport[portnum].iocount.brk++; + spin_unlock(&ch9344->read_lock); + + if (difference) + wake_up_interruptible(&ch9344->ttyport[portnum].wmodemioctl); + } else + break; + } else { + dev_err(&ch9344->data->dev, + "%s - wrong status received", + __func__); + } + i += 3; + } + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* this urb is terminated, clean up */ + dev_err(&ch9344->data->dev, + "%s - urb shutting down with status: %d\n", + __func__, status); + return; + default: + dev_err(&ch9344->data->dev, + "%s - nonzero urb status received: %d\n", + __func__, status); + goto exit; + } + + usb_mark_last_busy(ch9344->dev); + +exit: + retval = usb_submit_urb(urb, GFP_ATOMIC); + if (retval && retval != -EPERM) + dev_err(&ch9344->data->dev, "%s - usb_submit_urb failed: %d\n", + __func__, retval); +} + +static int ch9344_submit_read_urb(struct ch9344 *ch9344, int index, gfp_t mem_flags) +{ + int res; + + if (!test_and_clear_bit(index, &ch9344->read_urbs_free)) + return 0; + + res = usb_submit_urb(ch9344->read_urbs[index], mem_flags); + if (res) { + if (res != -EPERM) { + dev_err(&ch9344->data->dev, + "%s - usb_submit_urb failed: %d\n", + __func__, res); + } + set_bit(index, &ch9344->read_urbs_free); + return res; + } + + return 0; +} + +static int ch9344_submit_read_urbs(struct ch9344 *ch9344, gfp_t mem_flags) +{ + int res; + int i; + + for (i = 0; i < ch9344->rx_buflimit; ++i) { + res = ch9344_submit_read_urb(ch9344, i, mem_flags); + if (res) + return res; + } + + return 0; +} + +static void ch9344_process_read_urb(struct ch9344 *ch9344, struct urb *urb) +{ + int size; + char buffer[512]; + int i = 0; + int portnum; + u8 usblen; + int left = 0, right = 0; + + if (ch9344->chiptype == CHIP_CH9344) { + left = 4; + right = 8; + } else if (ch9344->chiptype == CHIP_CH348) { + left = 0; + right = 8; + } + + if (!urb->actual_length) + return; + size = urb->actual_length; + + memcpy(buffer, urb->transfer_buffer, urb->actual_length); + + for (i = 0; i < size; i += 32) { + portnum = *(buffer + i); + if (portnum < left || portnum >= right) { + break; + } + portnum -= ch9344->port_offset; + usblen = *(buffer + i + 1); + if (usblen > 30) { + break; + } + tty_insert_flip_string(&ch9344->ttyport[portnum].port, buffer + i + 2, + usblen); + tty_flip_buffer_push(&ch9344->ttyport[portnum].port); + } +} + +static void ch9344_read_bulk_callback(struct urb *urb) +{ + struct ch9344_rb *rb = urb->context; + struct ch9344 *ch9344 = rb->instance; + int status = urb->status; + + if (!ch9344->dev) { + set_bit(rb->index, &ch9344->read_urbs_free); + return; + } + + if (status) { + set_bit(rb->index, &ch9344->read_urbs_free); + dev_err(&ch9344->data->dev, "%s - non-zero urb status: %d\n", + __func__, status); + return; + } + + usb_mark_last_busy(ch9344->dev); + + ch9344_process_read_urb(ch9344, urb); + set_bit(rb->index, &ch9344->read_urbs_free); + ch9344_submit_read_urb(ch9344, rb->index, GFP_ATOMIC); +} + +/* data interface wrote those outgoing bytes */ +static void ch9344_write_bulk(struct urb *urb) +{ + struct ch9344_wb *wb = urb->context; + struct ch9344 *ch9344 = wb->instance; + unsigned long flags; + + spin_lock_irqsave(&ch9344->write_lock, flags); + ch9344_write_done(ch9344, wb); + spin_unlock_irqrestore(&ch9344->write_lock, flags); + schedule_work(&ch9344->tmpwork); +} + +static void ch9344_softint(struct work_struct *work) +{ + struct ch9344 *ch9344 = container_of(work, struct ch9344, tmpwork); + int i; + + for (i = 0; i < ch9344->num_ports; i++) { + if (ch9344->ttyport[i].isopen) + tty_port_tty_wakeup(&ch9344->ttyport[i].port); + } +} + +/* + * TTY handlers + */ +static int ch9344_tty_install(struct tty_driver *driver, struct tty_struct *tty) +{ + struct ch9344 *ch9344; + int retval; + + ch9344 = ch9344_get_by_index(tty->index); + if (!ch9344) + return -ENODEV; + + retval = tty_standard_install(driver, tty); + if (retval) + goto error_init_termios; + + tty->driver_data = ch9344; + + return 0; + +error_init_termios: + tty_port_put(&ch9344->ttyport[ch9344_get_portnum(tty->index)].port); + return retval; +} + +static int ch9344_tty_open(struct tty_struct *tty, struct file *filp) +{ + struct ch9344 *ch9344 = tty->driver_data; + int rv; + + rv = tty_port_open(&ch9344->ttyport[ch9344_get_portnum(tty->index)].port, tty, filp); + if (!rv) { + ch9344->ttyport[ch9344_get_portnum(tty->index)].write_empty = true; + } + + return rv; +} + +static inline void tty_set_portdata(struct ch9344_ttyport *port, void *data) +{ + port->portdata = data; +} + +static inline void *tty_get_portdata(struct ch9344_ttyport *port) +{ + return (port->portdata); +} + +static void ch9344_port_dtr_rts(struct tty_port *port, int raise) +{ + struct ch9344_ttyport *ttyport = container_of(port, struct ch9344_ttyport, port); + struct ch9344 *ch9344 = tty_get_portdata(ttyport); + int portnum = ttyport->portnum; + int val; + int rv; + + if (raise) + val = CH9344_CTO_D | CH9344_CTO_R; + else + val = 0; + + ch9344->ttyport[portnum].ctrlout = val; + + rv = ch9344_set_control(ch9344, portnum, 0x01); + if (rv) + dev_err(&ch9344->control->dev, "failed to set dtr\n"); + rv = ch9344_set_control(ch9344, portnum, 0x11); + if (rv) + dev_err(&ch9344->control->dev, "failed to set rts\n"); +} + +static int ch9344_port_activate(struct tty_port *port, struct tty_struct *tty) +{ + struct ch9344_ttyport *ttyport = container_of(port, struct ch9344_ttyport, port); + struct ch9344 *ch9344 = tty_get_portdata(ttyport); + int portnum = ttyport->portnum; + int retval = -ENODEV; + + mutex_lock(&ch9344->mutex); + if (ch9344->disconnected) + goto disconnected; + + + retval = usb_autopm_get_interface(ch9344->data); + if (retval) + goto error_get_interface; + + set_bit(TTY_NO_WRITE_SPLIT, &tty->flags); + ch9344->data->needs_remote_wakeup = 1; + + retval = ch9344_configure(ch9344, portnum); + if (retval) + goto error_configure; + + ch9344_tty_set_termios(tty, NULL); + + usb_autopm_put_interface(ch9344->data); + + mutex_unlock(&ch9344->mutex); + + ch9344->ttyport[portnum].isopen = true; + + return 0; + +error_configure: +error_get_interface: +disconnected: + mutex_unlock(&ch9344->mutex); + + return usb_translate_errors(retval); +} + +static void ch9344_port_destruct(struct tty_port *port) +{ + struct ch9344_ttyport *ttyport = container_of(port, struct ch9344_ttyport, port); + struct ch9344 *ch9344 = tty_get_portdata(ttyport); + + if (ch9344->opencounts-- == 1) { + ch9344_release_minor(ch9344); + usb_put_intf(ch9344->data); + kfree(ch9344); + } +} + +static void ch9344_port_shutdown(struct tty_port *port) +{ + struct ch9344_ttyport *ttyport = container_of(port, struct ch9344_ttyport, port); + struct ch9344 *ch9344 = tty_get_portdata(ttyport); + int portnum = ttyport->portnum; + + ch9344->ttyport[portnum].isopen = false; +} + +static void ch9344_tty_cleanup(struct tty_struct *tty) +{ + struct ch9344 *ch9344 = tty->driver_data; + + tty_port_put(&ch9344->ttyport[ch9344_get_portnum(tty->index)].port); +} + +static void ch9344_tty_hangup(struct tty_struct *tty) +{ + struct ch9344 *ch9344 = tty->driver_data; + tty_port_hangup(&ch9344->ttyport[ch9344_get_portnum(tty->index)].port); +} + +static void ch9344_tty_close(struct tty_struct *tty, struct file *filp) +{ + struct ch9344 *ch9344 = tty->driver_data; + int portnum = ch9344_get_portnum(tty->index); + int ret; + + tty_port_close(&ch9344->ttyport[portnum].port, tty, filp); + if (!ch9344->disconnected && (ch9344->ttyport[portnum].uartmode == M_HF)) { + ret = ch9344_set_uartmode(ch9344, portnum, portnum, M_NOR); + if (!ret) { + ch9344->ttyport[portnum].uartmode = M_NOR; + } + } +} + +static int ch9344_tty_write(struct tty_struct *tty, + const unsigned char *buf, int count) +{ + struct ch9344 *ch9344 = tty->driver_data; + int stat; + unsigned long flags; + int wbn; + struct ch9344_wb *wb; + int portnum = ch9344_get_portnum(tty->index); + int timeout; + int maxep = ch9344->writesize / 20; + int packnum, maxpacknum; + int packlen, total_len, sendlen; + + if (!count) + return 0; + + if (ch9344->writesize % (maxep - 3)) + maxpacknum = ch9344->writesize / (maxep - 3) + 1; + else + maxpacknum = ch9344->writesize / (maxep - 3); + + count = count > (ch9344->writesize - maxpacknum * 3) ? \ + (ch9344->writesize - maxpacknum * 3) : count; + total_len = count; + sendlen = 0; + + if (count % (maxep - 3)) + packnum = count / (maxep - 3) + 1; + else + packnum = count / (maxep - 3); + +transmit: + spin_lock_irqsave(&ch9344->write_lock, flags); + wbn = ch9344_wb_alloc(ch9344); + if (wbn < 0) { + spin_unlock_irqrestore(&ch9344->write_lock, flags); + return 0; + } + wb = &ch9344->wb[wbn]; + + if (!ch9344->dev) { + wb->use = 0; + spin_unlock_irqrestore(&ch9344->write_lock, flags); + return -ENODEV; + } + + /* packets deal */ + if (total_len > maxep - 3) { + packlen = maxep - 3; + total_len -= packlen; + } else { + packlen = total_len; + total_len = 0; + } + *(wb->buf) = ch9344->port_offset + ch9344_get_portnum(tty->index); + *(wb->buf + 1) = packlen; + *(wb->buf + 2) = packlen >> 8; + memcpy(wb->buf + 3, buf + sendlen, packlen); + wb->len = packlen + 3; + sendlen += packlen; + wb->portnum = portnum; + + stat = usb_autopm_get_interface_async(ch9344->data); + if (stat) { + wb->use = 0; + spin_unlock_irqrestore(&ch9344->write_lock, flags); + return stat; + } + + if (ch9344->susp_count) { + usb_anchor_urb(wb->urb, &ch9344->delayed); + spin_unlock_irqrestore(&ch9344->write_lock, flags); + return sendlen; + } + + if (!ch9344->ttyport[portnum].write_empty) { + spin_unlock_irqrestore(&ch9344->write_lock, flags); + timeout = wait_event_interruptible_timeout(ch9344->ttyport[portnum].wioctl, + ch9344->ttyport[portnum].write_empty, + msecs_to_jiffies(DEFAULT_TIMEOUT)); + if (timeout <= 0) { + ch9344->ttyport[portnum].write_empty = true; + return sendlen ? sendlen : -ETIMEDOUT; + } + spin_lock_irqsave(&ch9344->write_lock, flags); + } + + ch9344->ttyport[portnum].write_empty = false; + stat = ch9344_start_wb(ch9344, wb); + if (stat < 0) { + ch9344->ttyport[portnum].write_empty = true; + spin_unlock_irqrestore(&ch9344->write_lock, flags); + return stat; + } + spin_unlock_irqrestore(&ch9344->write_lock, flags); + + if (total_len != 0) + goto transmit; + + return sendlen; +} + +static int ch9344_tty_write_room(struct tty_struct *tty) +{ + struct ch9344 *ch9344 = tty->driver_data; + /* + * Do not let the line discipline to know that we have a reserve, + * or it might get too enthusiastic. + */ + return ch9344_wb_is_avail(ch9344) ? ch9344->writesize : 0; +} + +static int ch9344_tty_chars_in_buffer(struct tty_struct *tty) +{ + struct ch9344 *ch9344 = tty->driver_data; + /* + * if the device was unplugged then any remaining characters fell out + * of the connector ;) + */ + if (ch9344->disconnected) + return 0; + /* + * This is inaccurate (overcounts), but it works. + */ + return (CH9344_NW - ch9344_wb_is_avail(ch9344)) * ch9344->writesize; +} + +static int ch9344_tty_break_ctl(struct tty_struct *tty, int state) +{ + struct ch9344 *ch9344 = tty->driver_data; + int portnum = ch9344_get_portnum(tty->index); + char *buffer; + int retval; + const unsigned size = 3; + u8 rgadd = 0; + + if (ch9344->chiptype == CHIP_CH9344) + rgadd = 0x10 * portnum + 0x08; + else if (ch9344->chiptype == CHIP_CH348) { + if (portnum < 4) + rgadd = 0x10 * portnum; + else + rgadd = 0x10 * (portnum - 4) + 0x08; + } + + buffer = kzalloc(size, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + buffer[0] = CMD_W_BR; + buffer[1] = rgadd + R_C3; + + if (state != 0) + buffer[2] = 0x61; + else + buffer[2] = 0x60; + + retval = ch9344_cmd_out(ch9344, buffer, size); + + kfree(buffer); + return retval < 0 ? retval : 0; +} + +static int ch9344_tty_tiocmget(struct tty_struct *tty) +{ + struct ch9344 *ch9344 = tty->driver_data; + int portnum = ch9344_get_portnum(tty->index); + unsigned int result; + + result = (ch9344->ttyport[portnum].ctrlout & CH9344_CTO_D ? TIOCM_DTR : 0) | + (ch9344->ttyport[portnum].ctrlout & CH9344_CTO_R ? TIOCM_RTS : 0) | + (ch9344->ttyport[portnum].ctrlin & CH9344_CTI_C ? TIOCM_CTS : 0) | + (ch9344->ttyport[portnum].ctrlin & CH9344_CTI_DS ? TIOCM_DSR : 0) | + (ch9344->ttyport[portnum].ctrlin & CH9344_CTI_R ? TIOCM_RI : 0) | + (ch9344->ttyport[portnum].ctrlin & CH9344_CTI_DC ? TIOCM_CD : 0); + + return result; +} + +static int ch9344_tty_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) +{ + struct ch9344 *ch9344 = tty->driver_data; + unsigned int newctrl; + int rv = 0; + int portnum = ch9344_get_portnum(tty->index); + unsigned int xorval; + + newctrl = ch9344->ttyport[portnum].ctrlout; + set = (set & TIOCM_DTR ? CH9344_CTO_D : 0) | + (set & TIOCM_RTS ? CH9344_CTO_R : 0); + clear = (clear & TIOCM_DTR ? CH9344_CTO_D : 0) | + (clear & TIOCM_RTS ? CH9344_CTO_R : 0); + + newctrl = (newctrl & ~clear) | set; + + if (ch9344->ttyport[portnum].ctrlout == newctrl) + return 0; + xorval = newctrl ^ ch9344->ttyport[portnum].ctrlout; + if (xorval & CH9344_CTO_D) { + if (newctrl & CH9344_CTO_D) + rv = ch9344_set_control(ch9344, portnum, 0x01); + else + rv = ch9344_set_control(ch9344, portnum, 0x00); + } + if (xorval & CH9344_CTO_R) { + if (newctrl & CH9344_CTO_R) + rv = ch9344_set_control(ch9344, portnum, 0x11); + else + rv = ch9344_set_control(ch9344, portnum, 0x10); + } + ch9344->ttyport[portnum].ctrlout = newctrl; + + return rv; +} + +static int ch9344_set_uartmode(struct ch9344 *ch9344, int portnum, u8 index, u8 mode) +{ + u8 *buffer; + int ret = 0; + u8 request; + u8 rgadd = 0; + u8 mode4 = 0; + int i; + + if (ch9344->chiptype == CHIP_CH9344) + rgadd = 0x10 * portnum + 0x08; + else if (ch9344->chiptype == CHIP_CH348) { + if (portnum < 4) + rgadd = 0x10 * portnum; + else + rgadd = 0x10 * (portnum - 4) + 0x08; + } + + buffer = kzalloc(ch9344->cmdsize, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + if ((ch9344->ttyport[portnum].uartmode == M_NOR) && (mode == M_HF)) { + buffer[0] = CMD_W_BR; + buffer[1] = rgadd + R_C4; + buffer[2] = 0x51; + ret = ch9344_cmd_out(ch9344, buffer, 0x03); + if (ret < 0) + goto out; + } + + if ((ch9344->ttyport[portnum].uartmode == M_HF) && (mode == M_NOR)) { + buffer[0] = CMD_W_BR; + buffer[1] = rgadd + R_C4; + buffer[2] = 0x50; + ret = ch9344_cmd_out(ch9344, buffer, 0x03); + if (ret < 0) + goto out; + } + + if (ch9344->chiptype == CHIP_CH348) + goto out; + + for (i = 0; i < MAXPORT; i++) { + if (i != index) + mode4 |= (ch9344->ttyport[i].uartmode) << (i * 2); + } + mode4 |= mode << (index * 2); + request = CMD_WB_E + portnum + ch9344->port_offset; + rgadd = R_MOD; + memset(buffer, 0x00, ch9344->cmdsize); + buffer[0] = request; + buffer[1] = rgadd; + buffer[2] = mode4; + ret = ch9344_cmd_out(ch9344, buffer, 0x04); + +out: + kfree(buffer); + return ret < 0 ? ret : 0; +} + +static int ch9344_set_gpiodir(struct ch9344 *ch9344, int portnum, u8 gpionumber, u8 gpiodir) +{ + u8 *buffer; + int ret; + u8 request; + u8 rgadd; + u16 gpiodirs = 0; + int i; + + buffer = kzalloc(ch9344->cmdsize, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + for (i = 0; i < MAXGPIO; i++) { + if (i != gpionumber) + gpiodirs |= (ch9344->gpiodir[i]) << i; + } + gpiodirs |= gpiodir << gpionumber; + + request = CMD_WB_E + portnum + ch9344->port_offset; + rgadd = R_IO_D; + buffer[0] = request; + buffer[1] = rgadd; + buffer[2] = gpiodirs; + buffer[3] = gpiodirs >> 8; + ret = ch9344_cmd_out(ch9344, buffer, 0x04); + kfree(buffer); + return ret < 0 ? ret : 0; +} + +static int ch9344_set_gpioval(struct ch9344 *ch9344, int portnum, u8 gpionumber, u8 gpioval) +{ + u8 *buffer; + int ret; + u8 request; + u8 rgadd; + u16 gpiovalues = 0; + int i; + + buffer = kzalloc(ch9344->cmdsize, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + for (i = 0; i < MAXGPIO; i++) { + if (i != gpionumber) + gpiovalues |= (ch9344->gpioval[i]) << i; + } + gpiovalues |= gpioval << gpionumber; + + request = CMD_WB_E + portnum + ch9344->port_offset; + rgadd = R_IO_O; + buffer[0] = request; + buffer[1] = rgadd; + buffer[2] = gpiovalues; + buffer[3] = gpiovalues >> 8; + ret = ch9344_cmd_out(ch9344, buffer, 0x04); + kfree(buffer); + return ret < 0 ? ret : 0; + +} + +static int ch9344_get_gpioval(struct ch9344 *ch9344, int portnum) +{ + u8 *buffer; + int ret; + u8 request; + u8 rgadd; + u16 gpiodirs = 0; + int i; + + buffer = kzalloc(ch9344->cmdsize, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + for (i = 0; i < MAXGPIO; i++) { + gpiodirs |= (ch9344->gpiodir[i]) << i; + } + + request = CMD_WB_E + portnum + ch9344->port_offset; + rgadd = R_IO_I; + buffer[0] = request; + buffer[1] = rgadd; + buffer[2] = gpiodirs; + buffer[3] = gpiodirs >> 8; + ret = ch9344_cmd_out(ch9344, buffer, 0x04); + kfree(buffer); + if (ret < 0) + goto out; + + ret = wait_event_interruptible_timeout(ch9344->wgpioioctl, + ch9344->gpio_recv, + msecs_to_jiffies(DEFAULT_TIMEOUT)); + if (ret == 0) { + return -ETIMEDOUT; + } + + if (ret < 0) { + return ret; + } + + ch9344->gpio_recv = false; + +out: + return ret < 0 ? ret : 0; +} + +static int ch9344_tty_ioctl(struct tty_struct *tty, + unsigned int cmd, unsigned long arg) +{ + struct ch9344 *ch9344 = tty->driver_data; + int rv = -ENOIOCTLCMD; + int portnum = ch9344_get_portnum(tty->index); + u8 uartmode; + int i; + + u16 inarg; + u8 inargH, inargL; + u16 __user *argval = (u16 __user *)arg; + u8 gpioval; + u8 gpiogroup; + u8 gpionumber; + u8 gpiodir; + u8 portindex; + + unsigned long arg1; + unsigned long arg2; + unsigned long arg3; + u8 *buffer; + + buffer = kmalloc(8, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + switch (cmd) { + case TIOCGSERIAL: /* gets serial port data */ + break; + case TIOCSSERIAL: + break; + case TIOCMIWAIT: + break; + case TIOCGICOUNT: + break; + case IOCTL_CMD_GPIOENABLE: + if (get_user(inarg, argval)) { + rv = -EFAULT; + goto out; + } + inargH = inarg >> 8; + inargL = inarg; + portindex = inargH; + if (inargL) + uartmode = M_IO; + else + uartmode = M_NOR; + if ((portindex > 3) || (ch9344->ttyport[portindex].uartmode == M_HF)) { + rv = -EINVAL; + goto out; + } + mutex_lock(&ch9344->gpiomutex); + rv = ch9344_set_uartmode(ch9344, portnum, portindex, uartmode); + if (!rv) { + ch9344->ttyport[portindex].uartmode= uartmode; + if (uartmode == M_NOR) { + /* restores gpio related vars here */ + for (i = portindex * 3; i < portindex * 3 + 3; i++) { + ch9344->gpiodir[i] = G_DI; + ch9344->gpioval[i] = IO_L; + } + } + } + mutex_unlock(&ch9344->gpiomutex); + break; + case IOCTL_CMD_GPIODIR: + if (get_user(inarg, argval)) { + rv = -EFAULT; + goto out; + } + /* inargH indicates gpio number, inargL indicates gpio direction */ + inargH = inarg >> 8; + inargL = inarg; + gpionumber = inargH; + gpiogroup = gpionumber / 3; + if (inargL) + gpiodir = G_DO; + else + gpiodir = G_DI; + if ((gpionumber > MAXGPIO) || (ch9344->ttyport[gpiogroup].uartmode != M_IO)) { + rv = -EINVAL; + goto out; + } + mutex_lock(&ch9344->gpiomutex); + rv = ch9344_set_gpiodir(ch9344, portnum, gpionumber, gpiodir); + if (!rv) { + ch9344->gpiodir[gpionumber] = gpiodir; + if (gpiodir == G_DI) + ch9344->gpioval[gpionumber] = IO_H; + } + mutex_unlock(&ch9344->gpiomutex); + break; + case IOCTL_CMD_GPIOSET: + if (get_user(inarg, argval)) { + rv = -EFAULT; + goto out; + } + /* inargH indicates gpio number, inargL indicates gpio output value */ + inargH = inarg >> 8; + inargL = inarg; + gpionumber = inargH; + gpiogroup = gpionumber / 3; + if (inargL) + gpioval = IO_H; + else + gpioval = IO_L; + if ((gpionumber > MAXGPIO) || (ch9344->ttyport[gpiogroup].uartmode != M_IO) + || (ch9344->gpiodir[gpionumber] != G_DO)) { + rv = -EINVAL; + goto out; + } + mutex_lock(&ch9344->gpiomutex); + rv = ch9344_set_gpioval(ch9344, portnum, gpionumber, gpioval); + if (!rv) { + ch9344->gpioval[gpionumber] = gpioval; + } + mutex_unlock(&ch9344->gpiomutex); + break; + case IOCTL_CMD_GPIOGET: + if (get_user(inarg, argval)) { + rv = -EFAULT; + goto out; + } + /* inargH indicates gpio number, inargL indicates gpio output value */ + inargH = inarg >> 8; + inargL = inarg; + gpionumber = inargH; + gpiogroup = gpionumber / 3; + if ((gpionumber > MAXGPIO) || (ch9344->ttyport[gpiogroup].uartmode != M_IO) + || (ch9344->gpiodir[gpionumber] != G_DI)) { + rv = -EINVAL; + goto out; + } + mutex_lock(&ch9344->gpiomutex); + rv = ch9344_get_gpioval(ch9344, portnum); + if (!rv) { + gpioval = ((ch9344->gpiovalin) & (1 << gpionumber)) ? IO_H : IO_L; + if (put_user(gpioval, argval)) { + rv = -EFAULT; + goto out; + } + } + mutex_unlock(&ch9344->gpiomutex); + break; + case IOCTL_CMD_CTRLIN: + get_user(arg1, (long __user *)arg); + get_user(arg2, ((long __user *)arg + 1)); + rv = ch9344_control_in(ch9344, (u8)arg1, 0x00, + 0x00, buffer, 0x08); + if (rv <= 0) { + rv = -EINVAL; + goto out; + } + rv = copy_to_user((char __user *)arg2, (char *)buffer, rv); + break; + case IOCTL_CMD_CTRLOUT: + get_user(arg1, (long __user *)arg); + get_user(arg2, ((long __user *)arg + 1)); + get_user(arg3, ((long __user *)arg + 2)); + rv = ch9344_control_out(ch9344, (u8)arg1, (u16)arg2, (u16)arg3); + break; + default: + break; + } + +out: + kfree(buffer); + return rv; +} + +static int ch9344_get(int clockRate, int bval, + unsigned char *bd1, unsigned char *bd2) +{ + int dis, x2; + + if (bval < 0 || bval > 12000000) { + return -1; + } + /* caculate dis from bval */ + if (bval == 2000000) { + *bd1 = 2; + *bd2 = 0; + } else { + dis = 10 * clockRate / 16 / bval; + x2 = dis % 10; + dis /= 10; + if (x2 >= 5) + dis++; + *bd1 = dis; + *bd2 = (unsigned char)(dis >> 8); + } + + return 0; +} + +void cal_outdata(char *buffer, u8 rol, u8 xor) +{ + u8 en_status, i; + + for(i = 0; i < rol; i++) { + en_status = buffer[0]; + buffer[0] = buffer[0] << 1; + buffer[0] = buffer[0] | ((buffer[1] & 0x80) ? 1 : 0 ); + buffer[1] = buffer[1] << 1; + buffer[1] = buffer[1] | ((buffer[2] & 0x80) ? 1 : 0 ); + buffer[2] = buffer[2] << 1; + buffer[2] = buffer[2] | ((buffer[3] & 0x80) ? 1 : 0 ); + buffer[3] = buffer[3] << 1; + buffer[3] = buffer[3] | ((buffer[4] & 0x80) ? 1 : 0 ); + buffer[4] = buffer[4] << 1; + buffer[4] = buffer[4] | ((buffer[5] & 0x80) ? 1 : 0 ); + buffer[5] = buffer[5] << 1; + buffer[5] = buffer[5] | ((buffer[6] & 0x80) ? 1 : 0 ); + buffer[6] = buffer[6] << 1; + buffer[6] = buffer[6] | ((buffer[7] & 0x80) ? 1 : 0 ); + buffer[7] = buffer[7] << 1; + buffer[7] = buffer[7] | ((en_status & 0x80) ? 1 : 0 ); + } + for (i = 0; i < 8; i++) { + buffer[i] = buffer[i] ^ xor; + } +} + +u8 cal_recv_tmt(__le32 bd) +{ + int dly = 1000000 * 15 / bd; + + if (bd >= 921600) + return 5; + + return (dly / 100 + 1); +} + +static void ch9344_tty_set_termios(struct tty_struct *tty, + struct ktermios *termios_old) +{ + struct ch9344 *ch9344 = tty->driver_data; + struct ktermios *termios = &tty->termios; + struct usb_ch9344_line_coding newline; + int portnum = ch9344_get_portnum(tty->index); + int newctrl = ch9344->ttyport[portnum].ctrlout; + int ret; + unsigned char bd1 = 0; + unsigned char bd2 = 0; + unsigned char bd3 = 0; + unsigned char dbit = 0, pbit = 0, sbit = 0; + unsigned char pedt = 0x00; + int clrt = 1843200; + u8 rgadd = 0; + char *buffer; + u8 xor, rol; + u8 rbytes[2]; + + if (termios_old && + !tty_termios_hw_change(&tty->termios, termios_old)) { + return; + } + + buffer = kzalloc(ch9344->cmdsize, GFP_KERNEL); + if (!buffer) { + /* restore termios */ + if (termios_old) + tty->termios = *termios_old; + return; + } + + newline.dwDTERate = tty_get_baud_rate(tty); + if (newline.dwDTERate == 0) + newline.dwDTERate = 9600; + if (newline.dwDTERate > 115200) { + pedt = 0x01; + clrt = 44236800; + } + ch9344_get(clrt, newline.dwDTERate, &bd1, &bd2); + switch (newline.dwDTERate) { + case 250000: + bd3 = 1; + break; + case 500000: + bd3 = 2; + break; + case 1000000: + bd3 = 3; + break; + case 1500000: + bd3 = 4; + break; + case 3000000: + bd3 = 5; + break; + case 12000000: + bd3 = 6; + break; + default: + bd3 = 0; + break; + } + + newline.bCharFormat = termios->c_cflag & CSTOPB ? 2 : 1; + if (newline.bCharFormat == 2) + sbit = 0x04; + + newline.bParityType = termios->c_cflag & PARENB ? + (termios->c_cflag & PARODD ? 1 : 2) + + (termios->c_cflag & CMSPAR ? 2 : 0) : 0; + + switch (newline.bParityType) { + case 0x01: + pbit = 0x08; + break; + case 0x02: + pbit = (0x01 << 4) + 0x08; + break; + case 0x03: + pbit = (0x02 << 4) + 0x08; + break; + case 0x04: + pbit = (0x03 << 4) + 0x08; + break; + default: + pbit = 0x00; + break; + } + + switch (termios->c_cflag & CSIZE) { + case CS5: + newline.bDataBits = 5; + dbit = 0x00; + break; + case CS6: + newline.bDataBits = 6; + dbit = 0x01; + break; + case CS7: + newline.bDataBits = 7; + dbit = 0x02; + break; + case CS8: + default: + newline.bDataBits = 8; + dbit = 0x03; + break; + } + + /* FIXME: Needs to clear unsupported bits in the termios */ + ch9344->ttyport[portnum].clocal = ((termios->c_cflag & CLOCAL) != 0); + + if (C_BAUD(tty) == B0) { + newline.dwDTERate = ch9344->ttyport[portnum].line.dwDTERate; + newctrl &= ~CH9344_CTO_D; + } else if (termios_old && (termios_old->c_cflag & CBAUD) == B0) { + newctrl |= CH9344_CTO_D; + } + + if (ch9344->chiptype == CHIP_CH9344) { + rgadd = 0x10 * ch9344_get_portnum(tty->index) + 0x08; + + memset(buffer, 0x00, ch9344->cmdsize); + buffer[0] = CMD_W_BR; + buffer[1] = rgadd + 0x01; + buffer[2] = pedt + 0x50; + ret = ch9344_cmd_out(ch9344, buffer, 0x03); + if (ret < 0) + goto out; + + memset(buffer, 0x00, ch9344->cmdsize); + buffer[0] = CMD_S_T; + buffer[1] = rgadd + 0x03; + buffer[2] = bd1; + buffer[3] = bd2; + buffer[4] = bd3; + ret = ch9344_cmd_out(ch9344, buffer, 0x06); + if (ret < 0) + goto out; + + memset(buffer, 0x00, ch9344->cmdsize); + buffer[0] = CMD_W_R; + buffer[1] = rgadd + 0x03; + buffer[2] = dbit | pbit | sbit; + ret = ch9344_cmd_out(ch9344, buffer, 0x03); + if (ret < 0) + goto out; + + /* uart receive timeout */ + memset(buffer, 0x00, ch9344->cmdsize); + buffer[0] = CMD_WB_E + portnum + ch9344->port_offset; + buffer[1] = R_TM_O; + buffer[2] = portnum + ch9344->port_offset; + buffer[3] = cal_recv_tmt(newline.dwDTERate); + ret = ch9344_cmd_out(ch9344, buffer, 0x04); + if (ret < 0) + goto out; + } else if (ch9344->chiptype == CHIP_CH348) { + get_random_bytes(rbytes, 2); + rol = (u8)rbytes[0] & 0x0f; + xor = (u8)rbytes[1]; + buffer[0] = CMD_WB_E | (portnum & 0x0F); + buffer[1] = R_INIT; + buffer[2] = (u8)((portnum & 0x0F) | ((rol << 4) & 0xf0)); + buffer[3] = (char)(newline.dwDTERate >> 24); + buffer[4] = (char)(newline.dwDTERate >> 16); + buffer[5] = (char)(newline.dwDTERate >> 8); + buffer[6] = (char)(newline.dwDTERate); + if (newline.bCharFormat == 2) + buffer[7] = 0x02; + else if (newline.bCharFormat == 1) + buffer[7] = 0x00; + buffer[8] = newline.bParityType; + buffer[9] = newline.bDataBits; + buffer[10] = cal_recv_tmt(newline.dwDTERate); + buffer[11] = xor; + cal_outdata(buffer + 3, rol & 0x0f, xor); + ret = ch9344_cmd_out(ch9344, buffer, 0x0c); + if (ret < 0) + goto out; + } + + if (ch9344->chiptype == CHIP_CH9344) + rgadd = 0x10 * portnum + 0x08; + else if (ch9344->chiptype == CHIP_CH348) { + if (portnum < 4) + rgadd = 0x10 * portnum; + else + rgadd = 0x10 * (portnum - 4) + 0x08; + } + + buffer[0] = CMD_W_R; + buffer[1] = rgadd + R_C1; + if (ch9344->modeline9) + buffer[2] = 0x0F; + else + buffer[2] = 0x07; + ret = ch9344_cmd_out(ch9344, buffer, 0x03); + if (ret < 0) + goto out; + + if (newctrl != ch9344->ttyport[portnum].ctrlout) { + if (newctrl & CH9344_CTO_D) + ch9344_set_control(ch9344, portnum, 0x01); + else + ch9344_set_control(ch9344, portnum, 0x00); + ch9344->ttyport[portnum].ctrlout = newctrl; + } + + if (memcmp(&ch9344->ttyport[portnum].line, &newline, sizeof newline)) { + memcpy(&ch9344->ttyport[portnum].line, &newline, sizeof newline); + } + + if (C_CRTSCTS(tty)) { + if (ch9344->ttyport[portnum].uartmode == M_IO) { + ret = -EINVAL; + goto out; + } + ret = ch9344_set_uartmode(ch9344, portnum, portnum, M_HF); + if (!ret) { + ch9344->ttyport[portnum].uartmode = M_HF; + } + } else { + if (ch9344->ttyport[portnum].uartmode == M_HF) { + ret = ch9344_set_uartmode(ch9344, portnum, portnum, M_NOR); + if (!ret) { + ch9344->ttyport[portnum].uartmode = M_NOR; + } + } + } + +out: + kfree(buffer); + return; +} + +static const struct tty_port_operations ch9344_port_ops = { + .dtr_rts = ch9344_port_dtr_rts, + .shutdown = ch9344_port_shutdown, + .activate = ch9344_port_activate, + .destruct = ch9344_port_destruct, +}; + +/* + * USB probe and disconnect routines. + */ + +/* Little helpers: write/read buffers free */ +static void ch9344_write_buffers_free(struct ch9344 *ch9344) +{ + int i; + struct ch9344_wb *wb; + struct usb_device *usb_dev = interface_to_usbdev(ch9344->data); + + for (wb = &ch9344->wb[0], i = 0; i < CH9344_NW; i++, wb++) + usb_free_coherent(usb_dev, ch9344->writesize, wb->buf, wb->dmah); +} + +static void ch9344_read_buffers_free(struct ch9344 *ch9344) +{ + struct usb_device *usb_dev = interface_to_usbdev(ch9344->data); + int i; + + for (i = 0; i < ch9344->rx_buflimit; i++) + usb_free_coherent(usb_dev, ch9344->readsize, + ch9344->read_buffers[i].base, ch9344->read_buffers[i].dma); +} + +/* Little helper: write buffers allocate */ +static int ch9344_write_buffers_alloc(struct ch9344 *ch9344) +{ + int i; + struct ch9344_wb *wb; + + for (wb = &ch9344->wb[0], i = 0; i < CH9344_NW; i++, wb++) { + wb->buf = usb_alloc_coherent(ch9344->dev, ch9344->writesize, GFP_KERNEL, + &wb->dmah); + if (!wb->buf) { + while (i != 0) { + --i; + --wb; + usb_free_coherent(ch9344->dev, ch9344->writesize, + wb->buf, wb->dmah); + } + return -ENOMEM; + } + } + return 0; +} + +static int ch9344_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_interface *data_interface; + struct usb_endpoint_descriptor *epcmdread = NULL; + struct usb_endpoint_descriptor *epcmdwrite = NULL; + struct usb_endpoint_descriptor *epread = NULL; + struct usb_endpoint_descriptor *epwrite = NULL; + struct usb_device *usb_dev = interface_to_usbdev(intf); + struct ch9344 *ch9344; + int minor; + int portnum = 0; + int cmdsize, readsize; + u8 *buf; + unsigned long quirks; + int num_rx_buf = CH9344_NR; + int i; + struct device *tty_dev; + int rv = -ENOMEM; + + /* normal quirks */ + quirks = (unsigned long)id->driver_info; + + /* handle quirks deadly to normal probing*/ + data_interface = usb_ifnum_to_if(usb_dev, 0); + + if (data_interface->cur_altsetting->desc.bNumEndpoints < 3 || + data_interface->cur_altsetting->desc.bNumEndpoints == 0) + return -EINVAL; + + epread = &data_interface->cur_altsetting->endpoint[0].desc; + epwrite = &data_interface->cur_altsetting->endpoint[1].desc; + epcmdread = &data_interface->cur_altsetting->endpoint[2].desc; + epcmdwrite = &data_interface->cur_altsetting->endpoint[3].desc; + + /* workaround for switched endpoints */ + if (!usb_endpoint_dir_in(epread)) { + /* descriptors are swapped */ + swap(epread, epwrite); + } + + if (!usb_endpoint_dir_in(epcmdread)) { + /* descriptors are swapped */ + swap(epcmdread, epcmdwrite); + } + + ch9344 = kzalloc(sizeof(struct ch9344), GFP_KERNEL); + if (ch9344 == NULL) + goto alloc_fail; + + minor = ch9344_alloc_minor(ch9344); + if (minor < 0) { + dev_err(&intf->dev, "no more free ch9344 devices\n"); + kfree(ch9344); + return -ENODEV; + } + + cmdsize = usb_endpoint_maxp(epcmdread); + readsize = usb_endpoint_maxp(epread); + ch9344->writesize = usb_endpoint_maxp(epwrite) * 20; + ch9344->data = data_interface; + ch9344->minor = minor; + ch9344->dev = usb_dev; + ch9344->cmdsize = cmdsize; + ch9344->readsize = readsize; + ch9344->rx_buflimit = num_rx_buf; + + init_waitqueue_head(&ch9344->wgpioioctl); + spin_lock_init(&ch9344->write_lock); + spin_lock_init(&ch9344->read_lock); + mutex_init(&ch9344->mutex); + mutex_init(&ch9344->gpiomutex); + + ch9344->rx_endpoint = usb_rcvbulkpipe(usb_dev, epread->bEndpointAddress); + ch9344->tx_endpoint = usb_sndbulkpipe(usb_dev, epwrite->bEndpointAddress); + ch9344->cmdrx_endpoint = usb_rcvbulkpipe(usb_dev, epcmdread->bEndpointAddress); + ch9344->cmdtx_endpoint = usb_sndbulkpipe(usb_dev, epcmdwrite->bEndpointAddress); + if (id->idProduct == 0xe018) { + ch9344->chiptype = CHIP_CH9344; + portnum = ch9344->num_ports = ch9344->port_offset = 4; + } else if (id->idProduct == 0x55d9) { + ch9344->chiptype = CHIP_CH348; + portnum = ch9344->num_ports = 8; + ch9344->port_offset = 0; + } + ch9344->opencounts = ch9344->num_ports; + + ch9344->modeline9 = true; + + for (i = 0; i < portnum; i++) { + tty_port_init(&ch9344->ttyport[i].port); + ch9344->ttyport[i].port.ops = &ch9344_port_ops; + tty_set_portdata(&ch9344->ttyport[i], ch9344); + ch9344->ttyport[i].portnum = i; + ch9344->ttyport[i].write_empty = true; + init_waitqueue_head(&ch9344->ttyport[i].wioctl); + init_waitqueue_head(&ch9344->ttyport[i].wmodemioctl); + INIT_WORK(&ch9344->ttyport[i].work, ch9344_softint); + } + INIT_WORK(&ch9344->tmpwork, ch9344_softint); + ch9344->gpio_recv = false; + init_usb_anchor(&ch9344->delayed); + ch9344->quirks = quirks; + + buf = usb_alloc_coherent(usb_dev, cmdsize, GFP_KERNEL, &ch9344->cmdread_dma); + if (!buf) + goto alloc_fail2; + ch9344->cmdread_buffer = buf; + + + if (ch9344_write_buffers_alloc(ch9344) < 0) + goto alloc_fail4; + + ch9344->cmdreadurb = usb_alloc_urb(0, GFP_KERNEL); + if (!ch9344->cmdreadurb) + goto alloc_fail5; + + for (i = 0; i < num_rx_buf; i++) { + struct ch9344_rb *rb = &(ch9344->read_buffers[i]); + struct urb *urb; + + rb->base = usb_alloc_coherent(ch9344->dev, readsize, GFP_KERNEL, + &rb->dma); + if (!rb->base) + goto alloc_fail6; + rb->index = i; + rb->instance = ch9344; + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) + goto alloc_fail6; + + urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + urb->transfer_dma = rb->dma; + + usb_fill_bulk_urb(urb, ch9344->dev, + ch9344->rx_endpoint, + rb->base, + ch9344->readsize, + ch9344_read_bulk_callback, rb); + + ch9344->read_urbs[i] = urb; + __set_bit(i, &ch9344->read_urbs_free); + } + for (i = 0; i < CH9344_NW; i++) { + struct ch9344_wb *snd = &(ch9344->wb[i]); + + snd->urb = usb_alloc_urb(0, GFP_KERNEL); + if (snd->urb == NULL) + goto alloc_fail7; + + usb_fill_bulk_urb(snd->urb, usb_dev, + usb_sndbulkpipe(usb_dev, epwrite->bEndpointAddress), + NULL, ch9344->writesize, ch9344_write_bulk, snd); + snd->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + snd->instance = ch9344; + } + + usb_set_intfdata(intf, ch9344); + + usb_fill_bulk_urb(ch9344->cmdreadurb, usb_dev, + usb_rcvbulkpipe(usb_dev, epcmdread->bEndpointAddress), + ch9344->cmdread_buffer, cmdsize, ch9344_cmd_irq, ch9344); + ch9344->cmdreadurb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + ch9344->cmdreadurb->transfer_dma = ch9344->cmdread_dma; + + usb_driver_claim_interface(&ch9344_driver, data_interface, ch9344); + usb_set_intfdata(data_interface, ch9344); + + usb_get_intf(data_interface); + + for (i = 0; i < portnum; i++) { + tty_dev = tty_port_register_device(&ch9344->ttyport[i].port, ch9344_tty_driver, + NUMSTEP * minor + i, &data_interface->dev); + if (IS_ERR(tty_dev)) { + rv = PTR_ERR(tty_dev); + goto alloc_fail7; + } + } + + /* deal with urb when usb plugged in */ + rv = usb_submit_urb(ch9344->cmdreadurb, GFP_KERNEL); + if (rv) { + dev_err(&ch9344->data->dev, + "%s - usb_submit_urb(ctrl cmd) failed\n", __func__); + goto error_submit_urb; + } + + rv = ch9344_submit_read_urbs(ch9344, GFP_KERNEL); + if (rv) + goto error_submit_read_urbs; + + dev_info(&intf->dev, "ttyCH9344USB from %d - %d: ch9344 device attached.\n", NUMSTEP * minor, + NUMSTEP * minor + portnum - 1); + + return 0; + +error_submit_read_urbs: + for (i = 0; i < ch9344->rx_buflimit; i++) + usb_kill_urb(ch9344->read_urbs[i]); +error_submit_urb: + usb_kill_urb(ch9344->cmdreadurb); +alloc_fail7: + usb_set_intfdata(intf, NULL); + for (i = 0; i < CH9344_NW; i++) + usb_free_urb(ch9344->wb[i].urb); +alloc_fail6: + for (i = 0; i < num_rx_buf; i++) + usb_free_urb(ch9344->read_urbs[i]); + ch9344_read_buffers_free(ch9344); + usb_free_urb(ch9344->cmdreadurb); +alloc_fail5: + ch9344_write_buffers_free(ch9344); +alloc_fail4: + usb_free_coherent(usb_dev, cmdsize, ch9344->cmdread_buffer, ch9344->cmdread_dma); +alloc_fail2: + ch9344_release_minor(ch9344); + kfree(ch9344); +alloc_fail: + return rv; +} + +static void stop_data_traffic(struct ch9344 *ch9344) +{ + int i; + struct urb *urb; + struct ch9344_wb *wb; + + usb_autopm_get_interface_no_resume(ch9344->data); + ch9344->data->needs_remote_wakeup = 0; + usb_autopm_put_interface(ch9344->data); + + for (;;) { + urb = usb_get_from_anchor(&ch9344->delayed); + if (!urb) + break; + wb = urb->context; + wb->use = 0; + usb_autopm_put_interface_async(ch9344->data); + } + + usb_kill_urb(ch9344->cmdreadurb); + for (i = 0; i < CH9344_NW; i++) + usb_kill_urb(ch9344->wb[i].urb); + for (i = 0; i < ch9344->rx_buflimit; i++) + usb_kill_urb(ch9344->read_urbs[i]); + for (i = 0; i < ch9344->num_ports; i++) + cancel_work_sync(&ch9344->ttyport[i].work); + cancel_work_sync(&ch9344->tmpwork); +} + +static void ch9344_disconnect(struct usb_interface *intf) +{ + struct ch9344 *ch9344 = usb_get_intfdata(intf); + struct usb_device *usb_dev = interface_to_usbdev(intf); + struct tty_struct *tty; + int i; + + /* sibling interface is already cleaning up */ + if (!ch9344) + return; + + mutex_lock(&ch9344->mutex); + ch9344->disconnected = true; + wake_up_interruptible(&ch9344->wgpioioctl); + usb_set_intfdata(ch9344->data, NULL); + mutex_unlock(&ch9344->mutex); + + for (i = 0; i < ch9344->num_ports; i++) { + wake_up_interruptible(&ch9344->ttyport[i].wioctl); + wake_up_interruptible(&ch9344->ttyport[i].wmodemioctl); + tty = tty_port_tty_get(&ch9344->ttyport[i].port); + if (tty) { + tty_vhangup(tty); + tty_kref_put(tty); + } + } + stop_data_traffic(ch9344); + + for (i = 0; i < ch9344->num_ports; i++) { + tty_unregister_device(ch9344_tty_driver, NUMSTEP * ch9344->minor + i); + } + + usb_free_urb(ch9344->cmdreadurb); + for (i = 0; i < CH9344_NW; i++) + usb_free_urb(ch9344->wb[i].urb); + for (i = 0; i < ch9344->rx_buflimit; i++) + usb_free_urb(ch9344->read_urbs[i]); + ch9344_write_buffers_free(ch9344); + usb_free_coherent(usb_dev, ch9344->cmdsize, ch9344->cmdread_buffer, ch9344->cmdread_dma); + ch9344_read_buffers_free(ch9344); + + usb_driver_release_interface(&ch9344_driver, ch9344->data); + + for (i = 0; i < ch9344->num_ports; i++) { + tty_port_put(&ch9344->ttyport[i].port); + } + + dev_info(&intf->dev, "%s\n", "ch9344 usb device disconnect."); +} + +#ifdef CONFIG_PM +static int ch9344_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct ch9344 *ch9344 = usb_get_intfdata(intf); + int cnt; + + spin_lock_irq(&ch9344->write_lock); + if (PMSG_IS_AUTO(message)) { + if (ch9344->transmitting) { + spin_unlock_irq(&ch9344->write_lock); + return -EBUSY; + } + } + cnt = ch9344->susp_count++; + spin_unlock_irq(&ch9344->write_lock); + + if (cnt) + return 0; + + stop_data_traffic(ch9344); + + return 0; +} + +static int ch9344_resume(struct usb_interface *intf) +{ + struct ch9344 *ch9344 = usb_get_intfdata(intf); + struct urb *urb; + int rv = 0; + + spin_lock_irq(&ch9344->write_lock); + + if (--ch9344->susp_count) + goto out; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0)) + if (tty_port_initialized(&ch9344->ttyport[0].port)) { +#else + if (test_bit(ASYNCB_INITIALIZED, &ch9344->ttyport[0].port.flags)) { +#endif + rv = usb_submit_urb(ch9344->cmdreadurb, GFP_ATOMIC); + + for (;;) { + urb = usb_get_from_anchor(&ch9344->delayed); + if (!urb) + break; + + ch9344_start_wb(ch9344, urb->context); + } + + /* + * delayed error checking because we must + * do the write path at all cost + */ + if (rv < 0) + goto out; + + rv = ch9344_submit_read_urbs(ch9344, GFP_ATOMIC); + } +out: + spin_unlock_irq(&ch9344->write_lock); + + return rv; +} + +static int ch9344_reset_resume(struct usb_interface *intf) +{ + struct ch9344 *ch9344 = usb_get_intfdata(intf); + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0)) + if (tty_port_initialized(&ch9344->ttyport[0].port)) +#else + if (test_bit(ASYNCB_INITIALIZED, &ch9344->ttyport[0].port.flags)) +#endif + tty_port_tty_hangup(&ch9344->ttyport[0].port, false); + + return ch9344_resume(intf); +} + +#endif /* CONFIG_PM */ + +/* + * USB driver structure. + */ + +static const struct usb_device_id ch9344_ids[] = { + { USB_DEVICE(0x1a86, 0xe018), }, /* ch9344 chip */ + { USB_DEVICE(0x1a86, 0x55d9), }, /* ch348 chip */ + { } +}; + +MODULE_DEVICE_TABLE(usb, ch9344_ids); + +static struct usb_driver ch9344_driver = { + .name = "usb_ch9344", + .probe = ch9344_probe, + .disconnect = ch9344_disconnect, +#ifdef CONFIG_PM + .suspend = ch9344_suspend, + .resume = ch9344_resume, + .reset_resume = ch9344_reset_resume, +#endif + .id_table = ch9344_ids, +#ifdef CONFIG_PM + .supports_autosuspend = 1, +#endif + .disable_hub_initiated_lpm = 1, +}; + +/* + * TTY driver structures. + */ + +static const struct tty_operations ch9344_ops = { + .install = ch9344_tty_install, + .open = ch9344_tty_open, + .close = ch9344_tty_close, + .cleanup = ch9344_tty_cleanup, + .hangup = ch9344_tty_hangup, + .write = ch9344_tty_write, + .write_room = ch9344_tty_write_room, + .ioctl = ch9344_tty_ioctl, + .chars_in_buffer = ch9344_tty_chars_in_buffer, + .break_ctl = ch9344_tty_break_ctl, + .set_termios = ch9344_tty_set_termios, + .tiocmget = ch9344_tty_tiocmget, + .tiocmset = ch9344_tty_tiocmset, +}; + +/* + * Init / exit. + */ + +static int __init ch9344_init(void) +{ + int retval; + + ch9344_tty_driver = alloc_tty_driver(CH9344_TTY_MINORS); + if (!ch9344_tty_driver) + return -ENOMEM; + ch9344_tty_driver->driver_name = "ch9344", + ch9344_tty_driver->name = "ttyCH9344USB", + ch9344_tty_driver->major = CH9344_TTY_MAJOR, + ch9344_tty_driver->minor_start = 0, + ch9344_tty_driver->type = TTY_DRIVER_TYPE_SERIAL, + ch9344_tty_driver->subtype = SERIAL_TYPE_NORMAL, + ch9344_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV; + ch9344_tty_driver->init_termios = tty_std_termios; + ch9344_tty_driver->init_termios.c_cflag = B9600 | CS8 | CREAD | + HUPCL | CLOCAL; + tty_set_operations(ch9344_tty_driver, &ch9344_ops); + + retval = tty_register_driver(ch9344_tty_driver); + if (retval) { + put_tty_driver(ch9344_tty_driver); + return retval; + } + + retval = usb_register(&ch9344_driver); + if (retval) { + tty_unregister_driver(ch9344_tty_driver); + put_tty_driver(ch9344_tty_driver); + return retval; + } + printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_DESC "\n"); + printk(KERN_INFO KBUILD_MODNAME ": " VERSION_DESC "\n"); + + return 0; +} + +static void __exit ch9344_exit(void) +{ + usb_deregister(&ch9344_driver); + tty_unregister_driver(ch9344_tty_driver); + put_tty_driver(ch9344_tty_driver); + idr_destroy(&ch9344_minors); + printk(KERN_INFO KBUILD_MODNAME ": " "ch9344 driver exit.\n"); +} + +module_init(ch9344_init); +module_exit(ch9344_exit); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_CHARDEV_MAJOR(CH9344_TTY_MAJOR); diff --git a/root/package/link4all/ch9344/src/ch9344.h b/root/package/link4all/ch9344/src/ch9344.h new file mode 100644 index 00000000..d9c14941 --- /dev/null +++ b/root/package/link4all/ch9344/src/ch9344.h @@ -0,0 +1,199 @@ +#ifndef _CH9344_H +#define _CH9344_H + +/* + * Baud rate and default timeout + */ +#define DEFAULT_BAUD_RATE 9600 +#define DEFAULT_TIMEOUT 2000 + +/* + * CMSPAR, some architectures can't have space and mark parity. + */ + +#ifndef CMSPAR +#define CMSPAR 0 +#endif + +/* + * Major and minor numbers. + */ + +#define CH9344_TTY_MAJOR 168 +#define CH9344_TTY_MINORS 256 + + +#define CH9344_CTO_D 0x01 +#define CH9344_CTO_R 0x02 +#define CH9344_CTI_C 0x01 +#define CH9344_CTI_DS 0x02 +#define CH9344_CTI_R 0x04 +#define CH9344_CTI_DC 0x08 + +#define CH9344_LO 0x10 +#define CH9344_LP 0x20 +#define CH9344_LF 0x40 +#define CH9344_LB 0x80 + +#define CMD_W_R 0xC0 +#define CMD_W_BR 0x80 +#define CMD_S_T 0x20 + +#define CMD_WB_E 0x90 +#define CMD_RB_E 0xC0 + +#define M_NOR 0x00 +#define M_RS 0x01 +#define M_IO 0x02 +#define M_HF 0x03 + +#define G_DO 0x01 +#define G_DI 0x00 + +#define R_MOD 0x97 +#define R_IO_D 0x98 +#define R_IO_O 0x99 +#define R_IO_I 0x9b +#define R_TM_O 0x9c +#define R_INIT 0xa1 + +#define R_C1 0x01 +#define R_C2 0x02 +#define R_C3 0x03 +#define R_C4 0x04 +#define R_C5 0x06 + +#define R_II_B1 0x06 +#define R_II_B2 0x02 +#define R_II_B3 0x00 + +/* + * Internal driver structures. + */ + +#define CH9344_NW 16 +#define CH9344_NR 32 + +#define NUMSTEP 8 +#define MAXGPIO 12 +#define MAXPORT 8 + +#define IO_H 1 +#define IO_L 0 + +struct ch9344_wb { + unsigned char *buf; + dma_addr_t dmah; + int len; + int use; + struct urb *urb; + struct ch9344 *instance; + int portnum; +}; + +struct ch9344_rb { + int size; + unsigned char *base; + dma_addr_t dma; + int index; + struct ch9344 *instance; +}; + +struct usb_ch9344_line_coding { + __le32 dwDTERate; + __u8 bCharFormat; +#define SB1 0 +#define SB1_5 1 +#define SB2 2 + + __u8 bParityType; +#define PAN 0 +#define PAO 1 +#define PAE 2 +#define PAM 3 +#define PAS 4 + + __u8 bDataBits; +} __attribute__((packed)); + +struct ch9344_ttyport { + struct tty_port port; + int portnum; + void *portdata; + bool write_empty; + struct usb_ch9344_line_coding line; /* bits, stop, parity */ + unsigned int ctrlin; /* input control lines (DCD, DSR, RI, break, overruns) */ + unsigned int ctrlout; /* output control lines (DTR, RTS) */ + struct async_icount iocount; /* counters for control line changes */ + struct async_icount oldcount; /* for comparison of counter */ + u8 uartmode; /* uart mode */ + unsigned char clocal; /* termios CLOCAL */ + wait_queue_head_t wioctl; /* for write */ + wait_queue_head_t wmodemioctl; /* for ioctl */ + bool isopen; + struct work_struct work; /* work queue entry for line discipline waking up */ +}; + +typedef enum { + CHIP_CH9344 = 0, + CHIP_CH348 = 1, +} CHIPTYPE; + +struct ch9344 { + struct usb_device *dev; /* the corresponding usb device */ + struct usb_interface *control; /* control interface */ + struct usb_interface *data; /* data interface */ + unsigned int num_ports; + bool modeline9; + struct ch9344_ttyport ttyport[MAXPORT]; /* our tty port data */ + CHIPTYPE chiptype; + int port_offset; + + struct urb *cmdreadurb; /* urbs */ + u8 *cmdread_buffer; /* buffers of urbs */ + dma_addr_t cmdread_dma; /* dma handles of buffers */ + + struct work_struct tmpwork; /* work queue entry for line discipline waking up */ + int opencounts; + struct ch9344_wb wb[CH9344_NW]; + unsigned long read_urbs_free; + struct urb *read_urbs[CH9344_NR]; + struct ch9344_rb read_buffers[CH9344_NR]; + int rx_buflimit; + int rx_endpoint; + int tx_endpoint; + int cmdtx_endpoint; + int cmdrx_endpoint; + int ctrl_endpoint; + + spinlock_t read_lock; + int write_used; /* number of non-empty write buffers */ + int transmitting; + spinlock_t write_lock; + struct mutex mutex; + bool disconnected; + u8 gpiodir[MAXGPIO]; /* gpio direction */ + u8 gpioval[MAXGPIO]; /* gpio output value */ + u16 gpiovalin; /* gpio input value */ + bool gpio_recv; /* gpio input sync flag */ + wait_queue_head_t wgpioioctl; /* for gpio input ioctl */ + struct mutex gpiomutex; + unsigned int writesize; /* max packet size for the output bulk endpoint */ + unsigned int readsize, cmdsize; /* buffer sizes for freeing */ + unsigned int ctrlsize; + unsigned int minor; /* ch9344 minor number */ + unsigned int susp_count; /* number of suspended interfaces */ + u8 bInterval; + struct usb_anchor delayed; /* writes queued for a device about to be woken */ + unsigned long quirks; +}; + + +/* constants describing various quirks and errors */ +#define SINGLE_RX_URB BIT(1) +#define NO_DATA_INTERFACE BIT(4) +#define IGNORE_DEVICE BIT(5) +#define QUIRK_CONTROL_LINE_STATE BIT(6) +#define CLEAR_HALT_CONDITIONS BIT(7) + +#endif diff --git a/root/package/link4all/gobinet/Makefile b/root/package/link4all/gobinet/Makefile new file mode 100755 index 00000000..7a2ca33d --- /dev/null +++ b/root/package/link4all/gobinet/Makefile @@ -0,0 +1,54 @@ +# +# Copyright (c) 2014 The Linux Foundation. All rights reserved. +# + +include $(TOPDIR)/rules.mk +include $(INCLUDE_DIR)/kernel.mk + +PKG_NAME:=GobiNet +PKG_VERSION:=2011-07-29-1026 +PKG_RELEASE:=1 + + +PKG_BUILD_DIR:=$(KERNEL_BUILD_DIR)/$(PKG_NAME) + +include $(INCLUDE_DIR)/package.mk + +define KernelPackage/usb-gobi-net + CATEGORY:=LINK4ALL + DEPENDS:=+kmod-usb-net + TITLE:=Kernel USB driver for GobiNet + FILES:= $(PKG_BUILD_DIR)/GobiNet.ko + AUTOLOAD:=$(call AutoLoad,81,GobiNet) +endef + +define KernelPackage/GobiNet/Description +This package contains a USB driver for GobiNet3000 +endef + +define Build/Prepare + $(CP) src/* $(PKG_BUILD_DIR) + $(call Build/Prepare/Default) +endef + +# define Build/Compile +# $(MAKE) -C "$(LINUX_DIR)" \ +# CROSS_COMPILE="$(TARGET_CROSS)" \ +# ARCH="$(LINUX_KARCH)" \ +# SUBDIRS="$(PKG_BUILD_DIR)" \ +# EXTRA_CFLAGS="$(EXTRA_CFLAGS)" \ +# modules +# endef + +MAKE_OPTS:= \ + $(KERNEL_MAKE_FLAGS) \ + M="$(PKG_BUILD_DIR)" + +define Build/Compile + $(MAKE) -C "$(LINUX_DIR)" \ + $(MAKE_OPTS) \ + modules +endef + +$(eval $(call KernelPackage,usb-gobi-net)) + diff --git a/root/package/link4all/gobinet/Quectel_Linux_GobiNet_SR01A02V16.zip b/root/package/link4all/gobinet/Quectel_Linux_GobiNet_SR01A02V16.zip new file mode 100644 index 0000000000000000000000000000000000000000..61daff40954b14e1d8b03cfdf19c93fd5d1a7180 GIT binary patch literal 1496246 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≫+ML`ST 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 z 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 VMB*>|` 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�U`Pp6{oBHRx`Kgn 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 s!AL< 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�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 z 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

    <$1mAes;vJjrr=<4 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

  1. &J8|^CoWF=k025%fEWMtt!9&)cN!9S$d`PnO8DZxfOfp#!~0)7nXg8rfkgR zjsG)d@BamJ;qI6x?7)A*obM@o1ZH#z_uL(}t*?9?I!Dx3>(kGKVX&dtKphQwLu~`c z(j!eqQ@TT&60J8~2|g4TnW`pZrWJ=m9O409uE1rI%BO;n8Nh65)X0OZFR*2^AFam zH7Kg{41bri=d#zchuyQc1MIRFz@?(FfM~0ygm{TsV`!TahM*bH;4Lmn#!I72RS1b= z)2ONF7=t>ROn$_P%p^=}t0|DqbS6V5jBTgW;7l5WO-N{Z8I#D;_d917wfUQ!-Lq$Q zndNz&_x;}IdEI;>=dU$2lDlT^VsH_Id$tSk@~pqEe3AUIsrZ3te&$`6vTrgCrvJhv zSX*RHAv|-z-0PqNcIs;d*%wS*(C~$y}jpNPoQ_7@y zA9<6g%7Vl{23o3CWNxNr>7MQU+h}8nY$e_AEDUGPPXFwkH3re6T;ng_cxJ)s`TNWi zyOw*;;MF;Z)R_r=b)K5C8~jupSF2RgC%q;OO0-YJRK#LA7r^LtSs_TcLgOLg z3gMX$9u478k>ZSxyD)Oeu6Zshx)`zP9Ic_dykLl@8Lwn+mZx$>yi3Wne(pVExEtgK za-6Dba)X{+HQD`|OJrG1>&G5)X4CFRaAqa6QLmRsfRqv{5KNN1;Nb4>dqv62*f*8`Fxg`)moOw>g^ zR+cIhmxyb`7LgOno3m~@ERQ5}x9o_q?G?O)rC7FMum^lE3ubj>BcG@OJ85vl1WQCU zrwms-)p_{e2lt%s9sZX7sW!< z7bTIR`9(x5^cNbJ+lAB)jyx+$9#-C-`MthU4W@{qC`Af&a*CSF<0G2x6vzVhxgR{5 zgp$}=s`0jb1;=$?I5aP`J#;B_H8dLHyP%CjRusSj4S10uXujl3L3^ka&5g&cy44WX zy2QH1+G6FbIZ-E#l$^wVd1wf3jQM@l2+W4UmC0n*&nGL{-yxWHekMA6WXPU2eY`mx ziB$1fdKHDSk8a13Wq1 zvaI3d{)Pu1Z0JAxNd1nwL=*~f``DS1MD>>AnHxtlZ{dR%w(RTL{QB!#x(;k*^>}n< z!g34hk%!9AOZw)vWM`6WD0sSn><-=vl3PB!TgAOnpG0;G*h+(R4tF6qf-LDp1*jPItYt=Wd{TmWp^^=R2E&@-5<-OP(BTam)@>NsuwV57W6v}yNC zhNp+w%d|d5U0_>-*%~!$Wq@EL6J~Kh$FiG8LtG=SzveZIK919%AWx0b&T!1U(sLdX-C+*aRU2(mT%~3coK5&=> zMR}qlY?D)jT^z&)#|~R%-q;{3Dqzg`%jN5E4uC$K7B)$t)HvOdW?^pD?TjQt;%DcV zE?V34_~N>{19J)n`WuUD7hWvxZrMS~P!1{7=UYXJzFnmBs(}>pgan#~P=~&%T3jlU z5_y4aT$an!CWgeMNNui=E9s&(UWx0d&^zNLE-yg!0#t9lO=c*Cu+5#41*az20^rGc zyy3C`k9LTSp}TOirdxF%;%suHQvHf^-z{5uWEOgEt?FpF{O7@(+FzgF6ovH)`uY&W z$PvTSp3*}J3sZf>!f~93i*b}9hwKPDsLf(|d*;Sv0BnbMy##J7cB(lAD=#@O!LPtL zAjoYOCwRDfpXs12XJn_u%z2yh(=FPmNG#*W9jI#Q82iK=$G(7Usg< z8<5>i$U`RP$!~yez6;wF+?U&C@17Xn}D4-j@%M7;N81R3B`t|c}aZ0KWG1{ zy2X#TooU^hX{WzCS-+?F;=-2BFPIZOhY(i{C&YvDF&RW;X5w-|B!yX7%=an1M^RHJ zv{y9}Qj_XRmHt)zMEzQ&Js!NvgEz=GWl|;MKRG^jkf^=VPMBXkL?5HvR}`lRPCyvM z$U{5;)#E|P^DE^K3Xzn6QmarUkVHI00GnRVmd9W3EWDk)o9t?33d1JcM z%m_Zn2&gb1#4m|g#Zi$WF)ZE?DMYRgSl&ihEVvF%?^b-Z=9vV4yE>b@V594?$VnMp z>#;l|53%5%HUmLNN4f*D7;FH=i9|FCrHL@{m=UNxnuNNKWVJZ%%FFlf1rJ$r#$lnmss}5>7F00`5))i8XLuVMZd@F&TG9h`ch~zc z_Ac()-oXY`<0-f?6b!@xg9$c-gxEYNNS?G|uZFM}YbiJa3r6S_ zN^Df43n-=GjG{o%6s1hp9TL*Y>_5a<#!xh@VkjEcyi`ZNq{)A-XffTHI`0^ByyKwA z5p%rnppO5$XslxS45q9&R*_hJtO_+YS{Z?rk860NbK~)~9s9QbxZu6myQeYUHmL7d zI`Ae&1+KF2mqRYkn)OEq+gssgTRNPdoc4XAI#f8r=B*iY`xn6Zh3MaA%Yr~qH%O(s zlAKLpl5i*56ZGp5{7ev!YqvCVP{U=eau*TX)Gn1&+pv<3rnBiSX-?tNxGJlgh7xBy z-Ug90u1nvSej;6xXrEugB?Bar76d?{!U&UFq&p$Mdsk^}VT^P}xNgw5C{I zg(GBERzP*ClvLp@ys)CjgY=d0lpKSFj#3WEK?3tL?O@r%KU?|uJZ+A`HIGE^ZC%>F zAbDb_@s9^~4PpCZHLml)8+RWK9(gtVr|VBVJK8y`d$950CjHOJnvG3~K&nbxH+t%k z)^9E83H(#YrakI#~=(|JzRwEv%<`?^UP|#$ z7mJjZYBD*WlK_eWx<0(Hena#8rr{MW@rG!dPikpBy~_6bpwzbM)8-59dCyDEw&YH3 zVtC~mhF}yB3B25E)7vq(NW;I$17<;vY+KU=xC!%TPx)rwjGFDHm^=q1e{ol5he0h#%l{}lI zT2>4<&5u-Us9zY~yy|po%WO!>w_l+1H>IkiLHC-K!=Jsy44b(b0^R6@&W2GET{EH3 zvSNf?85h~*!io}A2-{bQgX1sHOqyS)sxdTPq#m1P=nh?qK=!~M6j39w2o-5WN;nnC zA!>SxH?2hFPtG&WJFwm1bC7a}BH}0&O~mu87}2D`&Zb+{j9MPYc#|i5<(>*$p=KxY zECUpa5n|213X@(`i0P|PDbzeMb`u^?G+SDc?{oPVmC{TXhCj}dpUpq4WHFb)3RR>5 zwqAL2^|aF(gVo>LUA2Aj^}+`yo?7xHHVB1**2S=e?Jf<(iq<#!8=tv=o&5tLZY?n4 z{#fne^j=V1W#%5yY%d`{>K;WNfSVIt#zP)>p(><|Dt3-fArt$2W;AZZe6>Du&mUw3bL+ zXw?@l*7*+O?S=ch@xB4_)ivR2en`#d&P)$7he!g4)7G~hFzdLOs2aW8(;T_X!MqYh z6*6TGS+pyqk@TWqnVLcZ_EW)>O8tr#`=znT<#D6QW5laG>l?@O+3v1B#@FJI4UmG``_4T3hwGcGRC(vl5&X$WU*S$cmJ>gF_{(1@ z*YV#9;nO3}HLh;xd-yQ^bzvF%9d>*-d#XCUyahV9ZszY?BbP!osLh<;>A)Qlu1-=a z)YPPsF`-rGwNvrbf&7?Qva}SrvAsf>B~!kx#s2P+KMSvo6#mt>;|@7K{nQI)2if*3 z&{e<|LWoWRGbMCu6aX_o%)d#Hp!R^yiz*y??6{ZUD2LZ@Teu;P63St_jpI4Y@sRT+ zh^!I}QA!ZKf&d>cv24!^Qm2RmVwFgEk(01Zn1^r=I*NXPUO{gnTMUXosDh#Zp7J*2 z5ZHKzpe$7a?iG#+r-XCDTY}9l_=HM<@~;L&e6%ye`EsD)L(ytPn;vi`qzD}clt13$HAU)96G7e}ZNN&3+r)^%IAL&vvU#5H3^U1<{O4?r z2c6rD!56bz8B#c1vK^4^NRE7*273d1*QU}hKP$?1$U5LW@I=MO&g6KzaN7Z3M8!5(c6~#L`MqT)yj8!wP~A9f-ViGrKjb|O=g120gKxCAu7zp* zbmiV6)`zEy3VSmvde{Nh&**6l_Gm{mGGBv(fj0w03zz{{)xxT(6{3i)G(j{YrfX6G zb!3CN)nqa?H6xbsNr8~+R94sODtxZ~M(p&F6 zMpukK{*yWI`8~S2<~6EFuWh>dC)N8~URa4xek;q$vEm{j1tjy%-ol?2lIQL3*@tX; z$o{!arrI-Yvc~?7z0;-#LvM!29^;5X<{3*2vX8t$h!F~gT-^xkx=|dPfuYb^8f*^| zISBI6;K?9a5L_0V@RL3oG<=v6q>!bXmD6hbb?DI#=tR?tbn>MR=Yj1Gcs>PemIQxL)&Vu)~IXhbt*;?BnxKCCF=oUk4D z!^+9gVP4JNgDqz{XLi;NBdih%LBqWW*91`>Wv&o4)vSE`dherYrMm)A@58%D&zkeV z(62Xa>uX*7?C_$obzO^FXI0Idgc~wl>lgKLhJ76w<{;iF;SU2s{ML!?L)t!9fbD{K*ElhthzOZFEhib;?bn_N)2diZr;Ek9gI zky6?ugN%-07&pLe3FUq?(3s_X3^d}1jSV%8e~9KBXAXbfMNz=t0KOqQ&=I=%I97^z zY{C$6OMy1y4yJgQIXl1q$vMvp{VLS_=;l|Kwm$i-gIQ>u0MvW+XG|&BL%9;q-b6wroS@Zb2j$Ba&spgK*`fo>cs`37$S~A)?Dd(CD-|tDP-Q zzcb)SJPQq@m=iA-(KAQ=)X(w&^f&DxCGn-~YiVd0_ z^~V!&>~cI%T9PT@T~6>Wmq1C4KMF$Bj3%R*C{v^UDhSdlHKXR4AnUb)3R}@kk&Fr| zCXahkEkD+-_5{*T`5+O|bqr{7EbAETNVco+8V zY+N|M@!@Xoa#!rz&(E6Qvw8N{wmsZ@Kff;SerZnK+UeqcgdldQE!86iKUeUvB$7*hi?0}BIG4%=a}KLRWgj*tR1##m7D7sP@fX?ev8P*F7X z+kzX9yE;)^JvJ8>+%T$l0)cmtS}S?xMMX_=vad(428Bu z-(LRA`zNaEpxnDSSbcmpwC`TG@u5a~^wQpA1>SQc>4W{ScO(j}ndTQ?!irk4qU%^u zL}<^fyn4{P2<3HEC*FT%`JbQdfp+$Cbi`ZZ zJ?$I+BWy1?w)YaO-LBOA8xt_|K$^@VIuZ5Ozbq?0Spj% z{6{zT^smb$v6{ZeGTuiM<&cQ?gRa?6EIkq(=~{gZ_WMZEdk!n&oNDzJ(Xo6c`cbB} z!fA9!zcpYH8UGqVjSL}s0)>J8z(C+Hfqw@i*LU7`)khT_=1ZW8h2nqdt`^{^$}{?Z z|Nr0TZuZ{I?%nJzm%ESbO?I=UL9(d9+Fj}tj68y^;Uxj;0|;QL5f!KfN^LP%+oBW{ zqzR7L0dxRKc}*)z%UIN5&?$9HEv1xBmD&`mGXhFB{qEf)b*5!^=D&AmGnu{TJLi1o zP)(%Z*K`Ooz0$0ZB2{Sm*mQMUD`@y^`wcVaTnm*I{v6Tb8r4Pw?U<6s+|l5cq@j-V zPWfT`or2&#?VRDn+a`$RM;_yM#}#mg=u9!d`pjjI?C)FiusgGHgJb-=o!P%|areeg zV6!u|(q0^8yE9Bs2qX6lyf2(_S!a_BIZ(JanyO39N-aupBW}lA;ajl!8b?t1yfSKoO4#p!px@+wR@fj6xu`j$Oa z>R;Qjy7Ql(u3J%Bdh#TF?+-hFHTSN0_q_7*F=#z~Zq7INzH|mUHZ>pWE`9v(pYPt+ z-FcD>43NiBvmfyq8ltlaLFqLQe8ApB233+evac|$LYF-dnhcofhh8tdB!enMg*xF4fy=#-3lXYVCW+}W8jGu+E9Wf7 z4|FnU?n4G2VvfO=&AiIMJ7W+__Gal{fBVJamEEhpHGlJo&KC}k|Hk%7chAnPt_=-* z^~=-Ox6P!1(#NIq6Q_0ma@ypzPfx$&;UAZJcQ0AfI&ncV08hfcJ9DEJ-RG)q-~)b% zpC`5CnZv|J54p$V&vhi%CZ9_FCdqFKfows0WJSdB^vhnD;GOQJ0dr6AXpjbjnkS|- zpnT#rCX+m7T++<2ClILcCl#B>ep?G&#FVuL<7=iOwZ}Ya(wLbysoCIg>m3wS-|6H1 zMaLFhSn_?kn^+MXmt6=8aa;y9mXt)p7&c6d`w738NDi7Nu0rSJmqy$)dV0bdR9;tm z=_I^=u+(3c{`NLCotm|)?5L9t{;afcqoYpLX5l(>+aRmXyR2G(tYU_A-%)6d%RYb3qbF=U!XxlN)10ZbYc#9ftWDHdt#qR_L1KnKE3|~+Vt61- z_3++sG0eiSiV_mg15{!GF_u*{sH(IJL@*48R?&vhxxGfwc*|hK(2Z_fHil6VN{+BA z96$CuxxwK{u^0_c?k^S{(2!F3K1Zw=?#q(Q;tNo0f$AZva@f>}zT7AbCj+{@r>C=1 znla~vt*uk1Onmg+p3c&5-_DG`K09_Jzwq{F!QJzhwA}vacfNKj`}NgZw|=FrDQO~G zKr$4wu>L(|8Op!aViTE?+*nu!&2AiDa-_?(=6T*5HulhRqxLgkMZXk28g=#-k12n>o{zkUEr!?&XY$}cW zGt6DxgV;qv2AK5HS5Y>soc50>paX{H}3Is+qYhA+_7xK`Xw(s zzqIrFi@un5W8~Oj5<*}WW6~1oeEKkD=h-KWu8(Yq?251j<|8KMBkvu#Y$TI>Le^7| zr^@qWMr7O|vaI?|Uf_kwaE=$yG&ByJM^&u~B&@AbP*qVRf={*O!C)L7-Z+XA%o0_0 z*xT?w(X2RRp_o|al>IZCC13c1jg6tq9y9mi3$+FG@hN%Nn1%Y`@YD+(5z?c`o8C40SGG0k5V^wy{n|E`Io4Skc zj{PDsez$gA0~i;YtUUz z+7tXr0=^gB5ZxYS(FoUeqwc5dn^_W86n$>1}!Tp8(>7uY(R8+Zr*CK z=2`+H7D%EM3TF$T>W-jWP|yjz+Vn}8dbo|wpbII>QmCg{Yy^IS|IZ58dMpNcpagWF z0IE@pKx9;HszHUL>M8ZC%3^9>ZBgf{TrK<6J8iEya6fSTGy>k#gm^oeZfncCYW@%1 zWdqy9d57_P?`&Uuc5-$cpTExEzMSJTXZw7Panj7^BtU5r^3kRxv?gQYyQrDbXvsP$ zYn|E!=s=|?TsOngb> zeW3N*i98`imZ))=iZ_;U_Qc-L?L$HwJcYS%-4u!cwAEyNp1zyLQ_0@s?c`sR znsv6rHauJfY>9Y+V{A20VKuDMl>k@*P;}y4;!1)r5{U%ih+m%mSg9ndWo5;l(B_PAupHZI=OyM!>RRW##6>1CkXRqacu0?WLY$Y0%AIe zXu>rKFU@jAV6R>mFnF6C=?UHn9sKrAZ5b~pb5&o0z}2YvmQONeb}7U%m{Pe0A4?tH z$bAVo#C31tr(1TI&zW*vn_hdqcf|`w@}LXC{_W{637$E})O>CC$syn23MzBHfEryME#OllV5woPVY7i~G=Q8T%T^kkSYH6>1E3;+>0cIr zz-u_YLGOa`pNHQ_VmU_=`N9o~P^i}^oT4l|H}kDLK5P`~s8WUaIlez#-I;e0}==*-x@MDKVrBM=>c8k9DGn?E|IXi=o|SSE=?9 zOKNJKH+JP4Ti@(@`kU9fM;@`J$(M$n?rMrQJkFwkGBnWr=3|TsN2x@+QhOK+V)KDX ziJt;M2U`o?N3J6WNn)E73_5@|Xbs|e!6D%Kis;fPUZwRw^bugv`jOr2f_cu*@+1#> z`GfphJh6lS0e^>oz<eks41CwMJwhjV0ivKF#l&?XFmVxp^zIzP{b;$d5z zY>wO5P`@}J?h^@GjEPyXQ=AYViaMhxCNO)*77AevrUZu>fXz^eQ6Q~Q0Hb`ALXA>4 zDJ@Asn}Hd8^!oCAany8g@^kBfBasmR{E&gnlS!3&3|djyrVzc2rb(g9w|^EddZCyT zR4XEMm!ugkaJbK5zCj5RK|=>ABh)HxIUO`3lzvIOa(Cf;@sm^a;ceS4fFE}D0^N#v z=JSpl51w~!zqa7`$aAh&PfAdGt|qZ0*U{EeJif#gLF8bkEW!+52fgHw(o$=O7&t0LEuaX$&l7bE z0go=h+6*SvKoe{jR0bn8u+QAY?!!PqlMhL}f|LVM4s!z=;a0pX3yu9kG9#;Ji(Ne{ zRish&u|Q$J%6`leYz(SIK=sjSzYd=ke2^%IkHH6ya`>crpv06+LhCM+vo?&{HEN4Y zmp!{IkJ(3>RI8-~%;je%5k(Zav(cs#i(3Qr-VTOp%biRH;d6YybrH%i*ey(E2pJtm!QN_43mzoM`L(?=AeF#r>Vm2-?P>=?}C=;glz^Mvzo4 zA4~vUf=SdSh|MC<(V(7QNIy$Ipg*OxyfJNDWc-~`v%t`6SY;p&1w>UxuyQlnXcjdR{S3*`3^pLwz2s;d)iuuhnBUT zo&~$7xW9vqv~PSDMdYcgk;a41wV`fhDRpc2LoK91NQwDHwlpI6LYopCA$Ew)`X=jCbZk?Xh(2SE3 ztsJ8;N+=@JjF-Wm$TI!R07^<;rW3wB$4oE}nQ5lNNH7d$*Hv;bBq_CZGAs?)1ojY{ z#ZJvjJbR+#dY4LAzc{jV{tZ?S38G_ zwWEkUYt#fg8Wf@$wu)osauF#Y_T?*BcC<`4TO1S0;dv6$>{r(o+-ce`=+TVUkk1SS zs2-^qMXD|bmlG^`Ds~87yT2^Uly8ZZ667tV4=&!gaO{|+;coj2NVR?sC~W%EK;LRV z+i(#GQ;n1?L9r_>JRUZ`9b;a*F_kG{D z``)r|zgQ?BEE+L@u!vF(JB=}7r!oD*50av`Y5bs*BFq$lCXto|lW9BJ)+A$^Hg+cQ zBdudQAc9Ut+Cfb0XlGiC4IL*=(xy%`j+sfvCLuu2eTyhfc6lH7?(+6`e)pd9`<>(E z=`COXm0ZD|>1)aad85VbvrM&$5yZm)YGtHyT_tZ{2%RZ7S9G}upDF??KR172J~!*~ z*5n~~oB{`3wOk6(<5U65^Sr0M=e$=v9`#DSRo;5pNv;wU{`I8?%_HIceu zK%w!Vfy{7?N`o7Q?{V+Y9kK*jGn{Q^>Z^(U>s7Z!r?u7B9Pl;gmf&O+XWo3urs~kF z!=06v&#O-?s!Sa38T;&y{r%@Z+P$HprewdbE1g@9+$lK&$tSDIVCm^~nVlQeQxgv# zC@9;!qBx$)YRbNiYi`TUVynF29$7Pc7*pP(6|tDZew2CmV*z909|HFX7}x);<2oIL zuIVX9c#b0+ZL_vp+pC??-qCJodYEg(YevzWj2BB6IN<1J9$3hP?r4rxVWDA#6eq=? zklMovU#~Ycjb^EuJUh}l)A5}>OZ3dPdf-!3R5Z7rQ{J06*gv;tQ%~K|n#owc#26o; zSZtPnbf&{jKv2Zdv0~gcqe{q#s5!c)Ct?Xa6d#C>#`(GUbASRE^lxfT}-=xZjL+8<&n6{S=5c9k%)$!9E+0wf^4#4%d%UF98c4g;hbATs30p; z9d}3lYETuT`PN%TwzY|<{em}9&x<+zRfogXNxXd@4)c-U|KQgLyZfI0{-JNa_Py>q z2kNQG`(Sd4(c0*=&z5Eiz)UfjRA_`0JEax|AOI>a)0ij= zngJQz71FCK!?bILrTdU)!dD!k4)FHDFZEB}?B{LSGyf~pO^w38K3$c)QoZ>$jN>u- z)iC?c5DU?|cmX$U@&v~iXPT>Fu_jo67OcOdE*((L@Oo0 z`Qda>?^5sn@x&)RH7f(#t(w?s{Ah+}_&^z?ow(zK>V{)!Ri><1v^dRFSH%UYEpuXj37C)w^9Ye6 z3IrjaVpikMFY9lD>bfb6|$WjYm(;Sa|nxT z<`4;cB((Y&m@xj>6Z3tP(BUIwC$WeS=CXY!T#3+?2qk9~ZoP{>Z>4JdG%aj~AsJg& zk5jjycL(}yIQ?-ZS}-pi2iTy(Dg$av_-_t;5`zRKW+u0RLoEl5 zM|mmdS?f|y+UNvwUne$f3Ohn;l*rgAug1d#-ck>}hzvb<8|(s1K@FUROYl!nm%AS5EY-vzkwqFo?xa=jOiho@_O6oxp3PvrpPQ}`V>ZdANYOZ=-9aohX)z@gu-%$UiUQ_vM zb)!0{@~2hM)U7J&13Dbip~HB_zzxPW162bY(@dE76?r~NI%eKwcAC$c$~6;SGGVLL zZoO~aw3K$M*BY?+s}`)Y9<}g*1?{3&;6|}spe5)bb|xR{OdE7VRZMQ709#a2qCQCV zd94OoMbij53n~v;%bINuTeHK~HkpNO{?+eC+Wbj>`qq5e?2B@9Sia<$+=*{THu;Z} z-=1SR{zrM$#x`}G;q#u4d#@dTT;E{F4gn`5Bm{?=5{D2l%MntcVTA-ubP$w}YJrrj zEhJs3)~XFFDMeKoL|O-`+LdjhMMPSrWMR<$z;r3=+L>HT4U??+F+a^R_bhxw88B04YqXL=_6TUwVs zN9X5srytK&kbE&h@*$}Caa(0&S!GQ4Vd3-j{jd2M@dNYv!~UqB85}&O zpZlCyK@x1{FbP^d`yd5Pe=z1Z^E@$2Y1l?wK{2jEL6{K)Ef@&WkY>Ts`>ie34(ph8 z+B#=xAqyN<$f9+o3Tmv*)8N_Y`JLwj&*vWP0K5*=3i?eCXujaRWCNka2;Dltiij_o zI@{8c%my`N&E~M@8dFNid^S&Q-QD=NoIK;9v~8}!)-(Kq>ij^#;@W)LpSiH0pnh9v zmAkU&-V#?xXqk(zGJ#SB5EPs{9q+^-(Z#QnZ4}LZ|Nt)R(V1lLr zOb7(mt!$V+Zm$JVcX%he1nb26_}3cgr14&{PrNFaI1WuRpA0xr(lzH2!r}ydg&N9N z(&r}7q|u!J-J-Nu)2scZYMQBqbz4R2sp9#Y#NMh`#t%Qcys1)Ec!+%t%Q5XZ$=%W^ z2Db5DzK=7mDwsyxNpz=!FA>go)>u>1-;QFXS1hhY|A*z_$fl|XwboOjZ1=Ktm8<6e zWE|iAJidLC{dUAp26|y1yoyR{<2T|OK^1`|M87$HT@7P3*h2s|Rc(rdPqk`%;i}4Y z%XS|gw`bWaycY&Vinz%`Cx{z0rA)VCj$pVfCW5oeV$QxinJ0C(?bu`JUM!FINp_sh z8FF$|s)V%W&WjOl@?y?(PWp7+>rp8uby`J8Gm&*Q72qSp9x+woLAjoxmDVi6iLo5N z1z@mqO;vn(WObKVy7u93u2>L}4PDdGZ8<>f`!XyhxJ3)aXUi{^dDKVoY zhQF~=WtD_9{WD`b&HW~oW|O(yWM)*|L$EtKs}itnmza%HqxGu$>}1-l5{7;JFv;O% z^qCmzD(p;556XtVCuWzuGxp4!9aWd!UsJ)GUfBGYBl9I}bD+Oi{nlT9v0)kqhfffW z^0f$gph4uI(o^T5tKH4+qwY7{@4Cf7euUG<`Ewk-1}d#Oi{=#0Cc@>Kk*A12r$i!V zkZi=4t|kltE)#A<1e$2kuaOApnh`Uj|G(R8yDGc-OjI81SzQaf)ff#9?wIq;*gIvj zpXh6lFRyL6#6Pq8Gylwd;mGuZ?s#nT3t!$JL1o!aKIO~!Z%|pnw$fZ%m)J_PnWXqi zOye5qk;R0XG(rDBKcRw9FgT41DP(>iCV&qDz?-xg<(oj$2rWK@sXV*pc&pvofEd8% zL8sKv@w|rcP8X(9>B3;Dr7&dL-$^tPy?SV|>z=u#0HsjPR}N=RWk!eLr6Vauzsj)p zrXzbqhP@&z#>9DXK?tuvWEx`%7ZY(hh>m#;m?%EPfoIoD0fPaGzy#<*+yYn%ftiUc zDjO33UzvH}NM_40#G#%a|Eh+)hsY=vffD`*?7_K~kvY=orC}PSOs9Yscmz``2yWk3 zQlD&X!HvX`1NL-w;xqSQS0D4Ev{Af;&$db{$mX#n7*ZJoupdmfC{XP~h+N$ex5pPF zy9t<3tfZ5EOM2D*P(2&(*>e;N;)!ViC7<1?+;NI1!@_`x)}BurKYH<5@mA(<5LAqA zS6mll;&Vr{zH3vdA@f!f6#Bv%%$mQA<(Z&eHvFy8;! zzpzx$<|?xl_Dqgo&G-l|b?js^G2(E=z(Z{rcDQ4dbr3Atu2`g5_FC~WM3pH=NZa)+ z;@cF9voh!C-pMX}YY2xxf#xy@Cy}KZid%`u%+vTtNlgjeo8C{iXU_ddb&t=EPyPU( z>ALgo-P!IP4EvX=gwi*8*_D6NH2{`rse7} zdDC)J$70GOu8o~o$S$G^*xrkGa1j28-uqxS8C2X(GPvDT5WSOB0O9GW;3~2Qrl*31 zH%|q(69{gEY3@w|LC3T_@ERGS3^KU?ZBpu52PL=#O{|@9q+F*RAi11>SB%SWN$eya zey+Z=$1~U{6!^UBZ8RrcaHR_ESH0|Ar&$($WLM#(_NHcH&drsME`<&VUCh$@@!H`Do!*^ zuc$2u6sWO=`gUx7yKG(IKMGu~VC=4^;pI9Yj)N6__k*bNBWVPlr z@&x6?(1Ed?g+(zY3qmmm3$aSZ*1H+Z0_MQ}96HA_=lJB#Vf;s1+Y0cXH4-4G z0qa+wEf7r|D$1kXiP5(1oT%U+rlzvdHZ`S1lt1I#rY?L1qDI`c1v(vUzkIp<%700( z*4QYnD}2wrbMM`UcXxI?8}Ay7#}905Ll)S@i+Plh!&}OuP#hjvoFZQ3M}jHf;D`n( z7y?NP6q6=k1=EI<5;dYBad;@wA_ZD#oYJawC{<`9B{cm(T2%=p0&jcn%zC{BMg5U| z%+BoYneW_lzjMxaZaYV|dhiU71nZlcR=aRo4UrhEvs*s|sDVb94HrP;09W{D{1!(K z;qMZTNx0&N#s0N^TIX-_&+}jOYg3H*#!`b#GH02KOxEIuiNWUJf*=DLFiDUGd?CLt zRAdyJq{s+R>c~H%$TWlo43qGUNs5jOb z9ma0sk|C$;HVztR@a(F=ml)qOwiu_4i^iV~amaYxIBVQCG-;F=)XU9?Ioe!hZZbQ~ z^QN5AX&x}&#<(3ueD5-ZH;1sn0YJJfu`8wFvZ+>x8~cenCzXHW#TD zD7sSg84A|Ya3Zbu{V}anXCH8qmde2^KLOh$o7IZ`i88w=?Q|gzzz8JlT+j z4IA1}oVT^9H$cgK5wofig=z{C#l#KAc##g09Fm!lgTJ>Zxcnr6*N}u7s=PnxnSL-=qZ2pyVdb$N{ZDEmWm( zY`bK}j0xk5Q6yaJ=zy#2Z;sBtM0cQm9EYkf7RBaTb_$!p_)!%}3Gg43X%KW{(g81S)?u{6L!#SrCnr~Z5Sr1U52)P~0O8vUkD`}ONOJ*vO0pV!$_ zI_Q<&R_jTN#;xC3f3^N;X(8*dg-zP3e3zABGarFzDY31UA+jQWX(XD|C2W%?WJX@b zHY0UuNfuzzdj<_OR{B<*ULCN`vXNNz12{Y76hRad^8|tMc-e#Dz_H>HYzf%C)jtKP z#($RBCF-C4u@jRsN**q0Eny|`bgpIg-%zQ9RVg=j+`!t@q(%<++gwL=uka^D1^q?? z?ypi5t%?j-)9~m{U{zLaO4SYQdbKs~)16GPo~Vf>VyeQTo^;3V=0(|?bx%c(+T$iy zM^`>s`KUEw!K}7lHJAI_!!I0~u0PgMyr{B_{&Ls!vg^eMqUBTTqIzs(pkmV45G=?A;1?jA zfy_swNr()vAXAqiEnsSx$5_b(0j8Z|r6+>{DO7d_H3l-^<22;eg2ajc7~DZZER1`G zTqge_!cSEDu>?ynp=lQW{IJ%l${2<`s;idl^k_WAlycJu--b$~2OD;7bE7F>l9*-v2;EIC$NKO%WN`vy*S zxw)3!C-*s%-HTWPJ1}oJiP}D@+8Qv*v4zC@FlF>>*pw@(!m5pQbZ1Y&Cwx?Qft4hd z9YiayGSesycFGcEhim*%X3#pEaP8-Iv;JiXQnmbLE4!C!7)C_;J-m%Wz`-p&02*3FGXTM!bcJWUuHECj=9d zxHA?Gq^~K3lF|D!u0{#xb&7iP=en{#>QZ-|!~Q`Z;undJM931mNawGIp3(Mbhc(PX zc*L&p6?>+cK4L`7&}7}5Mk3&ec%{#)P3FF7d%a+Ld%V=EN`L>SSgiDZoOqlwOtXQE z{zV#(=m8cgS=*RGsl|s*=|klW%f_x1%g<#m4W9Ap9aFzuH>duU-ya=3aT&zUA%nbR zzJ32jv0czcF>McsEXw_op$)D zA_L(BJBN(To`zLWkCa6QX8WO@48vV&H&v_g5W2*xf^HD-fWT>^S|kM<<^>a;y|PEr z?Gn~YkOeLzu#lr0g+8c9-jCuTKaPBk*@r2ZmGIRtFJ{_xnlOd(LAwjqdqg2_lY1po zsNNye;hj9QCfn9)94KcXJU(Xx#L^{u1nd*AUThN-)xWc__&pf+ zLSSLLGoPtpmpqV1Xt5LTs6^FzCA_Qx=J9du9Sx3YC=NaQ1?&*eF0g?xxIkepT}&x; zB8sSkLCs7|gEP~k4|V0OC;f%HKDEPn;}m!#RQQMFTJy{dYO-*DK^VIzlC9A@(t zPE#B>qKN{kg|eXxp%rG}~4AH6Yj_20s_>!f3A(QY43m zAPl0Y`-fc~4y`R8yZq*1@D6^71fhG>(teN)vF{Jx!)AMQjv}}~3NLb4!`nGsq?iNF zisfDbE>+@Rhj};T8z1Llxo%*&Hrd6?b)rUlloL(Wirj#1=Utq(@YNivJr%j-4F)iM zR|HjmTD360SBebiT7ET~?fGjZJg3344B8niW@{PdSCY+S42h_+-6iR-HKo^92XneY~?3(iUaw=h!oIZi?i)N6|fQO1{GKxR2C01yD1*WUYJPV?OH)? zQeK6J-1;uukCDrsR&Mox4c0KfzGYW-GuXm*G5VT-N5z@~XZ}x4-J4Yo-c~Ra*AcA^ zB}pOH<95?g0r)ydk5l?IeV)>_6qZq#Plpm2D^;g}%}xfO11YB{4`zT`wadmrG7>la z@_SNJ;LT5Nb*qh0?$}?k(*^zrtd`|~ZHH0H_z@H=|Kq$|gR8o#IDY?YKTaa!OweL1 zQbq&|p+MR|2$YA1mOM&|l1_N3msFbY2z6|Lgakw(g*1UqrG!w@Tx&yl1w@^y1?(4e zS{#IEXB?>->5Pt3#}C%}p)){|Gp@bQz2}~Db5Y8@-_QE(z1M&J*S}MOc1diJsFhg3 z&%jlS;}C&Aj#)xzx9PN$3dRveg@h1~P|eIRLL-Ar9aJ> zoVl^>?&HjP%t4ny?PgffEU}l+MA#!xEJ{VAV9|=th)5$^8-JFS`;1}Du8A588se<+ zDWlzi;xuX%7P2;m8w~2()bVFetqEAq}$aKM^TKY4J#{FtWuQNl{ z4&P*jm{JAe-DpoOcZ;`LGdixYQh^<}TVS8S8A5`vkFbH7iRRK{riYE0aL`bW8RtcX zbLWQil02^e^Q|L4MDVi#pJ4F4_f%9v^W4uLRp?S^RoJG`ClDA33n}Cs_Nehlxe?0o zPkC9Cm>Oe)Q-0fB)?o+EZvN+C$=*vvr}vQ0hpCKrKANd zSdIm(q@k$>`?5H9Nhi}){_(=zy#)}Q|8Rlea;xW7u$+meL~Yq3MuvT1-0sf`luESma-!Y zU6Lz1Rd9VHXj-rvqq#M`3x?)V9yAw1G)`=mdtZ}mlZT}^VcWA0fF$kY8rRY+m%{#( zGxd)SR32#D8)r)N{wKj-zx6jY-v72$myVUW1yzE zN=?QDb4M4vpI6~K1U23}zX>hjujaqn~}_byi0>g@RS-gT}%{7d>$w0gBWV~WPT8lyCR=ip6C&?jyO;TyyMLcj=&7ra0F zm%xd$%e%!GL%$c(Llil&HSXMYg#v|9ihZ$zXbx5IW+(ooF~D3JCnU}idbpRAG)`fC zg6$&Z71 z{?Rid)l%Dzsg-Kw3#h|X?j@8bymxt}h}2u`PU_OfdG$Z!9CN7E!4@a5ilAQ3?dn-~ zs7Y1)bh={i2R4Q#JVz{w++~?N<&Zk2s7YZCD!9}!jn1TnQZk!zlTgqg(c7%C^=xO1 zC@8KahUfUc`ABG!d1#MGQ*7kr3OUN(lDSK^+b#*LoQIPX{7=7QQY_hqMbx}6c(v7- zfp6Fdh{U(Ni}Ykft8sVsFC3iLFeJyc;Kim>p-m-;R$C0;Qw#=WStRq`Dl#ZVBT+^e zOI;O~*vZAWl33%t!L61txk}v6y=Lok@xaX-|NNmr(VuzrXykj@d)BYSot0SYy~2?% zGWV<8hc>H&ifR>BC@fYeR+y|fU~QFDCebcVB@+_cg0yYsgojj?6*9Hgne=sEHFp=c z*x&1h8`wA_Z@$!>R_)((jzk~v3WQ{5`L*^WHz!*&wX_x%#^YJv+uvG)R;#PTxxd;o z*Lq*}mdo~7XbL&(OAj7WXjCXuE7UqAR>&qvfd}R*rjT zX>Tl)XmessZe^RsHjT?%oipIuLTpGch&DqFs6j@F0?X_F%-t;zDki@&91{9rCc)@7%v;|9P!nhbOhmwRp`tq5~ zqGf|a_D%JPIA^{JoB0e{1AXsJ#K+NRRGqfKbNG}o?{WY2&Utk<>h&rd zP}pmwI$L2t;|;3>9Ul=eDylt=>rbW4zuQhOT_!(EB$s(qFY(bW7q- z@}IUi&&wATb}3wuILRzBl?C5nggetw>IH&W7{?Q~_#@n;TOv2J)}7BiYBWTv(s9rc z`CP;{YLjl$#zCIv3klR?m)e|Uo{DT5p)iG-?5e59!;E9IU*6QA$7D&gGoNwGeAWC> z1zfYl;xv~DD87SUQ1~uyjcM_u7Qk3;I;Pz2wUB2J?$~qj#S#qn?Cp!Vm9p+hSJ`+ zwxrgM;1mdEQO-4;O)$o>d`JS~tf2*J{BXlE8VJ|Pjcs+yFKd)(l-M}Y#dFEQp=9>EBydjPhXVUWkD#3btHnk^ zK7yZs%=4MoyOS7}@qR({xaM`$<2>)r90w;3cQ2&@8wbObP41GLMgeX5k`|>Jvoxk^ z{F-gf!8!+}HlIDGuufs6!Ve5Ec8aqjS?LbKYKF^&V(U-qan28p#lu(lDu=I7afI`T z`_xAom#npMudmjat;;kq>qo-ivN7`;o^Wu$Ve~x?J?&tng9rG^4SbuTYK5}`JIsA= zyxXvuu|Y`#b9De8a4^P+(*{d@Xl+QORzTWZ=mSxMl(cFUT2-_X zX{9PEjnAZO?U^%Y=GcxeeL1pZY5ukL{?}gr_kr69z0t+-2L<>!C5p5_CnoXSwBQvB z_E@mZf(^X2@+_QV!TToNP{5~TP1ZR^&B{{}+p!0c{;_|IG@4vvewnS#?_Fcamz~ME z`1#lWbot{kfA*fzX-)EVi9aLJ7AgUEU_FIECesLw-vBrb?5)xeozjH##^lxbos~2o z$+W8?3-ou+BW%PMaW9Hx2-yMfMQ$v8!2s;_zRN+nvIy@IiN;&DP#ABgSh!N^eJdEj@+t%mRPrd zHiUq*O?B{^j|RQ<{!rFR9gDsqXmd#=-}ztzyyo=b-Y^rb*6AM;`MztzH3h8-ngy(* z1YIkH?{Ky^6+y=;aRfQEG^L8m&c|*QL@g~`#f5lGG=<6KIh|c(C~2|<|4{HJ1-BG* zDA=N^324MLMN`;mp>JPKVtg#)hRfILSiz0}bM~$nw-QOs;hgl|DPyI^5ZzME{ zH(he^JnM=Mk+^(&xeJ6m5MznktKbmi(Dz&YZWrlkt)fd{H_vMu;=W{{qJyTcJo8e} z4F@S;1u-LYhOzK{r<54CASRDEcFd*FR{vg~($n*7$g&}7^P>=DQ^~}Aau?B1piVh9 zZiHnLYoN^tQ8#@tD1$j6rNw$0J)5U(AiXDSf~!P;ptmvP-0{QdkYD-hF(c-DYLY_o z)N*uMwFS+1md;I(ipiv_RU({IcfK9cQ&Y18u}Y1KbTX?T$ABSM^fo-5`c~!W>4ZeIk6SG$ zw{}~Y3W?DnA(1T*2E}vEp(0~6`iX6J+6fec+vRCJ%jD68GEc6RVy1*V0a*gJVJ#j( zVIrl;lQOYF!<)w3yh)S1Y^D^E)$s0CO6QUATeDctAoMH{{OFz$Z_yLuA|8wY@Pc4R z_vyCstkWxQMM8<8R-vbjVB=7#1o#14;}B zik-`8eci7KSH4d_!x5N|w^7^_SS=t^hIqFOWvGsy`y49s%LptH=AMrNQ@xbvBKvFu_Or1_oX>p8DeGI!yyPB9nml9weG zODGTVVZ_Z4gYscXgpV-IL&PtgAH4)u;V;(I{@Dd`ZcvX2aoIw;M>SEQ$rFSwYiw9( zyCYm}HCT8>z zzr(}fD(Us9Q_TvODp=rETPK*pAk)gVc3F+qP0N~Pfo)Bs5P^pg8z@`I4v7sCYNY=Pl`xwH*l-+$igClRp;u#N zC|h4~uD=A=mrv3AV2?Vg@U(8xc6vU%KS!U#6qw0u@{3p{GNPeDu~J0`plBuN}F>W{EBI z=je#Cv?K*4FFC&}haLly-1)iw<#M^J?d7{%J?oV0lt{av9eWU8L)eacaKu!Z$oKWb zM-+@=g2K~7rYVH|u*twiIn-lI=dV>vXZOFHQ&T_W`YQ)UW7%e-PYYX9oo7fAS z-sj;orS|KQ&~2{Jb2|)#HYzAK*H~?xvanN**0i2}K)&6%T?i-1wcLLNT#Co!4OiQ9 z?GJU@JqxZ`A6eKZ?@6pf7?oiY_h|e|QbuS4oWe%#vDNQ!k)D=q1Lq}LLu}1`CJ!5T z(gTt3WjfJZs3SLLiW!+Rj2Z7cr6%&q@$sw}fm}c%8kwahcoAEw0el+#aN>)Z-m7kK zlnodYPhJRrbBRu)H#N(KNjB_3C_tF(2YEKfx2Qyteqb^8LOylcyv*+O#$c;?J(H)D zqtmjnfU%PdaZ*8<+NE%6h;X)p<*%C4`Nn&qayoH!Olc zD!KJKWkoOrstmcY=-=d~ny_ktel(v|`CPjtoRKhk-9fpBl%If&hQe6+L%e}=L&_`k zoIV?R&HF-)o1R}CGY$?SpKsn5x^I&2qs-VB?A(rI=t*x%po=|+5F#!)7;#06lbE^8 z8yf${IamY*H$p9*L0pe83)#emx5Qw;6mz;#KhWQMTS2ort8g9rwpDgX+%20VCRht?OS)%wE0wbXko7O;$NS(qL)L%4 zm~Xur(1=EM>f9s#>UPh;d((*a_3MI|d z<6CsBs4od({5Pv=1x%^DuSOOkH1h&X(>(YdmF3D^eJJQCOq6#&)FEG2 z(ziw38R$@yKOs@ep^Q4f=Nx--GsH2i3AjXa5e}JqY=o%&;v+2xF>#(u_aJPMmA0#T zRv9bE5(+5g_&0?olm}cwNs|t_I!x3-*AMF?3EOd1QRS-cs;1x;*NgfCv&Tk_qVej; zJBa$)8Jd@Z+R(Pi)o7!3^1pFndnt-ODoa@@_>jO30-K0hj!Okh5V|y#==;017fdUumP*>iExv?D#x9;Vj0>@p@k{2NtLPVCDe6Q zIB3aj?HM$$pLz=3?_x7Y&Z!Z=*fZT)ER%B%3TPD_f}jv-pw09srE4i1O_7p#TjQmW zY|7Y|f>%NQ%Usa}#~t5vZB9PD3@@eTRVpv2hWm&ZWvsZN^@BI6ys)eC{%1a7$5+cg zpGmYxp6HdbpihTd{c)Y3CsE$uWkKtNJ2Y-i<&V^jo|^cUN-LFWpPSFyD3m!;IQE!3 zNX(0*PL^!JJ3vvXNT56xa6rfVXg?lyn?i>A{QxIz9PSEIjp}pFO~C$X{;~-vwb(LF z3fR@ePv~+_-`B~7crg`nn8bk|vMTcuYWZzygB{%Z!i8N5b7r6A1&){+hA*wi?AXX(Z}%Ni!jyu`?G7dtre@G9Pr65Xdo<$?|Y zZB9=&@m5Y=qp*warDQGLK(|o(EE(;ypthF0P3pPxmFY;>82ru}0P}dFaTPfVJ&@%= z^J6*dsDM`m>=IBV0M{69BjYt049C?wCH-{C#OY9fQBacP*!dfN02kh4!X)+|cT!Eapo% znN47gLW3I}XOQl%#0)y?(a@%LX}QeBD`B;OVgYYkyl7*c>@cGn?wFW2 z5waW(!(1otQ|2HWwlh0?oF}u7b5Oa+ZSv1UymDxFLbuvpWYpmn%X_e9n`kQ~$*t=U zk2_BD^q>lzdH~yVQDtov{4dNJ%kj`ZUi)d6?MFJ?p5)0%mNK(EEH=bekWO41bxLT3 zT7|Vuq-j282gci?i=AHSjU16#y#)5SzReCTzA3Z|IWvj*VZ8Aale*}7__H_m4`gVG z4)b*wr^7A+zS~aXu+`0Do;CNncCZV6j^zjQ9o7Sj_`7)HHOO>Q8{!dO#wl+D)<NJTA?r0@EG^y~gYQp>lp5-KS^_(N!Bq4k}fSxB=Zxw()N%&3TGUk=9Y ze~;V03nC~)$z6I zT3K~8?!HGJ8mVMQ#Jx}Bd*%>(6K3;1tZ|K;F<&edm4bdCJ{1=QU0{?ORR%p_3>lXU z`WvIqAih4*ee88?4OXUGj_1A*i!c&k>Z_MAroTnHHDclXB-%Itk;EtJsL{rz#wH-s zr_q9}g8ca~J7iupuY{VX?&_Sv`^*H;wl)o}zsP#DM!plzF#b)g1W1WhEX6 zgM)(_xxRvJ)sFGFHcp$2svfo-wPau(%*UOD~)io*^bp#1j`Et6yGG z->_^&eR*l#{fkTU@=AH}iiTgTT=q!A@;q5~f8GNUPqhzuxE^_^PW7G)8hTK_Pp1YA z()%b4h>(Z~I+=<-vRY2%lFRR876y0-7b|aNGN3~i)g|&z?<4EvM5(b191-je#UH5p z0kN)oLz9{!@I-uB(=s%oYpoi<1^5yOsL1083(yc=IBkMBwT!ApMjPYRQHR#KfNTzC zoJC4Ie^`&*-|>N3=0L*}!uWypr#iQ8KGVL1ziVECp!y*5-pA9HCZ|p)29+_&+ITHV zPMyH11Draj#nUFI4u9WDOPFNN)LGx@;|tlhO{p{)QWK1s0m67kPwNW0Z}!X@rG85Y|_dG)v+doGYg7hF}4v|mafe~ zfbNS-y(xXXR^1I&fU{9G`~R+1FjN$ZpYB-fA9gVF7zXrmVA-Fn4rxuaj|P#77+UFk zMwn0!CIs}5PISEqdyM2DqRnN$)iIp;jI+^1JgTOE7N~>6`t~#b{k-$kMa*LNf!G^B zY{!i0+7oiNo>rL_OWT~rE~Z^cBZj}qU+;g?&o23|`pF6Zke}eNfiGPxg41|1LoI?S z0bj_6>li0CJ!C`-5-`+PufdFQY9k1DBb>%zu8yj$khw6D+ze{8C%fCBroaD$XFm)M zCO1UNwyKl&R=cX32;FWyUIl zoH9T&G7O>{+nk!6&N){dMKn2T+oUY5Fb=UE{y*u}0^7uS$G^vY=R4c+I}c12Cm1l` zj0@OKY)J4ajaujkBh>_h2-2yUj8iri*jO2x0xTtywux2igLYjRs+iQ$0c(;LST)R= zRv-Z)8bfG>wv(2@v_-HEQKTv7{r`7%>a?j!Hw1?XzVCkbfBYW4x>_39IrEtxSMLl_ zl`7KkzyJl&q4{U{zsV-?M`8zMwX^0eqEGAt?Kn;ZuTUO79bNBPVldL#vohuG+k@}>4upZ{Q zO0%8V?5dO$_GT64YRb6*n{10THbF_Vp=f9QCVAs&F?;XQj`WZHF&S>cLMp`&U?NRcD=IEBo)4rMDUt1kd)_CvU~!Po%BV?aD(3@K zr*OlN>WottNk>VMtDxh+Zl&A?-3R81oqXc@zl%T0^`ysc4Flc8&g*ul)5I~gOpEwdFcb^*pWOpF8IZQW0@*cxW9(gqv64Whdm%vry;@)7E z1J#zt-nf&=+%c|hqbmWYF(93!JLDe`L27gtC-eY6%1?356Ov^+E}_-BHiG2oMYpx% zsfica2XIsBDR|3&@|#q)Qpm8R_$#r@cXgKoW}zSfZq9J_Rk>MLh)`@etgoq#I9Hl& z+W6a1iaSu12CTOMCweo`FHc=!G1ipI#@}9tJt#N+{NS-sc;2s#4zfOf3OM?a6GX}1 z7_6BccOK8*&^_?M)(?&IaJj6%FoPkOFG7B0*4hQpN1rnooxt^Vi|@mB)@4FYe6j zG_skou}|P4VAD%+5z#GMmK|{MNnAX~FLGYX8{n(3P}aiYB@eH{r`?Tdab1|Wu&ALD z1`fJdcLY$;4X=)kWlB&v$R53#*a4QU6qnrw`yfdQ|NPh#>Q-o%vPJ1rxH9cRCR>MhD$*o>#5A=kpYB8!4-1ZlcZsSsH;O(|9Ds15Hf|RGPtJ zf7^W(e3YE_2!>OduF%02xbOg@?gHvbXIk`wx)VeKj&N4YjDz;7&Su>KM*5a+ zubvYZlHKl*&_3%9qzeh0w&Zr;^Z{fC(V|6iFh|Ijic*tVQhoWA zQ}=PUUN7_5ZA;`sWV@r?`@BaRzde3?jHD-e&3^RuMoT6r5s%D`G{tNc?|gmr=7*L! z^qPZ>q2=+tb%(D%5}W7zj`XF7*G_-?XaG^HF&-i>5kdmU^a1ZHcESUSD!*cPx=A`V zzBo;_*tzk=iyC5bq0CL?@SueZ>fh9zN-vC8HUw6rV=H4bmbZp8i3Ll_9J|6_h_%ku zcPB78z-A|4vpI2s75gHJMw2?Hs|Y= zRFmj?5-lV+F2jW01h1`axOVA0(avAPDan~LS_jGo()66s`W*Yf=y>;C`WU>i@jK%+ zdVl_hq=IywfT6vstBN;nSK#6TnHsFCkNZ)ss^Np|^H%7@x2sHd?(%tMCE(U2WmkpQ zNwi!pcr_Qimh+`x>4R5;=Yt-&z%-Z|5seC~AJ!7a0?-xnLhoO=Xzlt$qI2z{h3!3? z;P(&KC7avYnv?6k4ZjfI2)uO*xG9-z*1fV@!t@70*Gzw=LDI`DhcrzH^cF&()Qrw2 z4T?QO>#drYKJ*n93x5O!6H~)qUorcY;Q7ApW!-KO5}E)&VgNb0h(|&^6!6G^M=m18 zPzq!Yc(|o25K1SF>1Tz~DDmWiRpyUO(uvr@{upgJ_s!l5S8_)7<(Ez0XIue%k^E-J zJ>S!3+2plyM*glWU6((UnWB7E*`z$CNH>(b3Ol1*QJ7lHJ`#qrAifQ=2^EXXtXdzH zBo$N#{`=Kw4Ji^)Z>|{C3Ry@@Ml>2J320A#l+LE0l&pLvnfXj9Bs6K7geJp>*9#yy zA}-gbhDenwREPpf;j*e$Wnwg)-V~IdlGu!FA+Z^QfU+aM8aQe930U4MgRm8_uvhe& znsD6VfqywY@QF!2X_mwTl1C{1?FC=uarxs!x7p)F-LA;#GXHUQeAt`TRnx}mD)K0! zHfD+m?7C+Jgx9zX7$-)GgWyfU{vfa72AR7Po>dv1+XPf^iCBmH`*h~M&?@#gO(eUP zJ-Rg9;ym`(xJ{wJnRZq(DD3j*GWFb`u@oPllA?GSJ%T}nM*I^|lK$wYVr^N4SWoVX<yA_3eVqJdbUo+6X#p0nawQbrON%pF4YuKkPQUME*iM=o#qJH$Hcl z{3TuLQ)sbKgWj|X1sbs$vSIp;(0lC&JyX&W=m;&NDSEKjk^k~uwZTzcW%#`3+ zEi{Btv>+MTRtBqd3)+}}3stGWv>2S}AfxQY_uSnC=+ORf9Or)Q=I+h;c;EMV-sjmz z2j~gPXapXT5wIB<0gbMO7w|u9Cq+K+C4Be(gn-tCBj=A_g&XWNzBj0UMuQ;q!Vf`G zKm&RWKv>Z|>;OaIh}pT$unGw5pv*%l!?Zcrm?1~RV@mXzPlt^NMKpgnK)`{9V4SdM zfYg$`WQbfOV}y^3=b-4Mp3n^>jxK_=p>m;whLV`drtDQ|_!l%VA)zYqTd%CvRrWqB z{1U!_C*V@y#@*gVwv#p8?RknlBl7yU@oNx}-@)C~>QhDC(teznVm(bP8m$<$kn8Le zyz&dUK16|rVmH&sm5JH}PEz@-YRnM>C)8v5WB`JI1xjWSO8v1`V)XZQ*xyI8{ar%ia|JL_Do_PDl?mkfdkSkH zVf1&SV$ijs#etn^`$72iK^&sv@@2&6_%ht*FscQC(OTW6IAA?c55u9dJ;|fk!g0Ef zQfiWL_7ljm3f5N&hb8Ly91v4k+v!FKvPh|SII{;j8qc8)>B#)$+&NtHuQD&ga#Rss zvhg(WqIxqc&agwpn@3fticKX@ zOQ>(P3o+qOR{JjtIdQq&pUjB~gSWZ2G#oVeTVM|_SYGFRJY2h~XxhAbwQfq^j_`vYe?h_h|vThW_@P9s`w z>28-dY*+C{WQ#&=3UQdQvzCzB7&dTc}H~M~R49iG-sYH*Y$RC-?A( z>zniYY>iS>>zTXXj2TY9Dt|!NC>2JAoApO9ef(osnDASi-r~R=G6>o7xV&E8EBDJ6 zWu;#plF!Ol3%UAQ$dBhAf7KWRo_rEciB z*4Hb)A!MMy=n3gJuu49PJ*Vw|+H;I${1#M(1a#F^6uBzK4vB@%T^>mpkX*(7kO5R6aFxGjDobF6&ve z`cum*Uit$eEc1QoZD^JMYE)nz3g04DL+3~kN1{S8S!3e5+>Zd`acZdnZZ@X5LR2+`R?4dpFKk zBBGCCOOP`F*bPZV%0U>YrdVyPQkWf|Gu$Q5%1(7{`}M z&q+FAuzX%$SnQ=(W?5CvWX75$UCjIQirE4Z34LzSwD1W?z$IYCFbCtou%|g7094$@ zfhmv(NZ^oQy(Ef~Nyt9!+x%}e87jRp*G91^;Uc%?5FwTt!IT_sxec+*miJn+sN9LD z{3j_O-_b3|aZZODJY3*%5}z8P(k}4n5cpIimsiRw$N@7j3CuAo;A!2J2YD{9swomR z*Qx~qq*|?Y5~L#nmqOYgR3gRQx6+} z=JW=!sFq?2^Dv9<)HEGu&@c$PcGi2*d&SGVwmDqq^_+2k4Jm*EBQUY_=(cmI*x!Yx z8TE^tjbhqF{$lOzsmUN!S67D;b?Jn`YX-4lLg#4@AvZw<>xYNOvJ|(PcHN`6r-z66 z{N$f=Jh!B1eP%%t5oxe9KZ5KW1YeGy-P!!xv;6E1yF`?{r?NkZPd$o11;kI&jR0ED zST9&vj=_%=pDDgj%&5=hEAe4C3(90wDs%^R;_--RR$??gBAOoaR8zbV9EE|xp+e>; zBwQdKBgl1|+btp}xLz;2;v931JDJmL@PtBx#IO2`{rI9^pB4;?7lYzO+u8Dq^i%;tAdn9~r3|qAbwwmz!Qd!v|0-X)@$Munig@2;AJP`PI_D`^CIc4+g)9^uOIL1JU-OfiDN&JSx1IYEprmprAf>)5zm9BY zMu*@fQ%O0QMIvM|d4#MatH~G07s(dVPIi*r;pTj~!J_jrL6&{rUN`UG}9*N{Z}w zOrKp?A!X4XZ&e*;^J_lAPD<$)z*cDWTD&gWe@E^jfJh+MLjK4!G zX=Q1((n_)}TegLTjXc)1wkT;TaU>j0up7XDL52y!31@7eWo*D4t(mx?4V0^$W`N=e zM>1(Bj*0D*)*YMxnE_14Z3h}GcRG`yr7rGtrlhGQ_j_w`{!`g{``+8t+wc5+zc088 zZO5AJr{|wI`SLMf+Yl#xL-mO}L+oE!i9>yWYJq`kN}2`&;x(8~tO77)hzFaGH1gVB$WOjD2KyEY>hu-V`e!UAFv-DQr16CCTTu({Yliu^uQUYVW^TZe-|nG|H7&S+qAm;L^h9F)sp(t>A> zwI)H<6ypR7RF}9mZX9rS(FI-)EhScWljHFmgHiHG0f}lP@e^_!$`#KVt1peXj{BVZ z8}~J*GIG51E4YP_4n{tZR%&S?fnt9c8H9?#m|y}bC*aW}==Gq<12|Ge!j)03t?18h z?oEH$KL4P-&6{f9P5e7x%8h@&^ZkZ-ZRcT7B6`IWj%<3OuSL+(GraobH4@* z#mcMIFmmkGthK98DD0CI7ErIbuz=gUtvBP!xO=yak}0HA)rCnWgLf!eXV%i2fM#ms zi(9fd#u$-;V!H^Z3NR1@qyYd^0EW|4Kzvltn|sDobPTZTwQY z*-SntBqq*-yQ%Yh1&#F5Jj7Awg_QHYlQ?Z`>7kDE)I6n|y5rEl_(WgOM_2k9eJ6b9 zd;`8=pW4SeKcQ}56(M-U5aW;YWGA2E@AH%g$Q&6Q5eK0eOn(}IuAn~=8wrPFEU}r< zSWRJ_QWL8!*z23D6cK1zE0w>f%qq%63!&7MT$|Nu$+gi6HEcqk3pJ(^SM4&Zx9?js zqiS(Uu(0aji`ZMfd_ifMWoyCi7wX@SxpiRNJu}+;FUbdvL@oGsvAiY}UA_J}mx1Ta zcF#OdsXKqZcsSsAsVJ1U;N%Ytb(og9B(**N`AwUrWUW09tZhU2Xru?Z$M43=%vO1? z8}~SHzh%%us8R3H-_WP@Dx>HTZ-^;Tr9<=_9iY_8X;_svl|LsV4Ib8v0+woYI5(fU zz!^X>L=a=PHTVQpDb!X=G1j@J>%sPB>Hv4-Asuxh4cJ(Va;f`aQ|c1z@+iVVv>)}M zQKUjhL_tKgNWl5bTe_XX+d@jXFQ^1)2tO6CF;bl6z`);QBe4;11G9c#BCTL;q6wNF ztT4~S3g}(@xX|`Qac6#EUa(H~F~6uacwYLhTAsGLy<$gf?A>Lx3+hfDTT-)2>zkHX zxw% z+<-))*tJ*;2l~=VXHgL`0aQNU0w1n?-&g^;&a&Va;FoIfixGLydtFiq_(Wl4aCI?O z7J^A0+g*~6Hw)hrIs~c~P6?!0*RDIOqdW{mr+!aOC?Y2a(Q}sRJQ!OKMt8$i1=5I* z&FuhlS0h{iHm(7agSmrn2h42;>AQAnQ(B^hpzeRd%8HFAEXvFYBvxTh7R^)U)MTTa zMYa;q)g3j7cb6`BBhhwj+2S3+sp~dI1A*wqjnQ&i_20U(b;&cASJtdqb-HoxqS`rI zw#=X_BHa&&24aW93XDQhRw%-PxTc6HDi0eEO@n;PuHy`;-6-lS*V$_xM+x6P~*p zcf&1SxjJt@{>YUDjUV3>_Lp}?FgBb#U=A!@VbP$90#k&S{!hzZq#%0>lI zNgJk~1+Em76+klsvOV(S$8f>`C~syeYXT$vO;4pn`ZI8rA3-+QS!(znc`wU;mAdlC zcN%b?(=uP)Qp&3!a_X#za6M^^1ip&f} zOcNM}i(8JimRVX_7VSA(=>S`-ef4){e6#hRkdiY1 zjjt*=TGz#i5(Fg6#hkK`j5v`aC7ix{d^FB(=#A7v$u|NNQ$g1Yn(@^6u@PYn;&Zf2 z0ia|3B0wNWK6q_YeIlGqAirPI_@0vX>XfqbrEW{h#usX1ssl~Vy7>Ieo8no0+%X?|46BuUW76yjM zs#V&?D5Gxyyy_ug}^^osP6Bsru?sa9f|WYd?eix;({zE=5+!V_%4}z8K0s%_V!nu9lXbty`_m zT2B;i+U7+%;n=FP&Hq|uJeKnWWD&O)5zA(*vsUtRy@Bf%M~-s1!$0Tr06)Rca+c!L z`CQIA_*TA`Gbayn`Wc5I{sI3TXPi)O<&sr={SpFRd?S}6ldc){GDbNAD#D0ZB{snP zdZHO#gje7aNVDl-`V)GAN|KcVmvQG;~ znzG<1YMPnUf}aoyh(gwbF;nk>X5%ki#=Tuf{XS>tN7Bi$48372nmxC!1uh7W&!EQ( z&|??*g`Pl-;*?~sOx-RD(`BpV;P?mni3*-MWd(2n6f?T5grk4l!JJH7x+I5Ja#&q5 zl@v^1NqFgWa7B3Olo)V_Ri>^I$w`XxrZSvUg@Zcs?Z+r^WO7_NqYNrckaM87w&Tvo{Wc^7)ZY zzuz6}dHxIZbdvFV=!TY)iJD2dQ^sj5IJc!!biRBUQC_LL%UP0rgE1EajWOL(nqvcW z^?i-taF*uC9s(+afWZJ=th)$RDdp5m9M(q%u!U$GEkJr@VXSL(f_h91jA|HI)I;ar zI@Bv)Hbwx|Hjoyh9o`%NKH6TdJIWvd^}qWn3;kO>7WkO@dOQ+;7;m`)c8rP)G39WBkOqZz;&n<=QbN3-c- zSYr58YRO z-Sea|QUDZy>wlk__0qy^TaK^FHSOQulq;U$rPq6U>EGzz=g#lmIx9bS{l1!t@AQ6` zZp(%%_cx8DH_lzNX6{DwjI2P$?@Fr?ugN4sUe?!bo(d07g`PCbOF?S_mL{M#4(;LR z!t~o=csdA6gD^V?`5?4a!d?cG8C2E8fFyHuu}FlgE*JO3Y&L(4*CYejX}BB#x9SeL zF?_%c?m%rI27&(u&P41!Yyz-sI*Y$i_1189G;<%huN@GjBpNa0LSE2QLJ*Tc)8|FF z+@4T%K8H*0(rh>xPgPH!jY*OFXyAo6jQ5RC-nsk4vW=L6 z1WS(+ls_Ql3-H$mm3)W ztzO~x+wHT6UFViuqkVyWwf(qVvI7J#uu_mNr1Gg$p5XgT%y=!GW;r3=M)tuIu%7sJ zI|bqo*(|ewQvW3yHlIu7^CG9`8SPRUb)1g`NrJTNO#}38N>x%33b=Bm<_T?$o|kRl4aq$rb<#cnj4MIBTaRmUrV^zHs$8lRlT=E#cD#N5T1RUe4uhTTmvYlLYefh^w&pLy7!rg8A_O*3m zMlK&8kw>IBB2`O%{JK^inVjAu=i?n|jq9 z21nJa6SW!%9)phvY14@K3}NLiZ77&c+x_FKR`b4skLJU^sDLQ^Rr#9swYE+kS-kf8 zoi&f&`PJGb%a+N{FI#qT#g{)^G3A>*4>jbg&#i1|{N9XOD&OjV?cTk!{Wq^%KXBms zjnjYn^vvFl{rODaE~q!IeYz;q-+|fJWGs~SN;63sta>>Kt6^F}n9ArRIqC5xyfMue z_r+05p9^9g5b|=hTg!Pvc;!`brp3YUg}AuW7x(&naV_RaN^#$$K!_*eiq~hY4LEHj z0ZDPj(s)CC;*rGi1WR~gZzkVKQde?Aa%Ym+Su#1b&KHcuG+nfh=i)UvbF7M(qh5ig zhz2tUwRg3THKuD2(&~^@w5fH)2;3;7>jdIM!(vS+$kAcMy5U0<=;=|RF3q$z#fPan zpsP!?it6@0X=q688yYGK&y@wa_&MrLwDWlpxq0NsqM$cRB{Gi>vTWjuNPDy2I^5j! zZ@Q}mHj3+t&Uz-yBwZM^~4)TNkTfTVOG2t#CBsMsZZ z6M`CS<1`ACP#3E-K;(utfN%_?prnmuuuG&g(59hHfacRrNQnzcC0Z0b^uDzXv{JRJ z`RqtL_nmj{Ip@ZzBD1{G`fPZ$q$cek@X-efu! z)}Q*_$ZwG!z0{njcXqoQ;2xJ-A6pCI-T($Kw;F{@1zjxW4(67Xrl+Qx(i_u!GF};9 z5q~VsCnJ@S6_Ljxd}&~6pee91z^^YtCv~*jg}NQ+RT*{4VD-m&EUqIR1cS>E6-O{u zY<6cdY@;+3b`zUPsp?OS(by2B%obopX*BhSx?07m8i7MOn{4V6x7|xSt+3a}Jdaf#;6G*>~dB85l*>jFl&9`{#eHcHaIM z=GEYs^-S9Yr?nBag`eN?E=oVU_Q%it>p=77EzJjCTD9e=RV~Lw1=L zt~Sgf1OtOB1PPPzoqY@~8cSEgwpKFlOE6d}Omgwa=Q=4Twow}Gq6+*fi* z0f$AAqOgRiT`)CRHBi9LDcEkBbRX@d{gkKW`H_C@{F3~6=G4^%dJaLwQCHLjz5_cJ z<=bxl755HU`T)5kDArVK(;r7x!aESR&1%F~z*S{nQ(O>(7*iCKY}9E>o-MqMjk$yp z_R1v9(L%2jmt{DLYR*yg0Yx0u=tO#r@^%UnNI|y?ShAXy-d)tloucBbD1615i=H_4 z#xcB{{J{Fe>N|OJtME&xiMw@;zXc3Wa=*@Pmn2ZNkSR+ZITV!Pj+U7bk7-7Np-`Da z@i-jHLKzVm+3Uhc35LQE$stoyO1Q!XNrVYanN?=3Nrp_6HS5ey^M=W9h;&BqgW-no znlLF*vPzvo-cV4LQmeEp*A>3affmcn@+O%mL5CcY!V&mCw+WS{-Q`+;9&#pQLrcqO z^c)^dp1_pxP8AC(6P=mZrJ=&p*%{4Jw2nkfA@|GiJAWeB;-5!C=sn9#$o!zY%`x_i6+l<<%~wKt2UIy3 z7$m{&Qf_PRe$j3>1)E0@Yy@Ky!Z`wf1$#Kh2GnXYn;app6PrQ^!J>^rSh9(Bf{~3A z3eBU-o(#un9K(hbDBVObgxQzc!gqvUB_DO_{umH_T;Ew%;UB8_BVTpkp22vd{6e4aPv~zp1D~Z>} zQF{!{Hr_X`8f345P8;YO2I^3Lq~JzSicUZA*9L|H=L2Ly;LCw$0($~{!0YpQU7jE( zPn1{7#4gJ&$s070801yz@q6$M54y)w>S^+9^za^;x{|6kTU)9TiD+7o^Cta%P7s20 z%rA~Jn@9)CZwm*knT5MU?)xPfM7PLIw)A!N4{p(v% z)>^c$qt}6MoJB>r=BwMSG`fhkRH7bf{ocMtA7f$STA{%^FuZmq*l)$Q;cA`UGaVh{sK)S!w&I_Eb>84Ip zqH`eX@YQUheHHU zoiwo!U{F5k5k(!uSx6^Sg+p=*$2Vr*oR8qTI;FQ&vKdiWDGHl?83BgRoq8bjT3^{2&nL=DjOHE7>B~|P- zZJc2mc)Dp!H0BtqjUC20+Z8V~kB^leImC^t3T`oXP+<6?|-+wo|*<>MELK5=YB;>&(MQDOx z&_YCPeH`^MRiu^*$5}=30eYOM^)-kO>ZA4Ks@Bosjn*DiTd^W%m1ESqw$m%uaz1LW zb#6MX+OcQH&W)$C+;5Xe+i`3%*?%6plmC8xukRN{NLt*R_!SSLk7{DYj9?;)A$CQ1 zt^DwADfR zaXMP715T%xAR<*tw83p2aiIcXW5dY;t*^`gq8Js7TGg)3^4PzD&{-R@K<8d!l-7S|m@yKFiKyX|-mC zX0B$LhEZG05@8rCh>}2CG20RoQ3<(@P-m!*DVlN^{7yBfQOxKspa{U5#gZtBHl5JT zAl?<;x7#B!@!_L1ln44n(Q+ZCr&qpr^v=54RqJjx%$YhdEdRZ5>RTuO-Ag7_wXN^D zxuGRzy<0xGzHI4C@;>ghg`(&ixK|HV9tk%)S{?5?=(#%hn+B|WCQpdd&a$asvWt*P zfyQ94y200n*ZHhIDnXx+lW;YGQ70&UQGcQC=li`GiZ@8N(*2_2689_)ZCX*F(S0lN zpl@`hKT|$`@r>W3I*V9H#Aa@O=_tJ4NZm5T`^3F5wbZp!TYPdCXAYauz z@OIT-Q7#fnGk6X4^j0bjxARftC2I=ys2S-n-46|ZusAXt;~nLW>yH09*ypTp#{e^v z<|nO4qL&Msh26q~rG?$Yk~{*9W^ku_vi)YS#a@{Wx@_RGvzbi0lS{N0ag(@N9L@2f zoze0dv)|@Dm=5V~&mIqWvb;vGPLZd6;br( zjcdOwea|%d)#2f)@R{7LrDFo`b;;k>R=?5qTmR_B)L$)>Pfw_MZuz1$#Qfh^U$356 z^~C7v)e8@={dJ$bWkSisf~vW}+8r;Bm^f-S=F0rRZ6XmBO&nQrnVu1exa0&YgWH4qgFQi3O$P%K!+W)9 zdcQNqAI~j z2|v2Mk@ z$HMH)9Bc8G)noFDOSe{*<;{un{|xzOQ0e6xLoUd*lGwqvH+U3wRIU1wcH=i27l=c3U^5rf`trYUcyvZJ~ivb-2Oq?Ya^7%N$V1SBJMUYV;66XlBDx#_*nz%mJ?Ghhi z=zWNxE1&tSUp_r>=$GKNHf#jj$5K9=xE`anAV%%GpFn$gY<@)P1S*^+SFl4%l$-iv zq;`ZFI5o7Y52^{L&R5S-)0~>OxH*?I(aj|!It826!V9)Ur_;wfs{oE4h@DZC$6lNUfSMZ<RTfi*DQu!ml3&V`=yWIe-SC1M zGF|WpbX|06gdf7EdQpML5?70_fqa|2o%muBfLJ2>viQNhXb2l5M>HoJ=|cWo~lSnTyKF(vi_HsBWXi-9f<#snDn+z332`eolt2U$%=XT#p`_~C zo;UZ&ci(;Y-FNOe{e~u#vMMnVi(wY=4VoSSug(WTk~l7G7+OfAYOQZ)v~u!N!h`jUQYBN-DLXw|dF9P&#n!*!ZVb zt@*_fZRmXKs#Pr)F0`!qr_MU7GheZz@GdCKmUM&F0Uj#Y6+7{nTz;oio(jTL5t_v{ zB1t)58utuGOyGqgpg#GU44dSi%0!llRkoNV#LnDYw_tT=d%r4#6}F0<$u3}7Rwt%< z=kJ#cs+Y|GCTN7}VSm@NpeI7qHy=qQJOu_eqM3zFa}p8M=VeD}ye5+U>${&mc6-HZ zxcgLN_1;Ns6W_kRd>yRQmXk`jo5vmIY%`u4)K+L41^)`+BmE4)VO5WS&Or+pb)v<$eEgp zlA@YG=9dW3Lr7F7Q@!?|Koc9NQ-la>Q@A)+ z#nA=a3hpv@jr*Fzk97flEB?B%QN#zz#Pf|S^)p-9_x^)EEaCy9`3*c8b->VR1@2Ro zBFThQXfBjgiSyu-D%y4J#-jbrMG%49MG&O>?k{dGq0U_pf?dre+TDkhxE(98khz<7 zF&@!F0-S8N_@5DL0KqrF=bB|U zU=`#Xb6&t?vGOX`CKIq&$K_4Uo1aJX%#pM5Wf|q=3b|dTb}Gw}u+^0t;nTd14@INO zjZrBu#&iG|VR)$QeDJRb><=*lMJmS->cfIg=atl`fZktk#(CjJaR zgwbtn{Ro@r5EI+BvHktxw*G#dHZ(PISIl+eZx zeQp#%fnmb=5tDN_&-l~F4?coi_~o+j46GmBCUNbCHcz*8Ztz2GHd~FEjxb5)SEm^f z(X7ECmB$lMJQi+@1tuoOLNO{tm1sOl?bAxY8g2-an*6!>q{ctjPpIGrfn*4AG6X!F zJ>pCI(vJLcpIb16%4C)wC5OyzS5bI`o=O&b`8!M>ExA*>r3$M{~0lTb3O7^~;-E7H2ZdEL2^?Y$fqBQy3wj^D%CP0-Y4TAi$BWWRB-~ z$!-m2BTq9d#%_}0M$ixbH|nM=C&6au#RFH9YRHWbonYXAXG|;TvX zYU(<)TVT4`wwkzackS5^-`mwOy?+0m9?bqBWP4wkpCBn6Bf-Z^h7MTD6r?@CI7COt z@rq-UgA*JMAQJ%`r-_OhBL=a?B>KL|FkDFYC6h*cUqD~^1iWH{Q<;CCwDAm!UbgRg z{Q!|H$eqI#r)9mE&+y!Xzaw8O8HJq6TtBHOJU@|ua+(x8=C=*=dsFu#&yV5!or{w2bP~9vCrP@;K=){vHX3T9-BFSX!;=b3 z33LWXrx&Jr0grB(6P|Md?+iG}i#$BbgO$(a$zlR?2^bQ0YbmspLTOou6@i|l2k@nE^2syT9LsU)~uIYEJjxFeMBw#e?M7nRIL9MNg|_$ux}QI%qB_n zc~!jWUbc2E1J z_i!{;4ex4`t7Fl_y;zFo-lNU!+qH%IO%bu?{)IQi55G;%V!U8C(R8j{ew7ZKXgJ;xFZmc00jb2MgWoVL6jA!)5mgt$zmcL54uoDA~|CK zo-)IHAZ7rbg&ZI(fDAzEz0F>7#0U94AU?3MULR0bMA94o&GagagVLS!Fg-!pZz$+* z4}O9!OiNM|KBE>#&X8Ht-I zEih);=Owr(U6lxx{zG`x2S-(1@q6xl-}{l>eednQ-S^n+lDrLRXd`SOphL66D3$3) zQV^(h)*ve7qsoVxL8~Se`O2uE(gxI))LKBM)G$V>Oo22ef`1?|(9l*RSU-?IQs@kS zbVm2lbKi!*=(L^P_wIf7y?uLrzjN+6zXLLhMper#kd%P(xzNP_R?vkif!*|guvU0p zU~7f9g;T=Eg0NWFDbO}yy+Er4I1+|XSYWFABXPG#w_|dXHY=di7xNeyBMC04SNR1O zMfFhX`y=II>$fn8d^t||e6+G8Cb#<1y49~D_~k85UsFDhwb(B(t@tp%v%0wYsuqVT z#;RD3<#~b8^|6WYPp|jjh6fLRJRx>Kkizk;59UTEUkJG`{towgFVY1|Vezut-o4Rl zgD38XJIYh`U0!ujeWJ2DfMX^3d0}oBfE-K2Xia=UoGyz$5~oYk52vZDrG@SgghEO< zok}r<+G(a#1|b*>n>mtgMLx8!osnDELPiPeU{u-6&Z1vfS%C74j}o-6z8LwCb5rDr zmFHD1-tdO6xL5JLl+8vloleP{S^l|JI&m5Il6bN7LFp92=SbqBquCE3DV7v(`mRyl6qU1(jB-MO&>MxW+80qD~D;8cAXa z;#4#Zlm<|-`5`m{sMr0z#8+_xBa0mxwGAqEo9o0f_|F)4_Ch1(HH#8KrVsXBu_WR8^HsCQGbbq9#Xb)7f08UD_<| zkQnbzo1|u`Rce zp^*LLi`3WGXXMW|@qe&Q>dh3PmY|V-1x`lX6%&t6WsXvzXEpqFL1m zx4IIItO;xGr2vpXCU7pmjt3x6kKCcC!}3_Bx`<*qn)j0$k5cr+DEVWYm(S%rUKUWk zhLVt7sU}x*jrlU~zDO?Xz{$GU={&Sz@Uu0`wf()l2h6s|PwiUyQ0ldbC)=BEj&Mad z9^jK&xUXl~y^`Fu;f(hUA?MvZ^s}||@A!@Lx4Y-B-|FoI1@}JojN%;J`vDRrj}~Uh z;gF_;Ftjv7)dCnzA?9wu?cFCbQKZ3801Ib^1Pl^bL0|>}6<-hv1;dyKWig;pRS^lk zw`pMD^gz=9!sFW)lJ&!gY=gyHsmQ?Ju1j*LZxbG3+rY*j4^l2 zeAhSsFgMe}*Yxn@9SoQ4mqb+|pmF;LM(rQ=?Jo|0#r}~ZxBq_tU|+tq@vh$Dx?q=~E`$B95p(+Wf~A=8M)(;7#hhhsFPDnLY~i~<D9{avqrQX}+!H`w0~YrwtUIb#m`Xw*rFaU;gpQ|Kj*@ppxV*JE^j zjAO1cuXj}$uj}JQS6Ssq^YOanHh}rC%cv`2QW>@O71YB%>f%+{|Hp@4gI?Z(S=|rF zr$+tx%;&wmV4uIHf2B3P^#85hb-F>W#D8S4yTobgSA4jUr|l?;%KUm80NCNDJ}HmK1OUKysD zFjRy=3Df0bjIPYclugV<% z4Xe4l!y#KWC6cQ_A7DZ>O}xM&1X&QGc#>-0{>Q2Q3->JP?|SUt-c+&Rxm5EyO3UHa zX93;|m7b^f>5G<}fAtsyUhGKGy`?$ROBaM6mcHOo`3OcOU{ofFL;m$fBq9eI5^rAl zyzmlEv`8vKza^P))4asJ33|i^*O}r_!GTYl%MP_;ddzif!?9DQVY*JraGaE=IPW-c zivx8IIA$!S%PG6g&?S&V8a{@b}MxQAiky5OsE|ZZuqP-D1Y0W~BGt6PNK_JS@gq%4n~Aoz zrkQq19IY{#S}7(@rc;_`nwY89w9`78=rD0colaUg`|k1KM`LgAJH0@A-QJ_#^WH07)A?keli?EytQ<&O26v77H%B#daGD$EE^*|@ED8vN@EisK zJ&XZrPQdkkODm*X64@&aO6WHdNGwKT4rW2D5F5lDB9$V}6%j20Eo$ORBDqBb(aD6E5@vuI zW=0sgj@cL=o?*UVXvPzEyOesqmG9z7f@k@|{G0qJZ!+^36|BxQAycr#Bho{9da#}| z25sUiXd$}q@q)L-EHhsP%}fEGCdYhxhMaw|UyhBTbFq^DN00fH$pg5kx1z%j5QOVG zy%`haBmA)HZNiJ;`5ePzVFfmwC9Bz0>{Be6z=|wlovh5FWLkoSecl~jbX9>0e}f-U ze%`P8NyQ_0QvC|=S7b?)QvEXT_sf{4D}H#=54HGjOKhgcBba3+Q{)BO3@*}P#rNVb z`u)>TeJR3xu}VZ@2~mf8BUwib5QmHw*+Tq{K)AYvu?a^c5wNE^!Y2fHR{*B~W|QD> zgc&Yx*fHWb;~+~MVDOjYAPEO1*PV#_5B%K)go}0EaFLb|T(HqK>_VASj-Fc+ z?J=?|ryH@gC+_yx^DS{ra=UvTE$YN0FrB5;___ZDH83mXG_CaJX<8$9PMkocj|BGe zhFB+dfTjna1)@Fn7`5G;P1FJvKGa?dRkbT?QDt>wHQHGTeHE}J56VL;LTG&!H2I*$ z2b~GnWNPPrFs!~&P zGqkEIf^|A&O(Amd_F0yz>9i(_dhz1L;evvN;X*w(67Z$w;v*&7SC(H$Ci-kPWbq|A zok`9V9FL4NsE61IY^L!*$Da#A_;dlMvwt1W4RLx%hmancU=5}w^w2oIV%(g#z=(&O zVE}}24xiwrFILk2Npn@(NC`NKT8r`zF5zRJ)&FQs*Q?#f zV1L(|z0qxYQ(wcfo~vcPcZ#6=(7qRY7q+d+Six|DZ*9lp`^L6>ACAPzg*oLtt<`01 zwQa@K2aePh05eos^oEuD&Z>>2#lK&jodH87usU|^O5fM!%8T9=!rgC;cl*y`ZROZw z7`jRQtH!(gqoGtETl>$qE$%spkUl1STQCh+LuapA6 z=)knD@hn9S9$aF(UbAmy+s^ez+A5zY*V8v8D4qQN*5wC_e^LCqAK%kLOj)-i?_19t z9_krsSX94%{y%fDma$H(M&+KDB1OS@(Smi14Za7YdvMtk$Gw*epmj!!_xa$e1KXE1 z{`zMPRgzk>wY$2^$hQly_qc}pq=$Ghye8jO;X+3pP~m_kGaLXI#BWD{Gy`xrRGCba z+i69Vg=7_~&IwMZ)ve@sfbg&$WcG-(z1@D)e$sxfp_> zn}sioectipS;{+k+!|X5?=>xb8ht!j_ZnGu=NQx5bsc)~JgZ}!CUe|l4ThU}!S2jk z>Fjr+PAdehV6%cn;Ow-E_0kD83qvQST4WCDxE5|BH_TBSXBSd$;2xXY&j8I7FpW$* zLotrjejCuX0$Zc4-A38=+QE)@QZzb&d#FLZF|+Qeso{4#Jv=ePn?^GMZvuQ6QzIXm zm^nFpVDtEiyX9w(zpZUL5i638Bjt4Cw#S#6W6#0>2&8uBU$2p`-)UlgUi(92$0oV; z?p4zT(?K8Leib#8?Zg_{7H(m>)2zTil*cl{qu*Hx$+k zOYK)YZu1mNv>Ee3VMH)5Dn7>m8K{{qoEnRr{^0dr)zuvPE!08f=*d60`u1G>Wbfvr zZNv2tS^IQ!^>^3C-m2ff&DlS6`NFPe(8buP4=;ACe`n+kX!!F#E`50O;^3nVRUdQsgpHXSlV#0}i#9ZQbxG^D1%tE2el1!uobAqTNy+0k9(>>|P z{*mXF2d#AD5XdGWr%QIBzi?l2h;%YCgN#Us-lV~4l1&4qVH4#98O{L7(0*@9IK^1I zkcG=jk-UV;syS*{tyjC$ZR%ciR88PXRi${Z-JId2Y~Fyz%IfrbD{6Y4laL8yCW(-s zsg|%MtY+oOmJ&;yrNu&7LeZI3Xa9&7`w?A5oAh@cwuAXrRxDL z+*4X;_q3qWZuITB_1xBqh>lyYc)>VnHVz)dwuo})Hb#a!#w-!{_)J*=llD8pYN|NP&P-kc)s|tx1z{b z<^5PJUcxO{+TFEuKAj}(-Z{Ik`&+PgZhCv)f!0M;Rf}5Pm9J)3if^x2yKV*PQU!5r zKdEFj^|R9y8~lf_TodoXf=1^_#c)*kyFfE06~obW{1(20XL!u&e2N3V!Qbr`CG* zv+xH@Y`6yh6d%QiF=;Tz2KfE_5uPdIYj`?}2h8IsJezzQLm!42xDIc@9T@!y-@`PE z4`Z6pXjAqmX=O5+g8qnTt_VShh=4dHMQ%|lR*^h`A~M$Kp*Y~=e=-)-Kfys@sHVRn z*T)16xn^V_BKhc_VB#l(+%r@(G8IjZTtaB<#fG^B8PfHBY4w@Eq?q-N_w25`Rco0m zfr5syyT~@aqk8SqCirBvYgRBAt^k})=Ww^l^`zropkIS(D)l4{ALu~$Vel-)QQqL6 z2{0XpW&=&@PSc508sz|o!|(Ny6GChw&XE&751#<%D2j6WB582NfhDe$1atWuOwRej zr-+LSpA&zYPMSQY-s_e9;5BIp*A}YAB+5;EwZ?0KTodH#!5a&005t&10Ez%ARbaJg zRDwE6NKr;>mC9&)G(@Y^G)1MTqqOR%IyI=(9F7t+77bmc1dTE(N~vKy4x7PlGBZw- zPN61&l#Sx17%?3ohZga^+ns;@+IhKifg}+7{Q<{T;k*!i{yg{tdcnbw zH_!`22`X%^AzuYH)XMfyr%nY?LCa2SvD;J=KFbK<-c||WZsyvg)pM-=_dGuBjEp*$ zDo0v#4$>iWY;fBy=}YO$UE8*GfCe-j+b$L?IeT_VL3l;B#}_}CJrH|TC^q(>)UkIN zzw|5VopV>Oo`VWl@zM3`A4!M6u>07t-O?A*UpxByJ3?+NEy3}m2f}o(`r2ztt}( zh84i^|3!hTD_=ixV%5r(t4^GFedTF~SXn8ir^~D3IrPWxf3yGK!JdZ?dk!Al|IOhw z+qbVd99qe!V+jV>c2*>L&q>|zb+u`e)M_()Xgz90Of~`mS%~ANavZO>-KM}r z+%7xWwukTIf6E7X^a>A7KA-;qF>i~-WHhO@f}o|-Gg4DYzTh~P*mM4{oY>t?VOWmt z!{g+YByx}=fg0|Un)+tt|Qg6!i17u#Bg#M)|cs3j0+84{~&#i7Gd1ahnpMa-Nt%(cY5y|?w6Ol-`Go@ z%U>o>S~J?!YpSa=^>&Spc87P}qrF23#l;Cjy}g6-bCC4+n&96Q@%S1gP)2GY^~m@0 zss*6;&G)_JV-!9p&jUjqOv!_osi5=B^DOf)tOsfxP-}-Y8&sKL2ZnY8&30H7xhaya zMw=1sa%y8@3<}Pmq9|3|+*~W#Vo@b;$pN~w<(j!on}gzX-X`!)>!F9RELmK z-3iSZP?rJa8Bpv9Tl!qfix#@b3_CGY;s#8=iC{B=8Uz#S8U+%|tkdG+T;^QJotB*7 z+mi)Zu4Hpsej1&YYn(ADCxoQTGG^ka>uAIi}e8(Y3QwtLsHuUZ-#TK*|r zht&Q~B2ub!8tueTRyQ4()sJ@U3^drCOBoTMS(OGb-`dn zDHTbT_ymSTJ&O2hBw!{I`;a_KBQ};+LVxjQ4q1K5(sJmslmdoL`XrY)u@CYr4L1d zNR$GiV#_OS6Bi?hlTy|pzksSxRa6QuFFmshBx$RzcIMofGc)(jnKS2nAN!2`gUL)` zYTU7d%KCAHLe0r!L8>5?gz#v877V5k367en#3+Up#V)m<6!_z+so{xmrmW&C&c2Ml zvxvj+{_FD;lTKIBT|iN9@kMZwR%tuZHQ0cd#!-2`sKGi7)93dC(6WpLXq`U>K< z8Sg_td~l)as$CRV9e6b`7GSRi-VGcHNF)%jJh+bi47wpUN8|?NJLx{2;Fpodt&X=S z4!?u0)IpYWrmEfHpml2ktz` z3ukgqzaUVzNwdHN=Mpn&`m+}u6j~zqk?hN&D9g5{XUVL{JB%kzuSsA1*}&Er)z`nf zD^`Ez7vCFMJQo%pJo6W*W@S|nSsqqpdN%J#qkI4pKl=v96*L_yS(r0`TYL=gUh0Gs(4yIa?;$aS%>q1YCOLklX!wXdqoAx z7X7$R`wSQ{puhm#C@>ZntBk7#N2?j&l^NF^1Pr&TdJuK04(s^ioAGKzul=nB`z?@- zT!Vg~!XfpTN*#H)iFxEIc+{TM3_vF*^!KSv*%eayE*io17cc>m*W zPu#F-(fAlOh%=77G-r5FfzC?j&+-(#aVM9_4 z9kgtCN?pMZfbbQ3b$anl0%*&rDO(~McPOwX4%E9E&222mNmqpk+SxM)m7zX z1^G?0efBh2ceqWtsF(m3IET6<+3U-n-O@ZKJ}0g?YUj)=winKOzN2KiyCd8|b*Up( z=#Tpf3Pg0ZaX>*$NR>iWz{^ipU96&{YEczcs%F-CTy-&r8I6%}ax7_%k6{X!=t(3* zV?s18gov3e$h$UbPM}L<)Ix{d!b>JFflQ>vQG?kPmE$PPvCKa;{-0gwM0Tw`)o0g6 zBRS8W;Qu~J|D!MXr8e>6mMt${zPu$X9{npT>AtLd@b|B&>AkF++>w>ipZ4#+JDGzO zJjCk$U96<&)4qqdg#rt~6KhYem5#~`eZWsjNHw0|E9vO|8|6+N(B+gv8UTUab5kn zU}LC=`DC8}Ql<5EwWT-&yA_d0V}8NbVp4|0M02BAw58N#ZctEU-qM&zyrnUzsM()i zUR}Tbl{Jz13on05-|*w%1+$tTjf^a9Ti8zDDrmMc%DE4>Y+MsH+(FTfh#{63OW!Q3 zn7gojB(1+x+txn&$y)3;*;y~)gvlVP%PMH^@blZmlm{oGp=Yn0E4{JtD-$P&GOIoM z>(k}?pS^NDvciLiP(D>Gyy1RwHQuJkE9G_aYcjhbe<4#Z}CqWt|MhG#*4v}BoG4$@2m}h@TvgY3Nhr-gf~PfrYT7P zwY8xr1PX0vNeY&nSPo5FwFtMg&?sqxsz^=LSh3VLjT*f4yJJYnpRTOgyR&yk{?0ky zch1G!@^Rje?iWze^py~fI49RRLBc#x;E&cjF88@ zi2Y4rNFYrDIJm0;C8Y%8!MB4{;(}_Bs5I!#E8e)5*n-#NvG6w@ci!iAv&YzxQJDev zP4|$Sy2F7K=deRL4H#cl+K|S}S2fVmZ5Som2i14c6}1|i!B^Nj4&Xbc-CUSAh=ef~ zk)TS)%;ydN0lyjK(s${s#254Sd4uc=Bv!b~Y&#U3P>7fR%P?Ja55v$iaQN`yqObn~ z{G(sEIKuFMJ}B11c$&KPK5yT;PAhJ0MuvGj1$-{_Z~-pPJ&~XH3V>7+EFtB_5`%0s zo;1h?bGu0-H{b08pHKFikqDJZ)TFX*V^)#5gx}TV?eunesW)n2Nb080L_290r4(~~ zUs@f$T&Ss0|H8_}n9DM~hI;P%b05y90*hdvCK4LX7OVq}Ur*qG3K|m27YPjma4rA& z@CU=!jvw#u?uO<|-~4{Q>UTeG>Mh=a)hFk#gEPg&{KVZL6u;d4Nn+y8N7wg`DJk+3 z>#()}$I(xn+M?V$`A*F3E)ph32^C89(w9o9Ym713SZb^{wi!Zs6w0HMqD!Ldq8-t{ zMMY6@dA)8m77w+AIzwHdtDz4=qJoPLLsSVh;k~ON-p_@gjE3A?IF=XO{=7XQPZDLCK3h=$&!eaE;yiIYZu!4hO4W8dshMVX-74vDFn*F z$%(6lcIVXVK!phjq0-namof=lSoG69t>=r=TU#$|+VoN1wj({o_y5=nbu6$l5$27VoQFCh42fO+fTH6TAH zc-{g^t_U|~1iGyewCh=)joQsoDCYZC$ky{lx^FDK#dDP@@!~wTg zTBFQ3A1MLCX~@Fd)8rB%A~6W2ChVuViUuK(Xkv-2^_Y*8vWRGLQkB#v4M@B! z;YOknSr}#Ev%(S65+Ki-yIU9#$Y%mNETNmoKaAf=b03ZES11bK#sdp3=%4LbMUh>H&#nn1zlA z7cI3`FbZ3r`K-9VpNu`Y;QFF*OIs_(FRa>AJOEF1&fiqnJc=A>-a2>qtkC06om;;7 z#1l}mbVj4$8~*3mP0gDpbIuIjz$frbs&ixDh5RQj7z;U|v+jdsKwc%Vn7|livO<>V zkPg>}i7Olq6R#LEV#E+F2+B!ld@uP)k~AiPzMg`r6mTgul}{Z?@%}y==G!ann>PK8 z{k~1Oa)^2WW_6M-@)JT8Augl^s#tL=ZBfrq5~kXq*m1kbrhcWozMM4bbN*kLEn8$MR5*;EBZYKFKADsz=Ls#HdI@R2&`SJ+i+ETHq>B6opQ7wB{7d z6`Wt2MiSkS=^*LyW|%7yt-`GF)JQ2#22QqQ!pW*!B~$sT4076Omf?>+az?O2tmd5b z?y_EBa;U}|tif1^vGqNw_!`rk(|gEo0pA_k+u7q=+8yQ{+L@~GQ;M-ax*X}R(Q9k0 zQpUgNC;&qj%TOaJDOk%V3R}1CKezG1$;bP^+>)IV9NjqWfy3XOKV#bcFF!Q%u|i92 zS>f@8dxyUidY?a0{MU0wPVT4~4|}^e?LV}&>%_)A6W116R-?~xCyNVERHi;2rBi4X}}XCn%#;Wk<;Ws$Lumd}zns56*NvrCk_z zV>(?tZS6F2ItOCzqbr?tDaL$#rQV9v4(WofPb#geOPVXopsZ|CX-3JmWVd9W&30u4 zDVr^wU{0bDGtZa06c2c*M-XK$rUg|mE=MUckI_n z2&tDH3Tl`?G76dtPA+a6%!Sj=&cZM12P?mlVq3v^RXRr(Y)nQ{7Aay3CFvwbn|J6c z%T}}F(H-jWc%zq>uz8N%Y#98`7q`4>cC1;ub;Z0HO@ZR-r!PfTJ+^CaVMA;4p~AD7 zm6K~1J=nIPX`yBIG{W0&?fy>x_Ji=l-e*^a9-p&jdtJwb#SOJ}1m39H^T^7E2X^eA zJ>|5!w4r{%=9zUhwX0T4sDNwhS}$H{OT5DvuP!d8kK(MK0xK?Y+1nrg?7xhceQXo= z8OQJWoqcyX+wu9`PHf-B_MP8I>?BTt1Dg%&XaWrlZw)~>1Xj|~sZB#y80%UnTA>WB z1W{N)YRMQy4b!@;L>sh~mIW5Ae{^J0*9mn~k=CvtEn25;5^EQ;=XZ7>X=%EP{C0VJ z^nE_R=lOmGQ-(+9dk&=~=g|~5;bJs7;ZrBb&YY8Eofq_tZQwj-JmgFYR;4zl2&8N& z?0psjY`v6h%DB$oypcC0(izs{pq^JJVo{lhtmYpK4TVT3WM?k821QL271(ze8crEz;WqQ+funX9woN;u4PO z+|;Fk?es^dvN{b69r{Tq`Kf^cI?T~%e-RXACNv#{rsfhA^!;rh*e%~&@$FkYxQZ`j zhuzO??pnO$uEzMI70QE-ugBEQ9nXK|r=8I7{MLt`9D?|v?!5ic_A(1iIUW{AdUvEU zYn!)Jn&5}PipkjGd)A)wL$Y)Ik-Z)FQ4XjpKV^KKsb${JFFv{eDw0={*OM&&KpYMj z!0q*_TFj|wF#@14wdiweYYA{(cfg^=!dx=x;LO$vyWQeAPYR!m1o|oCkA$WsU=m6XdoaXRokY z&A_wfyaoTJAlhs)ANG2Jnc?t-FsTT`wJ@v;Zw;fVQe`!z;tL4Iuo`oFjWQ$%=24lR zs*u~4krk|Jfz`pQ=8sBt=c>TIhuSluA`&rGNFm5$QwO_7EbvKUqUTE}Dk(7sBkFS-oB*|~S`&gm0NTnBrVc1Pt=fSBd}984S^bJ3XuwBmvTV4pcvW}@G1~fURFq#0*XB(8GIIA zu?mN%wag291eOPeyYs<|HJ!i+-sU<3;bEo zjn~Sc7nhPfU&gu7^0VnDzliY|&XaC*O%disKH;Q{S2)0o!nOruXCNT!kVjD1NCY9b zOI}Mr_Ju4I^1H1ZRI!M;ugxna^CmB6;wa$(o$kPe0I3MTwE(OOYz-jYvMi@$d_lSc za!9fpeGs8^`zYNS((Pl+ygDe12puk+0Y591ZGymEH_zN zgVcTMp_3*Le6HcH7MG4sbK~RW6SBu>`Q&4AZo0$x+VmN+fjVY9TO#(}!gS{siAlYX zq*>rtUNo`lCIYmUGz|;AGy%+8}W_#SRZwqpE34@fl|Vu z27k;xC)X6dB-H2+J=O?slw!>v92alLnv2thal=#dlcNRMpW=`gp z^C3@Ye~86o6St9awuxI3`AiLzu7L{LuITW}gI;0Ym=WV`;cA-Ipeh*TI8z z&|M4P3Bj=%h|9-i^0W`W;e$T!4ln8Q!Z!0RGg)tjgm6M2aUPy)1TF+_8H{u$c|ml- zK?Cf_q!Js=Ci6n43HoGIc zMR-^QS6FguFlpqhc>J$JoJ~Pt><9dOB+}JYEgd8J;e{zWXCI~ zhP7O4>z{uI1y~M0UU)eC>Y2Y6r(b-%@*+GzR*(MV)8h9pRi;Vs?2ZG)-r}j}e#Bs( z!Z)uO?3h?;nJhD!-+n9u$J3yx5U+u)KIrnn2@4#w!1pX5SiBZ;Y$4ccq{8A;qFi`F1YU4@2 zF0<**GEYgr?qsx2 z@}~@sqGUT4EXaQ-80>lNU*GQDA8*TC-qiPT<6^w)&YSHUH@3fdXBQ5`)3r5=7R{PB z*wCO)g9fdZZKEAf1of)9iU1LvAewX&dpACS0?m%A?AhYUhqUfiF_Ryi4aqvtFX3^m;$aqZGZ@WlwnvNmGNYG zri`sJSdx@DJ4K0~=+FjffS}^<2)3iQ5&jZE9Km$diEblu)_28s(?{O$9q{3xZ-NiA zKKci~^jx2ZC_c)L!UODh%s@wn>d?PIe6O z&EZ_blNr4xr?YKS_O5xyJH~)ct!mFKnEm$YD-fN0YLRwzZ%h05=-_jm@blSl@td_z zx>i1#g1`6_6SRDA5w0GeckO%;lvY)5@7k_4bxwm-RPP$CmJHKQ3PZJ;Tjrfg-PG^B zXS}z)WV#1xJ-0pBk};BjB^v=hW0XOHmO+IKeKPQQnR)o-JZQ*k%fssf&=vqC1RsWA z9R>ksVf81k!v z7}ifws}vgy!@}^&@ZoT8mOwW-#9>}5UHyi z@Pz{+@*~Wl!u-Jb%o~}wG7~b5wS1l0xTL)F^uN*;>z$l*fAo`-taT|_4N>W{CAqPi z4Qch=o~UpBA$^U8muPRx>9pZxeSW5sVejf$i~aYN#lszKe{1c6Wj{Jp+N0>mpRhnLP77K(Q>Q1P zE-`xj9I71ct?W;luQEr<*fJ+HPv^n6G%vH6wT<+Q{sCU6V4%Bd?Go8Qy@drmeN+@q z(f{}X%vA+CWDg^3Cy1Vo@j;+5s-O!Ou-IcN)BxqE5*P&1K_9n(0NAW7RyZ>VirHj@ zFx(GG$y$u2ynar4Z1fA7eCdnC_hTa)v;(c(ztj$pda`|#Xq8YzT{Mktyw=F&(R_($ zfvO0s&uYRz>}DS^3o>G4ri4+M9){%!!*CpH0iS4*?V>8uIFHgWMF(4)8V>_H5a`h) znLv+E>aXk_?WK|E#)L*ekyekXjLSPX@{ZMG-AQ(GHw_p9r!DMl6>Ti2?fUW0Cy-IM}N0yJz&AXupAf z9BI`q8ClseO2}(>pVi}-t2MF}G&^pzL(LQgzks=zv|j3#$Po#UL{Y-Vm~`%SIvh4| z`3Uc{VJDGY*ymQ=G)M3IgE8Zs?lRu0&+dpJHy!crEW2z|ZK^9jW>cwGK5x5W!?xmP zB{g29Gc=3J*l8eX`iJ{dUeK`4bqUbrWehhMnm}(vJE`2bSOu-ak-p8X9g%|5Fz(!6 zPa8?b)lWV=y}jvoa7bIMwLgK+CbK^K>!NFMh zLyP9G0N2@8WlnJ<4u7HYVPw8HWZJ}jhJq*x=x91mHyaK0-|DX1KrBS!WIAah%v#eH z6RtJGaR&%~{dY$G^Z9swL3=@00hv_LSg^a`y#nTi0;nzoRe{<_Tck5W;*o~P&d9qF zrZw`<2u557w~fLhElUnqm)jdn17Cd<#3=A3Q8kLAQR_HgE}`uuaG`ztA^sFkMBdLg z@h|aPdDcew(AfTU>fBTU(NLNb(>Q75DLqVSh?G9Tjgox@mxw$4*m0dW8-cb`Gs}W znyWD7XD282?7sHNzWqOqi`wb*-S&!{Y1Zur;0c&>c;8ub%Zr19FSeNR%7L}(p{(>` z(~3n)-e2_wSRVOtVch?y+X|3%a`48d+WzXHSNHCHbqFPuVeZj=n}-zOj-$eRL+T?| zF=h(7f}tR34|WCd882A9P+qT*Mom3pN~VMXPhf;pln zelEvq3Fr7ZAz;oApdy6?B-JBH>i7vUNgY>2A7Xm!mfrTaB+NHSP%Yh{Hrkz5JkY22 zVzb_?jxs}@QtA5eXZPjNneM{nkEGFNz1$OYd+G}kQog9pP^wu0CzqQnb~iOWx;zuc zj9v+$V)U`PVQCSB zifW7SQxpyg=NID6z|H_(6@Yj?Ov;5WdAE$$$S}zPoo4tCnN~x>B+swbEEtq)AK~%gxTcPH4tdt@2gwb^PnobUU-6Iz@w z*$GdGkl-N5f!phi9+Y6W1Qy$2j|>ZKs&lejQBH`0yO)zm9c0P_(NIBoPzwb!vJ`-5 zG?NjM(Nb&_kDFO8t`{qaOZWWX`47h^zaImc`oS@uS&eW7hkU*5=yKLp?yal6*R-LJ z8%22u=Sn#$%Wum0`k(K-uLoPYEO`{{GEt?sER!RV$f!1ESvM z*Wstb#Gf8Q;)l-lU%zM`9wEM6Wt;ank;q2P2>PV?1MX+$LuPNN8 zIApPQxg4^yJ~MDn*+%nEu&ecrDfp;)ZrksE_yc(~2XAg&Z*y?ltkE32P>6*pR`+cG z=)ah_{~3#kv)jqg5IKh{=}F-X)k%t2-z7q%`BEtzOAY)b=!nxfu$<_I6Uc)P+)pL~ z7+ti4RN*K|HVh37-62G16;9*%*8F)`Mo9kr{f*zU&X4b+rNifO`@L*7^QQ0)8AJMw zMwP}Xb$I7`sq>Bx*8703Lye@=&8%y!TU{r(_-G`ONT<|vI+cz@Q+!n>95(#HZ1^6MDDarwg)fNlqN@3q~zb93#O-+do+%-A20UV_WhJB#F}P3pVZ$@Lci< z<@|^PvjllUk;IbHctyR9vmd2+d07cUUGEz+ZfyR87;B}&?I2-y@aAhTn>T-BeiZ2O zO`X5x!u9Vh|ImE9r)S^f#Po3mW4}9f?rPDK^(<|S)lVM>75jcPWWIgs%{Mlt;x*tL zIJv1~aF%dp@Mp76D7iXR@a)?b(i%*HL$*A!sa<=mxrja<6&1JQqFO zG|ya5n}_?S2R`-v&G&B~ciRVTK3G}>VvSxytv~B)+G}pt2t|d3Q>!c0>gvj(((1xW zE|CmHNT4s!AK)Y|5YV+mq*NDWS#!G3A+bs{ZzqEV*=ozg#qpuCx~Py}7{4C>GS0Dh zar_)U48;Wp7muqfJ`7N$!<{`ry3r?aq}GvOGDxx(QWJd|+|qz(F^yi*u4`1w;7DJ4 z7p*m4!Da1$U}JLFKs6y=eKuYTEpQPpA0nm<fbP#Z8T7CILgcZ zsK+5jgOrc4FbsuZZy3T@6`I%!0Hjcg8Tw4URp%`7Qk`W!w%p~FYdPeoat$-fZ$;`$BL_QNASQJ&6ycq|h{r|w1Pg)0g^0mu zF{R}K1&E}xSovJYjqLhf>y98=1_*Bt z50Fegzq|%po4Ys3hLIfk%Y7Z_$jYdvY3!1wSu-AEGB>Fs@5~0jJnCPWP00sp!@KC& zychYE*(_L2tpJ_vgXJ9Rq5|)9?>sMml|e9i$lvFue!m*ls4T0p2d7U+i5!d*Yfz5_rDkvdwS@Sh&wE@L;aM<);6?ws3ZVX506F#wNpRcA(c6xH8gf`1}BFmq}8ZvUk zXqrYqq?5EguTfkyyT>AK9ONS7$_Y3|3r@=(Yx{e7=41seR99d5gCZA z$hGn<%tIb2`h~S$L;RfO=rSYu<{f&V1{0n zWd`PYiCt%uWl)^wN3n9np19X=40SgR^xAgGX3d&hNq)5QcipE?DY2X<-}%C^cJt2p zUF)`_Dtn;1_b)wW8=VA)AA4Z(rd7X&&Wh<*7jC1(GW-@o*>U3+&0PWd#dXO?GO4lfXN=;Y*(Sb+$f&T$sBm$G$ht>F zm@C0m35wmY#J%2qz|A?_;3j@3^7x^Y_n#j6<4G6Gy76wTz=tm3qCgea%2u;2jJIz6 zjJw64gw?T$jGMv0&seP}s3LQVf*YUzLwB{uHg%ox^SJli>+9=hZer)!@gu}}8OX~4 zJXmvqt}sShKx5EBNLJ{!lyxC=sG|&#Rb2yX6|z>PAT)&*q_&$FXrv;v)0#mCj8#yn z>w}E|`=?}DY3a0$t|a!pbA0VMnHE|0jdZ{BJKyhn{uI?v6jhT=`GQOx-&A3Zf~+K7 zS(QCh@$1BhNKDb79HA1CVKHmY{FK5OY=;LA!;72QB3`&o)8eB)SV?HjlXuS0qEkqJywfUOzY~*Rzw?hDuU$7l z$3@3@CiAX*1TxZ#K0Afgpt zSLNpIZ0R+!QyxYR)L~W5WlPu^M$fSi*bPSKsY}(hDm|}$sE(=h6?Ir8j$tx5bR5hi zc(DAkBPumW#3S*aW0E8(uaiMUKzFp=gp?V<4HJLjeFwtdvlWwgjEW79?2KkU&)kmg z2tWNoNFL5iVA|IQnq~qt%>zoD%t2MQrujPV;+lq_5)UQ|3HB9hAhawyCWW~MLU-rh zN9S`Ih@b`Yf|c<8mnJn(;rv@L@8W_8$`1v)s zLqzzw)b{gA-p|U2rh%WaVn5Nw><#%i_fyXMd8FWHqS}G@ohzsLxf(EPxu0_4fBdXU zXu_synx8q%IxDYPQNPfvU_&rkxQL5ZLKaTNv32X2yl#cNt;ykl&4Mq_6E^8-IQM05 zNKXiyg|^Mi0#1E0Six=Tr}@*I9itBloNw9wJX64_PddG}%_I&)jpOWx-XFmwuayN| zzEp^}kiP1=)cg1&Ox~mTvWA(v%uO8razAmULthP%O(Cp=a9}=ylE+>g!&`7K9>C0W zmf>*B++y~c6q)as*G(D?VT2pakYR?(BNq0UQIpJ;O|!y*Vlx|3Fa(!%qg`lFth3h; z^S~o9BovBR6-4LVKIwF)2J}NZk#wx+-iU*Su(`uLX#UcqyUe{NF`dY$mSbT(=4`lm z)_e8lSzo%n$Cpk+MYcdU$tkmm5iZoAjUy0eL9Whz4?U+Z*(dJ=Wrm>X^CsQV&bON$ z(Svx$hX!3%%D2uKotvAkAk}I>tF_{IYh2o&Suxc8*vrpe&2;WOWp+OFyx?S5|ad_Cm*y zYDCvrW#e2aRHGTs*;Gu!=>(iVi>j}liX%}}QwtSLB~tVMaO9e)IABv*rwW_OX*kOO zXT9KVjg7M*UyWJ>qzLC=xW)8hISsE5NMu02I@&Dj_sq}lhKl0UB? z#5N0@)%Mh=0i4*7M0?BeaP~9HQ658OJoCzn%MUD{hN_({(6-q(d?LrQLm<9K`<=w0m+Pd)zz@JD&=uGopc`Y4#>mFRqX{yvZfPAU z3ro&&Pb3^xy=0IbV`MKIWJG6Sww`^D{fNmbtEh>nYCXfu?+L}c{+NG-pCW&w-|~<6 zB^UMkX9N?-LRP>`@xeHAMzu$4(Au;Pty`0o-0pg|-BxD4G@l?K)%@vpV8_SjOb_R1 z0<@>StAYf8WfhC*QNt!l^Kr78eZCglTEreq7A4)){a)tZk366GB+p}g`e(OKj33Ig zx$^M7O1Hvpg(Qf&^24;w>K!)5yd~OUr*5ulm>~ z&NF_`d-nNkpMAE^U-8%7**@p6aUF#u5I!1jkRVW5fYOL{Ga+jUOK9VGf(Q^ad^Kcj zG?W6(I#7bPMO|Tp3D^jmAVpLfe^AiK((R9mY17uCDGk!PtSiLX`<@*_Rw2!fe2!(= zet%!j?;*_8jFp#@R-keCy#(fqY#oKoqi{=1(-sMr#!6$AHcVZ)XPUbz+yW}+=}8d? z&(O#0ZfPIMfB90&`2H=Ms~3&5?*C2eh529GwPLr=bK%j|MbDjldqs84s^iC2E`iH` z+|haI(u(CfJ5NLAQ0-A7H7}3o@qPW-TJfs?7=-PScuKo8It}k^$hri zsM4t~(sj6{G02V301amS89g$vdg5q$yA!sSfYAr{eb5kXj-uu{9do+o(5;D`i9-pR zZgggyXix@929{ASPu)rHvREsI6zEbwiEv!N5sNUfc6E>1uhOa-oJ(pkRThY)gAiPu ze2_$9vL}i7WM2|xlh=~>l5{eV3f2U(!Oq}dP|pV&wWobSy&)LXiQAQK1-0PMR=7~w z4DIGUW@I*taqLxhNTf-sjA6@Ua%0scsZ2)eWtfl;Qe=06lomdO#LU1o!KQ{?quUXT!bjiG`rf)qHhr65GFW<_^!@Kh% z*m;KuO3!1rQMzYw0?!$uf>{0Cths_^ybLn_$^)YwX!7Jd$nKFnXw(OFp@vX%i2jQV zOxPVp6JgLRE=5sjP7SDYRhp+uY>CQ>xaPDt@bQ{>eVitjYjoAr> zj_MpXl2)`asW&i1_|*-B*;LVD7#YoAxdLyvVi!om{?e$btSYcZNSh(7;4ORQJch#N z`n0V+x45TOs8yAwRlQ5jtcLR5O>M9B$DjQnp$mKJz{dSeqWv(?ri8lqxpfx;kXXI` zt$nN1U#_Ws0hXR4e#*D#S?nj3`h8m3?trZxFnZy>7aGFNVMI4NvJNyT0wrRTM2JZO z*d#R(pd!HQBeKclv&CduDuqy~npR0!s>K@$rGgsw#nOK8`%?ZIf7ajWAN1>af1~!a z&#yQ5{d919q&tFI2)d?F+6YD?o=QNo&QMUZ!-vEV|7!=$3B}2C3rF0v8wi}U{D7f_ zh&}RuxWF|}&z?QGt?B5~b8XJnsTmAti2oJWGm2A%6H+iDT8%0wnhw9uU(w zJw&RhczhI*w6sOnqWlf+w#+{V1JV8Ey^l3?B;X z$iq8fxPE`oRCpFYLGEm109NEWtTBvAJRLGox#5T}%2!xh$OVbMy(VmuLaGcgx50wA<5{)$@so5hTCax`jvQeVRU7b_C`T3!8Yl22`N&g zR4e^K!rPbKW|!==Mehj+rZ657Tp}G{R4oHl;ME$n9w#HdWYt0Swt7#UQVpzXQc@uZ zg)F5Cm9QxFn%`)DryJa<%qTg0;pkK$ho%!sD`{8`jao~a24glHm9x+&%zWm397DS{ zwWVVD2(r{vHP@`IZ{k^c+5GfwZO&^cRJCYJ!!h{l-LyjjX}AaVWNd5-2%q_`4Q zm9!*Ed{%=PQ&%j>gr0cdiS{>tP4 zdhy}J8+D|lp2k0@8|z^&)`LiOr8g`G=s;Zv84V?d9ft24bSv0zvMubN*!!%0lLuOT zu-*qDdxGPv0?&By*{vp*V(xK(=%{hrbBsH5jSg@)EJ0-)f3|G5D7URZStaYAt+cgx z&`j}|!()#RIC-N`(uo1x5#;E-g5CG{PW_`@64ileb3zd99=A52UjK! z_8l!y4AiSrpXi71oC#1v>22@8dw^aC=+!|73(HLSZ=LC76Wzf>%+_ohu+f#aZMF|= zbj;c89B|SV&aF<=;eu11UwF`uy=S~=ffxLEzd<}Fa`jvf*T)TV3}+1)X?F-2{eFi} zp}45I(3L}u7?Wm*JWN5ULSlf&drvAey}j5*F~OsFl3-l4xVVv-S?`KBZBBztmP%uG z8xCE;fMm(Q*lixSOnf0%>W3zG=g(gKH5`I<@M-&CCVwga#o>Ls4gh?%Gm~F~{_*J1 zotw}A8R*ZiDuX5Op3MIzKi>P3QynMH5)Z7#JKz|e9>fFLk96QND`vFK{JI&futJL! zp0~YZLshmq8=5aH7d{f`b;8R6YVb6BkT2l(qa&7n3*s%J1sN@Tz+`X*klE+6msk`| z(>{+imZGL8otvhD_9#Xq9f?(l|E0TXW1G6p@cFnOzSoX@{dKQ>-6W2k4_a=E(vQw)#4u!Ql+^d zf!bArhH6rk{YXKYgo=jkz318?fiYXT=iKXa^SAl!zrGw{Q`sI#if5K}-y1XnN0sb54Ew(?j0b2Us`tdXPOq`nvz5|4)AERqNcOU%#!Bdi{h>eEMmfjOiEkC7tR);6u2HZ!?Ri!}O@r4|-hj zxUb(we3}|pncA-kH&v_x1+L|ai@6H(QME*5IniOChj~?8BYHedNW@dk=XhK*lEI1=@&Q){1$3{IFScu29gRqH!rpAuVr43afierGm*0JDCWF zVd(70g!S-;Bcm_wIWcmgG?G4ZCjG-xO}SjtDMU*j3$u8W_%ZUJDD*1qDKA3gDw1du zs(~?x;&3(LE{Jq;bxzZ2mE;HT2*^YdD4Q>G>nqs0v!WI6aPbI1WPu`2WT1;`S$`w1 z-RO8zI+;wTlj(^2D?V#Wtj&^XQR2TP)2+c_K1sB-27g_dqyY=%SA<#grt|`GLuaL9 z>J}AQa;ZS|1_+!a5SJ{^mv6G|s^6ZLFUpqzJhCRoWhyIwmT(#rpkJbrIIUh(FRN5_ z?-MW*Gy$3qfli}~=rW=|Mp!^0)QG5zFj*i8-10h#X7iC7yv@$v$mQ}kDnlVR&SQ@w zRwiQ>AolI;2K1RhIoa6)-;8X?Z(bO>z(4#dDub&ZM9S~MP6nl2uoDBd`~`XD$<-M- zsKPL330TlCK%g}q<|33YY~?vigAR5parCd74>pIQ4ejaVuGLXO8>2Pt4aszAwmLzy zybUaVByDmm#?bcj(R~rovCG_I0Mt~8M#DaZ-z$U>=GJTy=3e+5FD|8!BbQty`JdyG z_QeYK3jAnk?&Qh!JBe;-=5rA$=|{T~X^c|=!^t5`Gu)}$2M&nKyV?>PWA1e6KRof^ z^$`9Z&+s7dX{!7sc=^J4tkpHx3O^MD17Ul&azG&pZpGv-#%MzL05~BcH;^UBEHsA5 zWweAyMKJ^cs5Tpj+X4c9M6_$p83{B2TSfD4fF*tgNCo45yE7v>XUwV6Sz$H0rEx?E7@C7e1kaNMbC2?}MlgwE z8nyCfAy>#j%NZ>=B#m;Aq&PxaI^UU`oD^T2nVCVj^cnE5Rs0fU{OkglT%PqCHl8p4 zowdoDExOEFyM|x<5qR$30M86pP`5vozmm!4=D$o*Tc@xfov86fDR*L{QiJc--~-|- zB6&kRC6XF3Ce`6Pb$GcB9}MFkg`20)j-T)1m9*jk;UCqs?%}-$ zf;>N7UIYY9E&v_BVOcnM3L>Tidnm$~88O?3>#>F5kctTt3E_{9lN01Lp+3HfFo9AQ zZLld55CR;$4|BxVa!kNnBv!#GO*dfBu5gh?5vz&=&<=mbjA{-h03B^}`o%+e&}j^W>JXOdvAM)gaN1 ziK7L8gsD{|s8u8o2MGieK;gTA$a5sPvFw)DA)&a6gkVDE1Wz`1Xbn>pt9U^CqGC>0n6#OI~hCorgQ$I=e$&tU*T3 zv#N4 zht&apM~H>^>iUos%7$oPXf$*>bSor;ET3taS+ma^GpEh6DZ8i{M~qX7nXx+i3c87I z^9EN#{pbvW|Bjkfe!N*`8O3MIE->K|C0S5w+YM$gcxB1BWt0u-GwO{|W7>dMjBnL4 ze*~6mJkkohQXlYZLak+FD!45yI;?c2+9jj?f1e@?=7q)FVF&kxOva(EKa+8|U0L|h zFV(K71r&d^|B+kPnxSw=Zn8l?Aw9eaT|X!Le?HQep0p-Yzu3Fywc>ZI$#mb|qsNAG zucgSnon1YB{aZHo-uq{7wrgka<}Ll=*CWSLu)3{x@3CX0-#M=hA3vJwZ9CpI02hGh zf6&{rrT@9j*&PF2o1g1PyhuW3{Z3|M=q&p-`Y-iW8za?KhR-?oe9XPOGjnHmW@mRm zcE)9)IKZqscNx*ho~5M;S{R#BV#Ni-57MR;mR9|tWQU5b+f=p@QyPmdl4wYOxWFz& zXex_8YD`L(#FoScvV@pGe;_qcV~o4w`<^>r`vFqaL?^pv&OLMPx#vF5d7t-r6}3e` zwN$8;LbjBsZM8C1w`Jvl*kNFqTaDphCfFU!2X_TWg2f<^6?E@RR#SMd&CqPAaEZFr42FXvWY6HxC5)b|zN z20WkAN!Owlau1tw{boBm&c>O59R`$E7`>773NNfOip4F(K@ZHC(BXwCuY(*X?t#+)r+zgY217muD!x=---SKsZdDhQKe?FN*K_{Bfu8d(OpoPK8{@C#QlC!D z&gygh9r1nry#B75)oc9SEAtpUHg(ImNMGTO=`TTvXp1mup+?~-q$5UKxaLOTc!qa# zE6=y_VSbBq-WYZ=PPdbHb~z(X(E){XngAn{SJW+4R90BEs%>gSfx0O-s43LRZb*s_;R9W=-wOXC8=sPXm58S_A{5@#!fiomne18-muES&PLI-7m zg=MQ`_etXqXFu*4_;2qA7boHmZ&=s$y+jOF$cAQ6rJB4}E^${EE@loT|LDymD`=x@NCu?NWK*qH z+7+#|ciI&JB8Ae*b-q@TU4%`LNlfh^I)w9*=I}XMd9$jOG#T-7S+s^tSyUE3kYEG7 zr9dKd>K7PCrZzqLpQuega(A^UsnaeLrY9DV43wnRinQU*6=~#ORHXM%kFw(PfWR~2 zWniR{JgVz>v$MzxU;`)Z#wdI{=xr82rygvOc48hWNH=6_8~b*l!B?;gi8uYArGH=ca-97(IL{Zo4O?g6}D#hONS%ym)v9(u$x-#zQhw{Bk6;SrZ^-R%}!MqJkgN3_Ut3yrw&H*~#} z$c#j0Br+onGm>JO?3Q`iFNfqgS(JexIV?xygq)ND3Z;l8{|#n-Nt?fOCw4G*Zn&8+ zS!Zi+bp#VDZO(v!Ok75S%s_e;eKR^nXplq^1yKNnY4dWZHlT}0DOJbQKUuu)3#&Tw zOI}WQtL)(5(UFZCAI+rKuP0Uw!wMW!->C-IX~evA6_Rp`ZztlQ1*QfZbDKTVf~Y#0 z1l&L}UTHn;J;~;+y^5-Cbj^WRtVW-tNxqfHJIR{=YFyst_she;n9YGx6&tXy` zgHmsJ8X90wB7+hcl*ph&1|>2m4TF-l%8bNg!22bY^Kb|j|1Mc9l}d4W;(S!LsV0Glgi5)vYC2IXd9eGp#zz74?q;A~7(}iWFaKIKJJT}5( zBRt@e03Mr$?N+`5D{L4e1mpcifJ9!LDSo+oZ^AF>%q zF<56nQ7TR5z~Fl{JRlbnS1u+lU<{T55WPYrhSTb?!x`s-b)T@U6#@kgSk7)iQG@Yj=L z$rPi^!);CM* z~z&e3v9eGT|`0vxjq95QS#N_uxqpqDsVkp7d67;J?U3?EGUQTEU|C#z572&K1 zm%nh9u$+k=;~4o-cvU@t)g4gPeHvoW%S;!x{T{wXPcbHorRs}%o9oNk70lSfs=m-C zL^mU>O`p>D=vqNfxkD{Q#@rpPEjph-y#ZC_9iSI0Geh|Edi+IdB*Wne;YMU*b8XS? z2P*v`$oo&TsF9RSO3>;>yH%(kRD^V&W^KupBuj$ns+*H!DiRTfcPq;m?Uq5u4>}1m z%95s@EY2s-C)s?Gf)@JOJty;3yT8TLURIHKNNx~eE8F+C*xt)}rBpmOXh%KMVrv#- z>nO8~0PNx3OtvKxGc7h}nMd|$#`O4#&~KGY7HuQWw_s|Q-5WuAt&sLUhql{IKcmSA zC6PADu$_hMR+v@YbpvF#V8>k|*RZA!k-yWmW@M<5na-3l3z-ucXU6mfikW5u4CQw}%OP?>ZmS`D9uWqstbtDQ&)v`+ zMs(w5UMuxo>0`ZpeRe2mYNV&rrSwAjMB14aP|~YFk)7~XZN{;TnO&D5*zO|t7@09Z7W3USHjNHoy24EP+S{V1YP=SLu`#ClH>n!`e zpr`=#wb<`~XVw7#Qb4W0lz&9c{63I3%Ct+&ZXg^f8eMA~z<9LFMiwHH{q^h4_g!Z} za}YHz(LX3p-TgUVJ81N`czUq5UAy6&2Kh&&JaS*c472h4`8*5e*M~bHoO~mkIiYnN zxGatM@+zJqRy=_)EGbF@EnZ$BL|aDrtxDbn^01?5{cJVX#k`;qYtc9k?3Kpq<`sMW zOjS1+B5td0uK;Js9@wfl>%7$iUBqWeAzJr$8fcHu!{EK!48Q;cI+$~U!h^HvU0N21 zQAZ*^LOvF|xXfVRTsOV31oc8%SeH!=w0;fys);&I{tB(xS*AfT$nH%r$mc4-px7s> zT0g2iX~Pfl-=KWlCLfPSqYe4vz+<+ocz(?Ors8c2*t0TY&k9IwQc~TqMpo)qYBTI5 zlv*bzgRZkO>>w6%)?WvyVU&7IN|7s)IU|xeNyOuDH}Jt7!Lsh6Wz!j?X|Wy5pa!W9 zdZ5$cN01D0E=WOU+66kJsB-~iN63-0$T{^->9gbq;(k~$!+`*vF~s2$CIZ$F1WaF` z2vrn!`N{8bJkJgIJ8=v<_Bgbp!*onJN{$7`lH)2oec%Ukko9=>aXthe0z}@ry_`Gn z(pax93^AdDWs1&XZoVf#Cj--gQb0Qy2r$;t;%Z|Zf}XjFHnu6={8Ub@1nl+~e}0Fm z@($g;1GBtdb^8O5iyqlI6*pfBLfd^(35NV0u}h>k=>40hvrY9rTo@!Lb<7^{wh%lK zWK+RC!Ihv^3N8fMKGl49lt{l9?S5f1i|IGaFf~Io85$0iLQA1PhTwU4wGs+T=X0oe z2zI*7J7^|AGg+n~;oax<6^BD+XirExQ~$c5+PFRkQFBP)zNz}o?IBL9hksdHq+3yH zQcBSmL8=WHX3|NC(}f*6*RY3-Hzc-}w1-UgU#uYWE!LNahw%CbX4vNw`InxUopf@%RN_O9YI1Y!c^XTO6s$yd~MMBf2!H+nl*A)+IAm$ zMDCX!FSCfXDoju|iLpxirz6o_;HffidA?Y2OQTf3mgZfmB+ z)@)<1gxiSAzE|wFX4!X@RHCx!E3r4__EQ+@7Mrhd2`- zuW^mDoaqh}joeUg?x-=S{zFVxH8s(e`(;$>l|mxW<}Sr|r7uRz-?vTGRBFs6-?v0$7qoQ62(4MuzJ z^0K%c-Sht^y2FSLUlyq=Db|}x*`esv(5Lm1zM!Abow|UcuL4EBEMWIJzCm8wsCdmO zFHBZ1R}%?feMY-mXBSvCg-rH+Ygg{eW8-=>k3aa*#98x$B&Q! zb}DA_f@Vmt|0k7>j!0y}<^lI8mwQxrz}LtKMDjpc8xRc`Zifb}oE{j27sD#PtLR&! z1NI#x`|fGLaSmTCJJrtjV%(P|2uW2LiFb>z;bf{JC`RMW1jtB^0ejkV0KE85!Q54r z>>KVg`@sJfmA-x%yYSc#8`~DS=NY;ifSu11pcBwu&WNo|+#aK+r+v@ieTs&Vo|7_WZ>=vym+^Q5dlANl- zIP8%!V&0{yb-(|J0TFl`sGdwaX61_@Ao4L1Y`ajysHny#+jN|8=>zDc^u^lIl?Q1(u?4(YU z#$LJUTRM7zs?rstW-x7SA<8y_V1cR$jDFY$2;yZ!o6<xxMhX~3Fb?z~(Ev3H0bL%&DDn z9z!4Tt2<+UQvGVksS?g(ht+iR=gJm)9~tLgWW=B)gSHt)tQ&$h*6nQ{W`>x?3^9!v zVj44W8Z+_+6F(;n&7e7)c5?X~W}%?*@~RTH1Ic-RQ?W{{<@y5 z`1^+PcLx6cfqb5~!w{0p`Xa*B1h}fPB;T^tNQ!dM8c!HWF;Enf+~?4lzHfDryj_|cugM0VJo=qd>-tNNPF^Yvz}M&E%n?3 z-Eu#^{4SM&dn%JW@tQ9mhl+9?U;c=yB+oUliV@;Ifi+;a+MZ5`j?4RK?B#F$*oaL( zGyy;Nju|n2U<7`Q&%w_?!I+DQYuY57s}3>vII`oq;114XJv*vu(X9DR;fPzz7~z_4 zihcY$_X+%_-O^jf!=mo-7>)7KZ-|3GYd;u|!?WN%&f}LJRrfK8G2o?#S}AF5YY|-; zqeZ;TJ@~al%Bx4r3fbDu;8JL7&C3MfWj@5O_=@Tm&zoNnYSlb)7qz~O;Jde0_%q0l zL4Sb*bRl5Oass}9JmXU3j2S#bYFf}K)O1-j;&%7rckEH$@lEqPnu7QpO*P-q`SHEl z`}iGSRo`(I&gkJ$sANICpldJdujwUSBYMgg2DM%tF!A1cp|dRDw0t+}uR@8OAZZ?k zg|8482|O5ZkU)zIDl83Dr!S$*!PhP0)I2Jc2TyHm!Q0=U7@wsj?mitLw=6f?{n~R_)PhmTAyw4U7xfX=lo>!2Nae2&2={Qk1nv zJ1YH_F-yZ8t-kkp=Z#7>y@lD-bB58dxW*u?W+ixwa7<(tV~OhZ$-X1s|3>Bg-y**e z*#&xC=$oJj6TMSBJ!9F5SZWW#E|x&;KB0BbkZUpr7#Z4`NWic>RFeZggWckO5u5Z+ zD%Osgu^DJ_I%`5i*dGA?mtAnrbU@g9FF`dPN50u8E zuOL6k0~+H6nkwKK8tb*}RCwohUD2ZJqtQO4>-m4WCE5?Vo--QD;9Gh9#&m`r$Ykoy zmG^Dem)6(!G}OD375y^$-N8c|8{yNOrTDN^?rP|{MY&>84owfS)X*NO+-_5rAv2su zFw3)Iukv1kR+Bx=WN`TcMo>@dY)+roVcyR#>N*u8*65UYpE~U-XnRhxh|mTiabqMx z(~&}CBr+Fqyjbp9j6^JI5+=m;6X)-tpJ(Bog#HRdXC7`V0Du?#A!8u`W7u9E3Iv3X zq{%#Cgu{GK2ZZ~0ZKNToxM8FvAoVHInWm7jE@n+i=@U@y7Zle^7RvGnA-QuJ+Z&h< zz;pb3o$tYTswb@6CgEY2yuOAJT~HbRQ<<@p6zS3m64C$VR-qvM%D1`(R6h|AWzVRC}@f=(%VV-hfb%HXf+wc{)a6c^ep& zqjF=`YLzA0M=+cYBP$%f1(usj7CS6&{RV-#(ZgukU+{B3aAwZ={U+yZn{FU4l~xUQ$I*S^(n%EZT164t6I^|ly(_!PfpgKF{Gy7Hd)&>D|mxaGmw{> zUYkbb)w>u2eKk3{^E8c$QoYFwrTRO(Q)Xy@2I(oP<>?{Fs^jz|{Np0MOv}_G9)D_3 z?lkqxRg)R6Fu%?_Wv1RT)BTuaT5@)GO%6Q8J7t^(At*$$dAe6N?J41N-x@?&Rfb-m7k!s~5L5?w$mhEa?5N4ru1T(?vh-qYpE~Do zRHlh$_n-(;5u$U{sYZ&yH5kPWU@UC_qjb9rk-^$E%;0`YW!NoVJMS2CusO#m2VC}J zR$RzEn5NVu&P(D{pS3F3m@A)_N%q0Uw^`%@`I4G1-j?$MBi%g?$1tO*nwns@U#;4w zJJDWW*WR2jG;s7L_w>evMq^#IJ>RY>T2wG8WR zPh8_BN~rGR%^I7`PG{Lz_IP$S3r*q`a6q>^ARrh!<$t(_oy%l9**0Fy*WG+8OXmu? z>6|u_yP0F{xf~&|E)fp1^@HRjVFA)X#>ga@Ca)0}EKgHnl@!zlOqsC-o@FB$>djySI@4TK^+yLS+ zMVzf<@P3o%2}t5*_uy=T9#7C`6c#REFi-X>c!}JEv7FG#_fL(k$uDgv{|VUi-o);b zQ5p4mbz&#Jx{2Lxze=uveN5BH-KEk#N!yi6v>%i^plfEtt+mID4DO2X&XXBBn~{q^rmfD8DRZy26H7?k1;msh zioe|FYceKN(YOwbc-T}+RO^tI677#P`Y;HOdx1`xI(5t7p_BG7%}9HgR817x5~l^J z2&Pqg+D0OcN@#YfwL)wNr1hmzjrTj}`r6la>`)CVA*>tS-{<=~_x%2fZ=Xcwj9~UE zy7=V^&dz6N#0p@b>MKB7(w4jxF(ph@NmI2U{2_NvB@NP^rYS-6lCCqdmmI|N)w4Ht zmB2u50P9(v`vwLmUBCeI`t&i3FbYN|sdZMz?+k*aE53-HDAI$p_<7f*=!(36;i&#M zh2UVYUim#AQj{7Qcz1dpitrHFSu<`EM%>o>UN@~OU9V-v3@UkKE)VMBx8PRvlIM|! zic0AAc!aaWzT8PMUVZk0Wh39CEc^t+>>Lr zV7*+qXTK+7MKf}W+%$8fTuAMU?!p||(~u+07G-kDw76SHOiBx_qs48hVa#WGOc+qQ zw`o3WwkcX{l2x0D9c(tRZi?O}ri<&`scWrL+gOz>R_olE)LO+%D;dRx)u!}s9B=f{ zwaN|XhIFOOorhj9jGwCq@3z)k@*5hOce!((8)o|(HO1aqv2VaE^;kM!kSoMFF~QgZ zaF%05#+VG3?JSH83120-*m4ZR>`UGoi?vv;)Ux9|+RjmrrK24*t4M#)s3Lus09;+h zI>Mr=Dp*Wr3--)WmS+@^VklavXKfR;_>`!LDz)C(7HibODizIBDpyad>~#y(+nolc zF!2p+oDpVbh$VZQ=%uGe7ko!TWOI$a`GxUCvV!jI)3atoU0cg$cj8ues8;G$m71wi zGbZ)YS|;;kR5*&0+%fx2I!mTR+`=M!gdb;!duTpFAQDj&98)dx-*z^u@Fv>GxpsV9 z8jLqoi|=87_`ZBURn_1DL4c3V`TD`Zf#S)4-FE1whw2b;cdqCPEHY*%rv zGVY|O<%`xTt9)NGw8*-lC8FmYM~^P$e--wrR z$FRraacSQiKSv**v>g+jL_|8w;e3iSAPIE;l;WHVQ9i)(N$g%zF4qDLP41PkUuT$8 zNi(GkA6O{6dB8%+?R@BZ*O-B5EouP|PxGEt&gGGppE< zl_0@Hm8I>>k`r%@rE`y!C5KE zEIu!(Iev~)l`j~$Oq-KiXj>g=2hJU!>Vbm>ZMKD0DL+1GKQ6Oz9IaEwogf=9gWa)#~_(5&M8W5JtI=LL?0Rh!`Cs zPAsoMOKFrRZ6=iAK^wwQND>BjBGf9PBxoYawHOk_9R6?aj1CA4rNfpveA}}WJ8(Yb zPPNL}wG(H9%(mrxhW-pbp+BVt&Lr=xaZagcRlKhvIyKnrRKrcl?Ic*OdDfeX@O}of zX-R__-GEGP>yd@A)o@o0jH#r-*wf4atVs7|#w29dpEEWd8Hazw=jXYT!q6^oERdi` znQj-R%o!dI1~C%BOBC`pVM+2LIYCa5^Z4)~`75aqC&4XeMPyJLm2x|k{+h}UdAPs- zE2=ntVyMvX7}fBB)UY4Kr$w$XP)AUMk}-G6&D}UN{*s5?ZlR$NPI3sg0ZEdS)Dapb z(p}Y9GHXGrO7Uw^+4x#iN>5*<@?W7ME1jvbX;C>M;&f`SR2g-jaMQf|p&QfZ*H13A z(q3un*(kxw7}57+HPDQ#AvcZOV`H+a^iVJ+A<(`I7bQ?~YT&8Au z0`Re%AT-Q`WSr#VM2U~ZPsiDGJmMiJ7ORh1D`)EVCu;V0r2RM_q{%SJhlvtC9j4Ra zacLcHu@>8;*w~D15*FbjBbVehxtj-AUL!d8;WkP05dx8jSS3lsGVtL)FiEzo=|`2I zaF<~hMgp;Huolf#c++AL%k zmJ`2KQBN?Ch+65D;SdXFPlEx!*bWp6@#cPrg;$3yaLp3z00oM@~u;D2`4rM6>7AxDEO? zOFLTgc1Txac36j}Ci9uCwj?IR4pA@!6v8dCOtm;`^l4;kza6J7dH7`{?f5jAJ{t>= zY0^HWOKKcZ#Ei^H%tllR+9*&_@ii3>s;{WSs-Tw2D*%y}c?duBp_Ef&GX=86sD%sn!iJX*w)Bo+A)(hkZxNq~|_LsNj zpAAbBcQ-#Z_+tMTx3>;Lwq@~CFuMRzrcegWJh@2Uiow)F*k@TKV3SP3+?LFc)VAbU zdRr!g0*{k{Skso$uK7>rQGaEzqF-f+xSW2poW$YKCHPr2H@h!hT>A0Fi}Hnfik6<6 znVG3gqpX^oX%v`p!Kn~Fjan2+ui1oY6r+S8NzlV4e3Zo*i+f-U$vk)~LsM)8Tj;a$ z?K6Vz`mAj>Njm$aSmuJMW*epS)MyICX7+q4#kV%IrH@BFnvLb#LXGw-=m=(1)tqMHSQl z)f+VFpu!ZcrNu`jAFfUxF2AQ5E_)J3z}MU0YcK*u^b4AFV6Ew{%>A7N=56Q9+vzgz zUH5iqVHoK+^AZ#nDLD?i3P?w9&~=KC64nUW1QuwT zeB~_6e(b^+07AzYa-`rRgdzba1=gpW(_*$TO@?TaP(mYyO>|8U=!c^;O2aWbTH-ah zi!rJGJNLPh)f#`=Q$FavgEy%E8obawx;=pNLa*c}U2^>Od;ea#<1w!|fz4qb`8LLR zg05EuQz)()Cl#V3%7dV81ZQ1RNwP?R#t~M~5qCjKz>teah;b3(@kPfYMrQxD^sZ$g z3i-Iw7~;Lxk6x&2%cZ6J%VU%wMu&NG=;J5HyL4MDut2a*a)b~IwPK~UxwUHIsCn8X z5z`_qx#rwqWa(DU8n@0^QmbHDk59*tkX^36LY{a+^c&@am+w3ZDGLp$TaB5&<)yNL zoPSi$D0DBsh7t*~UYewNZGSZ4a$HD)-U?&Y&vDf^!8*e_XV-f6m}@O6+*)j~{sY+j zPq7_qv=JQ}VKu+AI)V?P?@bDfKh9YlHv3ECde*kW;XnZTIV1+b8~>b!VVDckVAu{5 zEHc7*zK{K(7Q9Ddw>TsUIgyG3;;1+-R>V0`q3}RN1O#@FO$?<@POp4?SQFDo^=E+T zr3*-?F3@N=EX$u84Blt{6!N%%YlV}?o3*TqI?rRgA&=Zz$u7fuljm_?4YMixQkUUl z6ky9R%wrjxc|_Qy8tk$kH-i1iKHj@K{QXh&A5Jgszc0RC{6Ap-67Ss|``PMj&AxtP z>h??_$SJN6T|TX`&*L0`r6REI$do8@X<%XKifWheFzD793~yzhK&F$MNFqusLLvguf@0`!kOmpW23?9>1j4w%sP(}T zGSeU-SShS4^!4>7@#$OMm=o*OtD0!>2cAnwD$j&+8hpGDa!{ z*U%8duOXv6nT~=yRCJ3EkdB!K=@1%2W71KJ%u$ZzvH7**_7l{OZl89fQ43aDjFxHw zM-!(L5N#q&TGB@5a60`N+V@2+7BhpNK`k1xwW?awvga3z#aq4{LPhe{REvs62eheQuqLa9FL0Xdc5RQsLpd3MIm>;oQg)77tR?WrH^?dbw7?YIew(~ zxu0Hi#X0M023|fpNpdW+Y7nNhm(S;N2*Lcxy)`L)-3WT>5*kGNf$)Kx-MNon{64tU zAB$JXyV76bLj{G$Ks!wb##B{7V^M5j0z;c39+GHO!bL&i zn7c~0^u9d4U%^VLJpZ@4c*S~w$k^|B)L*%_SV`;ijN74^RY14|*3`ax>eT$9EAPB> z>Xdl3Qo)I(Pbv>UCBGUZ<2WEQRO`_gKnaW?RaZ#_0Lp+0aa5Ee62}J=Jjg*Ed^l9= znC7^ses$&0YnM5yuPuGz;KJyeAgI^5c7B6utT)&M%DF=0oA?|Rv&oSt#{a@qOt?4( zP*ku4A{>SP+EM>}8J8Bz2j=}kxS_kuCH*?6IMZ%+?q6TpC*7AWK?N3g1@6St?DW;| z3vkk?M8gC89N!9|!oUnqpaaJ>s&IeRyWdlUigU$ZK{?+^7b)vREx{0A16x?i#wm-*^tl`D=gsS(Hfb=2w=<)TREf)}}{+%&A zFCSLS3vkS=kX>emb@C2aw-NR1tyl3z8X?^lwMJoRoU;}zdAfR)reJd9MM$CDj3SE_ z-w<4%TIJO`SNZqS2WS(~d^nzBUCXVh)>c8-fdtMu-~;a1f&e`hUti-XsS2w%63vIE zXA-wLJe9Ebx6QRJv@sV`ZEZDg=hZ$c48SFHnT7j$5psK6`T%mPIXNZ&1z({ljI34x zBmv?F@P8I3$Zmf3_s}NRGj0}y)!RdjH9kk&Mv`CsC?Yx`3ULd7cm)96;K~a4KhxC$ z+{AT7_wH98OZygEzm+BJdiAj-uoX$RV+2UpU@C*b(-O)jvPq})3~gGEY3q^%vy2Em#C zOX1y*$Q&epieme@E0l&h|7o_qN=%$*!EA6F))~HLIB(ztixDi0sJJ`{@o_}de(|_S zV?vW%-UuzKJJkc~gvtu43Z6)xJRsAu3{tI$tBZh13|Ori@2v~Cu)^@UDUE1~meyD; zVDs}$1yput@r|2uGdW%PAqy2!1?&v!rZ-y|#=KXSe7)IhL{%k}d(DZm)^iA``~=>H z-xrrun_6yc|I7!YWm;?cs)@ov}7@p`;8(Y$I{cG+se-fE`GEbywAGjgipW@X;c;CHev z!4wMGS)17s4Og>OHKGke&*qZTvL12~Qk@u;59)64i2P+FeREY6C3=G%EjUSmLPasB z#u|g72;%R+xgGNqx*=P*UeNdY#`Ww){W@5R#2b2%a#3=iF8{1isqVs!TrQU?SzO$v zd!{s7S>$NjGGfi=^IV>kuAg1Fd>(s`MX78y3)#{EyugPvL2C_$(D)_N+7f7u3wB4z zU<8A|nn7!KRO3n;ad1P)pYH#M%{A&-GCwq$EPga`0-Eik&i3}Y(dMz_q<41lCbTZS z`gm9IOtN$KyHBqlhIgi)+eUw}-|Bqv${*o0EINP1v}~nG-t^|cg*m+oKK+ji*3vUR z)2BaIE$lAm%VQrDZy(ap*8kuxH-y|0f%^37aJ@rLw4!RFIe^{t5go=bMNiZBX(r6g z<+`|Mxg8w$6C>oUkg!6h6=sJwhhGdce2u+kRSjcoal%^?6r>r6o+mAn=qd@!3}&j^ zubD{##r*B`x6D!^=<(P3ME8W7Hn`oh8VpF5NSJd!rr;&&5JlgipoIb+*|!s^*oh)W6l#t=s2D&r zfL?_a$jwRw%=`fj0x_l|v27!|b|VVV?8dT_by!|2#9|L2If!Sk?#4mA4Wd%j{i!LT zpzcUD#ySLAtsvAP#ceujE10$UtIMt|zH;*D$+uwP6g1A8{8I5R_lF0Tt~z$Oe|TN* zg7)^UVJ{??!8`Y!+j{E)tb%11uD*Y14#bY{otYXt_85Ko(5bmcL(lhp^ST{+b)Stq zn9m2f228?2`pLZ>ok)e=}{zUqWfzZ7PTu093dx2o-n%z5Xs{T zq-Ds-Ae$kuf`Uub14JCJ+SQ=SsOErGlv2{9bW8eB;!;wdv{Paz2_z}pP$IkRV6G6$ zUPC!V(CFU4p%NM|>fpjX&-P^p$wgFbx$C)9>T>K_j&#rDVvaaYlPx2NFfzhcfK~#T zs!53urUKk{M$(!Y4Vn`r7f4#gCDn1EC9X((@HL~<+PEMU+jZjV1bqL^iKOSmp@FOS zIxZX^((2%I^uBeW4$b2zUY$8yDE2V+zK`~Gzce$&2FHH=TR8kNU;G&=e?MmAJ=`0V z8}oe~6+jmKAl)FUKxrV*7#b|2G6A1=mV_^+DU4dfFsjQNvZU>^6suJyu_x=7CahLL zl6^r_8Un1*n8uM6#!E)pXcX{g0%NF=Xhu@2Yc<8@c7SVz4KCSk**>%}wg^|RDwwuJ zbDuCE&^v_#0!;~N;j(aBU1PM-d zR%ov2(fbk79U0k@CtR7=bc?K}I|uNaK*gO!_1uh2isKzxn`Vov&8h=JHfYuYAtY>Zn5EwO?aGxF#K8X75`M+yT3SVH6yMrzPkTym@Fbmt=IUj##G*wF+} zL|`Zg&!}+D^PY#^<^hvScG1SU&MqhIGy`h@!eC(|af_FG+uD-d-t;`WDQO7n74CUd zHQ`z^Mt(_NCrpsEkX59gusWntdS9nwQ1Je z6pcCslGqzP6`hJsNAE|um!l9>GOuU;m7#x-0Y?VzW*{?+wAV-)>7$J>)(DM!SCDil z9m5?A)j>LFp`#<|SUy)-kpwDrWbNYlXXX0g!)T5$IlG%q$iD6Qs(0`GMPM>5FsEJhk7b9Hw^5)`q=Ksd`NniUHn*F*Vbx-QSHx{`<-&zh4+x{y)O2KembMj=%4F&v&-tA9s!~wnLma z@voRTv12=SfCQY3gb+-~(%}4n0Ba#+l#PbAfrhovN|Y5%71hK#s8oU`ZE3f$jc5Wg zbxkSC*jA6IWsFTQb)A@C5*1^UR&C9(@7ZC6ZPMP|_wN15A349zuS9LQyz39=>EZA8 zA8cz@AL|PCb+)LpY7|cXeN7M=e=!(_^<~$6wf)Urr$1z`?`m0lq`&iE^N91*OKW^a zU%jzz^>TG5)DJIxywYtH=bzS1mj-uleMWtANx<9vBz^5;bKvn=3 zg8}s06u#+)n7`FeKM6spyBw&CIy~NB$XQZZRUJ}7d~uD>&vdonfM4(z*VV@oc3UJG zPsjy(?+7$N?#Gdu~R$vS|oknve`V+(v62Xn2SnalCDL z9Ly}V((Ctonq^;T|DKZz>hl009{PXX1s|w+h|&CGsL+E-=4JV6hzF5ES-iNiIA08D zRE+y89dTd`yhx^AF^D74>k!A(Tl;>pPp#iKyGOnI(TZ=4js0vJygL5pC*kPh?~ad8 zw%k(RP~UETPYrc;($>Ap)lXaRv}{)2YRJ_+llq@D`x(`OS^ew$yLaI@j&4uhMVEoG zR@Z~)S%J}W#Cq?}l&v&jMd_MC_JOnauBMOJf{E=G&|AQyqd%t9W>bhvWSKB7aSCX} zWji6V)nf5*$8-_i|bXT;`wWtnnTG z7fxF^oaDd-y==8v3~7b3J9;2!>X=?H6iH|SLD1XnoSakk}0g&P!mBTvA9nC-G?+f--FWRND8-tyEI3L0lS4XG)@P*) zhn$l3!h@nO8Ni zSB|QjzA{OlpWlylDl)0L#3-iTY5e^hd&HniI_9?0zBIP`=QN_!XUkg&$DY z;BUZ^(UT&N5fmfN5WiWfp8$axg(&Y70pZr&2mB!MNA^7`zZlZ!ggVga7f_7 zi8Q49O<*!NlHtvjk(_iTcR4nc8$zL%CLK zpG-LfkwYR_2uu#=aw?&t2Q2+RH@g-dO9-jfjV(p zy|yaZaQbZ5j==Z629F#)`z5KnX$01OqpbCy|DTU$mbSJm%dBp0{ah2B_}jCSmh5}u z>Rak>Uam?vg5$U9WO#Y));1f#ycE4x$KGGa5FeypaB{fHVY}^&jh=OZ(+yYMP@+k; z!{Tz6o6JsgX<0>)aghzh&Y(}J#9YF3MJ6lz0g|Z2U}lmLb{WBlD0IZHk2+#GM@+gh2QI)e=(~b&tAlq zg<_Lcj5nJ zMV6}|K@vffu0WiwBYk8u*-mzmJ!C(*n0{5Apsp{~St-ef$Nxw49 zi5{;qkFP20^M#vOe_0}Gmn3IdSLfL7-H#5pw>~$XjD2~Bt)wSQg{8~0cvWp0EH51# z-!VA2W1J0F87+(J>H<~6B=Y+lextd3h>b)d5+Wv-2bQFP=b_5)gGk{m{i?A4KQ{l@ ze#Dwx!%sXGTzD7%i|=ZUjpDk(_c0IKWABdFv!3xj$Lrm-ckT7;W7fvT$xfg=oCa)? z00|0@Hf`FtgqISiVo}`iFeIe48Xl!;8$w!CrANGh5pQHt9iM6{Ac zlpjU4v_sF`UGs2A=f}PG?A$$bb-sJf`M&b3mglMW;xv2uJpKK2J2K7x)c5Gcq-iGi zu;E%Jl}Ux!%I-|V^J*rQ%8>inSn#kP$krQs3%^gA>2z^3KFi+OkFfC?+q{RZ=sp;d zVA+4mWK!c`;Qg>z!ki7lfL~7=*}{BS?M)_4qL(+>A}p@3zkO^b&30Ho-@E<*cY>@I z(onk%tEQfd0>!{lyaZEnlDv3~@(8Bm7C!`L2<1GVdM2=OaUzw3+NIMbS0h<%`%Vph zPGbCPwv9JW_t?P4cwpf%@-U&8c|?>JJ#s_%%O0CZV^$}t2T$2P65~!_GFbHY7Fo+r zA_;}Z5D)(qpdNsaQA?^E;>%9bo{4nH4Gu%$gK`RWlP7EKPrkqK1#NrN_^x%hkq<0> z?B4Ci7NhB2CGd)Pp`z%OmxgG`>$%KLuL2a~NS4c_J37f)=FT3+QRbVU1dqeun6U^@ zHNe=S;5BP0lF*8I9w-s5lB!5VwF;(pJ}><#=Np- zbnV*FHTW`g;>+9c>-KCo_aS=`?uIWoQU4esK;haes!fMb>EoP8z%^8;i9r3b%o!%K z+?wfxL??adIwCN&{SlBecJp%QZw-<2^{vuS76h1PBqHvPRN{1PeVQ2siW4#b-H6=eT(nN{Z zth?_qomx;cwA0!JZCn#tXwsK5c#bzz;#UH`lv@d?#Ow2Vd=XVsR9{NfybW4I#N$;w z9~Zo~;uSt%aIJEK>Zxk=g{kbb zB&WvC^{D1G1q~n8&S?M9=wS`_YxpT`yS7)ORVuDh-&H47>QR@er(pSs$|qGz?N@13 z#fMd_`n;g^T*QN}1u$=j2_@jt!*T*U6Tt+q;Y6w>4B=-iqf$?s$Q2eXv1m!M`J#x$ zjK`4AOmT)XpD5%%WGr~1Oc$2Jz$i15KpPkh-oW&iXTb!(bfze)V0THet#2LX!9nZr z=C_AAAjo`~C^NeP<_fT#@IaSY$!Ph?5?^MlL#(TJm+;OqyiMRBv{`JzY8{Pc%S1#p z8Oc!8x(?3K-#B=>X8A);e{4a9^TY5zT((mN#Z5snbalid3CxexSh>FNJrqR#%{I^sYG zA$}Hg_W+8ZHk3k>)(nAS!Hp~H<%m{;VsebkwPO9TRWVvLL>h~Qt8jbV@>j>>R&(5p z$7_XvVHqsi->7z)$dHW@<8yM=#Y#00Ft=6dYaP{xQysHj&ss^sblBa^w5mnT^3&!0=DTT8sI zh-x~$f+5q&P-C#UiXx{K6<>nUifS_%FwUpbrB^`aGa0aoiUx`x@VLgtQXR;$TF?kk zs33G1V3|l1m|vrtH=9A?bcisd`B~rfcW$!(Z0~&Yd3Z?dM~nX5_vl3OE`JGyk+vTYbUV()HqDh+{4Sy#=2uQtfb>VLKbQ8d?H z#18->jetfsUN#1$=cD5dKC=;Wh|P(U*Uwr{K%93buO!KiHay&dNwce&>;b~EQdq{mp~LTED|K* z8cqzg_eE&VgSaTsu7UcwsW$o%|IvnV0QFyl zdQu6!Ez%KbT)HZ8NRlO8qEelXhM)uafpwn;G%`1FDCHmY)2|Hu?eQ)LSeyzddE=IV zj?V7cwCClnwwd#UCvJ@ze`RvFclX9pY|t>|$vWpCcub?nrGIuYO3|Dls1eMHaeRM!n9(hrJYQ6h*RQPqbpt3UCYkxL+=&8TI|8R?3HNW$Y5RuV0yLJ0e;ZIz2|B=lz7NmYq zc+Fpi4DR#U>tB%1_yfp;nsDun`|M!=9fbwMM8}k&0)RXwZWMQj^c8xFk`4*GrHGW3 zXgvU;ak>zYHC^l1R%zUbc2FZutxqGBR1-o(`XB368{5=%hR=J>x%b=~f5win2}$h4 z7-tQcVYwlNCFBg)R8~MtXljvSU|QBd*;Vz2lYt+=%BD$3Olr{96_r19ArV2U zI{!cwt7eg^sH!H>R%i@1$!fPsjE&`b?>YB|C6IR2v?w3;1)ukMzn17^H8hY|X3?h(e}sepzXb~g0 z&1p%gbYXJxOZ~bNnqq*=X+{1xh;O?mj=NhLXFC9-&fUZB;?|_ePT6OsM4iJ^P#-dz zk-s_3B%7R2Q%tm&ba4|8KzLD3*~pB|gl&^(GvxN+s7|9wwIY?gQ=BQQsoYVl;^wOn zM88gSGRpI0>ouu%cY7?F*E5z#MA6ABlgsZ;{HXkot;( zxO-^*&(3fA#o5yvevm)Arueq_>}wn=Alq*#2+y#h6U8HQq(rFu=&92a7Sn@7w{n zQTr!G*eXK?aMP4{ya^Iis2KJRD5yxY`Kg?|V0ii)+PL}Az`M3pa7Vn5x$ay-B;kga zk2~O{N9ILcFT?o2GEBlSr`fO=0Jt<5F^GagsG;fySU-aGn;#bIXLR@EyA2gcW%c2t z&JnqcoI!22YQM!BXOiraEs0V;gBKW#vKQDGd!KQ_q72tH{lE#2K@}4we+# z;}YpBq9C!7X)aEwMXFvyYN(RJi0@giX{s2(fixO$0+)3=_qhN)3C`RqYVCe}^X|PH zH|>6`uXFj@zRu3RyPNiYXUoQ?_deb!`N98 zUkKGZY>MuZF)FEIOnM{&@FCQ&J5zLYev36__Mktsp$xM}PlOYXNgou$gs4$Lj84*}0A+btARjEM| z9_3VU8qpP$e)-1z>t_N#tTPtmh%abKrV2TZLKcWVuw#EG- zoAi?s-BFxd^^~&3js6?``2N3pJt+dq#K1fuvZ7{ynV$9NlO4LoXC@%?^|(Qw=$5*L_Q`)#v76rZ9~llA@5 z*1RNbwZs$z4U>L#0ZTmJ$?&9B+EWOkYM--=G@OXlnS zL+jI9=wax7bm!W=U(EIeU;X2wOLu;wf5WPlCsbcA!O!6c`eW7Cc9|f&7l;v6eKTy! z>@}d5Ql#}l6+E3P&y*Eip|s);9I4aFA+oSYa->S(=rD0 zhJhJukWyj7AeVY^9{IeZSu@wt>b%OzY?`qQTfrn_1qQmG>Q{}SVJve5(pXXUZFr=^ z-*1g-=CZnpb|_pZ_N~;sGXzCr+fCR+=Ak+0d75!5eHQiqg-EsH8rknFH9+!EL z2v7wm`1b(;WKi*Th9Nm9p!9zN$^l7iCx4cYB8hoG7b+{loT<~>MIB4C0dG3QEbbrd5Rt;sCwj(3SxI_z$IdB>G2J6?v1zxf!Jgkj0Y z7`66YK1z<8&8XxXPf9kd((gDL?Khx#9Ry4z*unob5)H zgvhQ-#H!uC(6ZDCPT_sB5nqUsDNQLtj*^M1Ca$s>Dj^GE;J8VM%(Xj8#|wlB!b){W zaEgbcWrbjKRox8ymSb%@I^v#)Cp`V$zIo{BuXgnHF6X04vNy_(&UJ*k@A65=n}@O5 zA+lfQ1%HAM@#i?d#sAK!MW3X*>GPD|rgtg5Lf@q{)Ef*MScn8}Ff(AwqR5XybON=dEh7 z0>K4rIbH_pLUs_RwLF0z4u)n^RA3(T>+`@pWZ#AFbYJvb{GUC{o|!+pz^*)XiG*q& z$)zIZ*s1hV$4KF})Rh7c9D} z{@5n2JN|y$z4M*z_CLp~{F<;wL%j)JnVl3GtKK$4-OBc*EQZ1X{R4arN zVjqh^#?%4%mde?I8Q-5O?>Ec#K-;SMnSj6e?^?x_z|Mlga}lB1v*uR*knu2~QGc^1&4R6#w((Y>Xu7)&NH~&X5=Gw zmk}R8BoVlrWU!S|5>_!3INOA_@(N)wnpdx`$>eeXi34iPbU}$L-ZI@NIg(FWFr%4F z5ihK_-~v|N#j)yMxbuJPs-oi2!V*HZCF?F1;lF9#eA7IK=in3O zQiy_u z!A_b^BV7%FFyOtZTGL}?mR6AB)&&1lGkl~O>+mYk%q~2&vNbpGH2%)6V{-%U zqfN)#cOTv`XU6V7n8VLB5He1D6|F68AI|5fv zPT}Fgsi|1(``Qv(^29(mGQ~aJufDMU@yIIg)y37ZX{mj9dT)=rcE`MxjmBTfab|dU z$%-GYoLL=9tce|}ifH;n<`?G3>c=4xARB~Q$cAzhM)m0IJID=6Ynsm=_pkx>6GlB+ zP9uUF+mTQ81%1>hl<{gA)co<%85J`~jacTWFpy#x@^qtw)fuwl4|!9t=+-Z%1mo6X zj|$4OOf3tRQ70`!-bkn@0#Y6=2wf{yQ5Uv5k*vz~GPTKE4qYr{)r+x7C;q}3m$xF-nw~BO@rpOwx%7!*(WO&)>J-((;K#5$$u}=hCh%Ne{c_0>cZy6c&pF+t+xuQ=JN4dG%w79s=3Ijn*CtqX<%g=a-of> zh^hvw;vtN0*l>jsRY<8?p+;3|7agEDora*up$S17s4UVrC>d^N7)UX1QS}0~T@tVm z1<+6-FW!%Q%Qi)uK&0e6~tvtah(&De>jzGLn?b>{80>xh5cyeUM? zJNIyBL0#KXtdZ7v9q{!1RHB@pw;JP59oSz|Q$pl&e|b$gbxDSYF+nwC=7mF-x+~mK zH?>o@+v}VJQ1Jl3RF^l*Z2t>G(ckxBxU7_&bTkw{D;GH+6-|0}5|sBouuo01ck|{4 z(C*xs0Ed7~agSL5&5sAn_W>j3KB0qv(T<|2Da?>%EEc{zO*d$d8)>4$AKberxl{z4m<`! z(#m0ldK85M4ROu|=`Q*T&C=_1jEj+7^hYolfgzjCrwF3BgAULe zl!hoiOmQC&24V+FsmP2WXurR*V{9j~AV6_ZNQ=^@+yxe@1Qb%I-~$R)5DJ2Yoi+;O zA!soNaN1~Q7=os&8`uM<4q5nv$F43u2L<2g7a`b;9yQlrt{MW|Ra?#m-4$_4z9yqr zVadhKjTaKEs15%R zA`sUbiVP8=W6T=2TVvoBLbX-Jz zD|fqNk6zpRZbeaP<*^f$bLUp5IZ;H^YEeqW&(CqZMGoWQ{!)1X@4Nyw05~gkeku%` zi_EYeoCC0x1Qv{u(4wENhm^ZO1cc}i6|qJnX(S@r%_vYaQ%W{%2nQIzybb&C!asy0 zganU|8D2LTx+Ph*^JRQBkNC;Z@$$E*ctZrRi7|$>E*sRi2+N@XjygSYzUX{;xxCY1 z6m^vMsVwB-_y_5Kif@e9f@Ep1w!W|DF=&b@&>8QYb2c~vg7H6p;rWHUW$WPmSnJ=n z;Do@nFpa#7}r7#IZ0KWoYFL{K-K41a0mpVd`)l3rucNlw&kahx^ zcBi}jZp7oCS#?C3D=R!xU#f?*nEA}>49XLWMVN0cF?X8}naQP=b(YsG2ywDq4#;Qa zi?WW$R3p~3AS zXV}~w0#Jk+Atv+*q%gysgw2*k{Sm`K3n`_z9WE#7waT1immnntys|x|ETuXHrA(@A zlp;plX>c`Z&xyR7I2(;lp2zjaj*hAosBJ*hKQX$iwe0j08r7`U4sbDhC|}Z4w)*lV z3^FQZV!AEcdi`I6>Ki=0;-d*yeqN2tYy44NOTQXZazdCRTWb{=QhvTzX9tXdDd2Roa5++$) zS4=k49PAwIM1YtHQk2Wv0N6u=S4D79x*^?>(0~Mbr2z@UfGMOu<(EqXmy)`G(ox^j0 z<5-9L17d{uh(Oq3Qp7Bi+x(pSEPIi?!6J?guw^XLvud+cUBhj4tMdO9j3AMOQz%Gy zuSit21rr`YEg7JC1MRKO7`z4ilq0~Of^*6ZI1+9E{ZDK0IL(P2YRUqq1c^e^7>2fF@wt-uw8$&W_=dRR@kPEiGMg?9$8Lv5&{k(Z!3lcUCNfj`+UL z!eHL_ubqDfP`zs#Zd<|ft5(|c?2WM^EI4cNVKljQ9TeD_mVOd$>$~85$A}wy-&|EjKvT|NsLrFzNiPo8+pw1^u;x8!ETe+QFV%~rz z%n>@7rGRG=xRX8hlme}`$*J1LvE7W0Xc^#|yj@o~QHrE*xVWMZkAUI!u162M+MzJe z7{jyTo1Wu+2h}U9lMxDuE4S%(>tGw&ji9kc5A+5MYsUY+vc5nYU!?vSR~8eg_EbXB z*WUH;QCIu$+{zPu6~$2a`e5Jjo^MXq$6}2Gc;$EC(!Hngyg)Q43m5@>DvSy6lmHkO ztYSZ9$5?cV1ztLjPP9a$pU{oyAf?k$ykDP0r5Nzq2MDMqOvWV74eAaB3Ccl1y!0(9 zA8Yrg@v(-f8m@1~FYQ)c7^74xMuk;;b)b8_ZNK?^2wWQ6)O8_#`PY909{Bm$C57=d zq_=p<$useHF*Vrn9beO^qD`ZQiDSY<*of20Rs%1jh36S|8+r``1~QqAumddG#(`x# zxP|_KU>2HyZmgTcelBE!(EVJLYt8S0Wv$+^iWS@LrN5Cnr+1}P$ANb02rXqZO> z+Q1@4FyoZ~TE}q6Zk?SeV9J)_xM%Pdvpklu6}QkkshL5XD~y}9y-yE=M0WOcUX%+#^`%ZS)7E-2u_8v z5RzIjL^>!>2Yu-v-AdR&xgGS`fxXNIY&MblKLVAW21-m5I12a>rC2gD@OOhQUs&BL zD!kQXG-t#zP=<8`|8*cba$SQZti^2{xMp3SsfTLfT0X;N@X2-jkx)mg-e>BgQ;jWt z>kfD1mQU%F>Ho>jgiin7dL2K&==D`TUQ=uFTurUuhvPRs)K>jTIoU}*z>C|&ZTaUPcRD2TQ5*lpU`bkUAdX<4ymd`A^TOj9HlA$9!` zUVmdK^fUqZku)6f_=to<{tw+%9vj7Vhu<+XZ)Vqf&5rjrvp&}A#Re00!5jszZET1! zM=K!M#9&hcib>*w5)x2?8xkN2SJFU3DiTLRKpLf?HsAwLk!nm+6>t>S92E_znkY?V zh4znDq}p+RZ+4;l(XKtmd*ij{_r33Zzwe6|BcF2&>UXBbI1x-EQyg*Trt2)@wvylS zf;07ka{*2mzeq*Nu{F`E{2b#->gS~A-mxWbq;3g?hd&w|eB0>nRa|9L=VRf_g^%av zoO!?Pvu`S20>%em2Yk&LkLt}GAR#37nd1g?Tm;WY&2gi-Bc{U+Sr7U#38Mi-z>2>n zTN9UpBSBIVTooJ+((Awx@>d7;i@09AFOp$AhQGqplZ8)YVJ#R4M%2?J5ASa-e27^8!ATvv1SzukDEkNH3{3dWc@OeN8Xx?Ia=^iyu`~QZPHHZphP7QnBhiKW_e?Ub>MB*S3blS zg6F%`<;$=l2&R3Ud!3g9SK$fB(Gni^Ak#K>Vy?)YosbI6&_l~S>E6K?WQN-$WHttU zqU*P}o-_{6`M|Tc`H3G?JX7|zad@Wj2l990i??bkaIAaPVq8_w^jv9T#r6VqKb|7J zxy?B1eDa5Yayg(}0=+ec#s`oe80fJ&5{lp#J`rvS5M_|x=_g*v%1ho4csamXlw0Wp z=q}Qo`ZPVMH|gzqm)@Jh6|auft(nI20~u@=cLD3Fp;5^{nB zn3}0*1oRyMge&08d+typKe)tqTm^*TkM3~LVMAx^I{X%6@=)eyn3!g~U)rC8yD`|DoX*wAVDExuTAUD?}J{PnJh@ z?@7H+C)GMWQCdCf`V}iy?|!B6#({;0cz*;7 z@vYe(8%=e1s2*%>x_`p>n)&=GbDZ%@#A97*s>gOvm)W2mxoGPE@_>5O=0y^>L%257 z5F*u{l^!xo$0!NwBzEct{>Gg;|iWFzrM1q>fN*F_snaVm0)C5>X-IYi{ClV zEA~y?7xGO6Hkf`3;oG!Z97GJ+v7U^{1S{B11@Kx))t69`kr($(8h;0J~Vgs zRAx_D!!U2<^Hx4zXq#!Pu~A~f5wKs?3MC3yErm*gZ11Phq@9FmJ0+BgOhQ=br-5Xx z&xTLgZrZ-KQAK%NsaNQI1@C4@*a=4cEXE#Z^-MU$`q}RoRipvw6N%oE{w9$kX_iDJ zo5CR45m}stG47{@NfjX{T8^5~AtcJPU`?IEAT2~9_PtsM*j(3=zj@veV0&ka2 zF=hhvn$#|S_;J3tM;sb*?iu1{FQ4Ypd>nUENPmpd?ju`_=BM}J7x%YpO1QP$XYg%2 z#rTZgP4!{-zKRX2jXPpMad1YoS{?_EpT(zW|Od@ z!d;JqIAA?k5IIP335yoBRVA(JH|qB)?N{+Eb&7L zJ4ZutVl2v8C|eldAOHe?9vVt>j@Qb!EO4^3qZ8N(K#V|#xx|S&Zl1W!g|^}}i_dlO z;&Zd%$`9vfc;e0MJ(B+>w~AqUiCMFCpai;A+tP;Z}@6qBl!5Hm8*BMW5J={%`&q34PZNfobcd=OT- zLvv;H+Y{wI5~2%8yZgVl5K#4vtIry{@lVF;qOYI-^^+y7S8l8<+7R3PSkqVknipy) z(8%|1EUBb-ca$&KUtM~>M(5h-IOusX*t&9V zFfu5`<6v-2piHQWv1#KtR@}U1@QVLmyc%ArxvzngqH6@#e$1<3G`Oo>G z`T~s}U$F7bx=Q@}t@wQauBqy0{{uH-vSy>nnh>O%k`DxY!g|h1js~#e_XAyAe%DbK zwTW5TSu?UIMbt@&!$Gh|>(n#Q-UwOAngF(dlDJ#<5@z>;M2^awOgAq1l^t zqsE6W-mIBN+rmfd+_-k>u9|w|9lWZo;$m%dZ_DDDTRt8AsHQaFr`N|`emd-;cO4}| zn|8f>JSRJ4sVQAn$2}@k#m^#?_DUEz1meLr^Jd$8~A57=Fi1y&Y+ zpb=O^M8cblnxqAdR*g18OeSi^L2Xb#lUByb8u%kqtuvZdo2aQ%8*Ob9i3&PxXrpx0 zCORo&Mmwf7vBsJ(iD~1+6rMfz?qX>tnU)#eyLSin-gCZlzVAEuIGsnmc%jAp<$s*s zFmk#=rz;A0`RTfZzfPY{n!u=XOexDzyomgGyoZlJeSJ~uJ-WhwA74oar+>xqkJHh` zbcMIzaw{xofvK(H^s}r_IV+8me&I~W@MRc*QcpL>GgiXhGEPv8x$L#a=pY2aJ=ojz_`$weWlm!VYG}nsgCN;ywn*NoS0o@GZF-uLE*>YRD4)rIJV8 z_uETu@21-6b?cvcVeO)Q~57Z=*$%8e6xqzyW}`YrA|_*oFqWJ1WUp(Mx1SyNB$blqZ@O0 z(!$2WiQCkfd>#m2#at9 zwj#@xwrP9$oCH&;5mEE<4wX5jlPrqC$kw=Csa&rfZA{zq=Egd1lbV~G(}PW*Dm~ZO z6}VuNv(&w*w0WLim7kSjxmRb$VWQQysQ6(#l*#Ezk<<4a-7q9XU>>hm=%dbpy3R1| z9VSagP?8L01Ply@7%1#Hwv+vp$qHLUFBT`KVaST^r70AIF4yGf(5J&sbU@v&7wHRh zc{=Lv=xz)>rlNJxc zlECa0lFdKj%Dl-s)fu)X$@I27r#!--fF@vu3ScV_yLz zU7OAc?xCD00&6)#ILCSNnWV{IK{XE=2vK`&|OUhne5|@A!{6vJkI}5u~+FsIYtxG8?qcxygu0^fEwQAs7V!1)5o;F=*t;=dC1m%Nmz8(}R1%PKEwRz~CVq6k161spvLBf&&rA$(>de zs9+zoy8~_RU)oQ96wbAZ2VbQ`-8=L zH^t5LDALL`yibG4Sc}*|i6B|(W_eCFU=V1BvWFXL&JsXkmvC}MSi&DYP_ioX+q@b*C_YG{{35Y@JI)}e#??g{KUDp`;oZ$bQ z86)?v75CrsKkx5J-=CKPL%OI(ArLu)rsm1@;Jcm=$<_4#;&JY}v#)R@;%CIQHiD(y)uW06?n=^P#D_)BiR{|nzVobm>6z-MH@^?X&&=q2 zdF~UxTGFt%aqodgzQxYEzu8@RI)H!W}%jb)cqmgC`m^_XR$ z-;fG2f_!XO4&- zL0jXD?bZ36>kF5<05{|xyYIMb*nl2st)1Hu^SUo(Zv3Lau1NZ+G3E>6 zw!9B{AM=t`HOBI})ug63c+W--M4%&bD}vF;#0VL;2zZrNgvzddE5d%ZGRQ(oHd_CfS#6;Zf+{=m49 zs<`*F0sol#+4HX_qPf%N!kaI<-SftCjw_=q&!Q}AlJi>Hu&WZ|ysJ>LC!8@7S0INy z#2#bHs3l`w5BE%s&8LRRxYrH$3K@+HyFAP7PPyGIpv)fH$(qtW`J7Z{5*( zzV&}|%(Aq*s_PX^MVXwG8F)Cm5I+Wa7Be=B)kfowF8P7`@!UkpmpgmL!hJPmDc0UO zuc@Z$C+QGU8Ml*~k)-zpC)>1= z%*hFpK_Hnhxn1zm&d+y#zMB#XCv|_+j|FZTKDobS#TpB+0+&UvZh#BrQBmjH1g=MX}IDNymA7hpihcqzo`MkpPSQUOA0 zfeH$=RUx!$w`w1V=t>Dyfk~mzRb8T$ZhcurKA^2t8|n|2ZcqblR86l5>9S zOPt@^|M#~I+Q{=ZTx`>AjW&8yz_mh?a8jTxBK}^)2c?q|X_8)-F2Wuv)k-U*?<7lD znW=oD(0?m9p`?_T6k4WWo4`>~A_}(iYQ~4qs0`GN`aoh{`(-c`&&J)DJkl`+rNrb7 z@-S`A26h1j(L72|zQax<5tIY7u)2D(#`Lf?x1+}_GMUz5F1{ESQO-t-FzYT*T`~>E z^25a4eRn1CF9#>SKXl43m%n@FNUc}VAZ>mO(q9_-xBx|FXV2=l$(aeqm2<6o$(~Gx z{)ybQTtcO&7X9-rThJ-%1+2}51(!ZhfMWsN=v(I7=z~p9a}bX;#=BJgeF1-3@l^#m zt>efgnrApUm&X-yQM=1!)gYPt7iwtrWtxYqFkANdkNSzI?OU20|ktJHT#-I0~R#R0W^W?1tT#Ia{|I@U4)&(oPzO?YHgZto^+GDnwkY z10ah0G>mQmKJSfRuQr#F`muPtp4l1vQ&q{fL%-0V}h-#oLqA+<7GS&%y3go`TLmzI{hdxKBSTl4$+fP=%_{)PjQ zh5B!L5=B_Xz=nPaaz_D1{u=*BoScf_ zLxuP~ierov!Or{mQl9dBv8r7Hm5J=U3NKZ&KWR#Caj9N#I z+pD+a;jX;iJksUw^^-2OS0ydkxXan=Bn}6ri)~oqj&f%?DzHI9VCiAARwS~L0~^DW zVUb>&8NO){ea*Dy0p6Nws)OXG3n}T}oqY1d zibcogFF&ww`}SWyx@bpP&87|UQvBJ%_9wQz@KsxR_qvH=&mBEgbbD7!J_wz$Sv0$*S}o!mdwfzy(2)|xr5120uOO`Bsv-;mZ&>g9HlRNvD2I9 zje5WEj(M$iOVk_j1wr!{l17pZB$XHRV6rkvq$HM-x0CmhG$)y0rf?xn=9X~lI7>C< zxQI`5Tmd$+Q6oGX$Oqh&hmEEMdf0e4sY_ukOcqJuqv5mR^I=Qog0j=2Vd|veNDaF= zhC=4e5$ZF7UK##X$*Xx|UYZrnjYgt0>P;k3P!0xz)E;EKDjZ7Sy86W81c@bZBB3FX zWuAnp;Y~q8i_uy{?G&|y*>Ndqpn4(&898+{#6He$ybkYxQp{flXKdiGDb&GQMtW*Uu^UYpO_^48n_~INvQv^O z$n?5G2v88$ef|XpoA$L9n0H!U+*@~G)4Y}yyTaMdoyEJ7t?7BC{#8dlZYjWo!;+e) zdiZ!~-~8`h?>yY!H8=ItGfj0ywxi~?R3JPNoY<)hwAIAv*H6#%yPWR8^2S>KEb(Ne zztCN_?vE>%yVa$oGmG+0%ikP47@ygdE_|rIAno-F9_`|Xbu+->8^PfpLEmAe>-Hd` zAWiVN$a+W#TyO+iMJ!Ng(?6)V&x4T%yS~l6mrJBPEf3snM_HHdeewVxfZu;+uu0Ei za(2Q!BumL6ip7)Ve#ex76J#KBIl)=_3)~wVIqk=OE^uWs0QgMU8aed{pGCM5WpXx? zvOJgLVET{{-^U;0`}q;x8qByI?9I}($6QI`@oA3Z@wJoePA;J=W7F&+le55%S_CB# z2>GuSlzfz0c4}WsdiNu{o_%#sNxXA?ZF$kG^&4v>zG83oY|BkYg?0xJv3l-8zwuF*}oJ98l-S7(2kO|u->X~lv?LJZ-z z6tA8ws_&X8LH@zU;WwOG4jdEqq>;SK8JI3$m-N4q(f5BQW86e3gV}v}0a}rtMA1nZtvQtnGTV#y zegZsdCQLXRxiS7Fr`L5JuGv%n<7;gzmbSifX4R5DK2+DS`Z10cZf&pBdMRrR|1v1*}+$mALsAr^-u z!~_zw$_8LA$^YTJ>|>j{&Mj5s(C2 zg|s7pfCLD1kTB7;TGtKS>a8QX)`;g5s2l z7GCgnP~4X}=PuBFUTyFHRa-OO=j7#Pk~7$4l4}Fn1?`45rAZulJ-P;xYxlHSM!bt9 zXC<&$^M{weLGn%x0&~Ftw3WXov$K`VD5~(4)v3Yb?Jd|nFa7CM>nk%mx$HHSWX0(% zJ3;Nh)`pc#{pY^`+3VtLpJckHK=%luiO0f5NomYx6fcWq836G8;e+8{go)-r0h7&Z zE3uKfVzE}lii%9Co9S!6;hXZ^_Yt2D>(=vDY#~;AIxZ}rZG@c#SQdqd5qTpr5V;VM z%#kH5GBC-x=NhK%B4 z@^$&1JS&Syxl_gr6Ut;V1w;=DvOQz^)&^FyOkUo(({Z>k*4;1{#;k2Q@j_Ix8s+>2 ziyNBr_ME>J5S>Mf%b97&bj;vXh9L&<(sZm?J3%_H|hJncZIdYpR=^6_E0`P zIav|!*lK`4gu zhBBaBP$aXWWwLwN;m8epLVh-i++0Csc|8;g$>whyh4S4;0hez*jI{ZyxzpLY5e^0` zfluIm{vb9P3c5PsVVC2w_mNQ>slA9<>@c{9-o70|cT6r!V%QxU9&P+#chA^dMIihB`t<+y5HqpqufxNjThj8TISfuJmjLaGd#j2NX( zeT199@e%V*0#C_EfG8@4&N_%kcXouLy%14P8(k@-TBUCch-0`lSiGb1fZ>f zJJX%b5tAL2YF8$SBLBOwWNO*qF?G(7_iR(of#!CyR|%G`I$l6g%f2^Te{gvDb1$yh z)%#f0rml*TiLDjIxbD@4jmrvSkN4TCatdBvvTwuAuD9+yAKlVEb7Ff_TkDE#GoQCE zXA|oZt_ZiqGvG``Sos@8U=oF8-mQ&e=sLm%3LuP-rpksCkjkWSNtF1+4_n#TU9 zMu~$`0)L9VYY|Ki!6cHWNf&v693j%`ljIA6MS==U1pS!2L;g&@B%(%&$XPN%#HZI2 zR7B#WiIBe&MA&K?AViahicO*>AnASh&r(4HcdVBMs%lv=&4kPmx&j)Fx+#9@F8x>L zRv)GxQY_Oj#Y;r$pg4Yn{*_`%b7+bZL^TtZh#|BHF9{?KYEw{gl1lhvB&ALX3KdBV zV#ke%j$W%P-pgAIjI)~)E4RjY{rC-=i>)D@z3lVM$FE7NN)8wkueps95nc^-LY)Bn z1c_p0nxYDXhmTIT_4lK|yED^h675{P-HH~&=7S<8s!PZn}aKds{@f-^^0t6lj9t#4b8OaT!Gd zEra(1R8%O<4>0GX*S7R^dKrs5I9U8#fxo~~6&3a4;48$oG{&!1elnb(317O{O@)&| zKU>9|VtIdcpy)#XPxcks_xCivp4{wgJ$M_$n%Ez7+?we`9}R9_)-`hoU)%JfryhUt zbFLd?*Lz&IlWA@(1HIPaK8(Y-3geW%O@CWIr;q9|d~3i8g`H=`Ho<53aIWA-MY3}g zM03sFy%?&ExeQK79{7pIb4i4}azI(g35CYe-JtCP<6yy&4Fm8ECxH?i$iY`1*nA#N z%;g89$^yP~iP9YTl~`vg%xl2H#EIYY7wssIx(eKtx_s;p{XNN}E1!5K+m43zS2vrt zXtC^-=#%m3nLd1N^WMB*L-n={McxDrSZjUsJ9{DUqm!+F@al!vL^<5}*-zKyqCJ+M zS_Umdww$uyetM9er9^`Zrt&!%17QweoFzH9r?M7-qOpL9Ie>{t0RZ~drSI?_sDp7gdz-dY+@x)>n=Uh0Kx@_lRO}M~; z8S4J}f;iaH@vHS~c6R*i-Dleml+|qCQ5}s|@7PgYN}9h+E#2OA=E`#|TQ{EG)zsS7 z_`(ZKZR?vd3^K^s`)?zHK6`hlI2zL#dxS%UF|!JCY3^V+CX;M}i)2(|Yqa5F8>E!O zaNul7Wm*KFtOV6iaXC>{yJ#d7Q;h9L^+wVVvx#BhLNel}86mS4gfy{#ETAxic!sZ4 z(9fqNltkqdY?WHU$fZmnNu^UcuZ$_8!fR}Ss+o9;q|6SeTdTt4$x}bsO=S_@Cr(_IrK4W~ABgSq!KwY4|xPE!HC1@ke4fbd{Muz;7#f#ULVqq+ZO zyX<3|xbHCjd%OExe6jCrpWos*HZf^JQfeo5UYvyHC=i;YkU~OfD8ZyapcG0SRi(f> zGQ`v&)-Es=m7=P2NGymyieO}=9W>f#Md@If6frS0jja_G+p_MpD`PnO{>~0BWBKEJ zXGfOrx$pORJ`d4~@nkVZzN$jQ%fk2MIhP=F~n1pCZ_w2y$V3H zAgy=GEADveeP%I!prh->`CB(F-0{BjNu{a5b~iRGq{WGXGhp&#_TM_W-{t!g(k2`VS%m)&b13- z0eZC>-wCAIb`e>2=!h&0)7X+}1Xw}c=_dApCZ35hC>pcli)S0_iJd~{1GEM90*|Ke$%`wv* z#aGnFN}t1TrH{AWOlOxT-Nlkc9_JxJhvCk;OkLI?KEKlOKG$IvaT7dSwI)CJOua}I zdkuG1Jd#U6yOn`99I;{8)69fOKm?35(Na?*+6+99Xsz}0#1Bk3rzY8%AV)WL5AA-q zNL98f{uc(T4B*Z6TbD!|%G>ej>_ED-e_ebz{+3Xl6j7b^!S5S6g$NLlZg(X|93MM~ zQi#7U#9o&b%Y$yLxv}C_#FcLo$6%l~$~AA;8}m}9ta8Ngd7)ih8H# zi-iP_!lfu?vMH&UIQWJ?l&);1UVr?VY#;hg%;B z1u~hb!Z59NnpNds9^upzg?O-TH#-Nvmb=~Tc5-q3R;u44%f{~9GXgpshJ63I8MdC0 zEdqzH-S}|PNYTed^o`(fkQ}gKPVgCcQUQQKcxEt%MaN4#g<_}eV$sY(j0*L_#|x=a z=))zJaCs)=K5^Bo8DS%4029Ko#Yh`WgO^TYGIug*023Of0ZeEdG)UaQ$cV!=<%aQp zh%f;xD7Z*)A&kYSnA_ZBOaWo7l}K>7Jss9ben?pLZ3?`L4DqjX_=hL5%>oSZJK2W> z7~&T|DFj_*FDPY0=gbs06?>9!{Coa4PWJL2@Z+3T@h0BE>9c$r-_5CmmvHh|j?eOO z{yD52BHYHA%@PJ-1HO(kCa$347?cuDF%jU6Yz&G+z>K%y{rCtzg;^`vM-Gz_!kCR< zF6~5kOAeAC8_+I_H&fgR9S7zo1MMQ?OZD~&Nk9`<>J>?!Y7Yv$iUhatN01SSg`+s( z21KrRlfBu$WU^llz8Ua1B5$+dTQ%fjc5!P*ya$gU>+a9f2s@3+v3v?)aPK8BC3Z0{ zUKf@F_*wu5rsK~az%7{a-``RQMEFs2n7OUQ)Dh`EyZ#vZZmL!f&Jp2!U@2Ym>IaTf~f;g#XB@YS#s4-Z)zV4l0{@1Uf|;M}q#8d=modq(J?vgtEJ6_4~U znvj8Wu>O?*CnOiRL*R0w5V{;Gm9ntxw1TICc2wl-#Q20W6?3I>QQI<+ zTjYsUZ^rVlflNj)BsTKddRsl2d#!{8Pm9*p)F!Q{6}`?7ZN|#m&kpU_+qq@eV^0k% z>{`1xvvYeS+F1Lf^UteV>tac}YI8Q#wl1lOk-zWw;fCxU`jfE*RdZUOU%c6&zPfpK zeY4l-8J_n5Sk|Y*BXnMJX z8B-KBS-~0@`3`%74KvCtPf%p(18`a{(ARwMafT5S_MynOd(7Z~bW|enHJha`q<=}Y zQ^HbAXe=f@mC7rh^9Gn`q=P~DTfia~{I5sk;XXI_7V2jP1^`^6PVfui7614~_N&2j z7llt`C$O7bymg+`p?qT;m)?g=El2-3S=In$C;HyxInfwH?@f-GGemWa!(pErFSb8z zhpLE=*+y-o$%dO`d_}_J6xR{#fb0#O(2!AlCSMq*=UBlk+3&!o9TyzGaZrap6+b6Fg=?+mx#IqG|8hh5+j`H6>ENfmvp6s zKJbP!)jMDn%LL4}1tsDybAPw2f1n(4d74*@?)})fboo1PJ@rKU;-TiFq$K<8+27w5 zZ+f|=aoyU+L~V2bx`t}ncxd%A>sP=1`qR(KmHVGu^+^Acc}ri}*w($IeZ%_p?(Rp0 z?&uutV(+68RE2(KmMamE_@4ydRPeTf-32!bXoVeLc3gMdaL`M}HG^ELyjDr{*$xm@ zLs2*eLE;M7ZxQYsHeZI#*EE-0Aqm9v%CZ_3l6fLxMRZvHW3c!VWIWc9#YtgJ4XY&e zp*pFOPBpD=Qa@A~Qgt=1(yJ;~1>cD19=Eg;MgTS|f*q`3^wvkvP$sJ5V6c{mIkbU! z%PZ*13FezJh}$YU>+0)h*Q*^bz4ZE;bbHrM|92Z2*Ef_se0)*mU~yS{Oym6xHFV{V z|I2;3$Fy~xVf=f)<8zLUuLlgqTztVX_h9GPU`(MlBcXvUmjZ!6VnUOQm6D6145H?; zENVhXX|}ah+BIF4s9BnzXpNf7NE4-DlS=+5W>L!$X#rNLe@s<`Xs4#qmc;D+4raNi zotjPArdi+%w(xtN?|I+n_wJgX7K;Jh(f#_qXS=u#&(Ow2bryKPH`{u2Swlmpp(r&J z>r7l#KRW>~;{?!>6XBcz^%XTet{u?Q6a0CeYLWbiS|l134}wZVLEsWZ5I{+CB4TTc z8ZLoTmJgN2jGy4uT9rUJz)ns-qY)DXmT`6tM2(vATD2;{!GO1q!1wU0g&q|wP=U%5 zi42XR4nr70@h~48#OHhYg1tMGR>cP+-Wc(0A`!QX<6OX+(+Bi&-yg2`Lh>}(^TBXW z?5|hgm$B9GB|^<+*dMd7E?qmlolI~mab$>O{<|V!<)#QmumfFq8GaAc1P-788a01` zv!fSmzkwt46G{t_W;3n%IgrdiA9{4m!fQhF!KCayjv zO`GP3M&;0eMp@)c7~N=|(I{3S#|ITQo)~vVd{cv)`GrNz8Ro4U7Bq&aZ&O-JcyX+k zj;!B7lnU3;leG35cNBYkN3knAitQ116#iT9D7(LIMPXi?-cDb^`W`EF+T+w|`|naG zt$mO>(WkKv`YE^mF;mY4C1D(=k$WyE++!aQ4gUe^aX&`tIq(S3gDyjznak9meeBE= zw#A{x@J+yDj{}eGe-C(=MehS1%-vW$y^a$u3lsQmxOTtyke4>kh2}hH&+E>k`?C*a z(~j)tv#H>C+d(y%`!e6i?9WtbEU+)-jgJTtV@_8{$81)+dRpKc9X8S5CWPvK)_yMqblz5IaPS9ggm?!>nbW-7*#9(N0D82~+zPlW3P*Q!es!|GXUP*Mp5C=#QDkZLx~`h0B@2`@>EP<=3WbG!`|Vjs zcOMJc?qebQfMEE4At8WABO#m%mvJs=N&mk?Lgt4^$S{LJwy^7A4BAHe2vtC+Th*hY z{|XAhKS7~b$HOcHq&n8fA1Bv|fy@c()SQOH4n*+LbMMh9L_yEp!SN_~OcT-Qh!n){ za27iiM_X_(i$4DN-wvxP(i0ah{%%q_+=w;4Q+;}oZ^hB2g%w-k{~fQ)O-f07IF)jQ z_vpNZ8EJ_r=rem!VP-~=C*S-&ep3#~$9n^D(y7y_2LE|{NH7HO;jG9}oq>3KSLLxu z(^ci?E})w!FAiWok`u8oAF^)RZt%ZR=#;_W0RPZdbO&-=ox-at|@Qr^yEcZjx&v; zo;| zv)AS_s%a^q(-&fm!alN*SaEtcgfn-UV6zDtOlwWF#8hpfi4G%4GYXu}m?T-)L1Y+= z4(Xy&s~QXEIbf%Pr!hE$l}kbJjLXBOu_!KxtbehCo$LtEFpT<8>%>{@lSRLh>`qzO zcjw9F?KLmQ`k?D+SD7KN_Egu7&UG*BxpH(*U1w`6Z0?IShC=TcEDMUO%VU3PZCc(M z%UD}kRz}zrfSa%6o>6oy37dPYu*C}PR%o(O4~$9|THk%;b*VvD)ks%m7w z3NjV;zgP|jN}<0P`m*6yE;!(Vy)Nk0LU%a?ia;!Yeg~Y;!AYF7MVhxup;S=&&vUx4 zj<}tZ^=eJwMGo_ql+dZL!{POMk9aw6ZmG0TqInW% zB@m_EZfJCa+nr;*gqMK-4DWL}@pNfX8FPq6trZYp1QPQ324#kRcPAz#AC9nSD{5_; zh@kqD*P^CT8DCDCu8l^9e2GC5nkN`xznHDc)Q8z%ji;)BO&ypyq}i5WA)6(tlrSg> z;Ayd^iB2UFTnhHP$5!vSamewM*R`nox%RH?w4gq>qask0 zw88(2`n=ywHO+_q`j(GAjD58GJC)3ldj1BvB|1qGNh3cBR~)mPv(O&PYZlsNfksQa zg%(>DSg3$CXIr>LmY+0>Bj!|-CP63aO^GQwy~&!O;lm=&Go;ar3I3&g8~V=Bcp@l@gban6Hx?{dEV9;>5`aqaqWr&DLx-+ru3apz7_O` zUd_yKS0>aZYx7gPe^}Sz2rR2>5iV5}=33`xC#`s1s6MrEw-5>;c*fRNX6}G5*)k7*%30EMez&pwxl`9HUs6hNWpB4^=bVV)Y zx`_j|L;b_q#8d?&20c#<4kh|gKbq;TF5}~Dzm>)o3FV~MA$8q4AShN=-9S;-sqzWF zSYxWpBeomT(#v+3l^a#mWnW*JsGf1q%Ka=^5_v(ckZ0Xvo8|45_tZtiT0>HMt?4z9 zs)Z}&cHXt=drL~Lyxz{N1~aBgA|*oZRbc{JG1`on$l5a?m^u4d4P2^$^EDsT&{t|; zO$@#jgM~3@ih&XnvM_rMHHzvUhEBSSZ@WR87N z?Ts&}UXXj~_V7gRsK(se;*Sx~-$TxRDYNyc0|$a|Gyn$za72auD(q5WmAYA_KUJPm z=t^aivRh$G6_~F;h0>r=!69|NDdJy{Tg)(U$Yx;}^A}%8==(()Jg_vvh|!EyZbNu~e&- z)K|}tlBg{w-^ne-uJagCP*XWF%0wdix@ABGOQUo)Wj;C;`@s%0Zm_<77-`ndt3%e9 zvJ$njP-_<_b00Q-O{g%pK}%&_?qv_d7YiSfQ_?;}@Ny{6y!(s;|7P$s>u1C45)%$E z*u!8WgZmh?GsrMVGO$<~qm04782p*R8|*zsPct~mU^hF!C{A8EHMxQ{umy}=pre#J z)_*vRayY{W_(z;Q!Jp-HABVMk8>ipqu!zH44l_9vbI>?k zWkG)^;l=ysVvux7VdU0@nOUdx^ExG#Dfwm7^2;Z*UX4Df!Fp|n)~&Hl4Q6W@txaQ^ zE`U$)5+QqAt_~*hiQ098c`9$u#!ja={SAq#`6P5LM-|UE&(7RevdBxnM9p+5IXRgd z5Go-i9U&pHK6jmv$w{&TD(Fe^b=t^|p}DS@noCG1!-0gumemY}899(FB(?Ddh&Ozh z`FBsYuu;**`OoA0keGt*$)zNck%2^`;lgH!p<;MNx08AdVxCO!7Jm74sICbMPTBX@ zynWcyPRrPgw6w%x&8#(J1(U6wk*#RtO)DqQOzvb=Q@eed-;LalyOm~KMj9KVh0QFT zv7KZ(6W~cb4QX$`TWkNnJEAsUO^CQzs2~ownGk{5K*JJuP$sAp0-!~M#YyNv(WB#y zh9gm;p-r0VjeRGeC!Hu>c2L-%cB@BJ=BkYw8kP*Ub$X;@Yh%&iAsPA3!Cqi{nOCBQ3FA&3wW^Aj64rHoB0i&Qe846Sd_5>ae z2);lFzdpg6V}aKK7XqUJAs~oRpY$4i8~JY>b$5T)@{SF0C@>a{CkpWqWnl-7n4vV8 z*V~I9(+Z_m35RV(%_wc?x!@FQpg6m34LGrPf?IAXlLSxFVXOd6c zocsI4$>;5Of1J;6S)cnIaN4-jz{jFD`(cy^4;o9LbAbwy^(#Qsqp zg9Zm`954@C`r)8YJS_MQQ&p{u;-Vhfe>x=0v%sqD=rzVW&@42{JJAXGIDvtkYATOW zXkE;+u0AU4oM6A+Sh)PnYd<*t!jCS$)ls;qmrhV?VkGihWWlmO4BWdIZ#=sYT!anc z8MLb|<1D4CD7C10H7NNMe^eAL-Sf}D$&ekjYX8f8wFWnFUEzD~-qm_nmXPGt!?uhi zBn;Tb@?+)L2F$7LZNIwhc0&s~W}Nz(R5e)QVf@9dR!@A!+M z@Cdok3o}5d14d^xZwiA!XSj-oM94(QDXza=4Ypy}ba1G@Ype06x1VPA(CSv>hOq$l zo|{u^Plh=AiOBx2nJ*=}d!`+VSskNyvqOOiq{E8}nT{v6a?`hg>Ts2oo{Zs76Re~g@k1J-N70xK6+t^F&T_*S}w+f`3 zTi7C?;67h|Iw5;Pj{FGEL)TrsNBBPb;Mz{yqWB(_iME}fz)idMVlQ$fY&l|mJ@x7k0eJ(BzbiJ^$2FdUU#PkIuAi72ya5C(MBxB z6k{bZo}d?xTb#beTz@5+JvB9xIZW6%4x!hdCyy^jL$?>} zLSb>SZu4rz#p)D(QLddtL5iNBgJk;Z_`rL~fvO1adU@MBKUrBh2$7^Qf|5BdC9e!5 z(K}H^jj-;(!rOV@^m;^mcBT6h5Bx0WzUhIXicqMcJiIIoWw|zEw5jPrX43dHV?3Y6 zaQO?Nii%LU+_;>^BDDp+y)H{ulZfIC%AQ2^vRW(dTh#Yx<1W zA@Y=NCxwtN6c*Zp{>IRM_E)zCw*gKoy2hI6mH!mF>P!gabz93s{5$q-f<@Ciu~Lc)3;Y;n0{IYwk%^=co>H)L8a z>vBRCWGIWbl|;>2>b!7F$ilbpBd{~I0I#ctosIl1J7F81E!K5v(`%<``)s2&I>%;H zlo&|ngfuGAIg-R!j4(aNtBqlWEW(6^Yz=E+oop|An^^)37)0W2n{lzW^e0_SjfXd- z0MJ3;&hK~dBI=s}2MV~&==Fo^YhdEf}2k~`j}?ow0aYJrbIdDg|by6 z?s9jBd!L&*+^uLN+`o2<4QTdbCrC`HIHDyF4J=^VqW72w?=hd8df)Oo2tNbD+gZrr ziZr<8?$nB?2}?{k_V`@Q#2K?p?1`nnooa$EKZ!xotu{^NMR9tdK+*Wa z(v;vN`K&sY=G34xhg`!)nL;2R2J#`FuU0)pGtpaMVvo&25e_8U>&HAKx`%ngc# zS^}h6c$RA@>|{sqIi7HoCHQr@_8cvcXiX#%`w{~Q@j_xWLC+)-<>fIbiKm}VGAlJx zK5ePOJNyx^d_?#jo5xX{Bz|OK8T$@t#5=!bo=?>kCN!*pz#3R1o&c|&-`>7(VT=i6 zZ9{#1jFLr*YHMRKR9{aBE)wN`_)p5X4)}U|U{sL*`*TKw)ZfHw;zg|23P~xcCKvQY ze;5mg#=?%VP=`j|hk0~I9@OQ*2Oik(f$bj9JaEYcKX$=(7ermK+YYnsE9~@j1!gNN z6xwBhnU*Ikw2Q(lx}4IAlXNYXDvYkxaUqxa)PcsP6Cbq4P>L)dqV}4&*@58 zSv4tjl+sckp7vx}tNGPI;-G7f;!ZL>d1YjJ@_d{Z&G;f7xe~`s?juBAxPJbCO6GE_ zl!eBHX;gECz2g{lt1ebm8FocA8U|;u(&KTfSo(#W&Z+~t(wKHd+ z>C|c1bgI4E_|5)q0&|s33JqWlj18;66p>Ox% z8XRgj(z~)@I{naA`#5)Z?w!%@|F`G-&yFX2 zfK9RqRcT;jA#CwMivq1SXtY7K_-zrjX`xmNo3z0DW+BQ)1%$vrfDQ;wg`VSgn*CPF z2~MJs10imTEmyXQYXyzdvfiuLV>*RJM~Bq)6~JKwiv zhdcdqK7t>imogUymp%)%D7Iwtt3BUqd3;;-x+e04R*|Yc!po(@rDLV{OPT#);KR1?aCj`t=nTcKV$@R9 zQ-m7*E&d+=l>e4rqc3n4AiBfT??GKo&^ql-)MNl7a-$AJ6G%$)t;BBKzTll5%|+Rn zi^(loW3<%jjX07HiIXXoFN*U5nZyfZMo~N#UmB;Y`E5K3@eBFoJRRpl3muGBi)Xog z0+pmX$&=ZFtj#Z%``qGcv0tP`c`l!n>tsa91M;{`%W^W2Yvk&$O$gTrkD2L6dQIZQ zBdU@iwX+f{$k*ZZN~9jMC=uZXZ#-=y!-(~l_dmB^AbLxD6yff|#W2?Zrm zL75|&Z{o0l{c$oFC8kxs9uh{YKh| z=U9@s4kHQ=;Bic2oLKZ<J#M&0IKv40SJMRBY>o>904=>&r`o<#bo)P08IX+ z?ztb=jyyf`!J%(m-+thib*J}U2G{O+qm|3MI@T<%T-J4ZMLZKutm^I|0IWKBYbyEPkG4W*|fN~Y}4ncOGJ?qKw`74$}@5m{)QESwt^1eYiJTRXIwpirn5+64Md z{yiRHEaO7_B(BD%@HzZvj0GNpOdA0ltU;p{Nsbm7ou1+u-7KEcXa%DMfe3jfbG}`) z%QT~7B%58M)sn#|U6@>>(UL-<<;fg{;>{GrV^(Ap4x2d+=bJ?fGFUlaF+WSsP#jsM zfdJ82;5|`Y(jZ24l+&n5Xf|u;Jl3vZ16-0A>Q6{#;fZoq16hPY_f;)ugCdA`c#Ieg zuynpfmdq|h+wwSV338m5)@=On_3S|QeDC77P~__KcYCuJF61roILih*;Ty~MXSNX+pSiw6 zb16Fkt(lwt(#v~JG-uaLPWELtkxIFRXx~V*SEw)yCL7OrV4Vf3OzTZsO!PZ6Y=EtR zegiOqCJ#IZm_!{x7fTJJ} zC|K}M3XW5Q6uL#hdeyBa;O(<8_IW(d;%Ovq-yK z3`b!BBc3!*c8?>>6xk?AkQ9lg1qmbygMr1EGfO;cvS7U}AS)_RYL8;zbWv(h)Csow?933hk zxzMV4h{S}-K`sykYEn7<>+5IIiCR+{RKrjYY%Zg4-tt^M@vHfyJi=M5#2Nsx3MP8S zAgD>is%cz!y6^8($DZyzv8iGeJT>^Mk(QOc2X>ZRaTI2LR^JR?f9i6oxvyb=>6~&$ z5nR1}@W|@HPu4e$UWNC^s#hF%cl%IyL*;|V9*4c?AMs_YkGCHmdvQsGP-D(+rPIVI zC{z&kCAYN-e-hAN1*i_f`T$^`>_bO!ow4~<#Tk7}f_CX`2@R8mRmzvp z=Y$HIPgZ<)(@ArO3IZ<>6|35&O_y3S!XiL|sE&~k4dz1dtU0L!J3rxs` zSa6PMiz~@I!9qgELJ%D!GZNUm^~_5vFC4lnczPdh-cvjIgF}P+i$CzBwaax)4~jqa z__ga*@Zy>F^%1QZ{>dx*K!lCm84K*zW-hsy-;&ND_lQ(wwRI;}>nTS<*(k&QTgl{% zLy|0Zyk{3IL52epl6YxGGBXTsDFhQwQAPFbBaK3A;3{T5BLY^&Rn-|vql7wDx9U{c zvTr-~?!64JYaR5D^k@$~nrrRRWW-v0^bPhrGL(b3$VZuHELH&}gtwU;m?C(a(<(^B zC0J#KBH!Cgf|V6H-o}bn-p1>AX%?&~99EkZqX%muvQ+3WQ(lm)Hp0k3mWV+yE@s4& zA}5F9}fAJ|kQ$mpw@R&jWfT*%3ue^WbsSv!9W?rR0irHW$}7235^ zoHchR=Ye14zGM{Ia$?S$6WU$9`U|v$%qetiHRSWmq2@I77Q(Iqc+CT^iqIxPDFZ($ zf-NFUVqj+kB4x!fH5^>#+~cG!r(z4o8q+(|bW0VqRzX#ou?rpP2gt(N{3&6JJ}tN zY-{TWes$D&()Wi4EBw8`$$vRfzI*ElkFO_HT|Y&;)qDI;?ah1N3`dvNg_Ppbyvq5{ z&fC9qc5hv~^8TJVVJTcGRm_^Y@|~4U^z4kK*pUc!7{r;fU~ifiYRyEi}Md@+?$x`CT-~=fR}5ye)Zjm-}P)Z8!7S12a6Z+uQFw z=Vg3eSmuRY-jBVvy-cg}1BHH}fJsplTCIS^CA*5vmOQiB;>WVL%D~BPxmae19G5%g zZkZJr*+TD#D6w+_e3{+0to8Th^|9{+HWVG?%>F0S7=n zu!s4@{R2lmS~vBZp5O4hD;=%h$!vTEmLEAl04RF~`nhGqOuQEfp*M*@BwSfqgi`@A z{O7S;i0DPahW?T~pPGn=p5yqa&zev}aRQ^$(3xEt{Rq8yL#BY3{1Z|wVy8%n2;zX< zHNiz)jEiJkN=o-AX9|mqY5iOYB;GdVtUr}a;?@~+6{D+E3om&}Qu%x7Nv0`^F3Mv~yiEM$N z5JtPCErf>AOUR5WCdT(NHv2>%UYNpjrs7D-u6yk=JmKtubwYU$Q!l_i0bUXI34Ovn z0Uf(TpaLUUeZ@T)m({9EcBN=f^`Vk-_CS%RryC>N4Z;}OONt^r`=mbUp2R$oP<9*= z6{J8IavRwynI|pxjY@CPiyb3(j^mP z7&aaK5<&nY*niKVp?&Vs>gwgQ+t)X)tYU3djq}&cuC0jH)kQ06wZA<%`+DyBS+h3G zpS5l#XG^b}S5ZGT8l6^OF@8i0kJhj+l0kj}0--b`&tlQKqMc(H1Ewqzz-E@H7pbWjp@I<72$owp>999(b zu0H&Rv5o1~d{Q^62AL$zgp1qxD0e$|>(P(T!{_kD`Gocp*r91au~Cw?mTf>y@{lmO zmPzgofhnYfXtW5z0odb*s2>y`n0$(lT1@Vx?yudCQMV0oyP+)s z#*`n(xCGvTdG0U#KX}TkXxfOyVF_9oiLuy3(d-S6oAe2xl9U6BT2dgGi*43>PT1!J z;tV>eu{dM8Wf~aF4GcQ2ZyGo5Xh=vsZVr_yJH~e-`oth)#{9S8xefO{Hm(!h=u+IF zwC-3ODiWKZlZ9@t4C-JTHoyAUD=$I4_S>`Dw!x=g?Rw?IXBQmT?$yrz*|DbSH1F?k zy|?4~-s8vj(q`K9_D>(b-_fhB?0;d$%j-r*$->cx+-mM7_3-<#9eO5v$Zg?n5{48( zUFOf92(Vj(W^N5fuW)ddALJ>^gCO$oCI6VGk^r9w{}yPCI9psUz9zmUa{X2qmSMUK zOJp#s;9t_}Y9-fjjCRaWft6?QUA@hX0UWt+7pAcl!_Vtn zzhZM8Cw?Tv1Shc_2k6G7jM0V#L-gMCYFWxjv73{?FeJC*5QV z*+~wPi-fz$VsadWk4cG03L)+%rb#9aO_cAfMx_tm>5_!C6y$bXQKV9KQA6;z0?PWq_Z5ddGpAuH9S`jcn6rD2% zZ-?+eXn*L<5N*^k)4e+Rj(%PzOZYapPk{sxwv@=Rmk=aKjiC)a5nbF(0|t_kxf7 zLjJ8x6d40uVl|OkYKt)Sj{3x(pdnC-1W|!fC|C)o5cSQ%Eul)Rad7&0V_rmkM@|#I zP8&tSjOu=#@x}N?>%?!~8Mi(JP2nV7`DT&IxHjX1IArVeQ!`g4 z%lW7t_(BZ+>)F~F*TB(N=-b@+*`b*;_?NH?M!T%Pk{f`-RFpDH8vk9L@Za}xy4nOi|3eB1>ZH(lmdgg2nAs2_s6pI2WIEHZOH zB;|f}l*W;go(z4|w=FbNeoi9r^7I%)LZ!kLp`Ia;ms6>LSB9LF%;hpZZ>Al(vwI%e z^T1;p9(?gYqPI73;E~othgwAqV(hijQNA2GkoPQ-0gs?EDxqoE9E?xf{XQ3|6vnrj z=3###7;nw-<+*4u5on9eohSAm-K0DvrpS>&S^!q>AJnnh5?yIqxb|K z!xWE_KOryJX)=@U&1BM$vOxmlfyq)p9|8ZDS+87%Z9XvHehbxvSKYH~<5E}aw+0O- zUP6xj%3vH!1_1#{}4)P>9lp5g!mCqjt|vB zE!<(c=0r7*z}|r%M}!tA8OGRCAU97&B5EikohCOCCpUgy8w@fIz@R|!DTL}(7IGG- zW2C;GhoB#3NuJi;FJoEAir@{C3k0W17t3sG0qCV&ywd(r6!|cP<;J`YD7-yC%aovK z`Do0O(UXl8loRmdKm1^D&9#|Bc->F#JDS;^|MvZxbIt!ccI>5qZ|*{)aaEYF*t=oy zvuBP4YyIs{KhgWdvyTscz5}1ZAFNKUXfeJQQsJHvtdVvy4U!C~w`UZorB6qk0743t z9z$=cAW&>qWnLgR%o;?9je)jJ&6|~MisE#XuTn|;MNj!w#Y$D|ie+6KIQj(RscGzQr?)m^4bJ5Ch-DY1m4a$>CRep;Dq!U*hgOfu=IQnZR#~S5W zK63<)UAL=~T^<~qfODTIy@j4b$H8KWGZ2=O@Bx(kXIM2=!6{^0R-N_u3Tw%GzW~nu z=eqOxt`!~m?&kc875V(im57w~!9Av>7f}d&{^d~=EL}5I0B_C)UMuMZa(mb?4G~*5 zyEAJT4U&W!#M|0hR3M+24oP*_@&+0WtE#&|rbeRCFou2@p#XHIuP&SpFAmf7;Vt2v z;p5?R;frA@T)>Ubi&_Sb?XY;n@wskgIM=_$fv;w&!=m`9t#w<9LZEQ~ zzB1@10CH`-JBHKQP1ojad>2H1eOc48SZB-pM?0F*+dvk)@Rd>*-i&&yd0_jnVgpD| zM(j-DqL&fD(3>`ss#=XNtJ-al=}fz+NLY+9MU^oyBQXYUGzxl>L{dv(?R}tjUzUL5 zhJm!toGed%=RF#beRVg0XRy&u*h6g|a4rrnnf z3D2{uwrq%@|1n>7v2A2k9KUyFJo&E2)5abrj>lh()4HB<9NXhMb$lny7j1WK5TJ!B zN+hb4_-foj5zC9cpp^g#60NkLKA;rXu7p6!HtCY6JUChwQP>qtd0G&NNUQ`au}Z`) z53Hc(+&g2(34K7@RN}?*BtE}6=YP&U=bnCV4|5tiMlAio?NIK)^;WeVItSMVlptQF zb|6}c@@6y$qQmnbg$>eP1*}0vpq|g#${iL7=J|E}UNc*6hYp+}B7QBrUEymGk;Vn-^DVeT)qh60 z0hVkOks@3K16^X&v_5Oa6>FTB$3%uRaRFj>L(G3+F>AmJl3p-`G7u=hEx9>2>F#AE zFl$BoNR|-I&3&i9z^}gl8+3yBu{+3mmmaEwI24jXfe;CmLpMUA6v82B1G85sOL9Oaa#_A1i;|4x!)BlyKJIu$_(wCA41TqqMW;y& zqd*|Qp#TeLL9M8qu41REss(jUT~Zw?k>r$022Z9`T8gt*uCmlVCT9H(K91jGOe@w5 zY6s>piD1;zgCN!gXT{0sPV985&VqB!DL5(2Nny@C*kQ9!=E0Lfz;OvG@%xOW;g1S$ zqA*dQsgA_kWRt^EGLS4J=aP%bT2f3Ry`>tdb(&fKpvC$G8Q^eWGBL>|CJQG85%*J_4ugEE%Q%vu<(=X3e|@UxcE$Y_6ER*DM?n z%%@$Lj=XVcoFn5)<2T0nam(lQ6&$l(f~<}#S$N) za68D$cdWMOx_-uYB7z)z`)x^Tcl#;B<4>iqzuo6lx}B-7gOS|s^(u;cgXeGiP_UzY z1NS|rk4T$au+p9O>MmGgV;EMr)9h2xQK9dNe|z6j3h%h+x~FgcaT9hs*j?!Q5{t!- zhs8G7R-;kFJ(7eqFPIKyj7&NO&%Lnejqq3i2}@8-uqSz8s(-F*^KrN`=@6Q6egMnJj0 z+y$j?F{P9IUma(Vwg$IqsF+_8&xg+s(TahyU(!qSrRCB}sa6t8tf$%6ne^dtST`#$ z&YH!+OTaO1Q_CqR$<-cLYNP%);+Z1-Hg8g&oS$5tT$!v*ijz$q1ER-4L?>y76um(F zmDO?4g5z7Lw94TzdT6r#v#F)A?J*9GWnEr4d3h4Pn938`10PT>fqWuAm#^ek^0mC^ z%op;@?5iVB&s5AhO>~K|Iwt>TM=!kT*Y=x!EPMmyNQA@5(R5osz||a{&tWN7$Q5(* zxpHnLSIf2Ks&YEFQLnDL(z(U}WExv~@IQAv1I*WL=2wMxQ1TYd(`!VH_&Z&I4U=w| zpy6`38m@)^6n3C+A-oml!_3EhtSIyE4V!ft{5yLaMvo4-KdkE~JOQA=HM~j!E?3H_ zCA4|1s%>cwNh@eYcqnVj+KN`w+O;NowpHghnmQ+tUnHo!1}Pe+LQCq|RDex{t?}*g z-EncFeuw18sp#+%vwGp_lv&M9q)d{|!iOIoX=hW-0!$fx&Rj4nFl7WMJeOhM?3fPI zZfmi|vS7~vD-U;@c}SpVpoTs~obVoL6fcdwJi0l$J1T6{|4uqaPao~44&IakdsLxLPR>oxnPqm>u&R5LFLH08K~POj5eMCKyUx_(cjNC>h6e<_6LAu}u; z`?eaEkGsu$Bv2CkOsuj)xI;ShxDue|bS)x1;2M>`3i1n7{MP;Cp!UNk3EDpinVla4 z)>~v`?2KL9Ptf8{PrwH#oM#F{QQyn+iTqNYqXk}u65p{4{A#RG;ty4Jm?^8XYFuAu zQrb$oz+H)zxTT_)vs`ZO7zgwlMxO-z27k)oq9xzU#L474pq}l?e`z<&w2L!OZm%=t zY~h^L6RjbgL)+&Eiqr-ob`ny8OVAmd*uU*Of-T*# zxQK8OY)@J{))|B%Y}@-6kwYQNI4VWCC^e4m8QZpf{JUTqvurbs{mk#T48u~G3sb}J z0goSC7eCeFvCcdmA~H16W4g|68(fNX^`@Z7Zmz*?BZc%>Yo$Fhf!^o1SWD#knTCDa z;Gkt6qxLb|KK3yCmgx8FT}(Il5~cQ0+dg_Af@Ao&SP^SbIdK+2oG*)6q?1mJ;!->i zUyMUzc%Ssf<6T`Qw)$QLjvUF1r8FjIV-o{X+m{$gJ4l+6rUFxoQx<7zDi{RPbYZ$U zU6~f9DQUWuWK}aG=@toN|CCK zqywZmOZ|+RXrYdQf7=F@jDW!zHrQx_4P$T+z}Z5!n5|@mECpvL-^@- zZ4+tvqi9H=wX2xM@TwIR+FHpZrVRo1-aDH#D%1WLRGk*-?%l=r{k-@6e1G4m9gdeR zOmW8BG<3ciFIeoER3c>6o?hawy)}RB{j|T9_p?u02wk%fw3DRu$>D7GP1Bvr(OH5s zKyU_XI0G{{Gn$q6d)pk6I^chb9cvbAwGg_ivG)`JYPDV+HQlKc z&l3ofK$tZMb2bEFBvM|APSAZA}h%~NZ{^Hkl{*sNJc=G zB`J%d!%mmJI|}(|Fgg$&jE+W`Xth~_vm*0eVO4cTf10H;nJKj~ND}C2N1Z;T4y(AX z{L~b!q69%)h@&{MQT%oiQtvGiu26?5g<3RQKa325h&`)CN%U1lZgZGs$fv;jORjv6!z=i4hX*KGiv9HF6mnICItY(~b>U?Y@ zc0NXdm=GI|O~>dM1f1y-be%uwK+kyc~)h#R@Wo%7Sb3Prjjha%GvnvaAz(){i=MvZd@ z6*M;B2Z}c|W0^5z6pe~uGoWlbC0)kHAw+Dd_2;U^%>VSsNcAE_O%kJU!Iwm-ZLSb`3P7j;+ z8)36+9)3_d@Xgtz4}>>_(P(%)TntZzD`6%K-2^$2Gu&ndUw!yW&)40i$EUl8ouWSI z-rz2}r`$B}*4zW`AvZmW-;n#0NlUFdoi!HZiQ|#{X#9WbRP`X{^6hA5dT}Od2ltPm zc9IeC24?%RcAODEG9&F#gmaQ&rVU=v6jRxv(7duz8B#`Ymr+!v6jqr@-0EkHO>6aT)ZMwW7*+#r;>huy_Z$|^+{jEU+<^u%G2v!F0XndXoSApB*BM%x#wWe z)l#nfUFk}R?9%>*G;I7*pPvuF!{v7ux5DlNzg;*e?O*Z5?Dcu(BaeQNpUhnjY+t`1 zs0^<1IA6)_+j^el&)e+!U60|2hL(ko;P*!G?5m)$GHnXYEqVj(XIaY5iQY!aN0_a1 ziLYa3DKrHj`p9%Q_<@gRDUJyQfUnNt*5$I>Ij>I?In3=bpN}F7XodyCpm4r)2W0_G zn;|Y!s?`pT;&QKWL^VNV1TnHZ33S3 zS_+j5W`Yaof`D&Kz{%@vxw^(uqY0)bIux0j>&87Fr>)3u%wH-A6SpQNCoh(SOZkl@kja#KdaTQn);3Mvy7^8CrxE;N zK5tbt92#EM7HLy_5iu5NtB=G|y~&<>BkA)eds0d37yJ(oi}OpQjbxj8@stT z>uX=~xt5}B(3m?Yc~3NneXj4>-V8Kt8hd-HFfX_7xuseC;b))kH&Nv3!4n6z(SKkE zS7jE^`JdaDzt3LSUH<*epRL{b{q+aNAAV-n2Fx;e0JD4sv)m5mLV1gsky!oIl4Gc`C?f_yK;9r+HGF zO2WI?NSiL!jC84HA8ZV=s31VBG`J!^+1OvR)QWuVk0gF#4KZSnC8op-!7|mqF&q4} zvCi1ul%$uwHo0}}b4B({N( zgtxBJR>DiYY!kGMZUWP_Kxt{TCKMPeOygyAMJbF8CY3@`-o*~GMxV6g?OO=B@X%cn}F31v0DQ2rSWPAkBxx>@JVhG!XsWC4_HcY z8!He6ND1d>)8L#nC!j_ml_Zb!Ky61`pAx!pqt>O5ffzbBV>pI~-E(g*W3L>w)T^t% zzPJ2Cm;h7xQS{f!L?C5v@rmS73QxU9QHI-07ABybnos>R-?%%2b~U4M^_EIAsz+5- zY;~Aj|45*8BAXJtF266!n5k&0-UWn4^4a=<6hWo>-ZCSz+0(9cD|E0^X=Ucj4MZek zt-)aTSb|x%R_pPRg`79|JH^thK@pXxf~`tIb8zWg`mOYx z^u08jOZTVur7@L8>GZ+{1YV&~ERnr@vrxRL?{6NSZjq<4t5mL3(m8t*1+sK((*N6= ze!_g_f2Ke##Gc>}LA5agY+$azO$8~)J7c6h?Mx6$^0p^rr_c^lN~%v38j-*x+O$-X zh-QQZhTGvyyz`vM4VFI|DgXKfhh;=^4At*Bw(*yLUa;dD>XjY8H#^fU+48?oY=`6i z!JnU*Cm3wr=jrW(tL)bDGpL)+m7hbeEkw`|UWzmwc@%we@bu-&8)Ng-<;D@zIWzh@`|tieg4VJ&9_f{&b0oLAv$2#bi&cFZOusaO`A^747J0`%e2I zJJx(D-y+{t-+R74_zaW}8BHOyKD0HoD};AP(C)~Q$j>6!W_EH7L7A3Vvo3FgG$0d& zMD7NQSk~gv0;17nU@R7y(XcL_R|<0Y4yHMb?%^p+dSpWhxO;B<5743Sdk`8xW!H0g0e} z3Lp`inl>aOsiUK*qa$DW>6tH#wABo?zkK|u_a|1>^Oj^yd8$9Pc=MN+m{*)SO>e)x zwEPO{HuWyGlm&FTW%>0b=-tC-p6giNx?^9<7hk*h_FQ@K{1fJ$B|!cA+wP>)jW z$OPF8r#d^y<_x5Ue|9z}Nj7Kb#WbB!7prtv8BiDI7*FSeIA(EQuTtp$+E&fNaD|a_-KfR%b*G|6P4FR_ zId*9_uR|4Hp(-yej6Mw`bGRnl5Iz!SOhzoL?%AmF0zpNv%!ERaoF_;sL_Ad@@@yU@ z!s9+Mf(ndDJskl7;BaLsz2yI(FddCRVcGMIzW+*L)!HlT2!&@B9eIGlifdbj2!*Ms zk8l|%WYJgi>%27T70^}zH3TvN`i~vx#SXN!1Eo8AI_aIvhs@s@%$fufebR`YPNHpb z^iJ$oF?ukDEOq`m+PpftAxh)5$^qqsf;mM}=y^``o9th*G?$gK`?A=4S%R!@`9Gf--Q08~lXMR%GB}3&V$3C|S^61JGHW!LTXChH3@RP8a z6T~Gd3EN+&c1y5uxD;Wj9=f7HP6RSe>wZv4SMoCGs-~m5s4{pv5ueyb5Sm*nd$&Dy3K_oE>^xg@#g(Zm;Pbr)v_q+hnPDv=8mk=fK zk}TyV#wdY}Zzcs2LD%&AegVWu>t9ahX#!-sRe;W!Na&@rI#r-Zv0Ct_jo7QV{8Z|J zvgstzTB)OSADp#hhp;fz>R;A*u2tl#PVEo|2hW{99}ocSp1bDnJXG6g9OjFOW8-Y4 zZF=`2<2TFS+gtvzr4Ov3o}%zgCQ7L&e2EGG!+C(uLKtqtsDta}R&t{pvqeEEWs&lT zf<;MI1jPXrgM+(9a~_)JBphT#hmByn5R4mhM%s88fU`hxo`0ha*=&(@0%8S0&;(2g zbA(O8fWR2Bz&ZcTeAWLpao+L!KF^)+d}qgJoAdk2`7KW3m?U<9W<_wNyrqy5T3$js zLl~?h0TK+14JZk;5!nJ=H9A^A1IDJU9a;;l*%n{}Z!(ltWlSoC3J{>G2s8%cmr2u# z&AxXDQ8(=mn0&tHIr#_Q^ZkB5Zvo9|io}Q~m1(?6?vgJFC9wdA5t~AMdKW~SA4KX` z;+4Ff4g^gw?0>q^SiP#TY8I4S!NCxf5RyQwTq@`#9Y)CYOP8<@BPb~qb&SYo5GAEE zdVlh34O3bw=MYFuh86ASqreMz^<01--G9ZuM zGeR#uoClM~Jq}r!USQU>mFLjqxC*0iyT}HABLZxZfcgp)L3%`h+ z-E78j{eF2a+km_mW3tSB?Y&&X>IU*e!^{S9c`Q_oh08VY_2jA~X^XCik_G;k{DgP9 zoy6Qkn<@D;bSp&o8h1^yhE5302oWx%gc2cI6RHXvRUxb%RF9~qRaRB&eKsYm%u$vo z-O3T=w1N%s1?;?7VH3haoq!5FR;Uk{Y_5c&DO#|mQHjUvnO3Hqp~L=WoKPD|+z^2g zRgo~FEY^!u5DhO=!s=be)LR^NF{r6nZ`70XOL9hN$m@|or=%Q=@=-3AKnCK6J&PUL z#wfIlH*7x2jeGdnL({4iKluHoUxd=%>pFO%Yb$$1$~?XPZl(e@9KF)B?)b!6 zKi)Tc(v;TSZ?tF|ez<8z^YlGi>6>TU-*{$X%c5hxxrxlFalhX2v)%XKS+T8W#m^6{ z>h0-3bS#H!kq$yCMY!yPw!n%2c~M=dk`pS18JHJe9w#&6Ft>VXHJM%w zxzy?unVEuUz!$dkTF7b31q;!1AhL=hy`H=#FOKDmTvgf_V%^i?E_nv<@4>>!T6&O zxKrFGOe1t?Q(UT}w$UZy?X3*(8Fz*p&p@%^Vgq3t#x#(d+9 zx;<%6sw(O8*4E-~h*oJm-r6zVT94P~wk9oRzA9y|_3{!M7+cSSB&KVt8LurcMyd>@ zV$n$0#DmzGhMDQM^iY~w(t$KFrzIv;BPJ|W)nN(3Qk~Q*9g$d@G)Gz@bxW))IUr%f zAW2-z_=F>tv{ojV5Xw+UQ-j{2M|XZ>RvNv*;D{atNA)O(dK3gECdc9Q zUwG>bRr|(x7`MN-!jLl{gRpLY`D$hQU#$sp2uM}U>sv}E;XxT$>!2KlS?@RXk!2&& zRyqfr4q5?vuY-cSvJ?3sbDsw<1_v;#~EVuT+FDn=17rm0SsI~y4h7oLl_ zMmVT*mh-}z&F^*&c61CU-=x@5erS2BrDt8+RGBMVfjAVHhp{POZqa_ zaNwuqf8>aFxi>`+MEFdEeP-BX9xxNZ z49`blDqg{PK{CXiW62%%pDZ!4E_MUEon?JyaHEnh4AEubbz#yPh8Lroqogwmo+!AT zvR>s{@LR2RHDHnyyUC{{7&z)Gxc-8{q_V>5^e5!g)(cj`SXC>rCeVrIaU$gN^2L1N z<5EGFDV^3I*DXIv-V8b)eVfliOc~*E-J1?iQAD344$0i$ZWL>F+RBbK7hnEnS6#BR za}e&nzq0&!S(%`1Y2M;#mZ8+*q;xw36{qcL#$_i$z7tIxI1n>tail!%quhLs91 z31GBvR}7X$U`AwNgpBvWj4>eDpR|)E2|6i=eusYS*}$&}!O_so5DA5>QAPGxQ?3eA zG{8zTyx;N?-upW*5xjM1amWqj@5&>E>ZYg8Fg=eHlZ7fCXHcI(|L_cIID-#x29s@; z?^=k*atRw6MWN50zn3W@_7I0n zx%_`lYO!eGv&lf_y~)^xPYc6#=yCB!N}q$6iHSv-B$j&ogcugt#N(vm8pE08M@Mzr zR4CuRa<%+9Oq-uNbQ?~dO_I*Uhj8Ndq13#Gr9QT0!JY&2r@^hOBwz!-Rj@Nuzi4 z_wIpSF~hLS>>tegKELOAe$VsvzSlQz-LZ)x-v^0PwOnFCOcnDRqtdH^)&o@ViE0ph zQ?M&cq5-5v)4?8*P6c> zGt5_1)KoKoB^7z6sc_`A%~5o&zt+v=$dmv(#{EdpP&cJo2UmQlq+V?=cCOoedFz{_ z+u!}*(+f>)gRiW5JiYGaW9H zfNa~R`J0r76z7j9x;HZUI8I+skuuPbXprkPlX<7psxa4Nni@)tU_l?|tm#lpK_=?6%`>~^KElm$VuJ8NliYIsNJ%u`6`tpx) z(dT#3mS1f9VSm>f!`%mZX)5>R*5Y2sbfwI0qcW;Fiqb?^^`y=ctEjjMtHh8?t*~p> zMM0;7Dfb5S#deo&ghL^-J0{ZfZOFlJdL)FHkQN#U4TrcRArxX%O~q=m19B|XJu@-E z7X3>Ww#^uY$P7i9Ib14`Eq_-Z4Kt|))H8PsO}wdE+#6`9T^|mOkAJ-Yjg2QydYV35 zY5Codvn;w{^WiMI^$-H~)x$ko(?e95t+}<9LDnqP}+Y({HQN|FjG zaZ=S8;u>+2I4q8cHc?SLq$Z*1N$U}UGGvlqh9HLUL?f(=kl129!vNnk7y~4PVP=Hk z2+OoeI{X)ks;b1(Vq-LB#Egonbd08jE3xs|WNbFZsWB7->Q&5D!ue{*-=>b(0~wPl zzh^t__iI4N=^GuHo6~%rmDC}pO@?OX+eL-G0C1>{Vc_Fq;+!*<=1WJOoUpFK3(092 zEEvaUxcb@Lm5bY+Ur-(IeCIIqO7u?loz~TZ&>o>lo@igX^x>%fzIm#B!^p!5c^9o7 zfOdI*8Z92~+Pi1V5LC*&TWH?v2aiDD5dl>9YgzJ!(*#t0&K*vR)Is}zkLK5yh|Y^+ z;%$-rtB7RT&+|km7DZhWypkmFgy??R>-WnrUE*b4kdY{t%HNmSdKoR2k)7Qq92Nd1 zupB%vCm?c9K%D}b6p$uV3wV>zD+~xM1L>O+c)6JKNTStm5RcQ(6K`6&F#DN7M`xEL z37?V9OaGDBGZJ!0veYQCl8pRB5@p^hFxJI7{3 zKgcFT@)ZK5I8-ppRPNN?(NXmFC%G=PzNf%hu(~U^(Q@hjWi*o8k)37G?ETzYbQEUd zVGP5~Gj8Ifu+po!bz%@oX#xqGDge(AM2OEzxd_+E?L(k;-Hc!vqh~* z;*Xf8jFsp{1YM1why=@^n&_*OXgoQa#B7p@^KmWCIxKNX4;TS@4UfZ>HUa|yJP;TO zOa<6LptdF-Jmz#*3xl}X2|E|=#NUJgUN-~E#ElH~Ju%fWr@Tk*nWjr1q6sjKw5cyT z79Uj|1Z+V_S@#!!@U%mCtZHfQ{^;eiOM;`Pxz+TN<(-978roR;L?oDdKYJYi zYG$bYj1Ugy{i0RC(i;N13a*f-U?ibKO_X#&%7D8u2Xi!DP zYD~oxrhr!o+GJ~#NOWiFL?_Y7D4vL-iRe@mM@s{u&Pa%nc&S>Nl4c}}U7|*my-q>c zK5b5zfvg8BV?o~RFG_85I6^;rB({Z{l=1>pSFVAYmM>{_7n;9v^p!P_H@6MGy0Qfae*S!>=IR5G zn?q33EM`aRJwE(w|G;x6Pj~G<)D13h4-&-~;H)w)7+TCyZ&_v;vv3^e<)R$RGC@A5 z1zDHHqZmcR0(hQt@o9zPS_ywx6c*6*TkW!QB)=9h6Xqq|q1g2a8O`Kjo03_X=C15@ zDz7n`{!LERK12q9>Fhu~^ReO!B85pUJhrs8^~HE-LjedIPJg?1Bgeg2+SmErTz5f` z;9{pPQkTi(*0S%y4V#eNXxSM+JL{39!Pl^^;Z_5;qZa+361`u3ryTDqM^4?ZW5?s(3S-)?b*NG3twibiJbNSB@!fC~Ps%X_@kr!u2VG%6Wxdu53|u z0w_F5bSL&ESm*!9F8AOjt}Bf1-Ic7BguJ_2Nqa4=)(^cc%PU)c8)IR>V`FTG6o@_G zkT8^oNdQ3!r8Eg4fO#YhFvP$lDHKSc&rs4*XlIH$6^3?(x=cN^p+o3v0&UQQnWlt? zEqd-sb{t15&8~J1#Q{-gWJJ5no2%q#UWqrH z=dFVtt(zM4dg7iqOhk_eNnBv1t@W@Dqz1>3fgo9GSla_2q_mYPDiR3ugP0xDs7#%2wZ!74C?YBkZzbt#7p#|U8MgfB0qx&*x8C@&Ly33{YID`_}3_5}szEXvR?8khi0XU;sCfXjBz zD%}+{)%uPpW0swMRXaO>2z*nxq8#w*s^#)+`QzQ_;vT-joquG*moi{<+ltlm zUZ8i9B;Y{dN1$*%btS&%mL2)s&F&p;#?LqNbeD)uV(kvi3s6^XyCTTgu2@~7)nx`(8|fsMl_Ql!?(~ z<^~5Zkc-HRzKSIX!8;}qt*QwjGLODQhpxrQN=b{e9m(~a^LYHt(&~3!163@c(F(Y6J`*^{|^8)!x zUzdEE4kb@iZ6hh zGKAhB|Ir}9Xp(fJNiFC2ix6w07q4g(ov9>{gS2fTF-eBPN|fU%gziOEu|j5ef&`;1 zePVrAx|3Nt`sAiw^vQ((zMo-p_HP{dr;+TRvtj>?!sMu-TB%h0;J1X`0?mq8qz$Ls z=$qc1Ub;Di7KE0C?uD3~kR#*|T?^@qx6BCV26Jn2ne!ZyrUj-|Ph+ZNnj0!Yi;GZE zpsj9U9bG54;4XX{-^aQ)@J&(}fxH&fVrrJBn4_#okpqSz-&)TW&t4DXY4UQs$~W>1 z&qhrqT zbFZ%QwCB>-nu4;$EUEtJyhz{&4aCD1@K6R13t-E55I1{xr@O*~ZBj+axyP5|GXNuv zYPU+SR3E6cF^uko(dPkV^rLnDt$vy@^5_MA2TxlBWLRL?Vd=MgY|+sctA)prs4Qh&(N15uLddIpvLU*M8J5sYDALSXdTv|q z^4j|0-dBIs+tmEsIq7%m=e&IotZ9Gq!0ZP7ZvZX4-H)1x9fyfcKi<>Yy!qwtp`P8d z_TM?$_QXi^hpS)T+57I%HGNxF5|7%zqY>y8KaBi8ou?uo=1j0B6K%|<(5?{8a_A{D zGMSxb+OW~t=RD|S3{J@za5CmiZp1i6Cm7--oxvIM4K%MaC`N@OR4Z&+Yn3g^AC

    zl#2W^egm3-!7``jtDWj%b&aa$)W<4qP*p*ejpnc{7@$`aZ+^gQFH^BM-_mI5v@jM3 zvU2_-K;TqTBR2@Lr+e0=62KW?1ti^=8`@fdNT@xnb;5WTj7J*DFMy0fN5+nG#uzjyfE zob%>^(IJ`x`m)M>z}N2e%u4ole6W6dCK(U^i@_YGLFK+=D}=a5*{_DfshBUNYB%tnEs#X zS1X3I$_W?i7jEr(Zt3SB(G@i$dY(JAWyi?w#~(R?b6u}29`4BGmgbU!vH&T7)_?hH z{?^q?Th{irEfvpHkkRny&adqL@oQ^4_8wces(d9lmQ1%RYimsSdsuJjQP(Khdlzkt zl}|E=SU$`_#_83XqU#)Ly#3)A07F>Ujln246KwFj>>)2_@T$zl48a+Dh6tp9L$pCi zH!9!*x71~+tU^6b4?xHTRe?7!2o0jHP!~KB8HtccM8{|G+3cHHlD#LGHwxkiSb>d!hUUowplRI^!WFx& z3C(QB|XImn^ zv154gD_uA@yy>ysBRDy-c4dCmoOa<&v7T)i<^HB#8wc>wnslP2{r<5aPIo|qOnT%Yc}AvkP6Xb@e94$($99nyL-3m{z<)*X#!4EVsfHFe(6e^0UHL6}l$C#{Dxl z_in?~TNSk;-ga~Ukol~JQ|X=3Stf7$R(Dp|g#UK>n=ie1{2x_m!|R@X=A+UfEqIMG zeOtpku-B1re9l4Ro<<?8ySy2_cMEwZxeMY3Bs0#+eMsHBZ`C}9461t8k58g{m6x9Kx#h4rHPnA?lK#?RY z!5UO_`06we`vb<^>DgsrX%MF8Wn05G1+I%#{e_Q*ip9#lu!Vv3`TtCIrKdgRPw+54@%sSoDwn^F z@a-31AG#s;MbSPrZVpd{iLKt<3=+k~xkipsz6SdYG#J5wpv4CHnMZ719;c`vBtUAX z97U$MWVggsk0h~sc32lA2@+AI8Hq?zJf>~UAY;&FKa;!?leG$CN>?kp!)T+Od@g~3 zzq{rS-&AdlaQ+)&*(Oja0!T}rz>kS)fgAX2&S$93lE0~` zaRF)cbsD0b-@5Y7dI;m=T=b`_+U{F4-n+m(7yW2_PxF_X2FcOV*x}4@GWGpKq%cb- zjM^c|c;TFB^sjRO^sO<$siar{KbS}>=BO`vCVDM;Bl>C7;H^#6l4vTKibtZZNF=&b z|E!*H`hY&Ir@Y=p`LGlwj&Lx-r?8kx*`kId{z5XIt~a>6!4W!2p@;?hZCJ3$wycfX z_-HC}Jn~Y6D3N6mqKmj9#Pn3;0NAUOPfAJRpvhnisoEwvIh(?`e|McL=CtVJtxoLB z6qzr+1+rV^6WbSiff?PPy-+}{#evdQNIk8k-)<;g(DLPlX?TUB0IxWp7t#lIO|pT5 zQpH_57_@jtoNunGY$J>C(MG@|+!%Iaoz7eS=+CEj64N}Re(hYk`iUd!IyZEkDdMi< zP32DdaQQHP_2R4VuW1WaS#?ENxoCM?>-t^F-6!z|!_lY8ha^zihw44`OQz2u z3V8);Ezc0R2QCZ*_vyFmiJr$6%8B^jA|CQ!RS}R?uYIM1r z7C+#dxk3vyYBot$2WZ67d%s#pp6ER6^S(X^UChN4!_|N{96t-J^sI}S8Hq(*BQO{&g{(W z%R9TX@0Zu>wby2Cukns;)(+YU#o$L1n*=))uuW@2z;+6TipN$ZPEb*hK*3FUC`5&n zmJmo}zyxe)>Q#kki+DH^po$+PQlu(BN^w&uO4O{~@7^^wZThpaJTn@vXTCk>`_6X` z5;c*uirwOfI3e;Pv*m+x`+%;2>~$u4Pqsj8PWC8fF>1h{4n9-}tumlRPAb1jzuVFJ z&hwrBEZY%&?arT$dB`W}0|Pz3{iY-Kxovp*@6&%9rMo?We@Tu2J^>WPLCr`gGK3<- z&mv<0vpi~Xsb<a0$8@G9VFM0gHIEl+{DKq*8v1DbXnh0})Inwy++<5qXKdjv8S?~a3jK1y8coCnpG zq=q>8Kd_|>Hfdvj?dn_z_MG)7?AEPR6e!jVUb_pBcI3KM5TiA-nWu_Vp<8pEdSp8w z9vIqlb!9{UK$~2%!kTP8*0$!$)|SqVRm;`bBJLh#VAYZXAM{q0CfqBQdsg%u{<@pKm%v%u02d2N;P6HaAM)cqf7(w(uggpH z8x`E4;L{d-)`)i*_8HzcaC$fpgK$P593m$OG36C_G+&#KY;j}W4XGz(-E4i;`lj`c z^;4@TD{i0F<`qp2R6zMK0UxC~RnSbIDwtPbD=Q8zKtcB)?Kf^*>C=2CeCK>T0u9mm z+)fTLmb`gR^-VMZ-vR z#7Se={quO|R+?9ylJ!BL)9OmFD|XXW1-6>Grj*TS;H^Nqr<_U{AKsT&&I=r4GoLFp8od7O)FP7ZQH!;g;g!HYP1@r zEs9;^NSc`%ja8_A7_89+Rg~T@v;sh5yR=t2EOCMthj_o;AMtbbMWh;xZ80_$+vyM~ zleW)nQxIBgyd^LafP@q9eas|11YC*KP!*S|NR-8c;tBDbs56VO5Ez4(?Dk->CqlWq z7z`r80~e!_h+w9C1S}{Yyee1Uin`Hp@cTfPhRy+RIs+1OpAc|zLbxM57G?y#Rp=H7 z5@exL;64Md3<5^UdPMyUoM6kca7qtw0)nnR@o z@WWOEO`l}-3yp(cMYk60uP-U92z%Rnjphn<`^%-9(aaB;kwB8 z^kO&0;-b=lUCND``rcJ)9b#-e!1r{2hTYJk8H_fcEoS3nLmov^1oiQNf(I@*kZFwb zUdWS8?E53Vh>SS7&NyT|2sYM;r;NDU_^Oc@jcyk+t+4sJIy1L#W;5660!(GH8PRM$uYw$n;Uls4Vni&# zhoh&WM2g^@kvAgb8y`;ku)*bZ)w!;?Zn$*%x8*bP6`A7@hfO+INtsJVSv43OmI>DO z@U>a%=g)lc5p381i;~#E8K8Pp@#~kjE;c=_*B9nCSR4{Xh^Qla|Y)e5=|WP z80p3_pat+5pGWg(>SEQSne3{?gNu*{C*gOoGB6e+4Vgae&`)p5DiTcb=Ta7pTTG5WK&AmrI z|6%;)m!^N+-_+5*yl?s+?aOGN_2LKQL){gmpdV=t!3|>cl?fltJC#R{lGB7(h?&Zd zDWAs{{xMH@UXNioF52`6TLI2XTfiL>WTcm^5)Mh2`oTwcJF^)CiH1d5>Ihk~q1`o` zQX$Xel3Id%m~Mal{TfgD$nG+G8s@XNXGd)x{^N9VWMjMo?}Mj%;BJX-4AjYjI!1ZH zj3|*_td~sGN|440d3<-i@-ew!XaYOdZ5uUn*^BI$;n%C zc5?Fe!b$}iU74Ly+VBt4>E`BiOG~<0SGGUfH$8?Me}K!T@8%Fg5BbwjP1QI;P>AEZ zMd)~Z1-u}E*QZi*uX{w_!=4SG-$wuEXEBuc_rNqYUqA*-A#&?@i!ffhclD)8!%-BggN>l7ehwNCk580>e zoPEfF9R(2bAYV*orX&1B?B~f^??rRdPY>{d(TkrUKI=yzS zt}eBut4`a{KAc{A|3ILkz+c*OH*fUo*=+)i7O14{r{z$?QHar=-L9#4|WX{0J zvcnEl1upO!*!aw-Xi)j90IZFA>hm%!=2j|(m}Ie{K#pix4_RUVv4N`#8QFot^7WN* zSD`Cm5mV_D{x{iW9~{M9#_`|F-tKMgF1xwi+uOa|`{jjjiI-mXE(sHwO(}#(HCWq9 zaNeL1`CW7MmmfRTKkuFI?bT|VR0ZA zpWki{Tbjx3%kPp)e&2nb@ALVdem3{8Z<&}g=2g-6@BXml9T;Ym3uVs(X)yc6PNkl7 z4RcH*?=aqtNgQJs#+VJtiO?1JS3*#|I01hqL2z-&5Kh4d{b6#C{h8=ZF2lWnW}=VI zplaYGaHKj2BvvI{e4^l^p);XNA^PLWS1QTox{*3EQisKJHtAHtu#C~rshXtU=n!FP z280U3o)QVDG)P}o45OGFTNtD6Sik`wFN9qQbt598ngCmPP|{*rMx*mHTDw-zR%yF5 z7rR`gJ*{z)eL?+5vovC@)(&W-QNu`UgnKGzT78;n`wbfioa03HJ$pLH&IvXTUDnhD z-pDk7A7Kpi*#}Tzn|=Tq&$PWCE}8;t9#%W;$5w&y@?jw&cX%)9YragHL{O z*H7yo-|<&`YO^`VySywH?85)B^hMW;1^mZ@D{|i~?j%>1KDBYQ<(liz$T$kYXtyrgA_bjS5CeBivI#QR1=B ziP>bUIA)6pHZV5E)NIdYK}tu-YG=a0>t2_Adf0H8!396q*8oBd=A-Fp?JnZ=Yd3BB z7yh_7`};}dIzCX$fMR|4DX7P(OXyAV8q{M9orGuxyl#S0i#!`Vq{fr?0NStMUgC~% zXE_&@g}6W@Atn$%3d`Zf@QE-NMG;)b2bpBX)Q6*0vrL2UVAxg(D}FfpKws805OggM z0v;hUMVf9qjHH3a(8*G_hT|I6oNP2O?w|*&Dd^$}8#yOz+;A|%R;yr>UaC|}pDi$_ zoU{OWZOBSx&sWudI|mh|n3C{h_luXS1eheFn`;9f7r*jMvt3Q0PI9Y%-LI05IUP#p z+KL=Jg91|@unrACqOlsH3G-{*dXCg_tsGe_t`J`mkBMhRSI}GQC2p_oC4N4DVS6Bn z%p~zUlK4FxUXVhCm55`X%(Y;!@F0S?+W5dAhE!2*GlqoY5|*ZwU=s9BjDJo623iBX zc0Eix?!A^OZGGB->D;v0U!P(s46W>b0rt*IrcyiZos)YO-?TN@K(5$ZFIIQyxw4Y) z9RuGJ(O#>*QNWx~B@j+f;Y8;NPY9%)U=K+X;vtoAqFtniK$zp%*oB@UD#iqU!OgP~ zh*?I1qX2vLTQSrQ-LndDtJxdFJYjVp_Hhslhk?FI`@Rs8|I#}Un!LauYs}x;x=l`R z+Xz;UdfCR|1)j=wivh5X4Hrj;hVdhe6e=cffz#e16>M^|YSzErP{BSvS!GWyxZSfuU(ZRAEWk<`X2nSNe zAA?~?ju7Gvu^m@PdQ30qtMpyE%b>a*RXfVyF?*-yKij^H3>rQO^MgS`Ww~y4BOC`r zUx;>t@7SzP-4y8|CP& zP3exo17q@n%}|ii7?Q*H0L!8!CoU1VCe%0pF1QlYgifv>>@FPXd30s()}JkY{26ak zb5Bo8lNTQ<_PDaGOP97hKo{IuT>ssruU;%HT)t%4;^wxt=EX~wcPv=Y!8Av|Bk#IK zftA82h2F7RYf^Y#66a%2#K`2{mRsX&5+ zC@jpJ^w=hQ3K@PEQ>Gyz-QsbaZ{&%@gD?zD`go`!#AAji!#q9CW1g?Bl93*@o6c{? zUZ?Rm#>#fGYHx3v$l5PlO1Nv7QcX>h*({4ZARW8!?OkJk*+~ZKt;SKV87vp%63p6) zd~@DaVe=Px_rcDcL)$v%Se?%eZRwh`ab!VlviR?jWDV_n@mm9G@xR!u_V?|1zHdbU z^Tj*r^1kP9rYAoNJlgpSqNLyNoBL(xi7fc>R&bv@-fnsPZdEs{;=r0SShYR`1|(qo z3C0{&aTTUFgZQlku1mBg$le4tQgx}W6!li&yH$83|MUE4p1SgZJn>N}j&&EWah`KF z#mFFmId$!n;4w|6YbH_?`A>x0i2OZ54o6N$E=1^t2#z%9h4iX4$)vBQ|CYX$=A`ug zG?8e!!TFRL5LN>gizG}NenSD>lr=o8ozTX#YZ`|%S&M7bM>YMd(+8twBoj(A)SJnW zxYQ83S2s@}%(~zJ18&wAQ~)1kB@ig**)If825yp8(#Guitl6IIP8O0hnJM+b4RFhe z@lW@{dlT?*TcIJQSEr+^a|ds+){rNrSKVjM=E5oqyHF2XpL_VL8{2?=mD-@~!b5>WhpUbLb^4DU>st~P%6v?QaadBnBK99LbQA;ySBvoRARvKa0;izh9io*7;g-v$0Vo=t*fwsmJiWFHPGbIJjIrSEAoAZZVhyX}kBDaBB zH55ntt+>Fr^`h`6;kH2g!Pbm20aF0-S(II(`(*)`nz0dDy*W<-zgUfyIl5@XDRt zW0liN{Ow=oEt;(qHx8X^Sk}H8&#U}ZT&n|X>o#m`7S5vu&fq?rK?*M}A`2*QgBdN{ zmcEr9NmQ2bE#;o_KZSAy2OEtJW2v#x*lE0P$eQ6AWSO(c8FD^w7)98?1XEYn(i#cL z8rNMR^sbl56*^t4yEoiBF4f(%JLyil4CWvqR0cGdiavt3il^r|ZXrYWP=csIo2f^8 zsiaYA3RdEtF5U`XD4t#$e5P5)NH6JSog&@P@9Gb9_+~xGxv%9s!<#wn7~wZKIm*S%au|p?! z_4oCbw=V4NU9|0bcc1SJ$H{TPCyM@X(J*YM5E8nShsEDR--HO4m&&W6B`DY+<* z%iqFAju*)|A>WYi2(h`agnG>t+0cb`UNJiPC5M`#w8C4U`&Yw-6n;;uhCvy$N0w~(AIb+ULY!<-Y zHGn#ipImFXcTv*PNT*@noAO@F30|2gzW+<=EG%lhXoz#qM%Yzm1fL|~*3vXe+f zpBE8>|E)a`DufuGU{j2I%+9bYjLv2S_A;aE+0WTtMlmxPsWALG`u6e!CVsaEn}ZMrhhHl!Uszc|u2(teD{>mrPY9jyET-D(Y?T5<&^)D_s@!i$ zPN+=?>?PrB0PFKa4OXQL!mg*OKpGs+p&a=J=UK}sWW>Q9LnTy350GSlq7kt71Y9h+ zK_?UW)2$4uN2>Gy&eIdpjrd>?BDf~>bd+KJXbL!?bQLrD2gw4mb9O{%G` zninC*Gz7^FbJgZtO6+)oO4smQb@{+mCCuF|v=@ zCA(}tuqDF|!J#BznimKfoaED-_3(bao1f()T;fmHJX))H3~Cl6S2c3AvO6cfSNNQo z5Ph%y8qLv;rg=NF`cwsvudA2M3)Lhb>_B_T8R z6v1rWzhF9rB*aE<7A%f9xZT3DEvyCIU_J;v06-=smKI(l=J6PK;fJ1X+J#UIXIad( zf>y-x&qoW|nih-%!=Kh%c;aHpOQZ@Z(DL(fFD{PFfc?36DPE4#cucd>u}}TrqC!_W zR#4L;O%ZZ8f)Qj{FVY)fO2if4y%d3KATJ;^fVM-X6n!Rd7acbMyOQX*unt>2qhX{y z&`3hp00r%aHmOZ(tViqDc57#~5lzybLSb<1Z{iN?iBaD&3k*P;ZkCKM2J^^gqbdtM z>BX81fgII*gKw&~yHoXg3;N1#Pdz-k40f-nU{1vGW9uj{i^@(tB4KY^@qUftez zyH;J9iz?*gApSm#ezkx7M=LRY0x|zam`meJ>1*9W;z+!7yND z3$1!#ZcD*6;C6&ynP82}3As_GF(rtz+)mhyHeJl&-eE7b+5FU^)T$Kivo={n7PZ|Z zoOQ}GE!(y%du{~lk#vM`8iC(l@GRH$Q&7I8FyNM5l5v~e1MV@GNiKF>)lSC5$ixA3 z6u??Qt!5(EOWLlbTWPDwqAyQd*s^jKu@obTlVO2uozKY4KpQlGbf?Mbbo!nBj&#SF zbciDq#50kSQJR%rWk8Y2%H67DOpL7=6skBeI%swXJk4qpdSgxJ)U|8E>H{DsZd`-$ z67W18DYV1a&*}?|#p482KjVB0^|@SIdlf!KB%P~29XP!AOlVK)_di=O|5uwk|5XL< z(a@`_$gJP4f9vqO+Q{;S>o>o>Xk9&mLi>yh9$zKgw!aFOSdJ7(*$j# z;jF6Dz(l0RNrP~GbUV=>;*mHVcHkEB95je*QQLpx>V+ zYIR*6`9K7G+4a}MW z6db!7dRW&-Fpk3V03O1x;4GfTGgw481GhUMd?;3wVx!oAK4uQI9DL_Ej_gB`w8@%N ztp&|)1`ba%6Y~+@)YTi4%#fP>Fr{9-aSq6j~Fk6@-$Yw?cLxoIA;VfYbanq@uNkU$fQP znrtRwnkm(UTA@jx7i}NgZrMPdo*^R$Bn%n(m7z*Q)^{P~NCot%?-M8qj?4mGfc^sQ zQCY#6LpbgPil_qfl5T9yecs%^rek#2^C%Mr?+V6)FUsEEs@rjBtoV&6R1wTM6HS1Vb1Q zC=xOPd=`RE77Rc*CU8iInk&-+Zxy5#BpdR8Op%?k`hwFBg89%9wwGTp%g5 z7&W3lowk$%a|FldXN+2_Lmvy`NGV?J{hIeNFSW}7nYj6g=y2&5t@z{mTlM5M9oI1? zvk4BjBjkWicJKjE+kv+dG|uJl@anNnbnmU>AdPB)NTz}FI#U~IlCHAFRM3IPDXT$PE+dQP5b7kU#^37 zo3Y%1@oSN>$IlAni=bB6fKf#!umfku0SqGpW4WZw3WX`6eyCL$$h!czh*B6no`vCK zl)})tgK)H<;5Y=3je-iGNWp$q%PPfC44YFQP%_GxGOf%gJW`?xh&u%@yR6(+<`t?$ zcIM}}S*^LV>QtV4^2KBH3PY#J96CF)>A5Vc91NShrR-*&5~Av$+f>t@h3c_dxoSeJ zdV+C}>Kqb7%hFxjgvoEWtvz7>?D21&IQIBJ|CS@G zQ}t_hr%)m9S0L|pVCoM~7uQ10UE}i~y$yGeQXd%&@KDB9zwXDoloymE3Kf)N3fUv? z2eSx1<)lnx;SHg{4_4mUCP6Z-kknFBTm?X5FQn54>0FohlAVxJ4&_ZHq*G(bEEf&Y zutL9!U{OSv-r{BPwm2_xBjTIlZ$&EJJH03GPNSK`e9C&vVZDQ?rfO3){VQLVLbDgF zNh7&(q%(cAYs2>RzQdb0Jrix}>rX}^$^QOi{p{aU;l4*to`1Nr_tE1!+8^53zI*rD zja?n47MyM91^!^63aDGE@B)SfEMWG6GO{=kve>9X zS!m~MPR=IVdBTAo1c5>WZ!0#7Fi!o3!clCo$vm-3oRYV&Rb@f2um+@V2+cq@3W(8a zR-i%;_Z&yoaXlR2pmWacoJ^a>t;oa7LN?1zcpl~-Wb$%YGgYP?4p5qqFU)evS5jJu zX&mqmoDioZbMVZ22e9z@YyD1tQT3iz|F##G#XaVi-R6fNfu@WC)=$ND=Fty12Ah>@ojZpC8}C1YaWJK zt(-BQ)GDu&d)QD=Wk9Sfb4r^GO;vnXb9n%_3++|%KhY|wj9MKa{Q%Xa5gmd-7Imq5Bd+6Q-tYfF>+JxsjY--}Q5V zDbWOd*%lMJ$5RjO&249}O(hezPHf!{Xf5w;>tN`8MD?f{ z{n?1Qt8kapE0L6ldnxWExT6}kS7B$!f}YiRV~rJe`iGL{bi|c7ZFKVm;yXr~CEpE5R5(?GOpo0s7 zJF?&kg}9u4;=v&r`w~eyg4FHChf_TVw&;(&ht<&~8)}yRv}OC@(lb?r&A)*1uBSaRuYL;Zq+zc?y@D-zJLBo@|(SDVln| zX6~QVSAA>~=NW&W_uli_u^nIhb^f-`PK=3356GEdTI|cl8brs)x(W)zxT_d2q?IJB zw3<{EX)C3hXkDRdqHH78v^5MNMFiH2FcoR-$f&fz*cdP?%|beyq}ul?kYTfe#g+0{|D+&1|Q|Llb_v{W}( z1;W*)2;o=rM=?udBneMz2g8B*EcH?ldStN0^YK0L{c-k#=<89sFbvk

    |YsB(RF$ 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

    ^Z1z(04PhVQ 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±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 AEC2ui literal 0 HcmV?d00001 diff --git a/root/package/link4all/gobinet/gobinet.tar.gz b/root/package/link4all/gobinet/gobinet.tar.gz new file mode 100755 index 0000000000000000000000000000000000000000..f43377a228b6a2b182eefb50bddce6da4e83bdc9 GIT binary patch literal 1116160 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�yXoD4Rht`PI7;*D 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%0D3KtWKvHRc&&3y!8g??WLx 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 YtGAoAV9h|yK+QnSK+QnSzymPwA4xo5^8f$< literal 0 HcmV?d00001 diff --git a/root/package/link4all/gobinet/patches/001-atomic_read.patch b/root/package/link4all/gobinet/patches/001-atomic_read.patch new file mode 100644 index 00000000..45de57dc --- /dev/null +++ b/root/package/link4all/gobinet/patches/001-atomic_read.patch @@ -0,0 +1,14 @@ +Index: src/QMIDevice.c +=================================================================== +--- src.orig/QMIDevice.c ++++ src/QMIDevice.c +@@ -3391,7 +3391,8 @@ void DeregisterQMIDevice( sGobiUSBNet * + // but exists to prevent an infinate loop just in case. + for (tries = 0; tries < 30 * 100; tries++) + { +- int ref = atomic_read( &pDev->mQMIDev.mCdev.kobj.kref.refcount ); ++ // int ref = atomic_read( &pDev->mQMIDev.mCdev.kobj.kref.refcount ); ++ int ref = refcount_read( &pDev->mQMIDev.mCdev.kobj.kref.refcount ); + if (ref > 1) + { + DBG( "cdev in use by %d tasks\n", ref - 1 ); diff --git a/root/package/link4all/gobinet/src.ec25/GobiUSBNet.c b/root/package/link4all/gobinet/src.ec25/GobiUSBNet.c new file mode 100644 index 00000000..97ce2887 --- /dev/null +++ b/root/package/link4all/gobinet/src.ec25/GobiUSBNet.c @@ -0,0 +1,1443 @@ +/*=========================================================================== +FILE: + GobiUSBNet.c + +DESCRIPTION: + Qualcomm USB Network device for Gobi 3000 + +FUNCTIONS: + GobiNetSuspend + GobiNetResume + GobiNetDriverBind + GobiNetDriverUnbind + GobiUSBNetURBCallback + GobiUSBNetTXTimeout + GobiUSBNetAutoPMThread + GobiUSBNetStartXmit + GobiUSBNetOpen + GobiUSBNetStop + GobiUSBNetProbe + GobiUSBNetModInit + GobiUSBNetModExit + +Copyright (c) 2011, Code Aurora Forum. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Code Aurora Forum nor + the names of its contributors may be used to endorse or promote + products derived from this software without specific prior written + permission. + + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +===========================================================================*/ + +//--------------------------------------------------------------------------- +// Include Files +//--------------------------------------------------------------------------- + +#include "Structs.h" +#include "QMIDevice.h" +#include "QMI.h" +#include +#include +#include + +//----------------------------------------------------------------------------- +// Definitions +//----------------------------------------------------------------------------- + +// Version Information +#define DRIVER_VERSION "Quectel_Linux_GobiNet_SR01A02V16" +#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; + +#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; + } + + // 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 ); + +#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->mbRawIPMode) + return skb; + + // 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); //usbnet.c usbnet_start_xmit() will free it + 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; + memset(eth_hdr(skb)->h_source, 0, ETH_ALEN); +fix_dest: + memcpy(eth_hdr(skb)->h_dest, dev->net->dev_addr, ETH_ALEN); + return 1; +} +#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 + .rx_fixup = GobiNetDriverRxFixup, + .tx_fixup = GobiNetDriverTxFixup, +#endif + .data = (1 << 4), +}; + +/*=========================================================================*/ +// Qualcomm Gobi 3000 VID/PIDs +/*=========================================================================*/ +static const struct usb_device_id GobiVIDPIDTable [] = +{ + // Quectel UC20 + { + USB_DEVICE( 0x05c6, 0x9003 ), + .driver_info = (unsigned long)&GobiNetInfo + }, + // Quectel EC20 + { + USB_DEVICE( 0x05c6, 0x9215 ), + .driver_info = (unsigned long)&GobiNetInfo + }, + // Quectel EC25 + { + USB_DEVICE( 0x2c7c, 0x0125 ), + .driver_info = (unsigned long)&GobiNetInfo + }, + // Quectel EC21 + { + USB_DEVICE( 0x2c7c, 0x0121 ), + .driver_info = (unsigned long)&GobiNetInfo + }, + //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; + 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 --git a/root/package/link4all/gobinet/src.ec25/Makefile b/root/package/link4all/gobinet/src.ec25/Makefile new file mode 100644 index 00000000..3872ab79 --- /dev/null +++ b/root/package/link4all/gobinet/src.ec25/Makefile @@ -0,0 +1,30 @@ +obj-m := GobiNet.o +GobiNet-objs := GobiUSBNet.o QMIDevice.o QMI.o + +PWD := $(shell pwd) +OUTPUTDIR=/lib/modules/`uname -r`/kernel/drivers/net/usb/ + +ifeq ($(ARCH),) +ARCH := $(shell uname -m) +endif +ifeq ($(CROSS_COMPILE),) +CROSS_COMPILE := +endif +ifeq ($(KDIR),) +KDIR := /lib/modules/$(shell uname -r)/build +endif + +default: + ln -sf makefile Makefile + $(MAKE) ARCH=${ARCH} CROSS_COMPILE=${CROSS_COMPILE} -C $(KDIR) M=$(PWD) modules + +install: default + mkdir -p $(OUTPUTDIR) + cp -f GobiNet.ko $(OUTPUTDIR) + depmod + modprobe -r GobiNet + modprobe GobiNet + +clean: + rm -rf Makefile + rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions Module.* modules.order diff --git a/root/package/link4all/gobinet/src.ec25/QMI.c b/root/package/link4all/gobinet/src.ec25/QMI.c new file mode 100644 index 00000000..f161b8a2 --- /dev/null +++ b/root/package/link4all/gobinet/src.ec25/QMI.c @@ -0,0 +1,1386 @@ +/*=========================================================================== +FILE: + QMI.c + +DESCRIPTION: + Qualcomm QMI driver code + +FUNCTIONS: + Generic QMUX functions + ParseQMUX + FillQMUX + + Generic QMI functions + GetTLV + ValidQMIMessage + GetQMIMessageID + + Fill Buffers with QMI requests + QMICTLGetClientIDReq + QMICTLReleaseClientIDReq + QMICTLReadyReq + QMIWDSSetEventReportReq + QMIWDSGetPKGSRVCStatusReq + QMIDMSGetMEIDReq + QMIWDASetDataFormatReq + QMICTLSetDataFormatReq + QMICTLSyncReq + + Parse data from QMI responses + QMICTLGetClientIDResp + QMICTLReleaseClientIDResp + QMIWDSEventResp + QMIDMSGetMEIDResp + QMIWDASetDataFormatResp + QMICTLSyncResp + +Copyright (c) 2011, Code Aurora Forum. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Code Aurora Forum nor + the names of its contributors may be used to endorse or promote + products derived from this software without specific prior written + permission. + + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +===========================================================================*/ + +//--------------------------------------------------------------------------- +// Include Files +//--------------------------------------------------------------------------- +#include +#include +#include "Structs.h" +#include "QMI.h" + +/*=========================================================================*/ +// Get sizes of buffers needed by QMI requests +/*=========================================================================*/ + +/*=========================================================================== +METHOD: + QMUXHeaderSize (Public Method) + +DESCRIPTION: + Get size of buffer needed for QMUX + +RETURN VALUE: + u16 - size of buffer +===========================================================================*/ +u16 QMUXHeaderSize( void ) +{ + return sizeof( sQMUX ); +} + +/*=========================================================================== +METHOD: + QMICTLGetClientIDReqSize (Public Method) + +DESCRIPTION: + Get size of buffer needed for QMUX + QMICTLGetClientIDReq + +RETURN VALUE: + u16 - size of buffer +===========================================================================*/ +u16 QMICTLGetClientIDReqSize( void ) +{ + return sizeof( sQMUX ) + 10; +} + +/*=========================================================================== +METHOD: + QMICTLReleaseClientIDReqSize (Public Method) + +DESCRIPTION: + Get size of buffer needed for QMUX + QMICTLReleaseClientIDReq + +RETURN VALUE: + u16 - size of header +===========================================================================*/ +u16 QMICTLReleaseClientIDReqSize( void ) +{ + return sizeof( sQMUX ) + 11; +} + +/*=========================================================================== +METHOD: + QMICTLReadyReqSize (Public Method) + +DESCRIPTION: + Get size of buffer needed for QMUX + QMICTLReadyReq + +RETURN VALUE: + u16 - size of buffer +===========================================================================*/ +u16 QMICTLReadyReqSize( void ) +{ + return sizeof( sQMUX ) + 6; +} + +/*=========================================================================== +METHOD: + QMIWDSSetEventReportReqSize (Public Method) + +DESCRIPTION: + Get size of buffer needed for QMUX + QMIWDSSetEventReportReq + +RETURN VALUE: + u16 - size of buffer +===========================================================================*/ +u16 QMIWDSSetEventReportReqSize( void ) +{ + return sizeof( sQMUX ) + 15; +} + +/*=========================================================================== +METHOD: + QMIWDSGetPKGSRVCStatusReqSize (Public Method) + +DESCRIPTION: + Get size of buffer needed for QMUX + QMIWDSGetPKGSRVCStatusReq + +RETURN VALUE: + u16 - size of buffer +===========================================================================*/ +u16 QMIWDSGetPKGSRVCStatusReqSize( void ) +{ + return sizeof( sQMUX ) + 7; +} + +u16 QMIWDSSetQMUXBindMuxDataPortSize( void ) +{ + return sizeof( sQMUX ) + 29; +} + +/*=========================================================================== +METHOD: + QMIDMSGetMEIDReqSize (Public Method) + +DESCRIPTION: + Get size of buffer needed for QMUX + QMIDMSGetMEIDReq + +RETURN VALUE: + u16 - size of buffer +===========================================================================*/ +u16 QMIDMSGetMEIDReqSize( void ) +{ + return sizeof( sQMUX ) + 7; +} + +/*=========================================================================== +METHOD: + QMIWDASetDataFormatReqSize (Public Method) + +DESCRIPTION: + Get size of buffer needed for QMUX + QMIWDASetDataFormatReq + +RETURN VALUE: + u16 - size of buffer +===========================================================================*/ +u16 QMIWDASetDataFormatReqSize( void ) +{ + return sizeof( sQMUX ) + 25; +} + +/*=========================================================================== +METHOD: + QMICTLSetDataFormatReqSize (Public Method) + +DESCRIPTION: + Get size of buffer needed for QMUX + QMICTLSetDataFormatReq + +RETURN VALUE: + u16 - size of buffer +===========================================================================*/ +u16 QMICTLSetDataFormatReqSize( void ) +{ + return sizeof( sQMUX ) + 15; +} + +/*=========================================================================== +METHOD: + QMICTLSyncReqSize (Public Method) + +DESCRIPTION: + Get size of buffer needed for QMUX + QMICTLSyncReq + +RETURN VALUE: + u16 - size of buffer +===========================================================================*/ +u16 QMICTLSyncReqSize( void ) +{ + return sizeof( sQMUX ) + 6; +} + +/*=========================================================================*/ +// Generic QMUX functions +/*=========================================================================*/ + +/*=========================================================================== +METHOD: + ParseQMUX (Public Method) + +DESCRIPTION: + Remove QMUX headers from a buffer + +PARAMETERS + pClientID [ O ] - On success, will point to Client ID + pBuffer [ I ] - Full Message passed in + buffSize [ I ] - Size of pBuffer + +RETURN VALUE: + int - Positive for size of QMUX header + Negative errno for error +===========================================================================*/ +int ParseQMUX( + u16 * pClientID, + void * pBuffer, + u16 buffSize ) +{ + sQMUX * pQMUXHeader; + + if (pBuffer == 0 || buffSize < 12) + { + return -ENOMEM; + } + + // QMUX Header + pQMUXHeader = (sQMUX *)pBuffer; + + if (pQMUXHeader->mTF != 1 + || le16_to_cpu(get_unaligned(&pQMUXHeader->mLength)) != buffSize - 1 + || pQMUXHeader->mCtrlFlag != 0x80 ) + { + return -EINVAL; + } + + // Client ID + *pClientID = (pQMUXHeader->mQMIClientID << 8) + pQMUXHeader->mQMIService; + + return sizeof( sQMUX ); +} + +/*=========================================================================== +METHOD: + FillQMUX (Public Method) + +DESCRIPTION: + Fill buffer with QMUX headers + +PARAMETERS + clientID [ I ] - Client ID + pBuffer [ O ] - Buffer to be filled + buffSize [ I ] - Size of pBuffer (must be at least 6) + +RETURN VALUE: + int - 0 for success + Negative errno for error +===========================================================================*/ +int FillQMUX( + u16 clientID, + void * pBuffer, + u16 buffSize ) +{ + sQMUX * pQMUXHeader; + + if (pBuffer == 0 || buffSize < sizeof( sQMUX )) + { + return -ENOMEM; + } + + // QMUX Header + pQMUXHeader = (sQMUX *)pBuffer; + + pQMUXHeader->mTF = 1; + put_unaligned(cpu_to_le16(buffSize - 1), &pQMUXHeader->mLength); + //DBG("pQMUXHeader->mLength = 0x%x, buffSize - 1 = 0x%x\n",pQMUXHeader->mLength, buffSize - 1); + pQMUXHeader->mCtrlFlag = 0; + + // Service and Client ID + pQMUXHeader->mQMIService = clientID & 0xff; + pQMUXHeader->mQMIClientID = clientID >> 8; + + return 0; +} + +/*=========================================================================*/ +// Generic QMI functions +/*=========================================================================*/ + +/*=========================================================================== +METHOD: + GetTLV (Public Method) + +DESCRIPTION: + Get data buffer of a specified TLV from a QMI message + + QMI Message shall NOT include SDU + +PARAMETERS + pQMIMessage [ I ] - QMI Message buffer + messageLen [ I ] - Size of QMI Message buffer + type [ I ] - Desired Type + pOutDataBuf [ O ] - Buffer to be filled with TLV + messageLen [ I ] - Size of QMI Message buffer + +RETURN VALUE: + u16 - Size of TLV for success + Negative errno for error +===========================================================================*/ +int GetTLV( + void * pQMIMessage, + u16 messageLen, + u8 type, + void * pOutDataBuf, + u16 bufferLen ) +{ + u16 pos; + u16 tlvSize = 0; + u16 cpyCount; + + if (pQMIMessage == 0 || pOutDataBuf == 0) + { + return -ENOMEM; + } + + for (pos = 4; + pos + 3 < messageLen; + pos += tlvSize + 3) + { + tlvSize = le16_to_cpu( get_unaligned(((u16 *)(pQMIMessage + pos + 1) )) ); + if (*(u8 *)(pQMIMessage + pos) == type) + { + if (bufferLen < tlvSize) + { + return -ENOMEM; + } + + for (cpyCount = 0; cpyCount < tlvSize; cpyCount++) + { + *((char*)(pOutDataBuf + cpyCount)) = *((char*)(pQMIMessage + pos + 3 + cpyCount)); + } + + return tlvSize; + } + } + + return -ENOMSG; +} + +/*=========================================================================== +METHOD: + ValidQMIMessage (Public Method) + +DESCRIPTION: + Check mandatory TLV in a QMI message + + QMI Message shall NOT include SDU + +PARAMETERS + pQMIMessage [ I ] - QMI Message buffer + messageLen [ I ] - Size of QMI Message buffer + +RETURN VALUE: + int - 0 for success (no error) + Negative errno for error + Positive for QMI error code +===========================================================================*/ +int ValidQMIMessage( + void * pQMIMessage, + u16 messageLen ) +{ + char mandTLV[4]; + + if (GetTLV( pQMIMessage, messageLen, 2, &mandTLV[0], 4 ) == 4) + { + // Found TLV + if (*(u16 *)&mandTLV[0] != 0) + { + return le16_to_cpu( get_unaligned(&mandTLV[2]) ); + } + else + { + return 0; + } + } + else + { + return -ENOMSG; + } +} + +/*=========================================================================== +METHOD: + GetQMIMessageID (Public Method) + +DESCRIPTION: + Get the message ID of a QMI message + + QMI Message shall NOT include SDU + +PARAMETERS + pQMIMessage [ I ] - QMI Message buffer + messageLen [ I ] - Size of QMI Message buffer + +RETURN VALUE: + int - Positive for message ID + Negative errno for error +===========================================================================*/ +int GetQMIMessageID( + void * pQMIMessage, + u16 messageLen ) +{ + if (messageLen < 2) + { + return -ENODATA; + } + else + { + return le16_to_cpu( get_unaligned((u16 *)pQMIMessage) ); + } +} + +/*=========================================================================*/ +// Fill Buffers with QMI requests +/*=========================================================================*/ + +/*=========================================================================== +METHOD: + QMICTLGetClientIDReq (Public Method) + +DESCRIPTION: + Fill buffer with QMI CTL Get Client ID Request + +PARAMETERS + pBuffer [ 0 ] - Buffer to be filled + buffSize [ I ] - Size of pBuffer + transactionID [ I ] - Transaction ID + serviceType [ I ] - Service type requested + +RETURN VALUE: + int - Positive for resulting size of pBuffer + Negative errno for error +===========================================================================*/ +int QMICTLGetClientIDReq( + void * pBuffer, + u16 buffSize, + u8 transactionID, + u8 serviceType ) +{ + if (pBuffer == 0 || buffSize < QMICTLGetClientIDReqSize() ) + { + return -ENOMEM; + } + + // QMI CTL GET CLIENT ID + // Request + *(u8 *)(pBuffer + sizeof( sQMUX ))= 0x00; + // Transaction ID + *(u8 *)(pBuffer + sizeof( sQMUX ) + 1) = transactionID; + // Message ID + put_unaligned(cpu_to_le16(0x0022), (u16 *)(pBuffer + sizeof( sQMUX ) + 2)); + // Size of TLV's + put_unaligned(cpu_to_le16(0x0004), (u16 *)(pBuffer + sizeof( sQMUX ) + 4)); + // QMI Service Type + *(u8 *)(pBuffer + sizeof( sQMUX ) + 6) = 0x01; + // Size + put_unaligned(cpu_to_le16(0x0001), (u16 *)(pBuffer + sizeof( sQMUX ) + 7)); + // QMI svc type + *(u8 *)(pBuffer + sizeof( sQMUX ) + 9) = serviceType; + + // success + return sizeof( sQMUX ) + 10; +} + +/*=========================================================================== +METHOD: + QMICTLReleaseClientIDReq (Public Method) + +DESCRIPTION: + Fill buffer with QMI CTL Release Client ID Request + +PARAMETERS + pBuffer [ 0 ] - Buffer to be filled + buffSize [ I ] - Size of pBuffer + transactionID [ I ] - Transaction ID + clientID [ I ] - Service type requested + +RETURN VALUE: + int - Positive for resulting size of pBuffer + Negative errno for error +===========================================================================*/ +int QMICTLReleaseClientIDReq( + void * pBuffer, + u16 buffSize, + u8 transactionID, + u16 clientID ) +{ + if (pBuffer == 0 || buffSize < QMICTLReleaseClientIDReqSize() ) + { + return -ENOMEM; + } + + DBG( "buffSize: 0x%x, transactionID: 0x%x, clientID: 0x%x,\n", + buffSize, transactionID, clientID ); + + // QMI CTL RELEASE CLIENT ID REQ + // Request + *(u8 *)(pBuffer + sizeof( sQMUX )) = 0x00; + // Transaction ID + *(u8 *)(pBuffer + sizeof( sQMUX ) + 1 ) = transactionID; + // Message ID + put_unaligned( cpu_to_le16(0x0023), (u16 *)(pBuffer + sizeof( sQMUX ) + 2) ); + // Size of TLV's + put_unaligned( cpu_to_le16(0x0005), (u16 *)(pBuffer + sizeof( sQMUX ) + 4) ); + // Release client ID + *(u8 *)(pBuffer + sizeof( sQMUX ) + 6) = 0x01; + // Size + put_unaligned( cpu_to_le16(0x0002), (u16 *)(pBuffer + sizeof( sQMUX ) + 7)); + // QMI svs type / Client ID + put_unaligned(cpu_to_le16(clientID), (u16 *)(pBuffer + sizeof( sQMUX ) + 9)); + + // success + return sizeof( sQMUX ) + 11; +} + +/*=========================================================================== +METHOD: + QMICTLReadyReq (Public Method) + +DESCRIPTION: + Fill buffer with QMI CTL Get Version Info Request + +PARAMETERS + pBuffer [ 0 ] - Buffer to be filled + buffSize [ I ] - Size of pBuffer + transactionID [ I ] - Transaction ID + +RETURN VALUE: + int - Positive for resulting size of pBuffer + Negative errno for error +===========================================================================*/ +int QMICTLReadyReq( + void * pBuffer, + u16 buffSize, + u8 transactionID ) +{ + if (pBuffer == 0 || buffSize < QMICTLReadyReqSize() ) + { + return -ENOMEM; + } + + DBG("buffSize: 0x%x, transactionID: 0x%x\n", buffSize, transactionID); + + // QMI CTL GET VERSION INFO REQ + // Request + *(u8 *)(pBuffer + sizeof( sQMUX )) = 0x00; + // Transaction ID + *(u8 *)(pBuffer + sizeof( sQMUX ) + 1) = transactionID; + // Message ID + put_unaligned( cpu_to_le16(0x0021), (u16 *)(pBuffer + sizeof( sQMUX ) + 2) ); + // Size of TLV's + put_unaligned( cpu_to_le16(0x0000), (u16 *)(pBuffer + sizeof( sQMUX ) + 4) ); + + // success + return sizeof( sQMUX ) + 6; +} + +/*=========================================================================== +METHOD: + QMIWDSSetEventReportReq (Public Method) + +DESCRIPTION: + Fill buffer with QMI WDS Set Event Report Request + +PARAMETERS + pBuffer [ 0 ] - Buffer to be filled + buffSize [ I ] - Size of pBuffer + transactionID [ I ] - Transaction ID + +RETURN VALUE: + int - Positive for resulting size of pBuffer + Negative errno for error +===========================================================================*/ +int QMIWDSSetEventReportReq( + void * pBuffer, + u16 buffSize, + u16 transactionID ) +{ + if (pBuffer == 0 || buffSize < QMIWDSSetEventReportReqSize() ) + { + return -ENOMEM; + } + + // QMI WDS SET EVENT REPORT REQ + // Request + *(u8 *)(pBuffer + sizeof( sQMUX )) = 0x00; + // Transaction ID + put_unaligned( cpu_to_le16(transactionID), (u16 *)(pBuffer + sizeof( sQMUX ) + 1)); + // Message ID + put_unaligned( cpu_to_le16(0x0001), (u16 *)(pBuffer + sizeof( sQMUX ) + 3)); + // Size of TLV's + put_unaligned(cpu_to_le16(0x0008), (u16 *)(pBuffer + sizeof( sQMUX ) + 5)); + // Report channel rate TLV + *(u8 *)(pBuffer + sizeof( sQMUX ) + 7) = 0x11; + // Size + put_unaligned( cpu_to_le16(0x0005), (u16 *)(pBuffer + sizeof( sQMUX ) + 8)); + // Stats period + *(u8 *)(pBuffer + sizeof( sQMUX ) + 10) = 0x01; + // Stats mask + put_unaligned( cpu_to_le32(0x000000ff), (u32 *)(pBuffer + sizeof( sQMUX ) + 11) ); + + // success + return sizeof( sQMUX ) + 15; +} + +/*=========================================================================== +METHOD: + QMIWDSGetPKGSRVCStatusReq (Public Method) + +DESCRIPTION: + Fill buffer with QMI WDS Get PKG SRVC Status Request + +PARAMETERS + pBuffer [ 0 ] - Buffer to be filled + buffSize [ I ] - Size of pBuffer + transactionID [ I ] - Transaction ID + +RETURN VALUE: + int - Positive for resulting size of pBuffer + Negative errno for error +===========================================================================*/ +int QMIWDSGetPKGSRVCStatusReq( + void * pBuffer, + u16 buffSize, + u16 transactionID ) +{ + if (pBuffer == 0 || buffSize < QMIWDSGetPKGSRVCStatusReqSize() ) + { + return -ENOMEM; + } + + // QMI WDS Get PKG SRVC Status REQ + // Request + *(u8 *)(pBuffer + sizeof( sQMUX )) = 0x00; + // Transaction ID + put_unaligned(cpu_to_le16(transactionID), (u16 *)(pBuffer + sizeof( sQMUX ) + 1)); + // Message ID + put_unaligned(cpu_to_le16(0x0022), (u16 *)(pBuffer + sizeof( sQMUX ) + 3)); + // Size of TLV's + put_unaligned(cpu_to_le16(0x0000), (u16 *)(pBuffer + sizeof( sQMUX ) + 5)); + + // success + return sizeof( sQMUX ) + 7; +} + +u16 QMIWDSSetQMUXBindMuxDataPortReq( + void * pBuffer, + u16 buffSize, + u16 transactionID ) +{ + if (pBuffer == 0 || buffSize < QMIWDSSetQMUXBindMuxDataPortSize() ) + { + return -ENOMEM; + } + + // QMI WDS Set QMUX Bind Mux Data Port REQ + // Request + *(u8 *)(pBuffer + sizeof( sQMUX )) = 0x00; + // Transaction ID + put_unaligned(cpu_to_le16(transactionID), (u16 *)(pBuffer + sizeof( sQMUX ) + 1)); + // Message ID + put_unaligned(cpu_to_le16(0x00a2), (u16 *)(pBuffer + sizeof( sQMUX ) + 3)); + // Size of TLV's + put_unaligned(cpu_to_le16(0x0016), (u16 *)(pBuffer + sizeof( sQMUX ) + 5)); + + *(u8 *)(pBuffer + sizeof( sQMUX ) + 7) = 0x10; + put_unaligned(cpu_to_le16(0x08), (u16 *)(pBuffer + sizeof( sQMUX ) + 8)); + put_unaligned(cpu_to_le32(0x02), (u32 *)(pBuffer + sizeof( sQMUX ) + 10)); // ep_type + put_unaligned(cpu_to_le32(0x04), (u32 *)(pBuffer + sizeof( sQMUX ) + 14)); // iface_id + + *(u8 *)(pBuffer + sizeof( sQMUX ) + 18) = 0x11; + put_unaligned(cpu_to_le16(0x01), (u16 *)(pBuffer + sizeof( sQMUX ) + 19)); + *(u8 *)(pBuffer + sizeof( sQMUX ) + 21) = 0x81; // MuxId + + *(u8 *)(pBuffer + sizeof( sQMUX ) + 22) = 0x13; + put_unaligned(cpu_to_le16(0x04), (u16 *)(pBuffer + sizeof( sQMUX ) + 23)); + put_unaligned(cpu_to_le32(0x01), (u32 *)(pBuffer + sizeof( sQMUX ) + 25)); + + // success + return sizeof( sQMUX ) + 29; +} + +/*=========================================================================== +METHOD: + QMIDMSGetMEIDReq (Public Method) + +DESCRIPTION: + Fill buffer with QMI DMS Get Serial Numbers Request + +PARAMETERS + pBuffer [ 0 ] - Buffer to be filled + buffSize [ I ] - Size of pBuffer + transactionID [ I ] - Transaction ID + +RETURN VALUE: + int - Positive for resulting size of pBuffer + Negative errno for error +===========================================================================*/ +int QMIDMSGetMEIDReq( + void * pBuffer, + u16 buffSize, + u16 transactionID ) +{ + if (pBuffer == 0 || buffSize < QMIDMSGetMEIDReqSize() ) + { + return -ENOMEM; + } + + // QMI DMS GET SERIAL NUMBERS REQ + // Request + *(u8 *)(pBuffer + sizeof( sQMUX )) = 0x00; + // Transaction ID + put_unaligned( cpu_to_le16(transactionID), (u16 *)(pBuffer + sizeof( sQMUX ) + 1) ); + // Message ID + put_unaligned( cpu_to_le16(0x0025), (u16 *)(pBuffer + sizeof( sQMUX ) + 3) ); + // Size of TLV's + put_unaligned( cpu_to_le16(0x0000), (u16 *)(pBuffer + sizeof( sQMUX ) + 5)); + + // success + return sizeof( sQMUX ) + 7; +} + +/*=========================================================================== +METHOD: + QMIWDASetDataFormatReq (Public Method) + +DESCRIPTION: + Fill buffer with QMI WDA Set Data Format Request + +PARAMETERS + pBuffer [ 0 ] - Buffer to be filled + buffSize [ I ] - Size of pBuffer + transactionID [ I ] - Transaction ID + +RETURN VALUE: + int - Positive for resulting size of pBuffer + Negative errno for error +===========================================================================*/ +int QMIWDASetDataFormatReq( + void * pBuffer, + u16 buffSize, + bool bRawIPMode, + u16 transactionID ) +{ + if (pBuffer == 0 || buffSize < QMIWDASetDataFormatReqSize() ) + { + return -ENOMEM; + } + + // QMI WDA SET DATA FORMAT REQ + // Request + *(u8 *)(pBuffer + sizeof( sQMUX )) = 0x00; + + // Transaction ID + put_unaligned( cpu_to_le16(transactionID), (u16 *)(pBuffer + sizeof( sQMUX ) + 1) ); + + // Message ID + put_unaligned( cpu_to_le16(0x0020), (u16 *)(pBuffer + sizeof( sQMUX ) + 3) ); + + // Size of TLV's + put_unaligned( cpu_to_le16(0x0012), (u16 *)(pBuffer + sizeof( sQMUX ) + 5)); + + /* TLVType QOS Data Format 1 byte */ + *(u8 *)(pBuffer + sizeof( sQMUX ) + 7) = 0x10; // type data format + + /* TLVLength 2 bytes - see spec */ + put_unaligned( cpu_to_le16(0x0001), (u16 *)(pBuffer + sizeof( sQMUX ) + 8)); + + /* DataFormat: 0-default; 1-QoS hdr present 2 bytes */ +#ifdef QOS_MODE + *(u8 *)(pBuffer + sizeof( sQMUX ) + 10) = 1; /* QOS header */ +#else + *(u8 *)(pBuffer + sizeof( sQMUX ) + 10) = 0; /* no-QOS header */ +#endif + + /* TLVType Link-Layer Protocol (Optional) 1 byte */ + *(u8 *)(pBuffer + sizeof( sQMUX ) + 11) = 0x11; + + /* TLVLength 2 bytes */ + put_unaligned( cpu_to_le16(0x0004), (u16 *)(pBuffer + sizeof( sQMUX ) + 12)); + + /* LinkProt: 0x1 - ETH; 0x2 - rawIP 4 bytes */ +if (bRawIPMode) { //#ifdef DATA_MODE_RP + /* Set RawIP mode */ + put_unaligned( cpu_to_le32(0x00000002), (u32 *)(pBuffer + sizeof( sQMUX ) + 14)); + DBG("Request RawIP Data Format\n"); +} else { //#else + /* Set Ethernet mode */ + put_unaligned( cpu_to_le32(0x00000001), (u32 *)(pBuffer + sizeof( sQMUX ) + 14)); + DBG("Request Ethernet Data Format\n"); +} //#endif + + /* TLVType Uplink Data Aggression Protocol - 1 byte */ + *(u8 *)(pBuffer + sizeof( sQMUX ) + 18) = 0x13; + + /* TLVLength 2 bytes */ + put_unaligned( cpu_to_le16(0x0004), (u16 *)(pBuffer + sizeof( sQMUX ) + 19)); + + /* TLV Data */ + put_unaligned( cpu_to_le32(0x00000000), (u32 *)(pBuffer + sizeof( sQMUX ) + 21)); + + // success + return QMIWDASetDataFormatReqSize(); +} + + + +/*=========================================================================== +METHOD: + QMICTLSetDataFormatReq (Public Method) + +DESCRIPTION: + Fill buffer with QMI CTL Set Data Format Request + +PARAMETERS + pBuffer [ 0 ] - Buffer to be filled + buffSize [ I ] - Size of pBuffer + transactionID [ I ] - Transaction ID + +RETURN VALUE: + int - Positive for resulting size of pBuffer + Negative errno for error +===========================================================================*/ +int QMICTLSetDataFormatReq( + void * pBuffer, + u16 buffSize, + u8 transactionID ) +{ + if (pBuffer == 0 || buffSize < QMICTLSetDataFormatReqSize() ) + { + return -ENOMEM; + } + + /* QMI CTL Set Data Format Request */ + /* Request */ + *(u8 *)(pBuffer + sizeof( sQMUX )) = 0x00; // QMICTL_FLAG_REQUEST + + /* Transaction ID 1 byte */ + *(u8 *)(pBuffer + sizeof( sQMUX ) + 1) = transactionID; /* 1 byte as in spec */ + + /* QMICTLType 2 bytes */ + put_unaligned( cpu_to_le16(0x0026), (u16 *)(pBuffer + sizeof( sQMUX ) + 2)); + + /* Length 2 bytes of 2 TLVs each - see spec */ + put_unaligned( cpu_to_le16(0x0009), (u16 *)(pBuffer + sizeof( sQMUX ) + 4)); + + /* TLVType Data Format (Mandatory) 1 byte */ + *(u8 *)(pBuffer + sizeof( sQMUX ) + 6) = 0x01; // type data format + + /* TLVLength 2 bytes - see spec */ + put_unaligned( cpu_to_le16(0x0001), (u16 *)(pBuffer + sizeof( sQMUX ) + 7)); + + /* DataFormat: 0-default; 1-QoS hdr present 2 bytes */ +#ifdef QOS_MODE + *(u8 *)(pBuffer + sizeof( sQMUX ) + 9) = 1; /* QOS header */ +#else + *(u8 *)(pBuffer + sizeof( sQMUX ) + 9) = 0; /* no-QOS header */ +#endif + + /* TLVType Link-Layer Protocol (Optional) 1 byte */ + *(u8 *)(pBuffer + sizeof( sQMUX ) + 10) = TLV_TYPE_LINK_PROTO; + + /* TLVLength 2 bytes */ + put_unaligned( cpu_to_le16(0x0002), (u16 *)(pBuffer + sizeof( sQMUX ) + 11)); + + /* LinkProt: 0x1 - ETH; 0x2 - rawIP 2 bytes */ +#ifdef DATA_MODE_RP + /* Set RawIP mode */ + put_unaligned( cpu_to_le16(0x0002), (u16 *)(pBuffer + sizeof( sQMUX ) + 13)); + DBG("Request RawIP Data Format\n"); +#else + /* Set Ethernet mode */ + put_unaligned( cpu_to_le16(0x0001), (u16 *)(pBuffer + sizeof( sQMUX ) + 13)); + DBG("Request Ethernet Data Format\n"); +#endif + + /* success */ + return sizeof( sQMUX ) + 15; + +} + +/*=========================================================================== +METHOD: + QMICTLSyncReq (Public Method) + +DESCRIPTION: + Fill buffer with QMI CTL Sync Request + +PARAMETERS + pBuffer [ 0 ] - Buffer to be filled + buffSize [ I ] - Size of pBuffer + transactionID [ I ] - Transaction ID + +RETURN VALUE: + int - Positive for resulting size of pBuffer + Negative errno for error +===========================================================================*/ +int QMICTLSyncReq( + void * pBuffer, + u16 buffSize, + u16 transactionID ) +{ + if (pBuffer == 0 || buffSize < QMICTLSyncReqSize() ) + { + return -ENOMEM; + } + + // Request + *(u8 *)(pBuffer + sizeof( sQMUX )) = 0x00; + // Transaction ID + *(u8 *)(pBuffer + sizeof( sQMUX ) + 1) = transactionID; + // Message ID + put_unaligned( cpu_to_le16(0x0027), (u16 *)(pBuffer + sizeof( sQMUX ) + 2) ); + // Size of TLV's + put_unaligned( cpu_to_le16(0x0000), (u16 *)(pBuffer + sizeof( sQMUX ) + 4) ); + + // success + return sizeof( sQMUX ) + 6; +} + +/*=========================================================================*/ +// Parse data from QMI responses +/*=========================================================================*/ + +/*=========================================================================== +METHOD: + QMICTLGetClientIDResp (Public Method) + +DESCRIPTION: + Parse the QMI CTL Get Client ID Resp + +PARAMETERS + pBuffer [ I ] - Buffer to be parsed + buffSize [ I ] - Size of pBuffer + pClientID [ 0 ] - Recieved client ID + +RETURN VALUE: + int - 0 for success + Negative errno for error +===========================================================================*/ +int QMICTLGetClientIDResp( + void * pBuffer, + u16 buffSize, + u16 * pClientID ) +{ + int result; + + // Ignore QMUX and SDU + // QMI CTL SDU is 2 bytes, not 3 + u8 offset = sizeof( sQMUX ) + 2; + + if (pBuffer == 0 || buffSize < offset) + { + return -ENOMEM; + } + + pBuffer = pBuffer + offset; + buffSize -= offset; + + result = GetQMIMessageID( pBuffer, buffSize ); + if (result != 0x22) + { + return -EFAULT; + } + + result = ValidQMIMessage( pBuffer, buffSize ); + if (result != 0) + { + return -EFAULT; + } + + result = GetTLV( pBuffer, buffSize, 0x01, pClientID, 2 ); + if (result != 2) + { + return -EFAULT; + } + + return 0; +} + +/*=========================================================================== +METHOD: + QMICTLReleaseClientIDResp (Public Method) + +DESCRIPTION: + Verify the QMI CTL Release Client ID Resp is valid + +PARAMETERS + pBuffer [ I ] - Buffer to be parsed + buffSize [ I ] - Size of pBuffer + +RETURN VALUE: + int - 0 for success + Negative errno for error +===========================================================================*/ +int QMICTLReleaseClientIDResp( + void * pBuffer, + u16 buffSize ) +{ + int result; + + // Ignore QMUX and SDU + // QMI CTL SDU is 2 bytes, not 3 + u8 offset = sizeof( sQMUX ) + 2; + + if (pBuffer == 0 || buffSize < offset) + { + return -ENOMEM; + } + + pBuffer = pBuffer + offset; + buffSize -= offset; + + result = GetQMIMessageID( pBuffer, buffSize ); + if (result != 0x23) + { + return -EFAULT; + } + + result = ValidQMIMessage( pBuffer, buffSize ); + if (result != 0) + { + return -EFAULT; + } + + return 0; +} + +/*=========================================================================== +METHOD: + QMIWDSEventResp (Public Method) + +DESCRIPTION: + Parse the QMI WDS Set Event Report Resp/Indication or + QMI WDS Get PKG SRVC Status Resp/Indication + + Return parameters will only be updated if value was received + +PARAMETERS + pBuffer [ I ] - Buffer to be parsed + buffSize [ I ] - Size of pBuffer + pTXOk [ O ] - Number of transmitted packets without errors + pRXOk [ O ] - Number of recieved packets without errors + pTXErr [ O ] - Number of transmitted packets with framing errors + pRXErr [ O ] - Number of recieved packets with framing errors + pTXOfl [ O ] - Number of transmitted packets dropped due to overflow + pRXOfl [ O ] - Number of recieved packets dropped due to overflow + pTXBytesOk [ O ] - Number of transmitted bytes without errors + pRXBytesOk [ O ] - Number of recieved bytes without errors + pbLinkState [ 0 ] - Is the link active? + pbReconfigure [ 0 ] - Must interface be reconfigured? (reset IP address) + +RETURN VALUE: + int - 0 for success + Negative errno for error +===========================================================================*/ +int QMIWDSEventResp( + void * pBuffer, + u16 buffSize, + u32 * pTXOk, + u32 * pRXOk, + u32 * pTXErr, + u32 * pRXErr, + u32 * pTXOfl, + u32 * pRXOfl, + u64 * pTXBytesOk, + u64 * pRXBytesOk, + bool * pbLinkState, + bool * pbReconfigure ) +{ + int result; + u8 pktStatusRead[2]; + + // Ignore QMUX and SDU + u8 offset = sizeof( sQMUX ) + 3; + + if (pBuffer == 0 + || buffSize < offset + || pTXOk == 0 + || pRXOk == 0 + || pTXErr == 0 + || pRXErr == 0 + || pTXOfl == 0 + || pRXOfl == 0 + || pTXBytesOk == 0 + || pRXBytesOk == 0 + || pbLinkState == 0 + || pbReconfigure == 0 ) + { + return -ENOMEM; + } + + pBuffer = pBuffer + offset; + buffSize -= offset; + + // Note: Indications. No Mandatory TLV required + + result = GetQMIMessageID( pBuffer, buffSize ); + // QMI WDS Set Event Report Resp + if (result == 0x01) + { + // TLV's are not mandatory + GetTLV( pBuffer, buffSize, 0x10, (void*)pTXOk, 4 ); + put_unaligned( le32_to_cpu(*pTXOk), pTXOk); + GetTLV( pBuffer, buffSize, 0x11, (void*)pRXOk, 4 ); + put_unaligned( le32_to_cpu(*pRXOk), pRXOk); + GetTLV( pBuffer, buffSize, 0x12, (void*)pTXErr, 4 ); + put_unaligned( le32_to_cpu(*pTXErr), pTXErr); + GetTLV( pBuffer, buffSize, 0x13, (void*)pRXErr, 4 ); + put_unaligned( le32_to_cpu(*pRXErr), pRXErr); + GetTLV( pBuffer, buffSize, 0x14, (void*)pTXOfl, 4 ); + put_unaligned( le32_to_cpu(*pTXOfl), pTXOfl); + GetTLV( pBuffer, buffSize, 0x15, (void*)pRXOfl, 4 ); + put_unaligned( le32_to_cpu(*pRXOfl), pRXOfl); + GetTLV( pBuffer, buffSize, 0x19, (void*)pTXBytesOk, 8 ); + put_unaligned( le64_to_cpu(*pTXBytesOk), pTXBytesOk); + GetTLV( pBuffer, buffSize, 0x1A, (void*)pRXBytesOk, 8 ); + put_unaligned( le64_to_cpu(*pRXBytesOk), pRXBytesOk); + } + // QMI WDS Get PKG SRVC Status Resp + else if (result == 0x22) + { + result = GetTLV( pBuffer, buffSize, 0x01, &pktStatusRead[0], 2 ); + // 1 or 2 bytes may be received + if (result >= 1) + { + if (pktStatusRead[0] == 0x02) + { + *pbLinkState = true; + } + else + { + *pbLinkState = false; + } + } + if (result == 2) + { + if (pktStatusRead[1] == 0x01) + { + *pbReconfigure = true; + } + else + { + *pbReconfigure = false; + } + } + + if (result < 0) + { + return result; + } + } + else + { + return -EFAULT; + } + + return 0; +} + +/*=========================================================================== +METHOD: + QMIDMSGetMEIDResp (Public Method) + +DESCRIPTION: + Parse the QMI DMS Get Serial Numbers Resp + +PARAMETERS + pBuffer [ I ] - Buffer to be parsed + buffSize [ I ] - Size of pBuffer + pMEID [ O ] - Device MEID + meidSize [ I ] - Size of MEID buffer (at least 14) + +RETURN VALUE: + int - 0 for success + Negative errno for error +===========================================================================*/ +int QMIDMSGetMEIDResp( + void * pBuffer, + u16 buffSize, + char * pMEID, + int meidSize ) +{ + int result; + + // Ignore QMUX and SDU + u8 offset = sizeof( sQMUX ) + 3; + + if (pBuffer == 0 || buffSize < offset || meidSize < 14) + { + return -ENOMEM; + } + + pBuffer = pBuffer + offset; + buffSize -= offset; + + result = GetQMIMessageID( pBuffer, buffSize ); + if (result != 0x25) + { + return -EFAULT; + } + + result = ValidQMIMessage( pBuffer, buffSize ); + if (result != 0) + { + return -EFAULT; + } + + result = GetTLV( pBuffer, buffSize, 0x12, (void*)pMEID, 14 ); + if (result != 14) + { + return -EFAULT; + } + + return 0; +} + +/*=========================================================================== +METHOD: + QMIWDASetDataFormatResp (Public Method) + +DESCRIPTION: + Parse the QMI WDA Set Data Format Response + +PARAMETERS + pBuffer [ I ] - Buffer to be parsed + buffSize [ I ] - Size of pBuffer + +RETURN VALUE: + int - 0 for success + Negative errno for error +===========================================================================*/ +int QMIWDASetDataFormatResp( + void * pBuffer, + u16 buffSize, bool bRawIPMode ) +{ + + int result; + + u8 pktLinkProtocol[4]; + + // Ignore QMUX and SDU + // QMI SDU is 3 bytes + u8 offset = sizeof( sQMUX ) + 3; + + if (pBuffer == 0 || buffSize < offset) + { + return -ENOMEM; + } + + pBuffer = pBuffer + offset; + buffSize -= offset; + + result = GetQMIMessageID( pBuffer, buffSize ); + if (result != 0x20) + { + return -EFAULT; + } + + /* Check response message result TLV */ + result = ValidQMIMessage( pBuffer, buffSize ); + if (result != 0) + { + DBG("EFAULT: Data Format Mode Bad Response\n"); +// return -EFAULT; + return 0; + } + + /* Check response message link protocol */ + result = GetTLV( pBuffer, buffSize, 0x11, + &pktLinkProtocol[0], 4); + if (result != 4) + { + DBG("EFAULT: Wrong TLV format\n"); + return 0; + + } + +if (bRawIPMode) { ////#ifdef DATA_MODE_RP + if (pktLinkProtocol[0] != 2) + { + DBG("EFAULT: Data Format Cannot be set to RawIP Mode\n"); + return pktLinkProtocol[0]; + } + DBG("Data Format Set to RawIP\n"); +} else { ////#else + if (pktLinkProtocol[0] != 1) + { + DBG("EFAULT: Data Format Cannot be set to Ethernet Mode\n"); + return pktLinkProtocol[0]; + } + DBG("Data Format Set to Ethernet Mode \n"); +} //#endif + + return pktLinkProtocol[0]; +} + +/*=========================================================================== +METHOD: + QMICTLSyncResp (Public Method) + +DESCRIPTION: + Validate the QMI CTL Sync Response + +PARAMETERS + pBuffer [ I ] - Buffer to be parsed + buffSize [ I ] - Size of pBuffer + +RETURN VALUE: + int - 0 for success + Negative errno for error +===========================================================================*/ +int QMICTLSyncResp( + void *pBuffer, + u16 buffSize ) +{ + int result; + + // Ignore QMUX (2 bytes for QMI CTL) and SDU + u8 offset = sizeof( sQMUX ) + 2; + + if (pBuffer == 0 || buffSize < offset) + { + return -ENOMEM; + } + + pBuffer = pBuffer + offset; + buffSize -= offset; + + result = GetQMIMessageID( pBuffer, buffSize ); + if (result != 0x27) + { + return -EFAULT; + } + + result = ValidQMIMessage( pBuffer, buffSize ); + + return result; +} diff --git a/root/package/link4all/gobinet/src.ec25/QMI.h b/root/package/link4all/gobinet/src.ec25/QMI.h new file mode 100644 index 00000000..c1e78b0e --- /dev/null +++ b/root/package/link4all/gobinet/src.ec25/QMI.h @@ -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 --git a/root/package/link4all/gobinet/src.ec25/QMIDevice.c b/root/package/link4all/gobinet/src.ec25/QMIDevice.c new file mode 100644 index 00000000..6f032ca4 --- /dev/null +++ b/root/package/link4all/gobinet/src.ec25/QMIDevice.c @@ -0,0 +1,4088 @@ +/*=========================================================================== +FILE: + QMIDevice.c + +DESCRIPTION: + Functions related to the QMI interface device + +FUNCTIONS: + Generic functions + IsDeviceValid + PrintHex + GobiSetDownReason + GobiClearDownReason + GobiTestDownReason + + Driver level asynchronous read functions + ResubmitIntURB + ReadCallback + IntCallback + StartRead + KillRead + + Internal read/write functions + ReadAsync + UpSem + ReadSync + WriteSyncCallback + WriteSync + + Internal memory management functions + GetClientID + ReleaseClientID + FindClientMem + AddToReadMemList + PopFromReadMemList + AddToNotifyList + NotifyAndPopNotifyList + AddToURBList + PopFromURBList + + Internal userspace wrapper functions + UserspaceunlockedIOCTL + + Userspace wrappers + UserspaceOpen + UserspaceIOCTL + UserspaceClose + UserspaceRead + UserspaceWrite + UserspacePoll + + Initializer and destructor + RegisterQMIDevice + DeregisterQMIDevice + + Driver level client management + QMIReady + QMIWDSCallback + SetupQMIWDSCallback + QMIDMSGetMEID + +Copyright (c) 2011, Code Aurora Forum. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Code Aurora Forum nor + the names of its contributors may be used to endorse or promote + products derived from this software without specific prior written + permission. + + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +===========================================================================*/ + +//--------------------------------------------------------------------------- +// Include Files +//--------------------------------------------------------------------------- +#include +#include "QMIDevice.h" +#include + +//----------------------------------------------------------------------------- +// Definitions +//----------------------------------------------------------------------------- + +extern int debug; +extern int interruptible; +#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,22 )) +static int s_interval; +#endif + +#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,14 )) +#include +static char devfs_name[32]; +int device_create(struct class *class, struct device *parent, dev_t devt, const char *fmt, ...) +{ + va_list vargs; + struct class_device *class_dev; + int err; + + va_start(vargs, fmt); + vsnprintf(devfs_name, sizeof(devfs_name), fmt, vargs); + va_end(vargs); + + class_dev = class_device_create(class, devt, parent, "%s", devfs_name); + if (IS_ERR(class_dev)) { + err = PTR_ERR(class_dev); + goto out; + } + + err = devfs_mk_cdev(devt, S_IFCHR|S_IRUSR|S_IWUSR|S_IRGRP, devfs_name); + if (err) { + class_device_destroy(class, devt); + goto out; + } + + return 0; + +out: + return err; +} + +void device_destroy(struct class *class, dev_t devt) +{ + class_device_destroy(class, devt); + devfs_remove(devfs_name); +} +#endif + +#ifdef CONFIG_PM +// Prototype to GobiNetSuspend function +int GobiNetSuspend( + struct usb_interface * pIntf, + pm_message_t powerEvent ); +#endif /* CONFIG_PM */ + +// IOCTL to generate a client ID for this service type +#define IOCTL_QMI_GET_SERVICE_FILE 0x8BE0 + 1 + +// IOCTL to get the VIDPID of the device +#define IOCTL_QMI_GET_DEVICE_VIDPID 0x8BE0 + 2 + +// IOCTL to get the MEID of the device +#define IOCTL_QMI_GET_DEVICE_MEID 0x8BE0 + 3 + +#define IOCTL_QMI_RELEASE_SERVICE_FILE_IOCTL (0x8BE0 + 4) + +// CDC GET_ENCAPSULATED_RESPONSE packet +#define CDC_GET_ENCAPSULATED_RESPONSE_LE 0x01A1ll +#define CDC_GET_ENCAPSULATED_RESPONSE_BE 0xA101000000000000ll +/* The following masks filter the common part of the encapsulated response + * packet value for Gobi and QMI devices, ie. ignore usb interface number + */ +#define CDC_RSP_MASK_BE 0xFFFFFFFF00FFFFFFll +#define CDC_RSP_MASK_LE 0xFFFFFFE0FFFFFFFFll + +const int i = 1; +#define is_bigendian() ( (*(char*)&i) == 0 ) +#define CDC_GET_ENCAPSULATED_RESPONSE(pcdcrsp, pmask)\ +{\ + *pcdcrsp = is_bigendian() ? CDC_GET_ENCAPSULATED_RESPONSE_BE \ + : CDC_GET_ENCAPSULATED_RESPONSE_LE ; \ + *pmask = is_bigendian() ? CDC_RSP_MASK_BE \ + : CDC_RSP_MASK_LE; \ +} + +// CDC CONNECTION_SPEED_CHANGE indication packet +#define CDC_CONNECTION_SPEED_CHANGE_LE 0x2AA1ll +#define CDC_CONNECTION_SPEED_CHANGE_BE 0xA12A000000000000ll +/* The following masks filter the common part of the connection speed change + * packet value for Gobi and QMI devices + */ +#define CDC_CONNSPD_MASK_BE 0xFFFFFFFFFFFF7FFFll +#define CDC_CONNSPD_MASK_LE 0XFFF7FFFFFFFFFFFFll +#define CDC_GET_CONNECTION_SPEED_CHANGE(pcdccscp, pmask)\ +{\ + *pcdccscp = is_bigendian() ? CDC_CONNECTION_SPEED_CHANGE_BE \ + : CDC_CONNECTION_SPEED_CHANGE_LE ; \ + *pmask = is_bigendian() ? CDC_CONNSPD_MASK_BE \ + : CDC_CONNSPD_MASK_LE; \ +} + +#define SET_CONTROL_LINE_STATE_REQUEST_TYPE 0x21 +#define SET_CONTROL_LINE_STATE_REQUEST 0x22 +#define CONTROL_DTR 0x01 +#define CONTROL_RTS 0x02 + +/*=========================================================================*/ +// UserspaceQMIFops +// QMI device's userspace file operations +/*=========================================================================*/ +struct file_operations UserspaceQMIFops = +{ + .owner = THIS_MODULE, + .read = UserspaceRead, + .write = UserspaceWrite, +#ifdef CONFIG_COMPAT + .compat_ioctl = UserspaceunlockedIOCTL, +#endif +#if (LINUX_VERSION_CODE >= KERNEL_VERSION( 2,6,36 )) + .unlocked_ioctl = UserspaceunlockedIOCTL, +#else + .ioctl = UserspaceIOCTL, +#endif + .open = UserspaceOpen, + .flush = UserspaceClose, + .poll = UserspacePoll, +}; + +/*=========================================================================*/ +// Generic functions +/*=========================================================================*/ +u8 QMIXactionIDGet( sGobiUSBNet *pDev) +{ + u8 transactionID; + + if( 0 == (transactionID = atomic_add_return( 1, &pDev->mQMIDev.mQMICTLTransactionID)) ) + { + transactionID = atomic_add_return( 1, &pDev->mQMIDev.mQMICTLTransactionID ); + } + + return transactionID; +} + +static struct usb_endpoint_descriptor *GetEndpoint( + struct usb_interface *pintf, + int type, + int dir ) +{ + int i; + struct usb_host_interface *iface = pintf->cur_altsetting; + struct usb_endpoint_descriptor *pendp; + + for( i = 0; i < iface->desc.bNumEndpoints; i++) + { + pendp = &iface->endpoint[i].desc; + if( ((pendp->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == dir) + && + (usb_endpoint_type(pendp) == type) ) + { + return pendp; + } + } + + return NULL; +} + +/*=========================================================================== +METHOD: + IsDeviceValid (Public Method) + +DESCRIPTION: + Basic test to see if device memory is valid + +PARAMETERS: + pDev [ I ] - Device specific memory + +RETURN VALUE: + bool +===========================================================================*/ +bool IsDeviceValid( sGobiUSBNet * pDev ) +{ + if (pDev == NULL) + { + return false; + } + + if (pDev->mbQMIValid == false) + { + return false; + } + + return true; +} + +/*=========================================================================== +METHOD: + PrintHex (Public Method) + +DESCRIPTION: + Print Hex data, for debug purposes + +PARAMETERS: + pBuffer [ I ] - Data buffer + bufSize [ I ] - Size of data buffer + +RETURN VALUE: + None +===========================================================================*/ +void PrintHex( + void * pBuffer, + u16 bufSize ) +{ + char * pPrintBuf; + u16 pos; + int status; + + if (debug != 1) + { + return; + } + + pPrintBuf = kmalloc( bufSize * 3 + 1, GFP_ATOMIC ); + if (pPrintBuf == NULL) + { + DBG( "Unable to allocate buffer\n" ); + return; + } + memset( pPrintBuf, 0 , bufSize * 3 + 1 ); + + for (pos = 0; pos < bufSize; pos++) + { + status = snprintf( (pPrintBuf + (pos * 3)), + 4, + "%02X ", + *(u8 *)(pBuffer + pos) ); + if (status != 3) + { + DBG( "snprintf error %d\n", status ); + kfree( pPrintBuf ); + return; + } + } + + DBG( " : %s\n", pPrintBuf ); + + kfree( pPrintBuf ); + pPrintBuf = NULL; + return; +} + +/*=========================================================================== +METHOD: + GobiSetDownReason (Public Method) + +DESCRIPTION: + Sets mDownReason and turns carrier off + +PARAMETERS + pDev [ I ] - Device specific memory + reason [ I ] - Reason device is down + +RETURN VALUE: + None +===========================================================================*/ +void GobiSetDownReason( + sGobiUSBNet * pDev, + u8 reason ) +{ + set_bit( reason, &pDev->mDownReason ); + DBG("%s reason=%d, mDownReason=%x\n", __func__, reason, (unsigned)pDev->mDownReason); + + netif_carrier_off( pDev->mpNetDev->net ); +} + +/*=========================================================================== +METHOD: + GobiClearDownReason (Public Method) + +DESCRIPTION: + Clear mDownReason and may turn carrier on + +PARAMETERS + pDev [ I ] - Device specific memory + reason [ I ] - Reason device is no longer down + +RETURN VALUE: + None +===========================================================================*/ +void GobiClearDownReason( + sGobiUSBNet * pDev, + u8 reason ) +{ + clear_bit( reason, &pDev->mDownReason ); + + DBG("%s reason=%d, mDownReason=%x\n", __func__, reason, (unsigned)pDev->mDownReason); +#if 0 //(LINUX_VERSION_CODE >= KERNEL_VERSION( 3,11,0 )) + netif_carrier_on( pDev->mpNetDev->net ); +#else + if (pDev->mDownReason == 0) + { + netif_carrier_on( pDev->mpNetDev->net ); + } +#endif +} + +/*=========================================================================== +METHOD: + GobiTestDownReason (Public Method) + +DESCRIPTION: + Test mDownReason and returns whether reason is set + +PARAMETERS + pDev [ I ] - Device specific memory + reason [ I ] - Reason device is down + +RETURN VALUE: + bool +===========================================================================*/ +bool GobiTestDownReason( + sGobiUSBNet * pDev, + u8 reason ) +{ + return test_bit( reason, &pDev->mDownReason ); +} + +/*=========================================================================*/ +// Driver level asynchronous read functions +/*=========================================================================*/ + +/*=========================================================================== +METHOD: + ResubmitIntURB (Public Method) + +DESCRIPTION: + Resubmit interrupt URB, re-using same values + +PARAMETERS + pIntURB [ I ] - Interrupt URB + +RETURN VALUE: + int - 0 for success + negative errno for failure +===========================================================================*/ +int ResubmitIntURB( struct urb * pIntURB ) +{ + int status; + int interval; + + // Sanity test + if ( (pIntURB == NULL) + || (pIntURB->dev == NULL) ) + { + return -EINVAL; + } + + // Interval needs reset after every URB completion +#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,22 )) + interval = max((int)(pIntURB->ep->desc.bInterval), + (pIntURB->dev->speed == USB_SPEED_HIGH) ? 7 : 3); +#else + interval = s_interval; +#endif + + // Reschedule interrupt URB + usb_fill_int_urb( pIntURB, + pIntURB->dev, + pIntURB->pipe, + pIntURB->transfer_buffer, + pIntURB->transfer_buffer_length, + pIntURB->complete, + pIntURB->context, + interval ); + status = usb_submit_urb( pIntURB, GFP_ATOMIC ); + if (status != 0) + { + DBG( "Error re-submitting Int URB %d\n", status ); + } + + return status; +} + +/*=========================================================================== +METHOD: + ReadCallback (Public Method) + +DESCRIPTION: + Put the data in storage and notify anyone waiting for data + +PARAMETERS + pReadURB [ I ] - URB this callback is run for + +RETURN VALUE: + None +===========================================================================*/ +#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,18 )) +void ReadCallback( struct urb * pReadURB ) +#else +void ReadCallback(struct urb *pReadURB, struct pt_regs *regs) +#endif +{ + int result; + u16 clientID; + sClientMemList * pClientMem; + void * pData; + void * pDataCopy; + u16 dataSize; + sGobiUSBNet * pDev; + unsigned long flags; + u16 transactionID; + + if (pReadURB == NULL) + { + DBG( "bad read URB\n" ); + return; + } + + pDev = pReadURB->context; + if (IsDeviceValid( pDev ) == false) + { + DBG( "Invalid device!\n" ); + return; + } + +#ifdef READ_QMI_URB_ERROR + del_timer(&pDev->mQMIDev.mReadUrbTimer); + if ((pReadURB->status == -ECONNRESET) && (pReadURB->actual_length > 0)) + pReadURB->status = 0; +#endif + + if (pReadURB->status != 0) + { + DBG( "Read status = %d\n", pReadURB->status ); + + // Resubmit the interrupt URB + ResubmitIntURB( pDev->mQMIDev.mpIntURB ); + + return; + } + DBG( "Read %d bytes\n", pReadURB->actual_length ); + + pData = pReadURB->transfer_buffer; + dataSize = pReadURB->actual_length; + + PrintHex( pData, dataSize ); + +#ifdef READ_QMI_URB_ERROR + if (dataSize < (le16_to_cpu(get_unaligned((u16*)(pData + 1))) + 1)) { + dataSize = (le16_to_cpu(get_unaligned((u16*)(pData + 1))) + 1); + memset(pReadURB->transfer_buffer + pReadURB->actual_length, 0x00, dataSize - pReadURB->actual_length); + INFO( "Read %d / %d bytes\n", pReadURB->actual_length, dataSize); + } +#endif + + result = ParseQMUX( &clientID, + pData, + dataSize ); + if (result < 0) + { + DBG( "Read error parsing QMUX %d\n", result ); + + // Resubmit the interrupt URB + ResubmitIntURB( pDev->mQMIDev.mpIntURB ); + + return; + } + + // Grab transaction ID + + // Data large enough? + if (dataSize < result + 3) + { + DBG( "Data buffer too small to parse\n" ); + + // Resubmit the interrupt URB + ResubmitIntURB( pDev->mQMIDev.mpIntURB ); + + return; + } + + // Transaction ID size is 1 for QMICTL, 2 for others + if (clientID == QMICTL) + { + transactionID = *(u8*)(pData + result + 1); + } + else + { + transactionID = le16_to_cpu( get_unaligned((u16*)(pData + result + 1)) ); + } + + // Critical section + spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags ); + + // Find memory storage for this service and Client ID + // Not using FindClientMem because it can't handle broadcasts + pClientMem = pDev->mQMIDev.mpClientMemList; + + while (pClientMem != NULL) + { + if (pClientMem->mClientID == clientID + || (pClientMem->mClientID | 0xff00) == clientID) + { + // Make copy of pData + pDataCopy = kmalloc( dataSize, GFP_ATOMIC ); + if (pDataCopy == NULL) + { + DBG( "Error allocating client data memory\n" ); + + // End critical section + spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags ); + + // Resubmit the interrupt URB + ResubmitIntURB( pDev->mQMIDev.mpIntURB ); + + return; + } + + memcpy( pDataCopy, pData, dataSize ); + + if (AddToReadMemList( pDev, + pClientMem->mClientID, + transactionID, + pDataCopy, + dataSize ) == false) + { + DBG( "Error allocating pReadMemListEntry " + "read will be discarded\n" ); + kfree( pDataCopy ); + + // End critical section + spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags ); + + // Resubmit the interrupt URB + ResubmitIntURB( pDev->mQMIDev.mpIntURB ); + + return; + } + + // Success + VDBG( "Creating new readListEntry for client 0x%04X, TID %x\n", + clientID, + transactionID ); + + // Notify this client data exists + NotifyAndPopNotifyList( pDev, + pClientMem->mClientID, + transactionID ); + + // Possibly notify poll() that data exists + wake_up_interruptible_sync( &pClientMem->mWaitQueue ); + + // Not a broadcast + if (clientID >> 8 != 0xff) + { + break; + } + } + + // Next element + pClientMem = pClientMem->mpNext; + } + + // End critical section + spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags ); + + // Resubmit the interrupt URB + ResubmitIntURB( pDev->mQMIDev.mpIntURB ); +} + +/*=========================================================================== +METHOD: + IntCallback (Public Method) + +DESCRIPTION: + Data is available, fire off a read URB + +PARAMETERS + pIntURB [ I ] - URB this callback is run for + +RETURN VALUE: + None +===========================================================================*/ +#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,18 )) +void IntCallback( struct urb * pIntURB ) +{ +#else +void IntCallback(struct urb *pIntURB, struct pt_regs *regs) +{ +#endif + int status; + u64 CDCEncResp; + u64 CDCEncRespMask; + + sGobiUSBNet * pDev = (sGobiUSBNet *)pIntURB->context; + if (IsDeviceValid( pDev ) == false) + { + DBG( "Invalid device!\n" ); + return; + } + + // Verify this was a normal interrupt + if (pIntURB->status != 0) + { + DBG( "IntCallback: Int status = %d\n", pIntURB->status ); + + // Ignore EOVERFLOW errors + if (pIntURB->status != -EOVERFLOW) + { + // Read 'thread' dies here + return; + } + } + else + { + //TODO cast transfer_buffer to struct usb_cdc_notification + + // CDC GET_ENCAPSULATED_RESPONSE + CDC_GET_ENCAPSULATED_RESPONSE(&CDCEncResp, &CDCEncRespMask) + + VDBG( "IntCallback: Encapsulated Response = 0x%llx\n", + (*(u64*)pIntURB->transfer_buffer)); + + if ((pIntURB->actual_length == 8) + && ((*(u64*)pIntURB->transfer_buffer & CDCEncRespMask) == CDCEncResp ) ) + + { + // Time to read + usb_fill_control_urb( pDev->mQMIDev.mpReadURB, + pDev->mpNetDev->udev, + usb_rcvctrlpipe( pDev->mpNetDev->udev, 0 ), + (unsigned char *)pDev->mQMIDev.mpReadSetupPacket, + pDev->mQMIDev.mpReadBuffer, + DEFAULT_READ_URB_LENGTH, + ReadCallback, + pDev ); +#ifdef READ_QMI_URB_ERROR + mod_timer( &pDev->mQMIDev.mReadUrbTimer, jiffies + msecs_to_jiffies(300) ); +#endif + status = usb_submit_urb( pDev->mQMIDev.mpReadURB, GFP_ATOMIC ); + if (status != 0) + { + DBG( "Error submitting Read URB %d\n", status ); + + // Resubmit the interrupt urb + ResubmitIntURB( pIntURB ); + return; + } + + // Int URB will be resubmitted during ReadCallback + return; + } + // CDC CONNECTION_SPEED_CHANGE + else if ((pIntURB->actual_length == 16) + && (CDC_GET_CONNECTION_SPEED_CHANGE(&CDCEncResp, &CDCEncRespMask)) + && ((*(u64*)pIntURB->transfer_buffer & CDCEncRespMask) == CDCEncResp ) ) + { + DBG( "IntCallback: Connection Speed Change = 0x%llx\n", + (*(u64*)pIntURB->transfer_buffer)); + + // if upstream or downstream is 0, stop traffic. Otherwise resume it + if ((*(u32*)(pIntURB->transfer_buffer + 8) == 0) + || (*(u32*)(pIntURB->transfer_buffer + 12) == 0)) + { + GobiSetDownReason( pDev, CDC_CONNECTION_SPEED ); + DBG( "traffic stopping due to CONNECTION_SPEED_CHANGE\n" ); + } + else + { + GobiClearDownReason( pDev, CDC_CONNECTION_SPEED ); + DBG( "resuming traffic due to CONNECTION_SPEED_CHANGE\n" ); + } + } + else + { + DBG( "ignoring invalid interrupt in packet\n" ); + PrintHex( pIntURB->transfer_buffer, pIntURB->actual_length ); + } + } + + // Resubmit the interrupt urb + ResubmitIntURB( pIntURB ); + + return; +} + +#ifdef READ_QMI_URB_ERROR +static void ReadUrbTimerFunc( struct urb * pReadURB ) +{ + int result; + + INFO( "%s called (%ld).\n", __func__, jiffies ); + + if ((pReadURB != NULL) && (pReadURB->status == -EINPROGRESS)) + { + // Asynchronously unlink URB. On success, -EINPROGRESS will be returned, + // URB status will be set to -ECONNRESET, and ReadCallback() executed + result = usb_unlink_urb( pReadURB ); + INFO( "%s called usb_unlink_urb, result = %d\n", __func__, result); + } +} +#endif + +/*=========================================================================== +METHOD: + StartRead (Public Method) + +DESCRIPTION: + Start continuous read "thread" (callback driven) + + Note: In case of error, KillRead() should be run + to remove urbs and clean up memory. + +PARAMETERS: + pDev [ I ] - Device specific memory + +RETURN VALUE: + int - 0 for success + negative errno for failure +===========================================================================*/ +int StartRead( sGobiUSBNet * pDev ) +{ + int interval; + struct usb_endpoint_descriptor *pendp; + + if (IsDeviceValid( pDev ) == false) + { + DBG( "Invalid device!\n" ); + return -ENXIO; + } + + // Allocate URB buffers + pDev->mQMIDev.mpReadURB = usb_alloc_urb( 0, GFP_KERNEL ); + if (pDev->mQMIDev.mpReadURB == NULL) + { + DBG( "Error allocating read urb\n" ); + return -ENOMEM; + } + +#ifdef READ_QMI_URB_ERROR + setup_timer( &pDev->mQMIDev.mReadUrbTimer, (void*)ReadUrbTimerFunc, (unsigned long)pDev->mQMIDev.mpReadURB ); +#endif + + pDev->mQMIDev.mpIntURB = usb_alloc_urb( 0, GFP_KERNEL ); + if (pDev->mQMIDev.mpIntURB == NULL) + { + DBG( "Error allocating int urb\n" ); + usb_free_urb( pDev->mQMIDev.mpReadURB ); + pDev->mQMIDev.mpReadURB = NULL; + return -ENOMEM; + } + + // Create data buffers + pDev->mQMIDev.mpReadBuffer = kmalloc( DEFAULT_READ_URB_LENGTH, GFP_KERNEL ); + if (pDev->mQMIDev.mpReadBuffer == NULL) + { + DBG( "Error allocating read buffer\n" ); + usb_free_urb( pDev->mQMIDev.mpIntURB ); + pDev->mQMIDev.mpIntURB = NULL; + usb_free_urb( pDev->mQMIDev.mpReadURB ); + pDev->mQMIDev.mpReadURB = NULL; + return -ENOMEM; + } + + pDev->mQMIDev.mpIntBuffer = kmalloc( 64, GFP_KERNEL ); + if (pDev->mQMIDev.mpIntBuffer == NULL) + { + DBG( "Error allocating int buffer\n" ); + kfree( pDev->mQMIDev.mpReadBuffer ); + pDev->mQMIDev.mpReadBuffer = NULL; + usb_free_urb( pDev->mQMIDev.mpIntURB ); + pDev->mQMIDev.mpIntURB = NULL; + usb_free_urb( pDev->mQMIDev.mpReadURB ); + pDev->mQMIDev.mpReadURB = NULL; + return -ENOMEM; + } + + pDev->mQMIDev.mpReadSetupPacket = kmalloc( sizeof( sURBSetupPacket ), + GFP_KERNEL ); + if (pDev->mQMIDev.mpReadSetupPacket == NULL) + { + DBG( "Error allocating setup packet buffer\n" ); + kfree( pDev->mQMIDev.mpIntBuffer ); + pDev->mQMIDev.mpIntBuffer = NULL; + kfree( pDev->mQMIDev.mpReadBuffer ); + pDev->mQMIDev.mpReadBuffer = NULL; + usb_free_urb( pDev->mQMIDev.mpIntURB ); + pDev->mQMIDev.mpIntURB = NULL; + usb_free_urb( pDev->mQMIDev.mpReadURB ); + pDev->mQMIDev.mpReadURB = NULL; + return -ENOMEM; + } + + // CDC Get Encapsulated Response packet + pDev->mQMIDev.mpReadSetupPacket->mRequestType = 0xA1; + pDev->mQMIDev.mpReadSetupPacket->mRequestCode = 1; + pDev->mQMIDev.mpReadSetupPacket->mValue = 0; + pDev->mQMIDev.mpReadSetupPacket->mIndex = + cpu_to_le16(pDev->mpIntf->cur_altsetting->desc.bInterfaceNumber); /* interface number */ + pDev->mQMIDev.mpReadSetupPacket->mLength = cpu_to_le16(DEFAULT_READ_URB_LENGTH); + + pendp = GetEndpoint(pDev->mpIntf, USB_ENDPOINT_XFER_INT, USB_DIR_IN); + if (pendp == NULL) + { + DBG( "Invalid interrupt endpoint!\n" ); + kfree(pDev->mQMIDev.mpReadSetupPacket); + pDev->mQMIDev.mpReadSetupPacket = NULL; + kfree( pDev->mQMIDev.mpIntBuffer ); + pDev->mQMIDev.mpIntBuffer = NULL; + kfree( pDev->mQMIDev.mpReadBuffer ); + pDev->mQMIDev.mpReadBuffer = NULL; + usb_free_urb( pDev->mQMIDev.mpIntURB ); + pDev->mQMIDev.mpIntURB = NULL; + usb_free_urb( pDev->mQMIDev.mpReadURB ); + pDev->mQMIDev.mpReadURB = NULL; + return -ENXIO; + } + + // Interval needs reset after every URB completion + interval = max((int)(pendp->bInterval), + (pDev->mpNetDev->udev->speed == USB_SPEED_HIGH) ? 7 : 3); +#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,22 )) + s_interval = interval; +#endif + + // Schedule interrupt URB + usb_fill_int_urb( pDev->mQMIDev.mpIntURB, + pDev->mpNetDev->udev, + /* QMI interrupt endpoint for the following + * interface configuration: DM, NMEA, MDM, NET + */ + usb_rcvintpipe( pDev->mpNetDev->udev, + pendp->bEndpointAddress), + pDev->mQMIDev.mpIntBuffer, + min((int)le16_to_cpu(pendp->wMaxPacketSize), 64), + IntCallback, + pDev, + interval ); + return usb_submit_urb( pDev->mQMIDev.mpIntURB, GFP_KERNEL ); +} + +/*=========================================================================== +METHOD: + KillRead (Public Method) + +DESCRIPTION: + Kill continuous read "thread" + +PARAMETERS: + pDev [ I ] - Device specific memory + +RETURN VALUE: + None +===========================================================================*/ +void KillRead( sGobiUSBNet * pDev ) +{ + // Stop reading + if (pDev->mQMIDev.mpReadURB != NULL) + { + DBG( "Killng read URB\n" ); + usb_kill_urb( pDev->mQMIDev.mpReadURB ); + } + + if (pDev->mQMIDev.mpIntURB != NULL) + { + DBG( "Killng int URB\n" ); + usb_kill_urb( pDev->mQMIDev.mpIntURB ); + } + + // Release buffers + kfree( pDev->mQMIDev.mpReadSetupPacket ); + pDev->mQMIDev.mpReadSetupPacket = NULL; + kfree( pDev->mQMIDev.mpReadBuffer ); + pDev->mQMIDev.mpReadBuffer = NULL; + kfree( pDev->mQMIDev.mpIntBuffer ); + pDev->mQMIDev.mpIntBuffer = NULL; + + // Release URB's + usb_free_urb( pDev->mQMIDev.mpReadURB ); + pDev->mQMIDev.mpReadURB = NULL; + usb_free_urb( pDev->mQMIDev.mpIntURB ); + pDev->mQMIDev.mpIntURB = NULL; +} + +/*=========================================================================*/ +// Internal read/write functions +/*=========================================================================*/ + +/*=========================================================================== +METHOD: + ReadAsync (Public Method) + +DESCRIPTION: + Start asynchronous read + NOTE: Reading client's data store, not device + +PARAMETERS: + pDev [ I ] - Device specific memory + clientID [ I ] - Requester's client ID + transactionID [ I ] - Transaction ID or 0 for any + pCallback [ I ] - Callback to be executed when data is available + pData [ I ] - Data buffer that willl be passed (unmodified) + to callback + +RETURN VALUE: + int - 0 for success + negative errno for failure +===========================================================================*/ +int ReadAsync( + sGobiUSBNet * pDev, + u16 clientID, + u16 transactionID, + void (*pCallback)(sGobiUSBNet*, u16, void *), + void * pData ) +{ + sClientMemList * pClientMem; + sReadMemList ** ppReadMemList; + + unsigned long flags; + + if (IsDeviceValid( pDev ) == false) + { + DBG( "Invalid device!\n" ); + return -ENXIO; + } + + // Critical section + spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags ); + + // Find memory storage for this client ID + pClientMem = FindClientMem( pDev, clientID ); + if (pClientMem == NULL) + { + DBG( "Could not find matching client ID 0x%04X\n", + clientID ); + + // End critical section + spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags ); + return -ENXIO; + } + + ppReadMemList = &(pClientMem->mpList); + + // Does data already exist? + while (*ppReadMemList != NULL) + { + // Is this element our data? + if (transactionID == 0 + || transactionID == (*ppReadMemList)->mTransactionID) + { + // End critical section + spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags ); + + // Run our own callback + pCallback( pDev, clientID, pData ); + + return 0; + } + + // Next + ppReadMemList = &(*ppReadMemList)->mpNext; + } + + // Data not found, add ourself to list of waiters + if (AddToNotifyList( pDev, + clientID, + transactionID, + pCallback, + pData ) == false) + { + DBG( "Unable to register for notification\n" ); + } + + // End critical section + spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags ); + + // Success + return 0; +} + +/*=========================================================================== +METHOD: + UpSem (Public Method) + +DESCRIPTION: + Notification function for synchronous read + +PARAMETERS: + pDev [ I ] - Device specific memory + clientID [ I ] - Requester's client ID + pData [ I ] - Buffer that holds semaphore to be up()-ed + +RETURN VALUE: + None +===========================================================================*/ +void UpSem( + sGobiUSBNet * pDev, + u16 clientID, + void * pData ) +{ + VDBG( "0x%04X\n", clientID ); + + up( (struct semaphore *)pData ); + return; +} + +/*=========================================================================== +METHOD: + ReadSync (Public Method) + +DESCRIPTION: + Start synchronous read + NOTE: Reading client's data store, not device + +PARAMETERS: + pDev [ I ] - Device specific memory + ppOutBuffer [I/O] - On success, will be filled with a + pointer to read buffer + clientID [ I ] - Requester's client ID + transactionID [ I ] - Transaction ID or 0 for any + +RETURN VALUE: + int - size of data read for success + negative errno for failure +===========================================================================*/ +int ReadSync( + sGobiUSBNet * pDev, + void ** ppOutBuffer, + u16 clientID, + u16 transactionID ) +{ + int result; + sClientMemList * pClientMem; + sNotifyList ** ppNotifyList, * pDelNotifyListEntry; + struct semaphore readSem; + void * pData; + unsigned long flags; + u16 dataSize; + + if (IsDeviceValid( pDev ) == false) + { + DBG( "Invalid device!\n" ); + return -ENXIO; + } + + // Critical section + spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags ); + + // Find memory storage for this Client ID + pClientMem = FindClientMem( pDev, clientID ); + if (pClientMem == NULL) + { + DBG( "Could not find matching client ID 0x%04X\n", + clientID ); + + // End critical section + spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags ); + return -ENXIO; + } + + // Note: in cases where read is interrupted, + // this will verify client is still valid + while (PopFromReadMemList( pDev, + clientID, + transactionID, + &pData, + &dataSize ) == false) + { + // Data does not yet exist, wait + sema_init( &readSem, 0 ); + + // Add ourself to list of waiters + if (AddToNotifyList( pDev, + clientID, + transactionID, + UpSem, + &readSem ) == false) + { + DBG( "unable to register for notification\n" ); + spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags ); + return -EFAULT; + } + + // End critical section while we block + spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags ); + + // Wait for notification + result = down_interruptible( &readSem ); + if (result != 0) + { + DBG( "Interrupted %d\n", result ); + + // readSem will fall out of scope, + // remove from notify list so it's not referenced + spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags ); + ppNotifyList = &(pClientMem->mpReadNotifyList); + pDelNotifyListEntry = NULL; + + // Find and delete matching entry + while (*ppNotifyList != NULL) + { + if ((*ppNotifyList)->mpData == &readSem) + { + pDelNotifyListEntry = *ppNotifyList; + *ppNotifyList = (*ppNotifyList)->mpNext; + kfree( pDelNotifyListEntry ); + break; + } + + // Next + ppNotifyList = &(*ppNotifyList)->mpNext; + } + + spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags ); + return -EINTR; + } + + // Verify device is still valid + if (IsDeviceValid( pDev ) == false) + { + DBG( "Invalid device!\n" ); + return -ENXIO; + } + + // Restart critical section and continue loop + spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags ); + } + + // End Critical section + spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags ); + + // Success + *ppOutBuffer = pData; + + return dataSize; +} + +/*=========================================================================== +METHOD: + WriteSyncCallback (Public Method) + +DESCRIPTION: + Write callback + +PARAMETERS + pWriteURB [ I ] - URB this callback is run for + +RETURN VALUE: + None +===========================================================================*/ +#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,18 )) +void WriteSyncCallback( struct urb * pWriteURB ) +#else +void WriteSyncCallback(struct urb *pWriteURB, struct pt_regs *regs) +#endif +{ + if (pWriteURB == NULL) + { + DBG( "null urb\n" ); + return; + } + + DBG( "Write status/size %d/%d\n", + pWriteURB->status, + pWriteURB->actual_length ); + + // Notify that write has completed by up()-ing semeaphore + up( (struct semaphore * )pWriteURB->context ); + + return; +} + +/*=========================================================================== +METHOD: + WriteSync (Public Method) + +DESCRIPTION: + Start synchronous write + +PARAMETERS: + pDev [ I ] - Device specific memory + pWriteBuffer [ I ] - Data to be written + writeBufferSize [ I ] - Size of data to be written + clientID [ I ] - Client ID of requester + +RETURN VALUE: + int - write size (includes QMUX) + negative errno for failure +===========================================================================*/ +int WriteSync( + sGobiUSBNet * pDev, + char * pWriteBuffer, + int writeBufferSize, + u16 clientID ) +{ + int result; + struct semaphore writeSem; + struct urb * pWriteURB; + sURBSetupPacket writeSetup; + unsigned long flags; + + if (IsDeviceValid( pDev ) == false) + { + DBG( "Invalid device!\n" ); + return -ENXIO; + } + + pWriteURB = usb_alloc_urb( 0, GFP_KERNEL ); + if (pWriteURB == NULL) + { + DBG( "URB mem error\n" ); + return -ENOMEM; + } + + // Fill writeBuffer with QMUX + result = FillQMUX( clientID, pWriteBuffer, writeBufferSize ); + if (result < 0) + { + usb_free_urb( pWriteURB ); + return result; + } + + // CDC Send Encapsulated Request packet + writeSetup.mRequestType = 0x21; + writeSetup.mRequestCode = 0; + writeSetup.mValue = 0; + writeSetup.mIndex = cpu_to_le16(pDev->mpIntf->cur_altsetting->desc.bInterfaceNumber); + writeSetup.mLength = cpu_to_le16(writeBufferSize); + + // Create URB + usb_fill_control_urb( pWriteURB, + pDev->mpNetDev->udev, + usb_sndctrlpipe( pDev->mpNetDev->udev, 0 ), + (unsigned char *)&writeSetup, + (void*)pWriteBuffer, + writeBufferSize, + NULL, + pDev ); + + DBG( "Actual Write:\n" ); + PrintHex( pWriteBuffer, writeBufferSize ); + + sema_init( &writeSem, 0 ); + + pWriteURB->complete = WriteSyncCallback; + pWriteURB->context = &writeSem; + + // Wake device + result = usb_autopm_get_interface( pDev->mpIntf ); + if (result < 0) + { + DBG( "unable to resume interface: %d\n", result ); + + // Likely caused by device going from autosuspend -> full suspend + if (result == -EPERM) + { +#ifdef CONFIG_PM +#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,33 )) +#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,18 )) + pDev->mpNetDev->udev->auto_pm = 0; +#endif +#endif + GobiNetSuspend( pDev->mpIntf, PMSG_SUSPEND ); +#endif /* CONFIG_PM */ + } + usb_free_urb( pWriteURB ); + + return result; + } + + // Critical section + spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags ); + + if (AddToURBList( pDev, clientID, pWriteURB ) == false) + { + usb_free_urb( pWriteURB ); + + // End critical section + spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags ); + usb_autopm_put_interface( pDev->mpIntf ); + return -EINVAL; + } + + spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags ); + result = usb_submit_urb( pWriteURB, GFP_KERNEL ); + spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags ); + + if (result < 0) + { + DBG( "submit URB error %d\n", result ); + + // Get URB back so we can destroy it + if (PopFromURBList( pDev, clientID ) != pWriteURB) + { + // This shouldn't happen + DBG( "Didn't get write URB back\n" ); + } + + usb_free_urb( pWriteURB ); + + // End critical section + spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags ); + usb_autopm_put_interface( pDev->mpIntf ); + return result; + } + + // End critical section while we block + spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags ); + + // Wait for write to finish + if (interruptible != 0) + { + // Allow user interrupts + result = down_interruptible( &writeSem ); + } + else + { + // Ignore user interrupts + result = 0; + down( &writeSem ); + } + + // Write is done, release device + usb_autopm_put_interface( pDev->mpIntf ); + + // Verify device is still valid + if (IsDeviceValid( pDev ) == false) + { + DBG( "Invalid device!\n" ); + + usb_free_urb( pWriteURB ); + return -ENXIO; + } + + // Restart critical section + spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags ); + + // Get URB back so we can destroy it + if (PopFromURBList( pDev, clientID ) != pWriteURB) + { + // This shouldn't happen + DBG( "Didn't get write URB back\n" ); + + // End critical section + spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags ); + usb_free_urb( pWriteURB ); + return -EINVAL; + } + + // End critical section + spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags ); + + if (result == 0) + { + // Write is finished + if (pWriteURB->status == 0) + { + // Return number of bytes that were supposed to have been written, + // not size of QMI request + result = writeBufferSize; + } + else + { + DBG( "bad status = %d\n", pWriteURB->status ); + + // Return error value + result = pWriteURB->status; + } + } + else + { + // We have been forcibly interrupted + DBG( "Interrupted %d !!!\n", result ); + DBG( "Device may be in bad state and need reset !!!\n" ); + + // URB has not finished + usb_kill_urb( pWriteURB ); + } + + usb_free_urb( pWriteURB ); + + return result; +} + +/*=========================================================================*/ +// Internal memory management functions +/*=========================================================================*/ + +/*=========================================================================== +METHOD: + GetClientID (Public Method) + +DESCRIPTION: + Request a QMI client for the input service type and initialize memory + structure + +PARAMETERS: + pDev [ I ] - Device specific memory + serviceType [ I ] - Desired QMI service type + +RETURN VALUE: + int - Client ID for success (positive) + Negative errno for error +===========================================================================*/ +int GetClientID( + sGobiUSBNet * pDev, + u8 serviceType ) +{ + u16 clientID; + sClientMemList ** ppClientMem; + int result; + void * pWriteBuffer; + u16 writeBufferSize; + void * pReadBuffer; + u16 readBufferSize; + unsigned long flags; + u8 transactionID; + + if (IsDeviceValid( pDev ) == false) + { + DBG( "Invalid device!\n" ); + return -ENXIO; + } + + // Run QMI request to be asigned a Client ID + if (serviceType != 0) + { + writeBufferSize = QMICTLGetClientIDReqSize(); + pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL ); + if (pWriteBuffer == NULL) + { + return -ENOMEM; + } + + transactionID = QMIXactionIDGet( pDev ); + + result = QMICTLGetClientIDReq( pWriteBuffer, + writeBufferSize, + transactionID, + serviceType ); + if (result < 0) + { + kfree( pWriteBuffer ); + return result; + } + + + result = WriteSync( pDev, + pWriteBuffer, + writeBufferSize, + QMICTL ); + kfree( pWriteBuffer ); + + if (result < 0) + { + return result; + } + + result = ReadSync( pDev, + &pReadBuffer, + QMICTL, + transactionID ); + if (result < 0) + { + DBG( "bad read data %d\n", result ); + return result; + } + readBufferSize = result; + + result = QMICTLGetClientIDResp( pReadBuffer, + readBufferSize, + &clientID ); + + /* Upon return from QMICTLGetClientIDResp, clientID + * low address contains the Service Number (SN), and + * clientID high address contains Client Number (CN) + * For the ReadCallback to function correctly,we swap + * the SN and CN on a Big Endian architecture. + */ + clientID = le16_to_cpu(clientID); + + kfree( pReadBuffer ); + + if (result < 0) + { + return result; + } + } + else + { + // QMI CTL will always have client ID 0 + clientID = 0; + } + + // Critical section + spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags ); + + // Verify client is not already allocated + if (FindClientMem( pDev, clientID ) != NULL) + { + DBG( "Client memory already exists\n" ); + + // End Critical section + spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags ); + return -ETOOMANYREFS; + } + + // Go to last entry in client mem list + ppClientMem = &pDev->mQMIDev.mpClientMemList; + while (*ppClientMem != NULL) + { + ppClientMem = &(*ppClientMem)->mpNext; + } + + // Create locations for read to place data into + *ppClientMem = kmalloc( sizeof( sClientMemList ), GFP_ATOMIC ); + if (*ppClientMem == NULL) + { + DBG( "Error allocating read list\n" ); + + // End critical section + spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags ); + return -ENOMEM; + } + + (*ppClientMem)->mClientID = clientID; + (*ppClientMem)->mpList = NULL; + (*ppClientMem)->mpReadNotifyList = NULL; + (*ppClientMem)->mpURBList = NULL; + (*ppClientMem)->mpNext = NULL; + + // Initialize workqueue for poll() + init_waitqueue_head( &(*ppClientMem)->mWaitQueue ); + + // End Critical section + spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags ); + + return (int)( (*ppClientMem)->mClientID ); +} + +/*=========================================================================== +METHOD: + ReleaseClientID (Public Method) + +DESCRIPTION: + Release QMI client and free memory + +PARAMETERS: + pDev [ I ] - Device specific memory + clientID [ I ] - Requester's client ID + +RETURN VALUE: + None +===========================================================================*/ +void ReleaseClientID( + sGobiUSBNet * pDev, + u16 clientID ) +{ + int result; + sClientMemList ** ppDelClientMem; + sClientMemList * pNextClientMem; + struct urb * pDelURB; + void * pDelData; + u16 dataSize; + void * pWriteBuffer; + u16 writeBufferSize; + void * pReadBuffer; + u16 readBufferSize; + unsigned long flags; + u8 transactionID; + + // Is device is still valid? + if (IsDeviceValid( pDev ) == false) + { + DBG( "invalid device\n" ); + return; + } + + DBG( "releasing 0x%04X\n", clientID ); + + // Run QMI ReleaseClientID if this isn't QMICTL + if (clientID != QMICTL) + { + // Note: all errors are non fatal, as we always want to delete + // client memory in latter part of function + + writeBufferSize = QMICTLReleaseClientIDReqSize(); + pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL ); + if (pWriteBuffer == NULL) + { + DBG( "memory error\n" ); + } + else + { + transactionID = QMIXactionIDGet( pDev ); + + result = QMICTLReleaseClientIDReq( pWriteBuffer, + writeBufferSize, + transactionID, + clientID ); + if (result < 0) + { + kfree( pWriteBuffer ); + DBG( "error %d filling req buffer\n", result ); + } + else + { + result = WriteSync( pDev, + pWriteBuffer, + writeBufferSize, + QMICTL ); + kfree( pWriteBuffer ); + + if (result < 0) + { + DBG( "bad write status %d\n", result ); + } + else + { + result = ReadSync( pDev, + &pReadBuffer, + QMICTL, + transactionID ); + if (result < 0) + { + DBG( "bad read status %d\n", result ); + } + else + { + readBufferSize = result; + + result = QMICTLReleaseClientIDResp( pReadBuffer, + readBufferSize ); + kfree( pReadBuffer ); + + if (result < 0) + { + DBG( "error %d parsing response\n", result ); + } + } + } + } + } + } + + // Cleaning up client memory + + // Critical section + spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags ); + + // Can't use FindClientMem, I need to keep pointer of previous + ppDelClientMem = &pDev->mQMIDev.mpClientMemList; + while (*ppDelClientMem != NULL) + { + if ((*ppDelClientMem)->mClientID == clientID) + { + pNextClientMem = (*ppDelClientMem)->mpNext; + + // Notify all clients + while (NotifyAndPopNotifyList( pDev, + clientID, + 0 ) == true ); + + // Kill and free all URB's + pDelURB = PopFromURBList( pDev, clientID ); + while (pDelURB != NULL) + { + usb_kill_urb( pDelURB ); + usb_free_urb( pDelURB ); + pDelURB = PopFromURBList( pDev, clientID ); + } + + // Free any unread data + while (PopFromReadMemList( pDev, + clientID, + 0, + &pDelData, + &dataSize ) == true ) + { + kfree( pDelData ); + } + + // Delete client Mem + if (!waitqueue_active( &(*ppDelClientMem)->mWaitQueue)) + kfree( *ppDelClientMem ); + else + DBG("memory leak!\n"); + + // Overwrite the pointer that was to this client mem + *ppDelClientMem = pNextClientMem; + } + else + { + // I now point to (a pointer of ((the node I was at)'s mpNext)) + ppDelClientMem = &(*ppDelClientMem)->mpNext; + } + } + + // End Critical section + spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags ); + + return; +} + +/*=========================================================================== +METHOD: + FindClientMem (Public Method) + +DESCRIPTION: + Find this client's memory + + Caller MUST have lock on mClientMemLock + +PARAMETERS: + pDev [ I ] - Device specific memory + clientID [ I ] - Requester's client ID + +RETURN VALUE: + sClientMemList - Pointer to requested sClientMemList for success + NULL for error +===========================================================================*/ +sClientMemList * FindClientMem( + sGobiUSBNet * pDev, + u16 clientID ) +{ + sClientMemList * pClientMem; + + if (IsDeviceValid( pDev ) == false) + { + DBG( "Invalid device\n" ); + return NULL; + } + +#ifdef CONFIG_SMP + // Verify Lock + if (spin_is_locked( &pDev->mQMIDev.mClientMemLock ) == 0) + { + DBG( "unlocked\n" ); + BUG(); + } +#endif + + pClientMem = pDev->mQMIDev.mpClientMemList; + while (pClientMem != NULL) + { + if (pClientMem->mClientID == clientID) + { + // Success + VDBG("Found client's 0x%x memory\n", clientID); + return pClientMem; + } + + pClientMem = pClientMem->mpNext; + } + + DBG( "Could not find client mem 0x%04X\n", clientID ); + return NULL; +} + +/*=========================================================================== +METHOD: + AddToReadMemList (Public Method) + +DESCRIPTION: + Add Data to this client's ReadMem list + + Caller MUST have lock on mClientMemLock + +PARAMETERS: + pDev [ I ] - Device specific memory + clientID [ I ] - Requester's client ID + transactionID [ I ] - Transaction ID or 0 for any + pData [ I ] - Data to add + dataSize [ I ] - Size of data to add + +RETURN VALUE: + bool +===========================================================================*/ +bool AddToReadMemList( + sGobiUSBNet * pDev, + u16 clientID, + u16 transactionID, + void * pData, + u16 dataSize ) +{ + sClientMemList * pClientMem; + sReadMemList ** ppThisReadMemList; + +#ifdef CONFIG_SMP + // Verify Lock + if (spin_is_locked( &pDev->mQMIDev.mClientMemLock ) == 0) + { + DBG( "unlocked\n" ); + BUG(); + } +#endif + + // Get this client's memory location + pClientMem = FindClientMem( pDev, clientID ); + if (pClientMem == NULL) + { + DBG( "Could not find this client's memory 0x%04X\n", + clientID ); + + return false; + } + + // Go to last ReadMemList entry + ppThisReadMemList = &pClientMem->mpList; + while (*ppThisReadMemList != NULL) + { + ppThisReadMemList = &(*ppThisReadMemList)->mpNext; + } + + *ppThisReadMemList = kmalloc( sizeof( sReadMemList ), GFP_ATOMIC ); + if (*ppThisReadMemList == NULL) + { + DBG( "Mem error\n" ); + + return false; + } + + (*ppThisReadMemList)->mpNext = NULL; + (*ppThisReadMemList)->mpData = pData; + (*ppThisReadMemList)->mDataSize = dataSize; + (*ppThisReadMemList)->mTransactionID = transactionID; + + return true; +} + +/*=========================================================================== +METHOD: + PopFromReadMemList (Public Method) + +DESCRIPTION: + Remove data from this client's ReadMem list if it matches + the specified transaction ID. + + Caller MUST have lock on mClientMemLock + +PARAMETERS: + pDev [ I ] - Device specific memory + clientID [ I ] - Requester's client ID + transactionID [ I ] - Transaction ID or 0 for any + ppData [I/O] - On success, will be filled with a + pointer to read buffer + pDataSize [I/O] - On succces, will be filled with the + read buffer's size + +RETURN VALUE: + bool +===========================================================================*/ +bool PopFromReadMemList( + sGobiUSBNet * pDev, + u16 clientID, + u16 transactionID, + void ** ppData, + u16 * pDataSize ) +{ + sClientMemList * pClientMem; + sReadMemList * pDelReadMemList, ** ppReadMemList; + +#ifdef CONFIG_SMP + // Verify Lock + if (spin_is_locked( &pDev->mQMIDev.mClientMemLock ) == 0) + { + DBG( "unlocked\n" ); + BUG(); + } +#endif + + // Get this client's memory location + pClientMem = FindClientMem( pDev, clientID ); + if (pClientMem == NULL) + { + DBG( "Could not find this client's memory 0x%04X\n", + clientID ); + + return false; + } + + ppReadMemList = &(pClientMem->mpList); + pDelReadMemList = NULL; + + // Find first message that matches this transaction ID + while (*ppReadMemList != NULL) + { + // Do we care about transaction ID? + if (transactionID == 0 + || transactionID == (*ppReadMemList)->mTransactionID ) + { + pDelReadMemList = *ppReadMemList; + VDBG( "*ppReadMemList = 0x%p pDelReadMemList = 0x%p\n", + *ppReadMemList, pDelReadMemList ); + break; + } + + VDBG( "skipping 0x%04X data TID = %x\n", clientID, (*ppReadMemList)->mTransactionID ); + + // Next + ppReadMemList = &(*ppReadMemList)->mpNext; + } + VDBG( "*ppReadMemList = 0x%p pDelReadMemList = 0x%p\n", + *ppReadMemList, pDelReadMemList ); + if (pDelReadMemList != NULL) + { + *ppReadMemList = (*ppReadMemList)->mpNext; + + // Copy to output + *ppData = pDelReadMemList->mpData; + *pDataSize = pDelReadMemList->mDataSize; + VDBG( "*ppData = 0x%p pDataSize = %u\n", + *ppData, *pDataSize ); + + // Free memory + kfree( pDelReadMemList ); + + return true; + } + else + { + DBG( "No read memory to pop, Client 0x%04X, TID = %x\n", + clientID, + transactionID ); + return false; + } +} + +/*=========================================================================== +METHOD: + AddToNotifyList (Public Method) + +DESCRIPTION: + Add Notify entry to this client's notify List + + Caller MUST have lock on mClientMemLock + +PARAMETERS: + pDev [ I ] - Device specific memory + clientID [ I ] - Requester's client ID + transactionID [ I ] - Transaction ID or 0 for any + pNotifyFunct [ I ] - Callback function to be run when data is available + pData [ I ] - Data buffer that willl be passed (unmodified) + to callback + +RETURN VALUE: + bool +===========================================================================*/ +bool AddToNotifyList( + sGobiUSBNet * pDev, + u16 clientID, + u16 transactionID, + void (* pNotifyFunct)(sGobiUSBNet *, u16, void *), + void * pData ) +{ + sClientMemList * pClientMem; + sNotifyList ** ppThisNotifyList; + +#ifdef CONFIG_SMP + // Verify Lock + if (spin_is_locked( &pDev->mQMIDev.mClientMemLock ) == 0) + { + DBG( "unlocked\n" ); + BUG(); + } +#endif + + // Get this client's memory location + pClientMem = FindClientMem( pDev, clientID ); + if (pClientMem == NULL) + { + DBG( "Could not find this client's memory 0x%04X\n", clientID ); + return false; + } + + // Go to last URBList entry + ppThisNotifyList = &pClientMem->mpReadNotifyList; + while (*ppThisNotifyList != NULL) + { + ppThisNotifyList = &(*ppThisNotifyList)->mpNext; + } + + *ppThisNotifyList = kmalloc( sizeof( sNotifyList ), GFP_ATOMIC ); + if (*ppThisNotifyList == NULL) + { + DBG( "Mem error\n" ); + return false; + } + + (*ppThisNotifyList)->mpNext = NULL; + (*ppThisNotifyList)->mpNotifyFunct = pNotifyFunct; + (*ppThisNotifyList)->mpData = pData; + (*ppThisNotifyList)->mTransactionID = transactionID; + + return true; +} + +/*=========================================================================== +METHOD: + NotifyAndPopNotifyList (Public Method) + +DESCRIPTION: + Remove first Notify entry from this client's notify list + and Run function + + Caller MUST have lock on mClientMemLock + +PARAMETERS: + pDev [ I ] - Device specific memory + clientID [ I ] - Requester's client ID + transactionID [ I ] - Transaction ID or 0 for any + +RETURN VALUE: + bool +===========================================================================*/ +bool NotifyAndPopNotifyList( + sGobiUSBNet * pDev, + u16 clientID, + u16 transactionID ) +{ + sClientMemList * pClientMem; + sNotifyList * pDelNotifyList, ** ppNotifyList; + +#ifdef CONFIG_SMP + // Verify Lock + if (spin_is_locked( &pDev->mQMIDev.mClientMemLock ) == 0) + { + DBG( "unlocked\n" ); + BUG(); + } +#endif + + // Get this client's memory location + pClientMem = FindClientMem( pDev, clientID ); + if (pClientMem == NULL) + { + DBG( "Could not find this client's memory 0x%04X\n", clientID ); + return false; + } + + ppNotifyList = &(pClientMem->mpReadNotifyList); + pDelNotifyList = NULL; + + // Remove from list + while (*ppNotifyList != NULL) + { + // Do we care about transaction ID? + if (transactionID == 0 + || (*ppNotifyList)->mTransactionID == 0 + || transactionID == (*ppNotifyList)->mTransactionID) + { + pDelNotifyList = *ppNotifyList; + break; + } + + DBG( "skipping data TID = %x\n", (*ppNotifyList)->mTransactionID ); + + // next + ppNotifyList = &(*ppNotifyList)->mpNext; + } + + if (pDelNotifyList != NULL) + { + // Remove element + *ppNotifyList = (*ppNotifyList)->mpNext; + + // Run notification function + if (pDelNotifyList->mpNotifyFunct != NULL) + { + // Unlock for callback + spin_unlock( &pDev->mQMIDev.mClientMemLock ); + + pDelNotifyList->mpNotifyFunct( pDev, + clientID, + pDelNotifyList->mpData ); + + // Restore lock + spin_lock( &pDev->mQMIDev.mClientMemLock ); + } + + // Delete memory + kfree( pDelNotifyList ); + + return true; + } + else + { + DBG( "no one to notify for TID %x\n", transactionID ); + + return false; + } +} + +/*=========================================================================== +METHOD: + AddToURBList (Public Method) + +DESCRIPTION: + Add URB to this client's URB list + + Caller MUST have lock on mClientMemLock + +PARAMETERS: + pDev [ I ] - Device specific memory + clientID [ I ] - Requester's client ID + pURB [ I ] - URB to be added + +RETURN VALUE: + bool +===========================================================================*/ +bool AddToURBList( + sGobiUSBNet * pDev, + u16 clientID, + struct urb * pURB ) +{ + sClientMemList * pClientMem; + sURBList ** ppThisURBList; + +#ifdef CONFIG_SMP + // Verify Lock + if (spin_is_locked( &pDev->mQMIDev.mClientMemLock ) == 0) + { + DBG( "unlocked\n" ); + BUG(); + } +#endif + + // Get this client's memory location + pClientMem = FindClientMem( pDev, clientID ); + if (pClientMem == NULL) + { + DBG( "Could not find this client's memory 0x%04X\n", clientID ); + return false; + } + + // Go to last URBList entry + ppThisURBList = &pClientMem->mpURBList; + while (*ppThisURBList != NULL) + { + ppThisURBList = &(*ppThisURBList)->mpNext; + } + + *ppThisURBList = kmalloc( sizeof( sURBList ), GFP_ATOMIC ); + if (*ppThisURBList == NULL) + { + DBG( "Mem error\n" ); + return false; + } + + (*ppThisURBList)->mpNext = NULL; + (*ppThisURBList)->mpURB = pURB; + + return true; +} + +/*=========================================================================== +METHOD: + PopFromURBList (Public Method) + +DESCRIPTION: + Remove URB from this client's URB list + + Caller MUST have lock on mClientMemLock + +PARAMETERS: + pDev [ I ] - Device specific memory + clientID [ I ] - Requester's client ID + +RETURN VALUE: + struct urb - Pointer to requested client's URB + NULL for error +===========================================================================*/ +struct urb * PopFromURBList( + sGobiUSBNet * pDev, + u16 clientID ) +{ + sClientMemList * pClientMem; + sURBList * pDelURBList; + struct urb * pURB; + +#ifdef CONFIG_SMP + // Verify Lock + if (spin_is_locked( &pDev->mQMIDev.mClientMemLock ) == 0) + { + DBG( "unlocked\n" ); + BUG(); + } +#endif + + // Get this client's memory location + pClientMem = FindClientMem( pDev, clientID ); + if (pClientMem == NULL) + { + DBG( "Could not find this client's memory 0x%04X\n", clientID ); + return NULL; + } + + // Remove from list + if (pClientMem->mpURBList != NULL) + { + pDelURBList = pClientMem->mpURBList; + pClientMem->mpURBList = pClientMem->mpURBList->mpNext; + + // Copy to output + pURB = pDelURBList->mpURB; + + // Delete memory + kfree( pDelURBList ); + + return pURB; + } + else + { + DBG( "No URB's to pop\n" ); + + return NULL; + } +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION( 3,19,0 )) +#ifndef f_dentry +#define f_dentry f_path.dentry +#endif +#endif + +/*=========================================================================*/ +// Internal userspace wrappers +/*=========================================================================*/ + +/*=========================================================================== +METHOD: + UserspaceunlockedIOCTL (Public Method) + +DESCRIPTION: + Internal wrapper for Userspace IOCTL interface + +PARAMETERS + pFilp [ I ] - userspace file descriptor + cmd [ I ] - IOCTL command + arg [ I ] - IOCTL argument + +RETURN VALUE: + long - 0 for success + Negative errno for failure +===========================================================================*/ +long UserspaceunlockedIOCTL( + struct file * pFilp, + unsigned int cmd, + unsigned long arg ) +{ + int result; + u32 devVIDPID; + + sQMIFilpStorage * pFilpData = (sQMIFilpStorage *)pFilp->private_data; + + if (pFilpData == NULL) + { + DBG( "Bad file data\n" ); + return -EBADF; + } + + if (IsDeviceValid( pFilpData->mpDev ) == false) + { + DBG( "Invalid device! Updating f_ops\n" ); + pFilp->f_op = pFilp->f_dentry->d_inode->i_fop; + return -ENXIO; + } + + switch (cmd) + { + case IOCTL_QMI_GET_SERVICE_FILE: + DBG( "Setting up QMI for service %lu\n", arg ); + if ((u8)arg == 0) + { + DBG( "Cannot use QMICTL from userspace\n" ); + return -EINVAL; + } + + // Connection is already setup + if (pFilpData->mClientID != (u16)-1) + { + DBG( "Close the current connection before opening a new one\n" ); + return -EBADR; + } + + result = GetClientID( pFilpData->mpDev, (u8)arg ); +// it seems QMIWDA only allow one client, if the last quectel-CM donot realese it (killed by SIGKILL). +// can force release it at here +#if 1 + if (result < 0 && (u8)arg == QMIWDA) + { + ReleaseClientID( pFilpData->mpDev, QMIWDA | (1 << 8) ); + result = GetClientID( pFilpData->mpDev, (u8)arg ); + } +#endif + if (result < 0) + { + return result; + } + pFilpData->mClientID = (u16)result; + DBG("pFilpData->mClientID = 0x%x\n", pFilpData->mClientID ); + return 0; + break; + + + case IOCTL_QMI_GET_DEVICE_VIDPID: + if (arg == 0) + { + DBG( "Bad VIDPID buffer\n" ); + return -EINVAL; + } + + // Extra verification + if (pFilpData->mpDev->mpNetDev == 0) + { + DBG( "Bad mpNetDev\n" ); + return -ENOMEM; + } + if (pFilpData->mpDev->mpNetDev->udev == 0) + { + DBG( "Bad udev\n" ); + return -ENOMEM; + } + + devVIDPID = ((le16_to_cpu( pFilpData->mpDev->mpNetDev->udev->descriptor.idVendor ) << 16) + + le16_to_cpu( pFilpData->mpDev->mpNetDev->udev->descriptor.idProduct ) ); + + result = copy_to_user( (unsigned int *)arg, &devVIDPID, 4 ); + if (result != 0) + { + DBG( "Copy to userspace failure %d\n", result ); + } + + return result; + + break; + + case IOCTL_QMI_GET_DEVICE_MEID: + if (arg == 0) + { + DBG( "Bad MEID buffer\n" ); + return -EINVAL; + } + + result = copy_to_user( (unsigned int *)arg, &pFilpData->mpDev->mMEID[0], 14 ); + if (result != 0) + { + DBG( "Copy to userspace failure %d\n", result ); + } + + return result; + + break; + + default: + return -EBADRQC; + } +} + +/*=========================================================================*/ +// Userspace wrappers +/*=========================================================================*/ + +/*=========================================================================== +METHOD: + UserspaceOpen (Public Method) + +DESCRIPTION: + Userspace open + IOCTL must be called before reads or writes + +PARAMETERS + pInode [ I ] - kernel file descriptor + pFilp [ I ] - userspace file descriptor + +RETURN VALUE: + int - 0 for success + Negative errno for failure +===========================================================================*/ +int UserspaceOpen( + struct inode * pInode, + struct file * pFilp ) +{ + sQMIFilpStorage * pFilpData; + + // Optain device pointer from pInode + sQMIDev * pQMIDev = container_of( pInode->i_cdev, + sQMIDev, + mCdev ); + sGobiUSBNet * pDev = container_of( pQMIDev, + sGobiUSBNet, + mQMIDev ); + + if (pDev->mbMdm9x07) + { + atomic_inc(&pDev->refcount); + if (!pDev->mbQMIReady) { + if (wait_for_completion_interruptible_timeout(&pDev->mQMIReadyCompletion, 15*HZ) <= 0) { + if (atomic_dec_and_test(&pDev->refcount)) { + kfree( pDev ); + } + return -ETIMEDOUT; + } + } + atomic_dec(&pDev->refcount); + } + + if (IsDeviceValid( pDev ) == false) + { + DBG( "Invalid device\n" ); + return -ENXIO; + } + + // Setup data in pFilp->private_data + pFilp->private_data = kmalloc( sizeof( sQMIFilpStorage ), GFP_KERNEL ); + if (pFilp->private_data == NULL) + { + DBG( "Mem error\n" ); + return -ENOMEM; + } + + pFilpData = (sQMIFilpStorage *)pFilp->private_data; + pFilpData->mClientID = (u16)-1; + atomic_inc(&pDev->refcount); + pFilpData->mpDev = pDev; + + return 0; +} + +/*=========================================================================== +METHOD: + UserspaceIOCTL (Public Method) + +DESCRIPTION: + Userspace IOCTL functions + +PARAMETERS + pUnusedInode [ I ] - (unused) kernel file descriptor + pFilp [ I ] - userspace file descriptor + cmd [ I ] - IOCTL command + arg [ I ] - IOCTL argument + +RETURN VALUE: + int - 0 for success + Negative errno for failure +===========================================================================*/ +int UserspaceIOCTL( + struct inode * pUnusedInode, + struct file * pFilp, + unsigned int cmd, + unsigned long arg ) +{ + // call the internal wrapper function + return (int)UserspaceunlockedIOCTL( pFilp, cmd, arg ); +} + +/*=========================================================================== +METHOD: + UserspaceClose (Public Method) + +DESCRIPTION: + Userspace close + Release client ID and free memory + +PARAMETERS + pFilp [ I ] - userspace file descriptor + unusedFileTable [ I ] - (unused) file table + +RETURN VALUE: + int - 0 for success + Negative errno for failure +===========================================================================*/ +#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,14 )) +int UserspaceClose( + struct file * pFilp, + fl_owner_t unusedFileTable ) +#else +int UserspaceClose( struct file * pFilp ) +#endif +{ + sQMIFilpStorage * pFilpData = (sQMIFilpStorage *)pFilp->private_data; + struct task_struct * pEachTask; + struct fdtable * pFDT; + int count = 0; + int used = 0; + unsigned long flags; + + if (pFilpData == NULL) + { + DBG( "bad file data\n" ); + return -EBADF; + } + + // Fallthough. If f_count == 1 no need to do more checks +#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,24 )) + if (atomic_read( &pFilp->f_count ) != 1) +#else + if (atomic_long_read( &pFilp->f_count ) != 1) +#endif + { + rcu_read_lock(); + for_each_process( pEachTask ) + { + task_lock(pEachTask); + if (pEachTask == NULL || pEachTask->files == NULL) + { + // Some tasks may not have files (e.g. Xsession) + task_unlock(pEachTask); + continue; + } + spin_lock_irqsave( &pEachTask->files->file_lock, flags ); + task_unlock(pEachTask); //kernel/exit.c:do_exit() -> fs/file.c:exit_files() + pFDT = files_fdtable( pEachTask->files ); + for (count = 0; count < pFDT->max_fds; count++) + { + // Before this function was called, this file was removed + // from our task's file table so if we find it in a file + // table then it is being used by another task + if (pFDT->fd[count] == pFilp) + { + used++; + break; + } + } + spin_unlock_irqrestore( &pEachTask->files->file_lock, flags ); + } + rcu_read_unlock(); + + if (used > 0) + { + DBG( "not closing, as this FD is open by %d other process\n", used ); + return 0; + } + } + + if (IsDeviceValid( pFilpData->mpDev ) == false) + { + DBG( "Invalid device! Updating f_ops\n" ); + pFilp->f_op = pFilp->f_dentry->d_inode->i_fop; + return -ENXIO; + } + + DBG( "0x%04X\n", pFilpData->mClientID ); + + // Disable pFilpData so they can't keep sending read or write + // should this function hang + // Note: memory pointer is still saved in pFilpData to be deleted later + pFilp->private_data = NULL; + + if (pFilpData->mClientID != (u16)-1) + { + if (pFilpData->mpDev->mbDeregisterQMIDevice) + pFilpData->mClientID = (u16)-1; //DeregisterQMIDevice() will release this ClientID + else + ReleaseClientID( pFilpData->mpDev, + pFilpData->mClientID ); + } + atomic_dec(&pFilpData->mpDev->refcount); + + kfree( pFilpData ); + return 0; +} + +/*=========================================================================== +METHOD: + UserspaceRead (Public Method) + +DESCRIPTION: + Userspace read (synchronous) + +PARAMETERS + pFilp [ I ] - userspace file descriptor + pBuf [ I ] - read buffer + size [ I ] - size of read buffer + pUnusedFpos [ I ] - (unused) file position + +RETURN VALUE: + ssize_t - Number of bytes read for success + Negative errno for failure +===========================================================================*/ +ssize_t UserspaceRead( + struct file * pFilp, + char __user * pBuf, + size_t size, + loff_t * pUnusedFpos ) +{ + int result; + void * pReadData = NULL; + void * pSmallReadData; + sQMIFilpStorage * pFilpData = (sQMIFilpStorage *)pFilp->private_data; + + if (pFilpData == NULL) + { + DBG( "Bad file data\n" ); + return -EBADF; + } + + if (IsDeviceValid( pFilpData->mpDev ) == false) + { + DBG( "Invalid device! Updating f_ops\n" ); + pFilp->f_op = pFilp->f_dentry->d_inode->i_fop; + return -ENXIO; + } + + if (pFilpData->mClientID == (u16)-1) + { + DBG( "Client ID must be set before reading 0x%04X\n", + pFilpData->mClientID ); + return -EBADR; + } + + // Perform synchronous read + result = ReadSync( pFilpData->mpDev, + &pReadData, + pFilpData->mClientID, + 0 ); + if (result <= 0) + { + return result; + } + + // Discard QMUX header + result -= QMUXHeaderSize(); + pSmallReadData = pReadData + QMUXHeaderSize(); + + if (result > size) + { + DBG( "Read data is too large for amount user has requested\n" ); + kfree( pReadData ); + return -EOVERFLOW; + } + + DBG( "pBuf = 0x%p pSmallReadData = 0x%p, result = %d", + pBuf, pSmallReadData, result ); + + if (copy_to_user( pBuf, pSmallReadData, result ) != 0) + { + DBG( "Error copying read data to user\n" ); + result = -EFAULT; + } + + // Reader is responsible for freeing read buffer + kfree( pReadData ); + + return result; +} + +/*=========================================================================== +METHOD: + UserspaceWrite (Public Method) + +DESCRIPTION: + Userspace write (synchronous) + +PARAMETERS + pFilp [ I ] - userspace file descriptor + pBuf [ I ] - write buffer + size [ I ] - size of write buffer + pUnusedFpos [ I ] - (unused) file position + +RETURN VALUE: + ssize_t - Number of bytes read for success + Negative errno for failure +===========================================================================*/ +ssize_t UserspaceWrite( + struct file * pFilp, + const char __user * pBuf, + size_t size, + loff_t * pUnusedFpos ) +{ + int status; + void * pWriteBuffer; + sQMIFilpStorage * pFilpData = (sQMIFilpStorage *)pFilp->private_data; + + if (pFilpData == NULL) + { + DBG( "Bad file data\n" ); + return -EBADF; + } + + if (IsDeviceValid( pFilpData->mpDev ) == false) + { + DBG( "Invalid device! Updating f_ops\n" ); + pFilp->f_op = pFilp->f_dentry->d_inode->i_fop; + return -ENXIO; + } + + if (pFilpData->mClientID == (u16)-1) + { + DBG( "Client ID must be set before writing 0x%04X\n", + pFilpData->mClientID ); + return -EBADR; + } + + // Copy data from user to kernel space + pWriteBuffer = kmalloc( size + QMUXHeaderSize(), GFP_KERNEL ); + if (pWriteBuffer == NULL) + { + return -ENOMEM; + } + status = copy_from_user( pWriteBuffer + QMUXHeaderSize(), pBuf, size ); + if (status != 0) + { + DBG( "Unable to copy data from userspace %d\n", status ); + kfree( pWriteBuffer ); + return status; + } + + status = WriteSync( pFilpData->mpDev, + pWriteBuffer, + size + QMUXHeaderSize(), + pFilpData->mClientID ); + + kfree( pWriteBuffer ); + + // On success, return requested size, not full QMI reqest size + if (status == size + QMUXHeaderSize()) + { + return size; + } + else + { + return status; + } +} + +/*=========================================================================== +METHOD: + UserspacePoll (Public Method) + +DESCRIPTION: + Used to determine if read/write operations are possible without blocking + +PARAMETERS + pFilp [ I ] - userspace file descriptor + pPollTable [I/O] - Wait object to notify the kernel when data + is ready + +RETURN VALUE: + unsigned int - bitmask of what operations can be done immediately +===========================================================================*/ +unsigned int UserspacePoll( + struct file * pFilp, + struct poll_table_struct * pPollTable ) +{ + sQMIFilpStorage * pFilpData = (sQMIFilpStorage *)pFilp->private_data; + sClientMemList * pClientMem; + unsigned long flags; + + // Always ready to write + unsigned long status = POLLOUT | POLLWRNORM; + + if (pFilpData == NULL) + { + DBG( "Bad file data\n" ); + return POLLERR; + } + + if (IsDeviceValid( pFilpData->mpDev ) == false) + { + DBG( "Invalid device! Updating f_ops\n" ); + pFilp->f_op = pFilp->f_dentry->d_inode->i_fop; + return POLLERR; + } + + if (pFilpData->mpDev->mbDeregisterQMIDevice) + { + DBG( "DeregisterQMIDevice ing\n" ); + return POLLHUP | POLLERR; + } + + if (pFilpData->mClientID == (u16)-1) + { + DBG( "Client ID must be set before polling 0x%04X\n", + pFilpData->mClientID ); + return POLLERR; + } + + // Critical section + spin_lock_irqsave( &pFilpData->mpDev->mQMIDev.mClientMemLock, flags ); + + // Get this client's memory location + pClientMem = FindClientMem( pFilpData->mpDev, + pFilpData->mClientID ); + if (pClientMem == NULL) + { + DBG( "Could not find this client's memory 0x%04X\n", + pFilpData->mClientID ); + + spin_unlock_irqrestore( &pFilpData->mpDev->mQMIDev.mClientMemLock, + flags ); + return POLLERR; + } + + poll_wait( pFilp, &pClientMem->mWaitQueue, pPollTable ); + + if (pClientMem->mpList != NULL) + { + status |= POLLIN | POLLRDNORM; + } + + // End critical section + spin_unlock_irqrestore( &pFilpData->mpDev->mQMIDev.mClientMemLock, flags ); + + // Always ready to write + return (status | POLLOUT | POLLWRNORM); +} + +/*=========================================================================*/ +// Initializer and destructor +/*=========================================================================*/ +int QMICTLSyncProc(sGobiUSBNet *pDev) +{ + void *pWriteBuffer; + void *pReadBuffer; + int result; + u16 writeBufferSize; + u16 readBufferSize; + u8 transactionID; + unsigned long flags; + + if (IsDeviceValid( pDev ) == false) + { + DBG( "Invalid device\n" ); + return -EFAULT; + } + + writeBufferSize= QMICTLSyncReqSize(); + pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL ); + if (pWriteBuffer == NULL) + { + return -ENOMEM; + } + + transactionID = QMIXactionIDGet(pDev); + + /* send a QMI_CTL_SYNC_REQ (0x0027) */ + result = QMICTLSyncReq( pWriteBuffer, + writeBufferSize, + transactionID ); + if (result < 0) + { + kfree( pWriteBuffer ); + return result; + } + + result = WriteSync( pDev, + pWriteBuffer, + writeBufferSize, + QMICTL ); + + if (result < 0) + { + kfree( pWriteBuffer ); + return result; + } + + // QMI CTL Sync Response + result = ReadSync( pDev, + &pReadBuffer, + QMICTL, + transactionID ); + if (result < 0) + { + return result; + } + + result = QMICTLSyncResp( pReadBuffer, + (u16)result ); + + kfree( pReadBuffer ); + + if (result < 0) /* need to re-sync */ + { + DBG( "sync response error code %d\n", result ); + /* start timer and wait for the response */ + /* process response */ + return result; + } + +#if 1 //free these ununsed qmi response, or when these transactionID re-used, they will be regarded as qmi response of the qmi request that have same transactionID + // Enter critical section + spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags ); + + // Free any unread data + while (PopFromReadMemList( pDev, QMICTL, 0, &pReadBuffer, &readBufferSize) == true) { + kfree( pReadBuffer ); + } + + // End critical section + spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags ); +#endif + + // Success + return 0; +} + +static int qmi_sync_thread(void *data) { + sGobiUSBNet * pDev = (sGobiUSBNet *)data; + int result = 0; + +#if 1 + // Device is not ready for QMI connections right away + // Wait up to 30 seconds before failing + if (QMIReady( pDev, 30000 ) == false) + { + DBG( "Device unresponsive to QMI\n" ); + goto __qmi_sync_finished; + } + + // Initiate QMI CTL Sync Procedure + DBG( "Sending QMI CTL Sync Request\n" ); + result = QMICTLSyncProc(pDev); + if (result != 0) + { + DBG( "QMI CTL Sync Procedure Error\n" ); + goto __qmi_sync_finished; + } + else + { + DBG( "QMI CTL Sync Procedure Successful\n" ); + } + + // Setup Data Format + result = QMIWDASetDataFormat (pDev); + if (result != 0) + { + goto __qmi_sync_finished; + } + + // Setup WDS callback + result = SetupQMIWDSCallback( pDev ); + if (result != 0) + { + goto __qmi_sync_finished; + } + + // Fill MEID for device + result = QMIDMSGetMEID( pDev ); + if (result != 0) + { + goto __qmi_sync_finished; + } +#endif + +__qmi_sync_finished: + pDev->mbQMIReady = true; + complete_all(&pDev->mQMIReadyCompletion); + if (atomic_dec_and_test(&pDev->refcount)) { + kfree( pDev ); + } + return result; +} + +/*=========================================================================== +METHOD: + RegisterQMIDevice (Public Method) + +DESCRIPTION: + QMI Device initialization function + +PARAMETERS: + pDev [ I ] - Device specific memory + +RETURN VALUE: + int - 0 for success + Negative errno for failure +===========================================================================*/ +int RegisterQMIDevice( sGobiUSBNet * pDev ) +{ + int result; + int GobiQMIIndex = 0; + dev_t devno; + char * pDevName; + + if (pDev->mQMIDev.mbCdevIsInitialized == true) + { + // Should never happen, but always better to check + DBG( "device already exists\n" ); + return -EEXIST; + } + + pDev->mbQMIValid = true; + pDev->mbDeregisterQMIDevice = false; + + // Set up for QMICTL + // (does not send QMI message, just sets up memory) + result = GetClientID( pDev, QMICTL ); + if (result != 0) + { + pDev->mbQMIValid = false; + return result; + } + atomic_set( &pDev->mQMIDev.mQMICTLTransactionID, 1 ); + + // Start Async reading + result = StartRead( pDev ); + if (result != 0) + { + pDev->mbQMIValid = false; + return result; + } + + if (pDev->mbMdm9x07) + { + usb_control_msg( pDev->mpNetDev->udev, + usb_sndctrlpipe( pDev->mpNetDev->udev, 0 ), + SET_CONTROL_LINE_STATE_REQUEST, + SET_CONTROL_LINE_STATE_REQUEST_TYPE, + CONTROL_DTR, + /* USB interface number to receive control message */ + pDev->mpIntf->cur_altsetting->desc.bInterfaceNumber, + NULL, + 0, + 100 ); + } + + //for EC21&25, must wait about 15 seconds to wait QMI ready. it is too long for driver probe(will block other drivers probe). + if (pDev->mbMdm9x07) + { + struct task_struct *qmi_sync_task; + atomic_inc(&pDev->refcount); + init_completion(&pDev->mQMIReadyCompletion); + pDev->mbQMIReady = false; + qmi_sync_task = kthread_run(qmi_sync_thread, (void *)pDev, "qmi_sync/%d", pDev->mpNetDev->udev->devnum); + if (IS_ERR(qmi_sync_task)) { + atomic_dec(&pDev->refcount); + DBG( "Create qmi_sync_thread fail\n" ); + return PTR_ERR(qmi_sync_task); + } + goto __register_chardev_qccmi; + } + + // Device is not ready for QMI connections right away + // Wait up to 30 seconds before failing + if (QMIReady( pDev, 30000 ) == false) + { + DBG( "Device unresponsive to QMI\n" ); + return -ETIMEDOUT; + } + + // Initiate QMI CTL Sync Procedure + DBG( "Sending QMI CTL Sync Request\n" ); + result = QMICTLSyncProc(pDev); + if (result != 0) + { + DBG( "QMI CTL Sync Procedure Error\n" ); + return result; + } + else + { + DBG( "QMI CTL Sync Procedure Successful\n" ); + } + + // Setup Data Format + result = QMIWDASetDataFormat (pDev); + if (result != 0) + { + return result; + } + + // Setup WDS callback + result = SetupQMIWDSCallback( pDev ); + if (result != 0) + { + return result; + } + + // Fill MEID for device + result = QMIDMSGetMEID( pDev ); + if (result != 0) + { + return result; + } + +__register_chardev_qccmi: + // allocate and fill devno with numbers + result = alloc_chrdev_region( &devno, 0, 1, "qcqmi" ); + if (result < 0) + { + return result; + } + + // Create cdev + cdev_init( &pDev->mQMIDev.mCdev, &UserspaceQMIFops ); + pDev->mQMIDev.mCdev.owner = THIS_MODULE; + pDev->mQMIDev.mCdev.ops = &UserspaceQMIFops; + pDev->mQMIDev.mbCdevIsInitialized = true; + + result = cdev_add( &pDev->mQMIDev.mCdev, devno, 1 ); + if (result != 0) + { + DBG( "error adding cdev\n" ); + return result; + } + + // Match interface number (usb# or eth#) + if (!!(pDevName = strstr( pDev->mpNetDev->net->name, "eth" ))) { + pDevName += strlen( "eth" ); + } else if (!!(pDevName = strstr( pDev->mpNetDev->net->name, "usb" ))) { + pDevName += strlen( "usb" ); +#if 1 //openWRT like use ppp# or lte# + } else if (!!(pDevName = strstr( pDev->mpNetDev->net->name, "ppp" ))) { + pDevName += strlen( "ppp" ); + } else if (!!(pDevName = strstr( pDev->mpNetDev->net->name, "lte" ))) { + pDevName += strlen( "lte" ); +#endif + } else { + DBG( "Bad net name: %s\n", pDev->mpNetDev->net->name ); + return -ENXIO; + } + GobiQMIIndex = simple_strtoul( pDevName, NULL, 10 ); + if (GobiQMIIndex < 0) + { + DBG( "Bad minor number\n" ); + return -ENXIO; + } + + // Always print this output + printk( KERN_INFO "creating qcqmi%d\n", + GobiQMIIndex ); + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION( 2,6,27 )) + // kernel 2.6.27 added a new fourth parameter to device_create + // void * drvdata : the data to be added to the device for callbacks + device_create( pDev->mQMIDev.mpDevClass, + &pDev->mpIntf->dev, + devno, + NULL, + "qcqmi%d", + GobiQMIIndex ); +#else + device_create( pDev->mQMIDev.mpDevClass, + &pDev->mpIntf->dev, + devno, + "qcqmi%d", + GobiQMIIndex ); +#endif + + pDev->mQMIDev.mDevNum = devno; + + // Success + return 0; +} + +/*=========================================================================== +METHOD: + DeregisterQMIDevice (Public Method) + +DESCRIPTION: + QMI Device cleanup function + + NOTE: When this function is run the device is no longer valid + +PARAMETERS: + pDev [ I ] - Device specific memory + +RETURN VALUE: + None +===========================================================================*/ +void DeregisterQMIDevice( sGobiUSBNet * pDev ) +{ + struct inode * pOpenInode; + struct list_head * pInodeList; + struct task_struct * pEachTask; + struct fdtable * pFDT; + struct file * pFilp; + unsigned long flags; + int count = 0; + int tries; + int result; + + // Should never happen, but check anyway + if (IsDeviceValid( pDev ) == false) + { + DBG( "wrong device\n" ); + return; + } + + pDev->mbDeregisterQMIDevice = true; + + // Release all clients + spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags ); + while (pDev->mQMIDev.mpClientMemList != NULL) + { + u16 mClientID = pDev->mQMIDev.mpClientMemList->mClientID; + if (waitqueue_active(&pDev->mQMIDev.mpClientMemList->mWaitQueue)) { + DBG("WaitQueue 0x%04X\n", mClientID); + wake_up_interruptible_sync( &pDev->mQMIDev.mpClientMemList->mWaitQueue ); + spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags ); + msleep(10); + spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags ); + continue; + } + + DBG( "release 0x%04X\n", pDev->mQMIDev.mpClientMemList->mClientID ); + + spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags ); + ReleaseClientID( pDev, mClientID ); + // NOTE: pDev->mQMIDev.mpClientMemList will + // be updated in ReleaseClientID() + spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags ); + } + spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags ); + + // Stop all reads + KillRead( pDev ); + + pDev->mbQMIValid = false; + + if (pDev->mQMIDev.mbCdevIsInitialized == false) + { + return; + } + + // Find each open file handle, and manually close it + + // Generally there will only be only one inode, but more are possible + list_for_each( pInodeList, &pDev->mQMIDev.mCdev.list ) + { + // Get the inode + pOpenInode = container_of( pInodeList, struct inode, i_devices ); + if (pOpenInode != NULL && (IS_ERR( pOpenInode ) == false)) + { + // Look for this inode in each task + + rcu_read_lock(); + for_each_process( pEachTask ) + { + task_lock(pEachTask); + if (pEachTask == NULL || pEachTask->files == NULL) + { + // Some tasks may not have files (e.g. Xsession) + task_unlock(pEachTask); + continue; + } + // For each file this task has open, check if it's referencing + // our inode. + spin_lock_irqsave( &pEachTask->files->file_lock, flags ); + task_unlock(pEachTask); //kernel/exit.c:do_exit() -> fs/file.c:exit_files() + pFDT = files_fdtable( pEachTask->files ); + for (count = 0; count < pFDT->max_fds; count++) + { + pFilp = pFDT->fd[count]; + if (pFilp != NULL && pFilp->f_dentry != NULL) + { + if (pFilp->f_dentry->d_inode == pOpenInode) + { + // Close this file handle + rcu_assign_pointer( pFDT->fd[count], NULL ); + spin_unlock_irqrestore( &pEachTask->files->file_lock, flags ); + + DBG( "forcing close of open file handle\n" ); + filp_close( pFilp, pEachTask->files ); + + spin_lock_irqsave( &pEachTask->files->file_lock, flags ); + } + } + } + spin_unlock_irqrestore( &pEachTask->files->file_lock, flags ); + } + rcu_read_unlock(); + } + } + + // Send SetControlLineState request (USB_CDC) + result = usb_control_msg( pDev->mpNetDev->udev, + usb_sndctrlpipe( pDev->mpNetDev->udev, 0 ), + SET_CONTROL_LINE_STATE_REQUEST, + SET_CONTROL_LINE_STATE_REQUEST_TYPE, + 0, // DTR not present + /* USB interface number to receive control message */ + pDev->mpIntf->cur_altsetting->desc.bInterfaceNumber, + NULL, + 0, + 100 ); + if (result < 0) + { + DBG( "Bad SetControlLineState status %d\n", result ); + } + + // Remove device (so no more calls can be made by users) + if (IS_ERR( pDev->mQMIDev.mpDevClass ) == false) + { + device_destroy( pDev->mQMIDev.mpDevClass, + pDev->mQMIDev.mDevNum ); + } + + // Hold onto cdev memory location until everyone is through using it. + // Timeout after 30 seconds (10 ms interval). Timeout should never happen, + // but exists to prevent an infinate loop just in case. + for (tries = 0; tries < 30 * 100; tries++) + { + int ref = 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 --git a/root/package/link4all/gobinet/src.ec25/QMIDevice.h b/root/package/link4all/gobinet/src.ec25/QMIDevice.h new file mode 100644 index 00000000..e8306c55 --- /dev/null +++ b/root/package/link4all/gobinet/src.ec25/QMIDevice.h @@ -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 --git a/root/package/link4all/gobinet/src.ec25/Readme.txt b/root/package/link4all/gobinet/src.ec25/Readme.txt new file mode 100644 index 00000000..0df201a8 --- /dev/null +++ b/root/package/link4all/gobinet/src.ec25/Readme.txt @@ -0,0 +1,78 @@ +Gobi3000 network driver 2011-07-29-1026 + +This readme covers important information concerning +the Gobi Net driver. + +Table of Contents + +1. What's new in this release +2. Known issues +3. Known platform issues + + +------------------------------------------------------------------------------- + +1. WHAT'S NEW + +This Release (Gobi3000 network driver 2011-07-29-1026) +a. Signal the device to leave low power mode on enumeration +b. Add "txQueueLength" parameter, which will set the Tx Queue Length +c. Send SetControlLineState message during driver/device removal +d. Change to new date-based versioning scheme + +Prior Release (Gobi3000 network driver 1.0.60) 06/29/2011 +a. Add UserspacePoll() function, to support select() +b. Fix possible deadlock on GobiUSBNetTXTimeout() +c. Fix memory leak on data transmission + +Prior Release (Gobi3000 network driver 1.0.50) 05/18/2011 +a. Add support for kernels up to 2.6.38 +b. Add support for dynamic interface binding + +Prior Release (Gobi3000 network driver 1.0.40) 02/28/2011 +a. In cases of QMI read errors, discard the error and continue reading. +b. Add "interruptible" parameter, which may be disabled for debugging purposes. + +Prior Release (Gobi3000 network driver 1.0.30) 01/05/2011 +a. Fix rare kernel PANIC if a process terminates while file handle close + or device removal is in progress. + +Prior Release (Gobi3000 network driver 1.0.20) 11/01/2010 +a. Fix possible kernel WARNING if device removed before QCWWANDisconnect(). +b. Fix multiple memory leaks in error cases. + +Prior Release (Gobi3000 network driver 1.0.10) 09/17/2010 +a. Initial release + +------------------------------------------------------------------------------- + +2. KNOWN ISSUES + +No known issues. + +------------------------------------------------------------------------------- + +3. KNOWN PLATFORM ISSUES + +a. Enabling autosuspend: + Autosuspend is supported by the Gobi3000 module and its drivers, + but by default it is not enabled by the open source kernel. As such, + the Gobi3000 module will not enter autosuspend unless the + user specifically turns on autosuspend with the command: + echo auto > /sys/bus/usb/devices/.../power/level +b. Ksoftirq using 100% CPU: + There is a known issue with the open source usbnet driver that can + result in infinite software interrupts. The fix for this is to test + (in the usbnet_bh() function) if the usb_device can submit URBs before + attempting to submit the response URB buffers. +c. NetworkManager does not recognize connection after resume: + After resuming from sleep/hibernate, NetworkManager may not recognize new + network connections by the Gobi device. This is a system issue not specific + to the Gobi device, which may result in dhcp not being run and the default + route not being updated. One way to fix this is to simply restart the + NetworkManager service. + +------------------------------------------------------------------------------- + + + diff --git a/root/package/link4all/gobinet/src.ec25/Structs.h b/root/package/link4all/gobinet/src.ec25/Structs.h new file mode 100644 index 00000000..9d4665d9 --- /dev/null +++ b/root/package/link4all/gobinet/src.ec25/Structs.h @@ -0,0 +1,439 @@ +/*=========================================================================== +FILE: + Structs.h + +DESCRIPTION: + Declaration of structures used by the Qualcomm Linux USB Network driver + +FUNCTIONS: + none + +Copyright (c) 2011, Code Aurora Forum. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Code Aurora Forum nor + the names of its contributors may be used to endorse or promote + products derived from this software without specific prior written + permission. + + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +===========================================================================*/ + +//--------------------------------------------------------------------------- +// Pragmas +//--------------------------------------------------------------------------- +#pragma once + +//--------------------------------------------------------------------------- +// Include Files +//--------------------------------------------------------------------------- +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,21 )) +static inline void skb_reset_mac_header(struct sk_buff *skb) +{ + skb->mac.raw = skb->data; +} +#endif + +#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,22 )) +#define bool u8 +#ifndef URB_FREE_BUFFER +#define URB_FREE_BUFFER_BY_SELF //usb_free_urb will not free, should free by self +#define URB_FREE_BUFFER 0x0100 /* Free transfer buffer with the URB */ +#endif + +/** + * usb_endpoint_type - get the endpoint's transfer type + * @epd: endpoint to be checked + * + * Returns one of USB_ENDPOINT_XFER_{CONTROL, ISOC, BULK, INT} according + * to @epd's transfer type. + */ +static inline int usb_endpoint_type(const struct usb_endpoint_descriptor *epd) +{ + return epd->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK; +} +#endif + +#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,18 )) +/** + * usb_endpoint_dir_in - check if the endpoint has IN direction + * @epd: endpoint to be checked + * + * Returns true if the endpoint is of type IN, otherwise it returns false. + */ +static inline int usb_endpoint_dir_in(const struct usb_endpoint_descriptor *epd) +{ + return ((epd->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN); +} + +/** + * usb_endpoint_dir_out - check if the endpoint has OUT direction + * @epd: endpoint to be checked + * + * Returns true if the endpoint is of type OUT, otherwise it returns false. + */ +static inline int usb_endpoint_dir_out( + const struct usb_endpoint_descriptor *epd) +{ + return ((epd->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_OUT); +} + +/** + * usb_endpoint_xfer_int - check if the endpoint has interrupt transfer type + * @epd: endpoint to be checked + * + * Returns true if the endpoint is of type interrupt, otherwise it returns + * false. + */ +static inline int usb_endpoint_xfer_int( + const struct usb_endpoint_descriptor *epd) +{ + return ((epd->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == + USB_ENDPOINT_XFER_INT); +} + +static inline int usb_autopm_set_interface(struct usb_interface *intf) +{ return 0; } + +static inline int usb_autopm_get_interface(struct usb_interface *intf) +{ return 0; } + +static inline int usb_autopm_get_interface_async(struct usb_interface *intf) +{ return 0; } + +static inline void usb_autopm_put_interface(struct usb_interface *intf) +{ } +static inline void usb_autopm_put_interface_async(struct usb_interface *intf) +{ } +static inline void usb_autopm_enable(struct usb_interface *intf) +{ } +static inline void usb_autopm_disable(struct usb_interface *intf) +{ } +static inline void usb_mark_last_busy(struct usb_device *udev) +{ } +#endif + +#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,24 )) + #include "usbnet.h" +#else + #include +#endif + +#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,25 )) + #include +#else + #include +#endif + +// Used in recursion, defined later below +struct sGobiUSBNet; + +/*=========================================================================*/ +// Struct sReadMemList +// +// Structure that defines an entry in a Read Memory linked list +/*=========================================================================*/ +typedef struct sReadMemList +{ + /* Data buffer */ + void * mpData; + + /* Transaction ID */ + u16 mTransactionID; + + /* Size of data buffer */ + u16 mDataSize; + + /* Next entry in linked list */ + struct sReadMemList * mpNext; + +} sReadMemList; + +/*=========================================================================*/ +// Struct sNotifyList +// +// Structure that defines an entry in a Notification linked list +/*=========================================================================*/ +typedef struct sNotifyList +{ + /* Function to be run when data becomes available */ + void (* mpNotifyFunct)(struct sGobiUSBNet *, u16, void *); + + /* Transaction ID */ + u16 mTransactionID; + + /* Data to provide as parameter to mpNotifyFunct */ + void * mpData; + + /* Next entry in linked list */ + struct sNotifyList * mpNext; + +} sNotifyList; + +/*=========================================================================*/ +// Struct sURBList +// +// Structure that defines an entry in a URB linked list +/*=========================================================================*/ +typedef struct sURBList +{ + /* The current URB */ + struct urb * mpURB; + + /* Next entry in linked list */ + struct sURBList * mpNext; + +} sURBList; + +/*=========================================================================*/ +// Struct sClientMemList +// +// Structure that defines an entry in a Client Memory linked list +// Stores data specific to a Service Type and Client ID +/*=========================================================================*/ +typedef struct sClientMemList +{ + /* Client ID for this Client */ + u16 mClientID; + + /* Linked list of Read entries */ + /* Stores data read from device before sending to client */ + sReadMemList * mpList; + + /* Linked list of Notification entries */ + /* Stores notification functions to be run as data becomes + available or the device is removed */ + sNotifyList * mpReadNotifyList; + + /* Linked list of URB entries */ + /* Stores pointers to outstanding URBs which need canceled + when the client is deregistered or the device is removed */ + sURBList * mpURBList; + + /* Next entry in linked list */ + struct sClientMemList * mpNext; + + /* Wait queue object for poll() */ + wait_queue_head_t mWaitQueue; + +} sClientMemList; + +/*=========================================================================*/ +// Struct sURBSetupPacket +// +// Structure that defines a USB Setup packet for Control URBs +// Taken from USB CDC specifications +/*=========================================================================*/ +typedef struct sURBSetupPacket +{ + /* Request type */ + u8 mRequestType; + + /* Request code */ + u8 mRequestCode; + + /* Value */ + u16 mValue; + + /* Index */ + u16 mIndex; + + /* Length of Control URB */ + u16 mLength; + +} sURBSetupPacket; + +// Common value for sURBSetupPacket.mLength +#define DEFAULT_READ_URB_LENGTH 0x1000 + +#ifdef CONFIG_PM +#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,29 )) +/*=========================================================================*/ +// Struct sAutoPM +// +// Structure used to manage AutoPM thread which determines whether the +// device is in use or may enter autosuspend. Also submits net +// transmissions asynchronously. +/*=========================================================================*/ +typedef struct sAutoPM +{ + /* Thread for atomic autopm function */ + struct task_struct * mpThread; + + /* Signal for completion when it's time for the thread to work */ + struct completion mThreadDoWork; + + /* Time to exit? */ + bool mbExit; + + /* List of URB's queued to be sent to the device */ + sURBList * mpURBList; + + /* URB list lock (for adding and removing elements) */ + spinlock_t mURBListLock; + + /* Length of the URB list */ + atomic_t mURBListLen; + + /* Active URB */ + struct urb * mpActiveURB; + + /* Active URB lock (for adding and removing elements) */ + spinlock_t mActiveURBLock; + + /* Duplicate pointer to USB device interface */ + struct usb_interface * mpIntf; + +} sAutoPM; +#endif +#endif /* CONFIG_PM */ + +/*=========================================================================*/ +// Struct sQMIDev +// +// Structure that defines the data for the QMI device +/*=========================================================================*/ +typedef struct sQMIDev +{ + /* Device number */ + dev_t mDevNum; + + /* Device class */ + struct class * mpDevClass; + + /* cdev struct */ + struct cdev mCdev; + + /* is mCdev initialized? */ + bool mbCdevIsInitialized; + + /* Pointer to read URB */ + struct urb * mpReadURB; + +//#define READ_QMI_URB_ERROR +#ifdef READ_QMI_URB_ERROR + struct timer_list mReadUrbTimer; +#endif + + /* Read setup packet */ + sURBSetupPacket * mpReadSetupPacket; + + /* Read buffer attached to current read URB */ + void * mpReadBuffer; + + /* Inturrupt URB */ + /* Used to asynchronously notify when read data is available */ + struct urb * mpIntURB; + + /* Buffer used by Inturrupt URB */ + void * mpIntBuffer; + + /* Pointer to memory linked list for all clients */ + sClientMemList * mpClientMemList; + + /* Spinlock for client Memory entries */ + spinlock_t mClientMemLock; + + /* Transaction ID associated with QMICTL "client" */ + atomic_t mQMICTLTransactionID; + +} sQMIDev; + +/*=========================================================================*/ +// Struct sGobiUSBNet +// +// Structure that defines the data associated with the Qualcomm USB device +/*=========================================================================*/ +typedef struct sGobiUSBNet +{ + atomic_t refcount; + + /* Net device structure */ + struct usbnet * mpNetDev; + +#if 1 //def DATA_MODE_RP + bool mbMdm9x07; + /* QMI "device" work in IP Mode or ETH Mode */ + bool mbRawIPMode; +#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 --git a/root/package/link4all/gobinet/src/GobiUSBNet.c b/root/package/link4all/gobinet/src/GobiUSBNet.c new file mode 100755 index 00000000..d63cf9f2 --- /dev/null +++ b/root/package/link4all/gobinet/src/GobiUSBNet.c @@ -0,0 +1,1577 @@ +/*=========================================================================== +FILE: + GobiUSBNet.c + +DESCRIPTION: + Qualcomm USB Network device for Gobi 3000 + +FUNCTIONS: + GobiNetSuspend + GobiNetResume + GobiNetDriverBind + GobiNetDriverUnbind + GobiUSBNetURBCallback + GobiUSBNetTXTimeout + GobiUSBNetAutoPMThread + GobiUSBNetStartXmit + GobiUSBNetOpen + GobiUSBNetStop + GobiUSBNetProbe + GobiUSBNetModInit + GobiUSBNetModExit + +Copyright (c) 2011, Code Aurora Forum. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Code Aurora Forum nor + the names of its contributors may be used to endorse or promote + products derived from this software without specific prior written + permission. + + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +===========================================================================*/ + +//--------------------------------------------------------------------------- +// Include Files +//--------------------------------------------------------------------------- + +#include "Structs.h" +#include "QMIDevice.h" +#include "QMI.h" +#include +#include +#include + +//----------------------------------------------------------------------------- +// Definitions +//----------------------------------------------------------------------------- + +// Version Information +#define DRIVER_VERSION "GobiNet_SR01A02V14" +#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; + +#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,14 )) + 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 ); +#ifdef CONFIG_PM +#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,22 )) + pDev->udev->reset_resume = 0; +#endif +#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 +#ifdef CONFIG_PM +#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,22 )) + pDev->udev->reset_resume = 1; +#endif +#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) + DBG("Vendor = 0x%x Product = 0x%x\n",cpu_to_le16(pDev->udev->descriptor.idVendor), cpu_to_le16(pDev->udev->descriptor.idProduct)); + switch(cpu_to_le16(pDev->udev->descriptor.idVendor)){ + case 0x05c6: + if (cpu_to_le16(pDev->udev->descriptor.idProduct)==0x5013){ + if (pIntf->cur_altsetting->desc.bInterfaceNumber != 4) + { + DBG( "invalid interface %d\n", + pIntf->cur_altsetting->desc.bInterfaceNumber ); + return -ENODEV; + } + }else{ + if (pIntf->cur_altsetting->desc.bInterfaceNumber != 5) + { + DBG( "invalid interface %d\n", + pIntf->cur_altsetting->desc.bInterfaceNumber ); + return -ENODEV; + } + } + break; + + case 0x1199: + if (pIntf->cur_altsetting->desc.bInterfaceNumber != 8) + { + DBG( "invalid interface %d\n", + pIntf->cur_altsetting->desc.bInterfaceNumber ); + return -ENODEV; + } + break; + + case 0x413c: + if (pIntf->cur_altsetting->desc.bInterfaceNumber != 8) + { + DBG( "invalid interface %d\n", + pIntf->cur_altsetting->desc.bInterfaceNumber ); + return -ENODEV; + } + break; + + case 0x2c7c: + // if(cpu_to_le16(pDev->udev->descriptor.idProduct)==0x030a){ + // if (pIntf->cur_altsetting->desc.bInterfaceNumber != 1) + // { + // DBG( "invalid interface %d\n", + // pIntf->cur_altsetting->desc.bInterfaceNumber ); + // return -ENODEV; + // } + // break; + // }else{ + if (pIntf->cur_altsetting->desc.bInterfaceNumber != 4) + { + DBG( "invalid interface %d\n", + pIntf->cur_altsetting->desc.bInterfaceNumber ); + return -ENODEV; + } + break; + // } + case 0x1e0e: + if (pIntf->cur_altsetting->desc.bInterfaceNumber != 5) + { + DBG( "invalid interface %d\n", + pIntf->cur_altsetting->desc.bInterfaceNumber ); + return -ENODEV; + } + break; + + case 0x2949: + if (cpu_to_le16(pDev->udev->descriptor.idProduct)==0x8247){ + if (pIntf->cur_altsetting->desc.bInterfaceNumber != 4) + { + DBG( "invalid interface %d\n", + pIntf->cur_altsetting->desc.bInterfaceNumber ); + return -ENODEV; + } + } + if (cpu_to_le16(pDev->udev->descriptor.idProduct)==0x8241||cpu_to_le16(pDev->udev->descriptor.idProduct)==0x8242||cpu_to_le16(pDev->udev->descriptor.idProduct)==0x8243){ + if (pIntf->cur_altsetting->desc.bInterfaceNumber != 0) + { + DBG( "invalid interface %d\n", + pIntf->cur_altsetting->desc.bInterfaceNumber ); + return -ENODEV; + } + } + + + break; + + case 0x1c9e: + if (pIntf->cur_altsetting->desc.bInterfaceNumber != 4) + { + DBG( "invalid interface %d\n", + pIntf->cur_altsetting->desc.bInterfaceNumber ); + return -ENODEV; + } + break; + + + } + + // 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 ); + +#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->mbRawIPMode) + return skb; + + // Skip Ethernet header from message + if (skb_pull(skb, ETH_HLEN)) { + return skb; + } else { + dev_err(&dev->intf->dev, "Packet Dropped "); + } + + // 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; + memset(eth_hdr(skb)->h_source, 0, ETH_ALEN); +fix_dest: + memcpy(eth_hdr(skb)->h_dest, dev->net->dev_addr, ETH_ALEN); + return 1; +} +#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,14 )) +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 ); + + 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,14 )) + 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(pNet, 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, +#else + .flags = FLAG_ETHER, +#endif + .bind = GobiNetDriverBind, + .unbind = GobiNetDriverUnbind, +#if 1 //def DATA_MODE_RP + .rx_fixup = GobiNetDriverRxFixup, + .tx_fixup = GobiNetDriverTxFixup, +#endif + .data = 0, +}; + +/*=========================================================================*/ +// Qualcomm Gobi 3000 VID/PIDs +/*=========================================================================*/ +static const struct usb_device_id GobiVIDPIDTable [] = +{ + // Quectel UC20 + { + USB_DEVICE( 0x05c6, 0x9003 ), + .driver_info = (unsigned long)&GobiNetInfo + }, + // Quectel EC20 + { + USB_DEVICE( 0x05c6, 0x9215 ), + .driver_info = (unsigned long)&GobiNetInfo + }, + // Quectel EC25 + { + USB_DEVICE( 0x2c7c, 0x0125 ), + .driver_info = (unsigned long)&GobiNetInfo + }, + // // Quectel EM05-G + // { + // USB_DEVICE( 0x2c7c, 0x030a ), + // .driver_info = (unsigned long)&GobiNetInfo + // }, + // Quectel EC21 + { + USB_DEVICE( 0x2c7c, 0x0121 ), + .driver_info = (unsigned long)&GobiNetInfo + }, + // MEIG SLM730 SLM750 + { + USB_DEVICE(0x05c6, 0xf601), + .driver_info = (unsigned long)&GobiNetInfo + }, + // SIM7600 + { + USB_DEVICE(0x1e0e, 0x9001), + .driver_info = (unsigned long)&GobiNetInfo + }, + // N720 + { + USB_DEVICE(0x2949,0x8247), + .driver_info = (unsigned long)&GobiNetInfo + }, + // another N720 PID + { + USB_DEVICE(0x2949,0x8243), + .driver_info = (unsigned long)&GobiNetInfo + }, + //EM7355 + { + USB_DEVICE(0x1199,0x9041), + .driver_info = (unsigned long)&GobiNetInfo + }, + //EM7455 3P10Y + { + USB_DEVICE(0x413c,0x81b6), + .driver_info = (unsigned long)&GobiNetInfo + }, + // XinYi ndis port VID 5013 MI 04 + { + USB_DEVICE( 0x05c6, 0x5013 ), + .driver_info = (unsigned long)&GobiNetInfo + }, + + // longshang U9300 + { + USB_DEVICE( 0x01c9e, 0x9b3c), + .driver_info = (unsigned long)&GobiNetInfo + }, + //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 + 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,14 )) + .supports_autosuspend = true, +#endif +#else + .suspend = NULL, + .resume = NULL, +#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,14 )) + .supports_autosuspend = false, +#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 --git a/root/package/link4all/gobinet/src/Makefile b/root/package/link4all/gobinet/src/Makefile new file mode 100755 index 00000000..3872ab79 --- /dev/null +++ b/root/package/link4all/gobinet/src/Makefile @@ -0,0 +1,30 @@ +obj-m := GobiNet.o +GobiNet-objs := GobiUSBNet.o QMIDevice.o QMI.o + +PWD := $(shell pwd) +OUTPUTDIR=/lib/modules/`uname -r`/kernel/drivers/net/usb/ + +ifeq ($(ARCH),) +ARCH := $(shell uname -m) +endif +ifeq ($(CROSS_COMPILE),) +CROSS_COMPILE := +endif +ifeq ($(KDIR),) +KDIR := /lib/modules/$(shell uname -r)/build +endif + +default: + ln -sf makefile Makefile + $(MAKE) ARCH=${ARCH} CROSS_COMPILE=${CROSS_COMPILE} -C $(KDIR) M=$(PWD) modules + +install: default + mkdir -p $(OUTPUTDIR) + cp -f GobiNet.ko $(OUTPUTDIR) + depmod + modprobe -r GobiNet + modprobe GobiNet + +clean: + rm -rf Makefile + rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions Module.* modules.order diff --git a/root/package/link4all/gobinet/src/QMI.c b/root/package/link4all/gobinet/src/QMI.c new file mode 100755 index 00000000..241b8e66 --- /dev/null +++ b/root/package/link4all/gobinet/src/QMI.c @@ -0,0 +1,1343 @@ +/*=========================================================================== +FILE: + QMI.c + +DESCRIPTION: + Qualcomm QMI driver code + +FUNCTIONS: + Generic QMUX functions + ParseQMUX + FillQMUX + + Generic QMI functions + GetTLV + ValidQMIMessage + GetQMIMessageID + + Fill Buffers with QMI requests + QMICTLGetClientIDReq + QMICTLReleaseClientIDReq + QMICTLReadyReq + QMIWDSSetEventReportReq + QMIWDSGetPKGSRVCStatusReq + QMIDMSGetMEIDReq + QMIWDASetDataFormatReq + QMICTLSetDataFormatReq + QMICTLSyncReq + + Parse data from QMI responses + QMICTLGetClientIDResp + QMICTLReleaseClientIDResp + QMIWDSEventResp + QMIDMSGetMEIDResp + QMIWDASetDataFormatResp + QMICTLSyncResp + +Copyright (c) 2011, Code Aurora Forum. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Code Aurora Forum nor + the names of its contributors may be used to endorse or promote + products derived from this software without specific prior written + permission. + + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +===========================================================================*/ + +//--------------------------------------------------------------------------- +// Include Files +//--------------------------------------------------------------------------- +#include +#include +#include "Structs.h" +#include "QMI.h" + +/*=========================================================================*/ +// Get sizes of buffers needed by QMI requests +/*=========================================================================*/ + +/*=========================================================================== +METHOD: + QMUXHeaderSize (Public Method) + +DESCRIPTION: + Get size of buffer needed for QMUX + +RETURN VALUE: + u16 - size of buffer +===========================================================================*/ +u16 QMUXHeaderSize( void ) +{ + return sizeof( sQMUX ); +} + +/*=========================================================================== +METHOD: + QMICTLGetClientIDReqSize (Public Method) + +DESCRIPTION: + Get size of buffer needed for QMUX + QMICTLGetClientIDReq + +RETURN VALUE: + u16 - size of buffer +===========================================================================*/ +u16 QMICTLGetClientIDReqSize( void ) +{ + return sizeof( sQMUX ) + 10; +} + +/*=========================================================================== +METHOD: + QMICTLReleaseClientIDReqSize (Public Method) + +DESCRIPTION: + Get size of buffer needed for QMUX + QMICTLReleaseClientIDReq + +RETURN VALUE: + u16 - size of header +===========================================================================*/ +u16 QMICTLReleaseClientIDReqSize( void ) +{ + return sizeof( sQMUX ) + 11; +} + +/*=========================================================================== +METHOD: + QMICTLReadyReqSize (Public Method) + +DESCRIPTION: + Get size of buffer needed for QMUX + QMICTLReadyReq + +RETURN VALUE: + u16 - size of buffer +===========================================================================*/ +u16 QMICTLReadyReqSize( void ) +{ + return sizeof( sQMUX ) + 6; +} + +/*=========================================================================== +METHOD: + QMIWDSSetEventReportReqSize (Public Method) + +DESCRIPTION: + Get size of buffer needed for QMUX + QMIWDSSetEventReportReq + +RETURN VALUE: + u16 - size of buffer +===========================================================================*/ +u16 QMIWDSSetEventReportReqSize( void ) +{ + return sizeof( sQMUX ) + 15; +} + +/*=========================================================================== +METHOD: + QMIWDSGetPKGSRVCStatusReqSize (Public Method) + +DESCRIPTION: + Get size of buffer needed for QMUX + QMIWDSGetPKGSRVCStatusReq + +RETURN VALUE: + u16 - size of buffer +===========================================================================*/ +u16 QMIWDSGetPKGSRVCStatusReqSize( void ) +{ + return sizeof( sQMUX ) + 7; +} + +/*=========================================================================== +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; +} + +/*=========================================================================== +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, + 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 */ +#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 + /* 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 ) +{ + + 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; + + } + +#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 + 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 --git a/root/package/link4all/gobinet/src/QMI.h b/root/package/link4all/gobinet/src/QMI.h new file mode 100755 index 00000000..8888b76e --- /dev/null +++ b/root/package/link4all/gobinet/src/QMI.h @@ -0,0 +1,314 @@ +/*=========================================================================== +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 + +// QMI Service Types +#define QMICTL 0 +#define QMIWDS 1 +#define QMIDMS 2 +#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 ); + +// 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 ); + +// 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, + 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 ); + +// Pasre the QMI CTL Sync Response +int QMICTLSyncResp( + void *pBuffer, + u16 buffSize ); + diff --git a/root/package/link4all/gobinet/src/QMIDevice.c b/root/package/link4all/gobinet/src/QMIDevice.c new file mode 100755 index 00000000..c383d595 --- /dev/null +++ b/root/package/link4all/gobinet/src/QMIDevice.c @@ -0,0 +1,4046 @@ +/*=========================================================================== +FILE: + QMIDevice.c + +DESCRIPTION: + Functions related to the QMI interface device + +FUNCTIONS: + Generic functions + IsDeviceValid + PrintHex + GobiSetDownReason + GobiClearDownReason + GobiTestDownReason + + Driver level asynchronous read functions + ResubmitIntURB + ReadCallback + IntCallback + StartRead + KillRead + + Internal read/write functions + ReadAsync + UpSem + ReadSync + WriteSyncCallback + WriteSync + + Internal memory management functions + GetClientID + ReleaseClientID + FindClientMem + AddToReadMemList + PopFromReadMemList + AddToNotifyList + NotifyAndPopNotifyList + AddToURBList + PopFromURBList + + Internal userspace wrapper functions + UserspaceunlockedIOCTL + + Userspace wrappers + UserspaceOpen + UserspaceIOCTL + UserspaceClose + UserspaceRead + UserspaceWrite + UserspacePoll + + Initializer and destructor + RegisterQMIDevice + DeregisterQMIDevice + + Driver level client management + QMIReady + QMIWDSCallback + SetupQMIWDSCallback + QMIDMSGetMEID + +Copyright (c) 2011, Code Aurora Forum. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Code Aurora Forum nor + the names of its contributors may be used to endorse or promote + products derived from this software without specific prior written + permission. + + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +===========================================================================*/ + +//--------------------------------------------------------------------------- +// Include Files +//--------------------------------------------------------------------------- +#include +#include "QMIDevice.h" +#include + +#include "QMI.h" +//----------------------------------------------------------------------------- +// Definitions +//----------------------------------------------------------------------------- + +extern int debug; +extern int interruptible; +#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,22 )) +static int s_interval; +#endif + +#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,14 )) +#include +static char devfs_name[32]; +int device_create(struct class *class, struct device *parent, dev_t devt, const char *fmt, ...) +{ + va_list vargs; + struct class_device *class_dev; + int err; + + va_start(vargs, fmt); + vsnprintf(devfs_name, sizeof(devfs_name), fmt, vargs); + va_end(vargs); + + class_dev = class_device_create(class, devt, parent, "%s", devfs_name); + if (IS_ERR(class_dev)) { + err = PTR_ERR(class_dev); + goto out; + } + + err = devfs_mk_cdev(devt, S_IFCHR|S_IRUSR|S_IWUSR|S_IRGRP, devfs_name); + if (err) { + class_device_destroy(class, devt); + goto out; + } + + return 0; + +out: + return err; +} + +void device_destroy(struct class *class, dev_t devt) +{ + class_device_destroy(class, devt); + devfs_remove(devfs_name); +} +#endif + +#ifdef CONFIG_PM +// Prototype to GobiNetSuspend function +int GobiNetSuspend( + struct usb_interface * pIntf, + pm_message_t powerEvent ); +#endif /* CONFIG_PM */ + +// IOCTL to generate a client ID for this service type +#define IOCTL_QMI_GET_SERVICE_FILE 0x8BE0 + 1 + +// IOCTL to get the VIDPID of the device +#define IOCTL_QMI_GET_DEVICE_VIDPID 0x8BE0 + 2 + +// IOCTL to get the MEID of the device +#define IOCTL_QMI_GET_DEVICE_MEID 0x8BE0 + 3 + +#define IOCTL_QMI_RELEASE_SERVICE_FILE_IOCTL (0x8BE0 + 4) + +// CDC GET_ENCAPSULATED_RESPONSE packet +#define CDC_GET_ENCAPSULATED_RESPONSE_LE 0x01A1ll +#define CDC_GET_ENCAPSULATED_RESPONSE_BE 0xA101000000000000ll +/* The following masks filter the common part of the encapsulated response + * packet value for Gobi and QMI devices, ie. ignore usb interface number + */ +#define CDC_RSP_MASK_BE 0xFFFFFFFF00FFFFFFll +#define CDC_RSP_MASK_LE 0xFFFFFFE0FFFFFFFFll + +const int i = 1; +#define is_bigendian() ( (*(char*)&i) == 0 ) +#define CDC_GET_ENCAPSULATED_RESPONSE(pcdcrsp, pmask)\ +{\ + *pcdcrsp = is_bigendian() ? CDC_GET_ENCAPSULATED_RESPONSE_BE \ + : CDC_GET_ENCAPSULATED_RESPONSE_LE ; \ + *pmask = is_bigendian() ? CDC_RSP_MASK_BE \ + : CDC_RSP_MASK_LE; \ +} + +// CDC CONNECTION_SPEED_CHANGE indication packet +#define CDC_CONNECTION_SPEED_CHANGE_LE 0x2AA1ll +#define CDC_CONNECTION_SPEED_CHANGE_BE 0xA12A000000000000ll +/* The following masks filter the common part of the connection speed change + * packet value for Gobi and QMI devices + */ +#define CDC_CONNSPD_MASK_BE 0xFFFFFFFFFFFF7FFFll +#define CDC_CONNSPD_MASK_LE 0XFFF7FFFFFFFFFFFFll +#define CDC_GET_CONNECTION_SPEED_CHANGE(pcdccscp, pmask)\ +{\ + *pcdccscp = is_bigendian() ? CDC_CONNECTION_SPEED_CHANGE_BE \ + : CDC_CONNECTION_SPEED_CHANGE_LE ; \ + *pmask = is_bigendian() ? CDC_CONNSPD_MASK_BE \ + : CDC_CONNSPD_MASK_LE; \ +} + +#define SET_CONTROL_LINE_STATE_REQUEST_TYPE 0x21 +#define SET_CONTROL_LINE_STATE_REQUEST 0x22 +#define CONTROL_DTR 0x01 +#define CONTROL_RTS 0x02 + +/*=========================================================================*/ +// UserspaceQMIFops +// QMI device's userspace file operations +/*=========================================================================*/ +struct file_operations UserspaceQMIFops = +{ + .owner = THIS_MODULE, + .read = UserspaceRead, + .write = UserspaceWrite, +#ifdef CONFIG_COMPAT + .compat_ioctl = UserspaceunlockedIOCTL, +#endif +#if (LINUX_VERSION_CODE >= KERNEL_VERSION( 2,6,36 )) + .unlocked_ioctl = UserspaceunlockedIOCTL, +#else + .ioctl = UserspaceIOCTL, +#endif + .open = UserspaceOpen, + .flush = UserspaceClose, + .poll = UserspacePoll, +}; + +/*=========================================================================*/ +// Generic functions +/*=========================================================================*/ +u8 QMIXactionIDGet( sGobiUSBNet *pDev) +{ + u8 transactionID; + + if( 0 == (transactionID = atomic_add_return( 1, &pDev->mQMIDev.mQMICTLTransactionID)) ) + { + transactionID = atomic_add_return( 1, &pDev->mQMIDev.mQMICTLTransactionID ); + } + + return transactionID; +} + +static struct usb_endpoint_descriptor *GetEndpoint( + struct usb_interface *pintf, + int type, + int dir ) +{ + int i; + struct usb_host_interface *iface = pintf->cur_altsetting; + struct usb_endpoint_descriptor *pendp; + + for( i = 0; i < iface->desc.bNumEndpoints; i++) + { + pendp = &iface->endpoint[i].desc; + if( ((pendp->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == dir) + && + (usb_endpoint_type(pendp) == type) ) + { + return pendp; + } + } + + return NULL; +} + +/*=========================================================================== +METHOD: + IsDeviceValid (Public Method) + +DESCRIPTION: + Basic test to see if device memory is valid + +PARAMETERS: + pDev [ I ] - Device specific memory + +RETURN VALUE: + bool +===========================================================================*/ +bool IsDeviceValid( sGobiUSBNet * pDev ) +{ + if (pDev == NULL) + { + return false; + } + + if (pDev->mbQMIValid == false) + { + return false; + } + + return true; +} + +/*=========================================================================== +METHOD: + PrintHex (Public Method) + +DESCRIPTION: + Print Hex data, for debug purposes + +PARAMETERS: + pBuffer [ I ] - Data buffer + bufSize [ I ] - Size of data buffer + +RETURN VALUE: + None +===========================================================================*/ +void PrintHex( + void * pBuffer, + u16 bufSize ) +{ + char * pPrintBuf; + u16 pos; + int status; + + if (debug != 1) + { + return; + } + + pPrintBuf = kmalloc( bufSize * 3 + 1, GFP_ATOMIC ); + if (pPrintBuf == NULL) + { + DBG( "Unable to allocate buffer\n" ); + return; + } + memset( pPrintBuf, 0 , bufSize * 3 + 1 ); + + for (pos = 0; pos < bufSize; pos++) + { + status = snprintf( (pPrintBuf + (pos * 3)), + 4, + "%02X ", + *(u8 *)(pBuffer + pos) ); + if (status != 3) + { + DBG( "snprintf error %d\n", status ); + kfree( pPrintBuf ); + return; + } + } + + DBG( " : %s\n", pPrintBuf ); + + kfree( pPrintBuf ); + pPrintBuf = NULL; + return; +} + +/*=========================================================================== +METHOD: + GobiSetDownReason (Public Method) + +DESCRIPTION: + Sets mDownReason and turns carrier off + +PARAMETERS + pDev [ I ] - Device specific memory + reason [ I ] - Reason device is down + +RETURN VALUE: + None +===========================================================================*/ +void GobiSetDownReason( + sGobiUSBNet * pDev, + u8 reason ) +{ + set_bit( reason, &pDev->mDownReason ); + DBG("%s reason=%d, mDownReason=%x\n", __func__, reason, (unsigned)pDev->mDownReason); + + netif_carrier_off( pDev->mpNetDev->net ); +} + +/*=========================================================================== +METHOD: + GobiClearDownReason (Public Method) + +DESCRIPTION: + Clear mDownReason and may turn carrier on + +PARAMETERS + pDev [ I ] - Device specific memory + reason [ I ] - Reason device is no longer down + +RETURN VALUE: + None +===========================================================================*/ +void GobiClearDownReason( + sGobiUSBNet * pDev, + u8 reason ) +{ + clear_bit( reason, &pDev->mDownReason ); + + DBG("%s reason=%d, mDownReason=%x\n", __func__, reason, (unsigned)pDev->mDownReason); +#if 0 //(LINUX_VERSION_CODE >= KERNEL_VERSION( 3,11,0 )) + netif_carrier_on( pDev->mpNetDev->net ); +#else + if (pDev->mDownReason == 0) + { + netif_carrier_on( pDev->mpNetDev->net ); + } +#endif +} + +/*=========================================================================== +METHOD: + GobiTestDownReason (Public Method) + +DESCRIPTION: + Test mDownReason and returns whether reason is set + +PARAMETERS + pDev [ I ] - Device specific memory + reason [ I ] - Reason device is down + +RETURN VALUE: + bool +===========================================================================*/ +bool GobiTestDownReason( + sGobiUSBNet * pDev, + u8 reason ) +{ + return test_bit( reason, &pDev->mDownReason ); +} + +/*=========================================================================*/ +// Driver level asynchronous read functions +/*=========================================================================*/ + +/*=========================================================================== +METHOD: + ResubmitIntURB (Public Method) + +DESCRIPTION: + Resubmit interrupt URB, re-using same values + +PARAMETERS + pIntURB [ I ] - Interrupt URB + +RETURN VALUE: + int - 0 for success + negative errno for failure +===========================================================================*/ +int ResubmitIntURB( struct urb * pIntURB ) +{ + int status; + int interval; + + // Sanity test + if ( (pIntURB == NULL) + || (pIntURB->dev == NULL) ) + { + return -EINVAL; + } + + // Interval needs reset after every URB completion +#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,22 )) + interval = max((int)(pIntURB->ep->desc.bInterval), + (pIntURB->dev->speed == USB_SPEED_HIGH) ? 7 : 3); +#else + interval = s_interval; +#endif + + // Reschedule interrupt URB + usb_fill_int_urb( pIntURB, + pIntURB->dev, + pIntURB->pipe, + pIntURB->transfer_buffer, + pIntURB->transfer_buffer_length, + pIntURB->complete, + pIntURB->context, + interval ); + status = usb_submit_urb( pIntURB, GFP_ATOMIC ); + if (status != 0) + { + DBG( "Error re-submitting Int URB %d\n", status ); + } + + return status; +} + +/*=========================================================================== +METHOD: + ReadCallback (Public Method) + +DESCRIPTION: + Put the data in storage and notify anyone waiting for data + +PARAMETERS + pReadURB [ I ] - URB this callback is run for + +RETURN VALUE: + None +===========================================================================*/ +#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,14 )) +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; + } + + 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 ); + + 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,14 )) +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 ); + 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; +} + +/*=========================================================================== +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; + } + + 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,14 )) +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,14 )) + 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 cannot be 0 */ + transactionID = atomic_add_return( 1, &pDev->mQMIDev.mQMICTLTransactionID ); + if (transactionID == 0) + { + transactionID = atomic_add_return( 1, &pDev->mQMIDev.mQMICTLTransactionID ); + } + if (transactionID != 0) + { + result = QMICTLGetClientIDReq( pWriteBuffer, + writeBufferSize, + transactionID, + serviceType ); + if (result < 0) + { + kfree( pWriteBuffer ); + return result; + } + } + else + { + DBG( "Invalid transaction ID!\n" ); + return EINVAL; + } + + 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 = atomic_add_return( 1, &pDev->mQMIDev.mQMICTLTransactionID ); + if (transactionID == 0) + { + transactionID = atomic_add_return( 1, &pDev->mQMIDev.mQMICTLTransactionID ); + } + 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; + } +} + +#ifndef f_dentry +#define f_dentry f_path.dentry +#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->mpNetDev->udev->descriptor.idVendor == cpu_to_le16(0x2c7c)) || + (pDev->mpNetDev->udev->descriptor.idVendor == cpu_to_le16(0x05c6)) || + (pDev->mpNetDev->udev->descriptor.idVendor == cpu_to_le16(0x2DEE)) || + (pDev->mpNetDev->udev->descriptor.idVendor == cpu_to_le16(0x2949)) || + (pDev->mpNetDev->udev->descriptor.idVendor == cpu_to_le16(0x1199)) || + (pDev->mpNetDev->udev->descriptor.idVendor == cpu_to_le16(0x413c)) || + (pDev->mpNetDev->udev->descriptor.idVendor == cpu_to_le16(0x1c9e)) || + (pDev->mpNetDev->udev->descriptor.idVendor == cpu_to_le16(0x1e0e))) + { + 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 = -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; + u8 transactionID; + + 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; + } + + // 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->mpNetDev->udev->descriptor.idVendor == cpu_to_le16(0x05c6)) || + (pDev->mpNetDev->udev->descriptor.idVendor == cpu_to_le16(0x2DEE)) || + (pDev->mpNetDev->udev->descriptor.idVendor == cpu_to_le16(0x2949)) || + (pDev->mpNetDev->udev->descriptor.idVendor == cpu_to_le16(0x1199)) || + (pDev->mpNetDev->udev->descriptor.idVendor == cpu_to_le16(0x413c)) || + (pDev->mpNetDev->udev->descriptor.idVendor == cpu_to_le16(0x1c9e)) || + (pDev->mpNetDev->udev->descriptor.idVendor == cpu_to_le16(0x1e0e))) //SIM7600 + { + 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 9x07, must wait about 15 seconds to wait QMI ready. it is too long for driver probe(will block other drivers probe). + if ( (pDev->mpNetDev->udev->descriptor.idVendor == cpu_to_le16(0x05c6)) || + (pDev->mpNetDev->udev->descriptor.idVendor == cpu_to_le16(0x2DEE)) || + (pDev->mpNetDev->udev->descriptor.idVendor == cpu_to_le16(0x2949)) || + (pDev->mpNetDev->udev->descriptor.idVendor == cpu_to_le16(0x1199)) || + (pDev->mpNetDev->udev->descriptor.idVendor == cpu_to_le16(0x413c)) || + (pDev->mpNetDev->udev->descriptor.idVendor == cpu_to_le16(0x1c9e)) || + (pDev->mpNetDev->udev->descriptor.idVendor == cpu_to_le16(0x1e0e)) ) //SIM7600 + { + 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; + } + + + if (pDev->mpNetDev->udev->descriptor.idVendor == cpu_to_le16(0x2c7c)) + { + 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->mpNetDev->udev->descriptor.idVendor == cpu_to_le16(0x2c7c)) + { + 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); + } + } + else + { + // 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" ); + } 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 = 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 = atomic_add_return( 1, &pDev->mQMIDev.mQMICTLTransactionID ); + if (transactionID == 0) + { + transactionID = atomic_add_return( 1, &pDev->mQMIDev.mQMICTLTransactionID ); + } + 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 (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 + + 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 + { + + // 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; + } + + 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; + + // 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, + 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 ); + + kfree( pReadBuffer ); + +#if 1 //def DATA_MODE_RP + pDev->mbRawIPMode = (result == 2); + 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 --git a/root/package/link4all/gobinet/src/QMIDevice.h b/root/package/link4all/gobinet/src/QMIDevice.h new file mode 100755 index 00000000..f28fe30f --- /dev/null +++ b/root/package/link4all/gobinet/src/QMIDevice.h @@ -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,14 )) +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,14 )) +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,14 )) +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 --git a/root/package/link4all/gobinet/src/Structs.h b/root/package/link4all/gobinet/src/Structs.h new file mode 100755 index 00000000..85724019 --- /dev/null +++ b/root/package/link4all/gobinet/src/Structs.h @@ -0,0 +1,423 @@ +/*=========================================================================== +FILE: + Structs.h + +DESCRIPTION: + Declaration of structures used by the Qualcomm Linux USB Network driver + +FUNCTIONS: + none + +Copyright (c) 2011, Code Aurora Forum. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Code Aurora Forum nor + the names of its contributors may be used to endorse or promote + products derived from this software without specific prior written + permission. + + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +===========================================================================*/ + +//--------------------------------------------------------------------------- +// Pragmas +//--------------------------------------------------------------------------- +#pragma once + +//--------------------------------------------------------------------------- +// Include Files +//--------------------------------------------------------------------------- +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,22 )) +#define bool u8 +#define URB_FREE_BUFFER 0x0100 /* Free transfer buffer with the URB */ + +/** + * 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,14 )) +/** + * usb_endpoint_dir_in - check if the endpoint has IN direction + * @epd: endpoint to be checked + * + * Returns true if the endpoint is of type IN, otherwise it returns false. + */ +static inline int usb_endpoint_dir_in(const struct usb_endpoint_descriptor *epd) +{ + return ((epd->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN); +} + +/** + * usb_endpoint_dir_out - check if the endpoint has OUT direction + * @epd: endpoint to be checked + * + * Returns true if the endpoint is of type OUT, otherwise it returns false. + */ +static inline int usb_endpoint_dir_out( + const struct usb_endpoint_descriptor *epd) +{ + return ((epd->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_OUT); +} + +/** + * usb_endpoint_xfer_int - check if the endpoint has interrupt transfer type + * @epd: endpoint to be checked + * + * Returns true if the endpoint is of type interrupt, otherwise it returns + * false. + */ +static inline int usb_endpoint_xfer_int( + const struct usb_endpoint_descriptor *epd) +{ + return ((epd->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == + USB_ENDPOINT_XFER_INT); +} + +static inline int usb_autopm_set_interface(struct usb_interface *intf) +{ return 0; } + +static inline int usb_autopm_get_interface(struct usb_interface *intf) +{ return 0; } + +static inline int usb_autopm_get_interface_async(struct usb_interface *intf) +{ return 0; } + +static inline void usb_autopm_put_interface(struct usb_interface *intf) +{ } +static inline void usb_autopm_put_interface_async(struct usb_interface *intf) +{ } +static inline void usb_autopm_enable(struct usb_interface *intf) +{ } +static inline void usb_autopm_disable(struct usb_interface *intf) +{ } +static inline void usb_mark_last_busy(struct usb_device *udev) +{ } +#endif + +#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,24 )) + #include "usbnet.h" +#else + #include +#endif + +#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,25 )) + #include +#else + #include +#endif + +// Used in recursion, defined later below +struct sGobiUSBNet; + +/*=========================================================================*/ +// Struct sReadMemList +// +// Structure that defines an entry in a Read Memory linked list +/*=========================================================================*/ +typedef struct sReadMemList +{ + /* Data buffer */ + void * mpData; + + /* Transaction ID */ + u16 mTransactionID; + + /* Size of data buffer */ + u16 mDataSize; + + /* Next entry in linked list */ + struct sReadMemList * mpNext; + +} sReadMemList; + +/*=========================================================================*/ +// Struct sNotifyList +// +// Structure that defines an entry in a Notification linked list +/*=========================================================================*/ +typedef struct sNotifyList +{ + /* Function to be run when data becomes available */ + void (* mpNotifyFunct)(struct sGobiUSBNet *, u16, void *); + + /* Transaction ID */ + u16 mTransactionID; + + /* Data to provide as parameter to mpNotifyFunct */ + void * mpData; + + /* Next entry in linked list */ + struct sNotifyList * mpNext; + +} sNotifyList; + +/*=========================================================================*/ +// Struct sURBList +// +// Structure that defines an entry in a URB linked list +/*=========================================================================*/ +typedef struct sURBList +{ + /* The current URB */ + struct urb * mpURB; + + /* Next entry in linked list */ + struct sURBList * mpNext; + +} sURBList; + +/*=========================================================================*/ +// Struct sClientMemList +// +// Structure that defines an entry in a Client Memory linked list +// Stores data specific to a Service Type and Client ID +/*=========================================================================*/ +typedef struct sClientMemList +{ + /* Client ID for this Client */ + u16 mClientID; + + /* Linked list of Read entries */ + /* Stores data read from device before sending to client */ + sReadMemList * mpList; + + /* Linked list of Notification entries */ + /* Stores notification functions to be run as data becomes + available or the device is removed */ + sNotifyList * mpReadNotifyList; + + /* Linked list of URB entries */ + /* Stores pointers to outstanding URBs which need canceled + when the client is deregistered or the device is removed */ + sURBList * mpURBList; + + /* Next entry in linked list */ + struct sClientMemList * mpNext; + + /* Wait queue object for poll() */ + wait_queue_head_t mWaitQueue; + +} sClientMemList; + +/*=========================================================================*/ +// Struct sURBSetupPacket +// +// Structure that defines a USB Setup packet for Control URBs +// Taken from USB CDC specifications +/*=========================================================================*/ +typedef struct sURBSetupPacket +{ + /* Request type */ + u8 mRequestType; + + /* Request code */ + u8 mRequestCode; + + /* Value */ + u16 mValue; + + /* Index */ + u16 mIndex; + + /* Length of Control URB */ + u16 mLength; + +} sURBSetupPacket; + +// Common value for sURBSetupPacket.mLength +#define DEFAULT_READ_URB_LENGTH 0x1000 + +#ifdef CONFIG_PM +#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,29 )) +/*=========================================================================*/ +// Struct sAutoPM +// +// Structure used to manage AutoPM thread which determines whether the +// device is in use or may enter autosuspend. Also submits net +// transmissions asynchronously. +/*=========================================================================*/ +typedef struct sAutoPM +{ + /* Thread for atomic autopm function */ + struct task_struct * mpThread; + + /* Signal for completion when it's time for the thread to work */ + struct completion mThreadDoWork; + + /* Time to exit? */ + bool mbExit; + + /* List of URB's queued to be sent to the device */ + sURBList * mpURBList; + + /* URB list lock (for adding and removing elements) */ + spinlock_t mURBListLock; + + /* Length of the URB list */ + atomic_t mURBListLen; + + /* Active URB */ + struct urb * mpActiveURB; + + /* Active URB lock (for adding and removing elements) */ + spinlock_t mActiveURBLock; + + /* Duplicate pointer to USB device interface */ + struct usb_interface * mpIntf; + +} sAutoPM; +#endif +#endif /* CONFIG_PM */ + +/*=========================================================================*/ +// Struct sQMIDev +// +// Structure that defines the data for the QMI device +/*=========================================================================*/ +typedef struct sQMIDev +{ + /* Device number */ + dev_t mDevNum; + + /* Device class */ + struct class * mpDevClass; + + /* cdev struct */ + struct cdev mCdev; + + /* is mCdev initialized? */ + bool mbCdevIsInitialized; + + /* Pointer to read URB */ + struct urb * mpReadURB; + + /* 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 + /* QMI "device" work in IP Mode or ETH Mode */ + bool mbRawIPMode; +#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 --git a/root/package/link4all/gobinet_srm815/Makefile b/root/package/link4all/gobinet_srm815/Makefile new file mode 100755 index 00000000..e7017de2 --- /dev/null +++ b/root/package/link4all/gobinet_srm815/Makefile @@ -0,0 +1,54 @@ +# +# Copyright (c) 2014 The Linux Foundation. All rights reserved. +# + +include $(TOPDIR)/rules.mk +include $(INCLUDE_DIR)/kernel.mk + +PKG_NAME:=srm815 +PKG_VERSION:=2011-07-29-1026 +PKG_RELEASE:=1 + + +PKG_BUILD_DIR:=$(KERNEL_BUILD_DIR)/$(PKG_NAME) + +include $(INCLUDE_DIR)/package.mk + +define KernelPackage/srm815 + CATEGORY:=LINK4ALL + DEPENDS:=+kmod-usb-net + TITLE:=srm815 + FILES:= $(PKG_BUILD_DIR)/srm815.ko + AUTOLOAD:=$(call AutoLoad,81,srm815) +endef + +define KernelPackage/srm815/Description +driver for meige srm815 5g modem +endef + +define Build/Prepare + $(CP) src/* $(PKG_BUILD_DIR) + $(call Build/Prepare/Default) +endef + +# define Build/Compile +# $(MAKE) -C "$(LINUX_DIR)" \ +# CROSS_COMPILE="$(TARGET_CROSS)" \ +# ARCH="$(LINUX_KARCH)" \ +# SUBDIRS="$(PKG_BUILD_DIR)" \ +# EXTRA_CFLAGS="$(EXTRA_CFLAGS)" \ +# modules +# endef + +MAKE_OPTS:= \ + $(KERNEL_MAKE_FLAGS) \ + M="$(PKG_BUILD_DIR)" + +define Build/Compile + $(MAKE) -C "$(LINUX_DIR)" \ + $(MAKE_OPTS) \ + modules +endef + +$(eval $(call KernelPackage,srm815)) + diff --git a/root/package/link4all/gobinet_srm815/patches00/gobinet-4.19.patch b/root/package/link4all/gobinet_srm815/patches00/gobinet-4.19.patch new file mode 100644 index 00000000..364e6504 --- /dev/null +++ b/root/package/link4all/gobinet_srm815/patches00/gobinet-4.19.patch @@ -0,0 +1,14 @@ +Index: QMIDevice.c +=================================================================== +--- a/QMIDevice.c ++++ b/QMIDevice.c +@@ -3382,7 +3382,8 @@ void DeregisterQMIDevice( sGobiUSBNet * + // but exists to prevent an infinate loop just in case. + for (tries = 0; tries < 30 * 100; tries++) + { +- int ref = atomic_read( &pDev->mQMIDev.mCdev.kobj.kref.refcount ); ++ //int ref = atomic_read( &pDev->mQMIDev.mCdev.kobj.kref.refcount ); ++ int ref = refcount_read( &pDev->mQMIDev.mCdev.kobj.kref.refcount ); + if (ref > 1) + { + DBG( "cdev in use by %d tasks\n", ref - 1 ); diff --git a/root/package/link4all/gobinet_srm815/src.old/GobiUSBNet.c b/root/package/link4all/gobinet_srm815/src.old/GobiUSBNet.c new file mode 100755 index 00000000..1d470244 --- /dev/null +++ b/root/package/link4all/gobinet_srm815/src.old/GobiUSBNet.c @@ -0,0 +1,2171 @@ +/*=========================================================================== +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 +#include +#include +#include +#include + +#include +#include +#include + +#include "Structs.h" +#include "QMIDevice.h" +#include "QMI.h" + +//----------------------------------------------------------------------------- +// Definitions +//----------------------------------------------------------------------------- + +// Version Information +//add new module or new feature, increase major version. fix bug, increase minor version +#define DRIVER_VERSION "Meig_GobiNet_Driver_V1.3.9" +#define DRIVER_AUTHOR "Qualcomm Innovation Center" +#define DRIVER_DESC "GobiNet" + +// Debug flag +int meig_debug = 0; + +// Allow user interrupts +//int interruptible = 1; + +// Number of IP packets which may be queued up for transmit +static int txQueueLength = 100; + +// Class should be created during module init, so needs to be global +static struct class * gpClass; + +static const unsigned char meig_mac[ETH_ALEN] = {0x02, 0x50, 0xf3, 0x00, 0x00, 0x00}; +//static const u8 broadcast_addr[ETH_ALEN] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; + +//setup data call by "AT$QCRMCALL=1,1" +static uint __read_mostly qcrmcall_mode = 0; +module_param( qcrmcall_mode, uint, S_IRUGO | S_IWUSR ); + +static struct sk_buff * ether_to_ip_fixup(struct net_device *dev, struct sk_buff *skb) +{ + const struct ethhdr *ehdr; + + skb_reset_mac_header(skb); + ehdr = eth_hdr(skb); + + if (ehdr->h_proto == htons(ETH_P_IP)) { + if (unlikely(skb->len <= (sizeof(struct ethhdr) + sizeof(struct iphdr)))) { + goto drop_skb; + } + } else if (ehdr->h_proto == htons(ETH_P_IPV6)) { + if (unlikely(skb->len <= (sizeof(struct ethhdr) + sizeof(struct ipv6hdr)))) { + goto drop_skb; + } + } else { + DBG("%s skb h_proto is %04x\n", dev->name, ntohs(ehdr->h_proto)); + goto drop_skb; + } + + if (unlikely(skb_pull(skb, ETH_HLEN))) + return skb; + +drop_skb: + return NULL; +} + +//#define MEIG_REMOVE_TX_ZLP +#define USB_CDC_SET_REMOVE_TX_ZLP_COMMAND 0x5D + +//#define MEIG_WWAN_MULTI_PACKAGES + +#ifdef MEIG_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 MEIG_NET_MSG_SPEC (0x80) +#define MEIG_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 meig_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; +module_param( bridge_mode, int, S_IRUGO | S_IWUSR ); + +static int bridge_arp_reply(sGobiUSBNet * pGobiDev, struct sk_buff *skb) +{ + struct net_device *dev = pGobiDev->mpNetDev->net; + struct arphdr *parp; + u8 *arpptr, *sha; + u8 sip[4], tip[4], ipv4[4]; + struct sk_buff *reply = NULL; + + ipv4[0] = (pGobiDev->m_bridge_ipv4 >> 24) & 0xFF; + ipv4[1] = (pGobiDev->m_bridge_ipv4 >> 16) & 0xFF; + ipv4[2] = (pGobiDev->m_bridge_ipv4 >> 8) & 0xFF; + ipv4[3] = (pGobiDev->m_bridge_ipv4 >> 0) & 0xFF; + + 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)); + + DBG("sip = %d.%d.%d.%d, tip=%d.%d.%d.%d, ipv4=%d.%d.%d.%d\n", + sip[0], sip[1], sip[2], sip[3], tip[0], tip[1], tip[2], tip[3], ipv4[0], ipv4[1], ipv4[2], ipv4[3]); + if (tip[0] == ipv4[0] && tip[1] == ipv4[1] && tip[2] == ipv4[2] && tip[3] != ipv4[3]) + reply = arp_create(ARPOP_REPLY, ETH_P_ARP, *((__be32 *)sip), dev, *((__be32 *)tip), sha, meig_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; +} + +static ssize_t bridge_mode_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct net_device *pNet = to_net_dev(dev); + struct usbnet * pDev = netdev_priv( pNet ); + sGobiUSBNet * pGobiDev = (sGobiUSBNet *)pDev->data[0]; + + return snprintf(buf, PAGE_SIZE, "%d\n", pGobiDev->m_bridge_mode); +} + +static ssize_t bridge_mode_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct net_device *pNet = to_net_dev(dev); + struct usbnet * pDev = netdev_priv( pNet ); + sGobiUSBNet * pGobiDev = (sGobiUSBNet *)pDev->data[0]; + + if (!GobiTestDownReason( pGobiDev, NET_IFACE_STOPPED )) { + INFO("please ifconfig %s down\n", pNet->name); + return -EPERM; + } + + pGobiDev->m_bridge_mode = !!simple_strtoul(buf, NULL, 10); + + return count; +} + +static ssize_t bridge_ipv4_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct net_device *pNet = to_net_dev(dev); + struct usbnet * pDev = netdev_priv( pNet ); + sGobiUSBNet * pGobiDev = (sGobiUSBNet *)pDev->data[0]; + unsigned char ipv4[4]; + + ipv4[0] = (pGobiDev->m_bridge_ipv4 >> 24) & 0xFF; + ipv4[1] = (pGobiDev->m_bridge_ipv4 >> 16) & 0xFF; + ipv4[2] = (pGobiDev->m_bridge_ipv4 >> 8) & 0xFF; + ipv4[3] = (pGobiDev->m_bridge_ipv4 >> 0) & 0xFF; + + return snprintf(buf, PAGE_SIZE, "%d.%d.%d.%d\n", ipv4[0], ipv4[1], ipv4[2], ipv4[3]); +} + +static ssize_t bridge_ipv4_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct net_device *pNet = to_net_dev(dev); + struct usbnet * pDev = netdev_priv( pNet ); + sGobiUSBNet * pGobiDev = (sGobiUSBNet *)pDev->data[0]; + + pGobiDev->m_bridge_ipv4 = simple_strtoul(buf, NULL, 16); + + return count; +} + + +static DEVICE_ATTR(bridge_mode, S_IWUSR | S_IRUGO, bridge_mode_show, bridge_mode_store); +static DEVICE_ATTR(bridge_ipv4, S_IWUSR | S_IRUGO, bridge_ipv4_show, bridge_ipv4_store); +#endif + +#ifdef MEIG_WWAN_QMAP +/* + Meig_WCDMA<E_Linux_USB_Driver_User_Guide_V1.9.pdf + 5.6. Test QMAP on GobiNet or QMI WWAN + 0 - no QMAP + 1 - QMAP (Aggregation protocol) + X - QMAP (Multiplexing and Aggregation protocol) +*/ +static uint __read_mostly qmap_mode = 0; +module_param( qmap_mode, uint, S_IRUGO | S_IWUSR ); + +struct qmap_hdr { + u8 cd_rsvd_pad; + u8 mux_id; + u16 pkt_len; +} __packed; + +struct qmap_priv { + struct net_device *real_dev; + u8 offset_id; +}; + +static int qmap_open(struct net_device *dev) +{ + struct qmap_priv *priv = netdev_priv(dev); + struct net_device *real_dev = priv->real_dev; + + if (!(priv->real_dev->flags & IFF_UP)) + return -ENETDOWN; + + if (netif_carrier_ok(real_dev)) + netif_carrier_on(dev); + return 0; +} + +static int qmap_stop(struct net_device *pNet) +{ + netif_carrier_off(pNet); + return 0; +} + +static int qmap_start_xmit(struct sk_buff *skb, struct net_device *pNet) +{ + int err; + struct qmap_priv *priv = netdev_priv(pNet); + unsigned int len; + struct qmap_hdr *hdr; + + if (ether_to_ip_fixup(pNet, skb) == NULL) { + dev_kfree_skb_any (skb); + return NETDEV_TX_OK; + } + + len = skb->len; + hdr = (struct qmap_hdr *)skb_push(skb, sizeof(struct qmap_hdr)); + hdr->cd_rsvd_pad = 0; + hdr->mux_id = MEIG_QMAP_MUX_ID + priv->offset_id; + hdr->pkt_len = cpu_to_be16(len); + + skb->dev = priv->real_dev; + err = dev_queue_xmit(skb); +#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,14 )) + if (err == NET_XMIT_SUCCESS) { + pNet->stats.tx_packets++; + pNet->stats.tx_bytes += skb->len; + } else { + pNet->stats.tx_errors++; + } +#endif + + return err; +} + +#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,29 )) +#else +static const struct net_device_ops qmap_netdev_ops = { + .ndo_open = qmap_open, + .ndo_stop = qmap_stop, + .ndo_start_xmit = qmap_start_xmit, +}; +#endif + +static int qmap_register_device(sGobiUSBNet * pDev, u8 offset_id) +{ + struct net_device *real_dev = pDev->mpNetDev->net; + struct net_device *qmap_net; + struct qmap_priv *priv; + int err; + + qmap_net = alloc_etherdev(sizeof(*priv)); + if (!qmap_net) + return -ENOBUFS; + + SET_NETDEV_DEV(qmap_net, &real_dev->dev); + priv = netdev_priv(qmap_net); + priv->offset_id = offset_id; + priv->real_dev = real_dev; + sprintf(qmap_net->name, "%s.%d", real_dev->name, offset_id + 1); +#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,29 )) + qmap_net->open = qmap_open; + qmap_net->stop = qmap_stop; + qmap_net->hard_start_xmit = qmap_start_xmit; +#else + qmap_net->netdev_ops = &qmap_netdev_ops; +#endif + memcpy (qmap_net->dev_addr, real_dev->dev_addr, ETH_ALEN); + + err = register_netdev(qmap_net); + if (err < 0) + goto out_free_newdev; + netif_device_attach (qmap_net); + + pDev->mpQmapNetDev[offset_id] = qmap_net; + qmap_net->flags |= IFF_NOARP; + + INFO("%s\n", qmap_net->name); + + return 0; + +out_free_newdev: + free_netdev(qmap_net); + return err; +} + +static void qmap_unregister_device(sGobiUSBNet * pDev, u8 offset_id) +{ + struct net_device *net = pDev->mpQmapNetDev[offset_id]; + if (net != NULL) { + netif_carrier_off( net ); + unregister_netdev (net); + free_netdev(net); + } +} + +static ssize_t qmap_mode_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct net_device *pNet = to_net_dev(dev); + struct usbnet * pDev = netdev_priv( pNet ); + sGobiUSBNet * pGobiDev = (sGobiUSBNet *)pDev->data[0]; + + return snprintf(buf, PAGE_SIZE, "%d\n", pGobiDev->m_qmap_mode); +} + +static ssize_t qmap_mode_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct net_device *pNet = to_net_dev(dev); + struct usbnet * pDev = netdev_priv( pNet ); + sGobiUSBNet * pGobiDev = (sGobiUSBNet *)pDev->data[0]; + int err; + unsigned int qmap_mode; + int rx_urb_size = 4096; + + if (!GobiTestDownReason( pGobiDev, NET_IFACE_STOPPED )) { + INFO("please ifconfig %s down\n", pNet->name); + return -EPERM; + } + + if (!pGobiDev->mbQMIReady) { + INFO("please wait qmi ready\n"); + return -EBUSY; + } + + qmap_mode = simple_strtoul(buf, NULL, 10); + + if (pGobiDev->m_qcrmcall_mode) { + INFO("AT$QCRMCALL MODE had enabled\n"); + return -EINVAL; + } + + if (qmap_mode <= 0 || qmap_mode > MEIG_WWAN_QMAP) { + INFO("qmap_mode = %d is Invalid argument, shoule be 1 ~ %d\n", qmap_mode, MEIG_WWAN_QMAP); + return -EINVAL; + } + + if (pGobiDev->m_qmap_mode) { + INFO("qmap_mode aleary set to %d, do not allow re-set again\n", pGobiDev->m_qmap_mode); + return -EPERM; + } + + // Setup Data Format + err = MeigQMIWDASetDataFormat (pGobiDev, + qmap_mode, + &rx_urb_size); + if (err != 0) { + return err; + } + + pDev->rx_urb_size = rx_urb_size; + pGobiDev->m_qmap_mode = qmap_mode; + + if (pGobiDev->m_qmap_mode > 1) { + unsigned i; + for (i = 0; i < pGobiDev->m_qmap_mode; i++) { + qmap_register_device(pGobiDev, i); + } + } +#if (LINUX_VERSION_CODE < KERNEL_VERSION( 5,0,0 )) +#ifdef FLAG_RX_ASSEMBLE + if (pGobiDev->m_qmap_mode) + pDev->driver_info->flags |= FLAG_RX_ASSEMBLE; +#endif +#endif + + return count; +} + +static DEVICE_ATTR(qmap_mode, S_IWUSR | S_IRUGO, qmap_mode_show, qmap_mode_store); +#endif + +static struct attribute *gobinet_sysfs_attrs[] = { +#ifdef CONFIG_BRIDGE + &dev_attr_bridge_mode.attr, + &dev_attr_bridge_ipv4.attr, +#endif +#ifdef MEIG_WWAN_QMAP + &dev_attr_qmap_mode.attr, +#endif + NULL, +}; + +static struct attribute_group gobinet_sysfs_attr_group = { + .attrs = gobinet_sysfs_attrs, +}; + +#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 +===========================================================================*/ +static 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 + if (pGobiDev->m_qcrmcall_mode) { + } else { + 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 ); +} +int MeigGobiNetSuspend(struct usb_interface *pIntf, pm_message_t powerEvent ) +{ + return GobiNetSuspend(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 +===========================================================================*/ +static 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 + if (pGobiDev->m_qcrmcall_mode) { + nRet = 0; + } else { + 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 + if ( !test_bit(pIntf->cur_altsetting->desc.bInterfaceNumber, &pDev->driver_info->data)) { + DBG( "invalid interface %d\n", + pIntf->cur_altsetting->desc.bInterfaceNumber ); + 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 ); + +#if defined(MEIG_WWAN_MULTI_PACKAGES) + if (rx_packets && pDev->udev->descriptor.idVendor == cpu_to_le16(0x05c6)) { + struct multi_package_config rx_config = { + .enable = cpu_to_le32(1), + .package_max_len = cpu_to_le32((1500 + sizeof(struct meig_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( "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 + + if (qcrmcall_mode == 0 && pDev->net->sysfs_groups[0] == NULL && gobinet_sysfs_attr_group.attrs[0] != NULL) { +#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,32)) //see commit 0c509a6c9393b27a8c5a01acd4a72616206cfc24 + pDev->net->sysfs_groups[1] = &gobinet_sysfs_attr_group; //see netdev_register_sysfs() +#else + pDev->net->sysfs_groups[0] = &gobinet_sysfs_attr_group; +#endif + } + + if (!pDev->rx_urb_size) { +//to advoid module report mtu 1460, but rx 1500 bytes IP packets, and cause the customer's system crash +//next setting can make usbnet.c:usbnet_change_mtu() do not modify rx_urb_size according to mtu + pDev->rx_urb_size = ETH_DATA_LEN + ETH_HLEN + 6; + } + + 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 ); + + if (pGobiDev->m_qcrmcall_mode) { + } else { + 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 + INFO("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 +===========================================================================*/ +static 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 MEIG_WWAN_QMAP + if (pGobiDev->m_qmap_mode) { + struct qmap_hdr *qhdr; + + if (pGobiDev->m_qmap_mode > 1) { + qhdr = (struct qmap_hdr *)skb->data; + if (qhdr->cd_rsvd_pad != 0) { + goto drop_skb; + } + if ((qhdr->mux_id&0xF0) != 0x80) { + goto drop_skb; + } + } else { + if (ether_to_ip_fixup(dev->net, skb) == NULL) + goto drop_skb; + qhdr = (struct qmap_hdr *)skb_push(skb, sizeof(struct qmap_hdr)); + qhdr->cd_rsvd_pad = 0; + qhdr->mux_id = MEIG_QMAP_MUX_ID; + qhdr->pkt_len = cpu_to_be16(skb->len - sizeof(struct qmap_hdr)); + } + + return skb; + } +#endif + +#ifdef CONFIG_BRIDGE + if (pGobiDev->m_bridge_mode) { + struct ethhdr *ehdr; + const struct iphdr *iph; + + if (unlikely(skb->len <= ETH_ALEN)) + goto drop_skb; + skb_reset_mac_header(skb); + ehdr = eth_hdr(skb); +//meig_debug = 1; +// DBG("ethhdr: "); +// PrintHex(ehdr, sizeof(struct ethhdr)); + + if (ehdr->h_proto == htons(ETH_P_ARP)) { + bridge_arp_reply(pGobiDev, skb); + goto drop_skb; + } + + 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 + { + int save_debug = meig_debug; + memcpy(pGobiDev->mHostMAC, ehdr->h_source, ETH_ALEN); + INFO("PC Mac Address: "); + meig_debug=1; + PrintHex(pGobiDev->mHostMAC, ETH_ALEN); + meig_debug=save_debug; + } + } + +#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, meig_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)); + goto drop_skb; + } + +//meig_debug = 0; + } +#endif + + // Skip Ethernet header from message + if (likely(ether_to_ip_fixup(dev->net, skb))) { + 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 + } + +drop_skb: +#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,24 )) && defined(CONFIG_X86_32) + INFO("dev_kfree_skb_any() will make kernel panic on CentOS!\n"); + meig_debug=1; + PrintHex(skb->data, 32); + meig_debug=0; +#else + // Filter the packet out, release it + dev_kfree_skb_any(skb); +#endif + return NULL; +} + +#if defined(MEIG_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 meig_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 != MEIG_NET_MSG_SPEC || package_header.msg_id != MEIG_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; +} +#else +#ifdef MEIG_WWAN_QMAP +static int GobiNetDriverRxQmapFixup(struct usbnet *dev, struct sk_buff *skb) +{ + sGobiUSBNet * pGobiDev = (sGobiUSBNet *)dev->data[0]; + static int debug_len = 0; + int debug_pkts = 0; + int update_len = skb->len; + + while (skb->len > sizeof(struct qmap_hdr)) { + struct qmap_hdr *qhdr = (struct qmap_hdr *)skb->data; + struct net_device *qmap_net; + struct sk_buff *qmap_skb; + __be16 proto; + int pkt_len; + u8 offset_id = 0; + int err; + unsigned len = (be16_to_cpu(qhdr->pkt_len) + sizeof(struct qmap_hdr)); + +#if 0 + meig_debug = 1; + DBG("rx: %d\n", skb->len); + PrintHex(skb->data, 16); + meig_debug = 0; +#endif + + if (skb->len < (be16_to_cpu(qhdr->pkt_len) + sizeof(struct qmap_hdr))) { + INFO("drop qmap unknow pkt, len=%d, pkt_len=%d\n", skb->len, be16_to_cpu(qhdr->pkt_len)); + meig_debug = 1; + PrintHex(skb->data, 16); + meig_debug = 0; + goto out; + } + + debug_pkts++; + + if (qhdr->cd_rsvd_pad & 0x80) { + INFO("drop qmap command packet %x\n", qhdr->cd_rsvd_pad); + goto skip_pkt;; + } + + offset_id = qhdr->mux_id - MEIG_QMAP_MUX_ID; + if (offset_id >= pGobiDev->m_qmap_mode) { + INFO("drop qmap unknow mux_id %x\n", qhdr->mux_id); + goto skip_pkt; + } + + if (pGobiDev->m_qmap_mode > 1) { + qmap_net = pGobiDev->mpQmapNetDev[offset_id]; + } else { + qmap_net = dev->net; + } + + if (qmap_net == NULL) { + INFO("drop qmap unknow mux_id %x\n", qhdr->mux_id); + goto skip_pkt; + } + + switch (skb->data[sizeof(struct qmap_hdr)] & 0xf0) { + case 0x40: + proto = htons(ETH_P_IP); + break; + case 0x60: + proto = htons(ETH_P_IPV6); + break; + default: + goto skip_pkt; + } + + pkt_len = be16_to_cpu(qhdr->pkt_len) - (qhdr->cd_rsvd_pad&0x3F); + qmap_skb = netdev_alloc_skb(qmap_net, ETH_HLEN + pkt_len); + + skb_reset_mac_header(qmap_skb); + memcpy(eth_hdr(qmap_skb)->h_source, meig_mac, ETH_ALEN); + memcpy(eth_hdr(qmap_skb)->h_dest, qmap_net->dev_addr, ETH_ALEN); + eth_hdr(qmap_skb)->h_proto = proto; + memcpy(skb_put(qmap_skb, ETH_HLEN + pkt_len) + ETH_HLEN, skb->data + sizeof(struct qmap_hdr), pkt_len); + + if (pGobiDev->m_qmap_mode > 1) { + qmap_skb->protocol = eth_type_trans (qmap_skb, qmap_net); + memset(qmap_skb->cb, 0, sizeof(struct skb_data)); + err = netif_rx(qmap_skb); +#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,14 )) + if (err == NET_RX_SUCCESS) { + qmap_net->stats.rx_packets++; + qmap_net->stats.rx_bytes += qmap_skb->len; + } else { + qmap_net->stats.rx_errors++; + } +#endif + } else { + usbnet_skb_return(dev, qmap_skb); + } + +skip_pkt: + skb_pull(skb, len); + } + +out: + if (update_len > debug_len) { + debug_len = update_len; + INFO("rx_pkts=%d, rx_len=%d\n", debug_pkts, debug_len); + } + return 0; +} +#endif +/*=========================================================================== +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; + +#ifdef MEIG_WWAN_QMAP + if (pGobiDev->m_qmap_mode) { + return GobiNetDriverRxQmapFixup(dev, skb); + } +#endif + + 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, meig_mac, ETH_ALEN); +fix_dest: +#ifdef CONFIG_BRIDGE + if (pGobiDev->m_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 (pGobiDev->m_bridge_mode) { + struct ethhdr *ehdr = eth_hdr(skb); + meig_debug = 1; + DBG(": "); + PrintHex(ehdr, sizeof(struct ethhdr)); + meig_debug = 0; + } +#endif +#endif + + return 1; +} +#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 )) { + // 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 )) { + // 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 +===========================================================================*/ +static 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 +===========================================================================*/ +static 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 struct driver_info GobiNetInfo = { + .description = "Meig 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 +#if defined(MEIG_WWAN_MULTI_PACKAGES) + .rx_fixup = GobiNetDriverRxPktsFixup, +#else + .rx_fixup = GobiNetDriverRxFixup, +#endif + .tx_fixup = GobiNetDriverTxFixup, +#endif + .data = (1 << 5), +}; + +/*=========================================================================*/ +// 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 MeigGobiVIDPIDTable [] = { + GOBI_FIXED_INTF( 0x05c6, 0xf601 ), // SLM750V + GOBI_FIXED_INTF( 0x2dee, 0x4d22 ), // SRM815 + //Terminating entry + { } +}; + +MODULE_DEVICE_TABLE( usb, MeigGobiVIDPIDTable ); + +/*=========================================================================== +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 +===========================================================================*/ +static 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 + 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 + 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(0x05c6)); + pGobiDev->mbMdm9x07 = true; //only support 9x07 chipset + pGobiDev->mbRawIPMode = pGobiDev->mbMdm9x07; + pGobiDev->mbRawIPMode = true; + if ( pGobiDev->mbRawIPMode) + pGobiDev->mpNetDev->net->flags |= IFF_NOARP; +#ifdef CONFIG_BRIDGE + memcpy(pGobiDev->mHostMAC, pDev->net->dev_addr, 6); + pGobiDev->m_bridge_mode = bridge_mode; +#endif + +#ifdef MEIG_REMOVE_TX_ZLP + { + struct remove_tx_zlp_config { + __le32 enable; + } __packed; + + struct remove_tx_zlp_config cfg; + cfg.enable = cpu_to_le32(1); //1-enable 0-disable + + usb_control_msg( + interface_to_usbdev(pIntf), + usb_sndctrlpipe(interface_to_usbdev(pIntf), 0), + USB_CDC_SET_REMOVE_TX_ZLP_COMMAND, + 0x21, //USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE + 0, + pIntf->cur_altsetting->desc.bInterfaceNumber, + &cfg, sizeof(cfg), 100); + } +#endif + + pGobiDev->m_qcrmcall_mode = qcrmcall_mode; + + if (pGobiDev->m_qcrmcall_mode) { + INFO("AT$QCRMCALL MODE!"); + + GobiClearDownReason( pGobiDev, NO_NDIS_CONNECTION ); + usb_control_msg( + interface_to_usbdev(pIntf), + usb_sndctrlpipe(interface_to_usbdev(pIntf), 0), + 0x22, //USB_CDC_REQ_SET_CONTROL_LINE_STATE + 0x21, //USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE + 1, //active CDC DTR + pIntf->cur_altsetting->desc.bInterfaceNumber, + NULL, 0, 100); + status = 0; + } else { + if (pGobiDev->mbRawIPMode) { + pGobiDev->m_qmap_mode = qmap_mode; + } + 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; + } + +#if defined(MEIG_WWAN_QMAP) + if (pGobiDev->m_qmap_mode > 1) { + unsigned i; + for (i = 0; i < pGobiDev->m_qmap_mode; i++) { + qmap_register_device(pGobiDev, i); + } + } +#endif + +#if (LINUX_VERSION_CODE < KERNEL_VERSION( 5,0,0 )) +#ifdef FLAG_RX_ASSEMBLE + if (pGobiDev->m_qmap_mode) + pDev->driver_info->flags |= FLAG_RX_ASSEMBLE; +#endif +#endif + // Success + return 0; +} + +static void GobiUSBNetDisconnect (struct usb_interface *intf) +{ +#if defined(MEIG_WWAN_QMAP) + struct usbnet *pDev = usb_get_intfdata(intf); + sGobiUSBNet * pGobiDev = (sGobiUSBNet *)pDev->data[0]; + unsigned i; + + for (i = 0; i < pGobiDev->m_qmap_mode; i++) { + qmap_unregister_device(pGobiDev, i); + } +#endif + + usbnet_disconnect(intf); +} + +static struct usb_driver GobiNet = { + .name = "GobiNet", + .id_table = MeigGobiVIDPIDTable, + .probe = GobiUSBNetProbe, + .disconnect = GobiUSBNetDisconnect, +#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_named( debug, meig_debug, int, S_IRUGO | S_IWUSR ); +MODULE_PARM_DESC( debug, "Debuging enabled or not" ); + +//module_param_named( interruptible, Meiginterruptible, 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 --git a/root/package/link4all/gobinet_srm815/src.old/Makefile b/root/package/link4all/gobinet_srm815/src.old/Makefile new file mode 100755 index 00000000..9ab35101 --- /dev/null +++ b/root/package/link4all/gobinet_srm815/src.old/Makefile @@ -0,0 +1,30 @@ +obj-m := srm815.o +srm815-objs := GobiUSBNet.o QMIDevice.o QMI.o + +PWD := $(shell pwd) +OUTPUTDIR=/lib/modules/`uname -r`/kernel/drivers/net/usb/ + +ifeq ($(ARCH),) +ARCH := $(shell uname -m) +endif +ifeq ($(CROSS_COMPILE),) +CROSS_COMPILE := +endif +ifeq ($(KDIR),) +KDIR := /lib/modules/$(shell uname -r)/build +endif + +default: + ln -sf makefile Makefile + $(MAKE) ARCH=${ARCH} CROSS_COMPILE=${CROSS_COMPILE} -C $(KDIR) M=$(PWD) modules + +install: default + mkdir -p $(OUTPUTDIR) + cp -f srm815.ko $(OUTPUTDIR) + depmod + modprobe -r srm815 + modprobe srm815 + +clean: + rm -rf Makefile + rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions Module.* modules.order diff --git a/root/package/link4all/gobinet_srm815/src.old/QMI.c b/root/package/link4all/gobinet_srm815/src.old/QMI.c new file mode 100755 index 00000000..c7d68de5 --- /dev/null +++ b/root/package/link4all/gobinet_srm815/src.old/QMI.c @@ -0,0 +1,1446 @@ +#ifdef __MEIG_INCLUDE_QMI_C__ +/*=========================================================================== +FILE: + QMI.c + +DESCRIPTION: + Qualcomm QMI driver code + +FUNCTIONS: + Generic QMUX functions + ParseQMUX + FillQMUX + + Generic QMI functions + GetTLV + ValidQMIMessage + GetQMIMessageID + + Fill Buffers with QMI requests + QMICTLGetClientIDReq + QMICTLReleaseClientIDReq + QMICTLReadyReq + QMIWDSSetEventReportReq + QMIWDSGetPKGSRVCStatusReq + QMIDMSGetMEIDReq + QMIWDASetDataFormatReq + QMICTLSetDataFormatReq + QMICTLSyncReq + + Parse data from QMI responses + QMICTLGetClientIDResp + QMICTLReleaseClientIDResp + QMIWDSEventResp + QMIDMSGetMEIDResp + QMIWDASetDataFormatResp + QMICTLSyncResp + +Copyright (c) 2011, Code Aurora Forum. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Code Aurora Forum nor + the names of its contributors may be used to endorse or promote + products derived from this software without specific prior written + permission. + + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +===========================================================================*/ + +//--------------------------------------------------------------------------- +// Include Files +//--------------------------------------------------------------------------- +#include +#include +#include "Structs.h" +#include "QMI.h" + +/*=========================================================================*/ +// Get sizes of buffers needed by QMI requests +/*=========================================================================*/ + +/*=========================================================================== +METHOD: + QMUXHeaderSize (Public Method) + +DESCRIPTION: + Get size of buffer needed for QMUX + +RETURN VALUE: + u16 - size of buffer +===========================================================================*/ +static 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 +===========================================================================*/ +static 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 +===========================================================================*/ +static 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 +===========================================================================*/ +static 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 +===========================================================================*/ +static 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 +===========================================================================*/ +static u16 QMIWDSGetPKGSRVCStatusReqSize( void ) +{ + return sizeof( sQMUX ) + 7; +} + +/*=========================================================================== +METHOD: + QMIDMSGetMEIDReqSize (Public Method) + +DESCRIPTION: + Get size of buffer needed for QMUX + QMIDMSGetMEIDReq + +RETURN VALUE: + u16 - size of buffer +===========================================================================*/ +static u16 QMIDMSGetMEIDReqSize( void ) +{ + return sizeof( sQMUX ) + 7; +} + +#ifdef MEIG_WWAN_QMAP +struct QMIWDS_ADMIN_SET_DATA_FORMAT_TLV_QOS { + u8 TLVType; + u16 TLVLength; + u8 QOSSetting; +} __packed; + +struct QMIWDS_ADMIN_SET_DATA_FORMAT_TLV { + u8 TLVType; + u16 TLVLength; + u32 Value; +} __packed; + +struct QMIWDS_ENDPOINT_TLV { + u8 TLVType; + u16 TLVLength; + u32 ep_type; + u32 iface_id; +} __packed; + +struct QMIWDS_ADMIN_SET_DATA_FORMAT_REQ_MSG { + u8 CtlFlags; // 0: single QMUX Msg; 1: + u16 TransactionId; + u16 Type; + u16 Length; + struct QMIWDS_ADMIN_SET_DATA_FORMAT_TLV_QOS QosDataFormatTlv; + struct QMIWDS_ADMIN_SET_DATA_FORMAT_TLV UnderlyingLinkLayerProtocolTlv; + struct QMIWDS_ADMIN_SET_DATA_FORMAT_TLV UplinkDataAggregationProtocolTlv; + struct QMIWDS_ADMIN_SET_DATA_FORMAT_TLV DownlinkDataAggregationProtocolTlv; + struct QMIWDS_ADMIN_SET_DATA_FORMAT_TLV DownlinkDataAggregationMaxDatagramsTlv; + struct QMIWDS_ADMIN_SET_DATA_FORMAT_TLV DownlinkDataAggregationMaxSizeTlv; + struct QMIWDS_ENDPOINT_TLV epTlv; + //struct QMIWDS_ADMIN_SET_DATA_FORMAT_TLV dl_minimum_padding; +} __packed; +#endif + +/*=========================================================================== +METHOD: + QMIWDASetDataFormatReqSize (Public Method) + +DESCRIPTION: + Get size of buffer needed for QMUX + QMIWDASetDataFormatReq + +RETURN VALUE: + u16 - size of buffer +===========================================================================*/ +static u16 QMIWDASetDataFormatReqSize( int qmap_mode ) +{ + if (qmap_mode) + return sizeof( sQMUX ) + sizeof(struct QMIWDS_ADMIN_SET_DATA_FORMAT_REQ_MSG); + else + return sizeof( sQMUX ) + 18; +} + +/*=========================================================================== +METHOD: + QMICTLSyncReqSize (Public Method) + +DESCRIPTION: + Get size of buffer needed for QMUX + QMICTLSyncReq + +RETURN VALUE: + u16 - size of buffer +===========================================================================*/ +static 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 +===========================================================================*/ +static 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 +===========================================================================*/ +static 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 +===========================================================================*/ +static 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 +===========================================================================*/ +static 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 +===========================================================================*/ +static 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 +===========================================================================*/ +static 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 +===========================================================================*/ +static 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 +===========================================================================*/ +static 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 +===========================================================================*/ +static 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 +===========================================================================*/ +static 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; +} + +#if 1 //enable by zhaopf for net interface bond. +static u16 QMIWDSSetQMUXBindMuxDataPortSize( void ) +{ + return sizeof( sQMUX ) + 29; +} + +static u16 QMIWDSSetQMUXBindMuxDataPortReq( + void * pBuffer, + u16 buffSize, + u8 MuxId, + sGobiUSBNet * pDev, //add for net interface bond, by zhaopf + 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 + ////add for net interface bind, by zhaopf + put_unaligned(cpu_to_le32(pDev->mpIntf->cur_altsetting->desc.bInterfaceNumber), (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) = MuxId; // 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; +} +#endif + +/*=========================================================================== +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 +===========================================================================*/ +static 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 +===========================================================================*/ +static int QMIWDASetDataFormatReq( + void * pBuffer, + u16 buffSize, + bool bRawIPMode, int qmap_mode, u32 rx_size, + u16 transactionID ) +{ + if (qmap_mode) { + struct QMIWDS_ADMIN_SET_DATA_FORMAT_REQ_MSG *pMUXMsg = (struct QMIWDS_ADMIN_SET_DATA_FORMAT_REQ_MSG *)(pBuffer + sizeof( sQMUX )); + + pMUXMsg->CtlFlags = 0x00; + put_unaligned( cpu_to_le16(transactionID), &pMUXMsg->TransactionId); + put_unaligned( cpu_to_le16(0x0020), &pMUXMsg->Type); + put_unaligned( cpu_to_le16(sizeof( struct QMIWDS_ADMIN_SET_DATA_FORMAT_REQ_MSG) - 7), &pMUXMsg->Length); + +//Indicates whether the Quality of Service(QOS) data format is used by the client. + pMUXMsg->QosDataFormatTlv.TLVType = 0x10; + pMUXMsg->QosDataFormatTlv.TLVLength = cpu_to_le16(0x0001); + pMUXMsg->QosDataFormatTlv.QOSSetting = 0; /* no-QOS header */ +//Underlying Link Layer Protocol + pMUXMsg->UnderlyingLinkLayerProtocolTlv.TLVType = 0x11; + pMUXMsg->UnderlyingLinkLayerProtocolTlv.TLVLength = cpu_to_le16(4); + pMUXMsg->UnderlyingLinkLayerProtocolTlv.Value = cpu_to_le32(0x02); /* Set Ethernet mode */ +//Uplink (UL) data aggregation protocol to be used for uplink data transfer. + pMUXMsg->UplinkDataAggregationProtocolTlv.TLVType = 0x12; + pMUXMsg->UplinkDataAggregationProtocolTlv.TLVLength = cpu_to_le16(4); + pMUXMsg->UplinkDataAggregationProtocolTlv.Value = cpu_to_le32(0x05); //UL QMAP is enabled +//Downlink (DL) data aggregation protocol to be used for downlink data transfer + pMUXMsg->DownlinkDataAggregationProtocolTlv.TLVType = 0x13; + pMUXMsg->DownlinkDataAggregationProtocolTlv.TLVLength = cpu_to_le16(4); + pMUXMsg->DownlinkDataAggregationProtocolTlv.Value = cpu_to_le32(0x05); //UL QMAP is enabled +//Maximum number of datagrams in a single aggregated packet on downlink + pMUXMsg->DownlinkDataAggregationMaxDatagramsTlv.TLVType = 0x15; + pMUXMsg->DownlinkDataAggregationMaxDatagramsTlv.TLVLength = cpu_to_le16(4); + pMUXMsg->DownlinkDataAggregationMaxDatagramsTlv.Value = cpu_to_le32(rx_size/1024); +//Maximum size in bytes of a single aggregated packet allowed on downlink + pMUXMsg->DownlinkDataAggregationMaxSizeTlv.TLVType = 0x16; + pMUXMsg->DownlinkDataAggregationMaxSizeTlv.TLVLength = cpu_to_le16(4); + pMUXMsg->DownlinkDataAggregationMaxSizeTlv.Value = cpu_to_le32(rx_size); +//Peripheral End Point ID + pMUXMsg->epTlv.TLVType = 0x17; + pMUXMsg->epTlv.TLVLength = cpu_to_le16(8); + pMUXMsg->epTlv.ep_type = cpu_to_le32(0x02); // DATA_EP_TYPE_BAM_DMUX + pMUXMsg->epTlv.iface_id = cpu_to_le32(0x04); + +#if 0 +//Specifies the minimum padding bytes to be added in between aggregated downlink QMAP packets. + pMUXMsg->dl_minimum_padding.TLVType = 0x19; + pMUXMsg->dl_minimum_padding.TLVLength = cpu_to_le16(4); + pMUXMsg->dl_minimum_padding.Value = cpu_to_le32(0); +#endif + + } else { + if (pBuffer == 0 || buffSize < QMIWDASetDataFormatReqSize(qmap_mode) ) { + 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(0x000b), (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 + + } + + // success + return QMIWDASetDataFormatReqSize(qmap_mode); +} + +#if 0 +static int QMIWDASetDataQmapReq( + void * pBuffer, + u16 buffSize, + u16 transactionID ) +{ + // 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(0x002B), (u16 *)(pBuffer + sizeof( sQMUX ) + 3) ); + + // Size of TLV's + put_unaligned( cpu_to_le16(0x0004), (u16 *)(pBuffer + sizeof( sQMUX ) + 5)); + + /* TLVType QMAP In-Band Flow Control 1 byte */ + *(u8 *)(pBuffer + sizeof( sQMUX ) + 7) = 0x10; + put_unaligned( cpu_to_le16(0x0001), (u16 *)(pBuffer + sizeof( sQMUX ) + 8)); + *(u8 *)(pBuffer + sizeof( sQMUX ) + 10) = 0x01; + + // success + return ( sizeof( sQMUX ) + 11); +} +#endif + +#if 0 +/*=========================================================================== +METHOD: + QMICTLSetDataFormatReqSize (Public Method) + +DESCRIPTION: + Get size of buffer needed for QMUX + QMICTLSetDataFormatReq + +RETURN VALUE: + u16 - size of buffer +===========================================================================*/ +static u16 QMICTLSetDataFormatReqSize( void ) +{ + return sizeof( sQMUX ) + 15; +} + +/*=========================================================================== +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 +===========================================================================*/ +static 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; + +} +#endif + +/*=========================================================================== +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 +===========================================================================*/ +static 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 +===========================================================================*/ +static 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 +===========================================================================*/ +static 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 +===========================================================================*/ +static 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 +===========================================================================*/ +static 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 +===========================================================================*/ +static int QMIWDASetDataFormatResp( + void * pBuffer, + u16 buffSize, bool bRawIPMode, int *qmap_enabled, int *rx_size, int *tx_size) +{ + + 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 + + GetTLV( pBuffer, buffSize, 0x12, qmap_enabled, 4); + if (le32_to_cpu(*qmap_enabled) == 5) + GetTLV( pBuffer, buffSize, 0x13, qmap_enabled, 4); + + GetTLV( pBuffer, buffSize, 0x16, rx_size, 4); + GetTLV( pBuffer, buffSize, 0x18, tx_size, 4); + + 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 +===========================================================================*/ +static 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; +} +#endif diff --git a/root/package/link4all/gobinet_srm815/src.old/QMI.h b/root/package/link4all/gobinet_srm815/src.old/QMI.h new file mode 100755 index 00000000..8a634769 --- /dev/null +++ b/root/package/link4all/gobinet_srm815/src.old/QMI.h @@ -0,0 +1,337 @@ +/*=========================================================================== +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 meig_debug; +// DBG macro +#define DBG( format, arg... ) do { \ + if (meig_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; + +#if 0 +/*=========================================================================*/ +// 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( int qmap_mode ); + +// 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, + u8 MuxId, + sGobiUSBNet * pDev, //add for net interface bond by zhaopf + 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, int qmap_mode, u32 rx_size, + u16 transactionID ); + +#if 0 +int QMIWDASetDataQmapReq( + void * pBuffer, + u16 buffSize, + u16 transactionID ); +#endif + +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, int *qmap_enabled, int *rx_size, int *tx_size); + +// Pasre the QMI CTL Sync Response +int QMICTLSyncResp( + void *pBuffer, + u16 buffSize ); +#endif diff --git a/root/package/link4all/gobinet_srm815/src.old/QMIDevice.c b/root/package/link4all/gobinet_srm815/src.old/QMIDevice.c new file mode 100755 index 00000000..93790b1c --- /dev/null +++ b/root/package/link4all/gobinet_srm815/src.old/QMIDevice.c @@ -0,0 +1,3906 @@ +/*=========================================================================== +FILE: + QMIDevice.c + +DESCRIPTION: + Functions related to the QMI interface device + +FUNCTIONS: + Generic functions + IsDeviceValid + PrintHex + GobiSetDownReason + GobiClearDownReason + GobiTestDownReason + + Driver level asynchronous read functions + ResubmitIntURB + ReadCallback + IntCallback + StartRead + KillRead + + Internal read/write functions + ReadAsync + UpSem + ReadSync + WriteSyncCallback + WriteSync + + Internal memory management functions + GetClientID + ReleaseClientID + FindClientMem + AddToReadMemList + PopFromReadMemList + AddToNotifyList + NotifyAndPopNotifyList + AddToURBList + PopFromURBList + + Internal userspace wrapper functions + UserspaceunlockedIOCTL + + Userspace wrappers + UserspaceOpen + UserspaceIOCTL + UserspaceClose + UserspaceRead + UserspaceWrite + UserspacePoll + + Initializer and destructor + RegisterQMIDevice + DeregisterQMIDevice + + Driver level client management + QMIReady + QMIWDSCallback + SetupQMIWDSCallback + QMIDMSGetMEID + +Copyright (c) 2011, Code Aurora Forum. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Code Aurora Forum nor + the names of its contributors may be used to endorse or promote + products derived from this software without specific prior written + permission. + + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +===========================================================================*/ + +//--------------------------------------------------------------------------- +// Include Files +//--------------------------------------------------------------------------- +#include +#include +#include +#include + +//----------------------------------------------------------------------------- +// Definitions +//----------------------------------------------------------------------------- + +#define __MEIG_INCLUDE_QMI_C__ +#include "QMI.c" +#define __MEIG_INTER__ +#include "QMIDevice.h" + +#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,22 )) +static int s_interval; +#endif + +#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,14 )) +#include +static char devfs_name[32]; +static 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; +} + +static 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 MeigGobiNetSuspend( + 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 + +static 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 +/*=========================================================================*/ +static 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, +#ifdef meig_no_for_each_process + .release = UserspaceClose, +#else + .flush = UserspaceClose, +#endif + .poll = UserspacePoll, +}; + +/*=========================================================================*/ +// Generic functions +/*=========================================================================*/ +static 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 +===========================================================================*/ +static 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 MeigPrintHex( + void * pBuffer, + u16 bufSize ) +{ + char * pPrintBuf; + u16 pos; + int status; + + if (meig_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 MeigGobiSetDownReason( + sGobiUSBNet * pDev, + u8 reason ) +{ + DBG("%s reason=%d, mDownReason=%x\n", __func__, reason, (unsigned)pDev->mDownReason); + +#ifdef MEIG_WWAN_QMAP + if (reason == NO_NDIS_CONNECTION) + return; +#endif + + set_bit( reason, &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 MeigGobiClearDownReason( + 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 MeigGobiTestDownReason( + 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 +===========================================================================*/ +static 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 )) +static void ReadCallback( struct urb * pReadURB ) +#else +static 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 )) +static void IntCallback( struct urb * pIntURB ) +{ +#else +static void IntCallback(struct urb *pIntURB, struct pt_regs *regs) +{ +#endif + int status; + struct usb_cdc_notification *dr; + + sGobiUSBNet * pDev = (sGobiUSBNet *)pIntURB->context; + dr = (struct usb_cdc_notification *)pDev->mQMIDev.mpIntBuffer; + + 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 + + VDBG( "IntCallback: Encapsulated Response = 0x%llx\n", + (*(u64*)pIntURB->transfer_buffer)); + + switch (dr->bNotificationType) { + case USB_CDC_NOTIFY_RESPONSE_AVAILABLE: { //0x01 + // 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; + } + case USB_CDC_NOTIFY_SPEED_CHANGE: { //0x2a + 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" ); + } + } + default: { + 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 MeigStartRead( 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 MeigKillRead( 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 +===========================================================================*/ +static 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 +===========================================================================*/ +#define MEIG_SEM_MAGIC 0x12345678 +struct MeigSem { + struct semaphore readSem; + int magic; +}; + +static void UpSem( + sGobiUSBNet * pDev, + u16 clientID, + void * pData ) +{ + struct MeigSem *pSem = (struct MeigSem *)pData; + + VDBG( "0x%04X\n", clientID ); + + if (pSem->magic == MEIG_SEM_MAGIC) + up( &(pSem->readSem) ); + else + kfree(pSem); + 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 +===========================================================================*/ +static int ReadSync( + sGobiUSBNet * pDev, + void ** ppOutBuffer, + u16 clientID, + u16 transactionID ) +{ + int result; + sClientMemList * pClientMem; + sNotifyList ** ppNotifyList, * pDelNotifyListEntry; + struct MeigSem 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.readSem, 0 ); + readSem.magic = MEIG_SEM_MAGIC; + + // 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.readSem ); + if (result == -EINTR) { + result = down_timeout(&readSem.readSem, msecs_to_jiffies(200)); + } + if (result != 0) { + DBG( "Down Timeout %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 )) +static void WriteSyncCallback( struct urb * pWriteURB ) +#else +static 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 +===========================================================================*/ +static 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 = kmalloc(sizeof(sURBSetupPacket *), GFP_KERNEL); + 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 + MeigGobiNetSuspend( pDev->mpIntf, PMSG_SUSPEND ); +#endif /* CONFIG_PM */ + } + usb_free_urb( pWriteURB ); + kfree(writeSetup); + + return result; + } + + // Critical section + spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags ); + + if (AddToURBList( pDev, clientID, pWriteURB ) == false) { + usb_free_urb( pWriteURB ); + kfree(writeSetup); + + // 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 ); + kfree(writeSetup); + + // 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 (1 != 0) { //(interruptible != 0) + // Allow user interrupts + result = down_interruptible( &writeSem ); + if (result == -EINTR) { + result = down_timeout(&writeSem, msecs_to_jiffies(200)); + } + } 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 ); + kfree(writeSetup); + 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 ); + kfree(writeSetup); + 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 ); + kfree(writeSetup); + + 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 +===========================================================================*/ +static 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 +===========================================================================*/ +static 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 && pDev->mpNetDev->udev->state) { + // 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 + INFO("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 +===========================================================================*/ +static 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 +===========================================================================*/ +static 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 +===========================================================================*/ +static 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 +===========================================================================*/ +static 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 +===========================================================================*/ +static 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 +===========================================================================*/ +static 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 +===========================================================================*/ +static 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 +===========================================================================*/ +static 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 meig-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 +===========================================================================*/ +static 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; + pFilpData->mpDev = pDev; + atomic_inc(&pFilpData->mpDev->refcount); + + 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 +===========================================================================*/ +#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,36 )) +static 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 ); +} +#endif + +#ifdef meig_no_for_each_process +static int UserspaceClose( + struct inode * pInode, + struct file * pFilp ) +{ + sQMIFilpStorage * pFilpData = (sQMIFilpStorage *)pFilp->private_data; + + if (pFilpData == NULL) { + DBG( "bad file data\n" ); + return -EBADF; + } + + atomic_dec(&pFilpData->mpDev->refcount); + + if (IsDeviceValid( pFilpData->mpDev ) == false) { + 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 ); + } + + kfree( pFilpData ); + return 0; +} +#else +/*=========================================================================== +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; +} +#endif + +/*=========================================================================== +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 +===========================================================================*/ +static 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 +===========================================================================*/ +static 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 +===========================================================================*/ +static 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 +/*=========================================================================*/ +static 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" ); + } + + if (pDev->m_qmap_mode) { + // Setup Data Format + int rx_urb_size = 0; + result = QMIWDASetDataFormat (pDev, pDev->m_qmap_mode, &rx_urb_size); + if (result != 0) { + goto __qmi_sync_finished; + } + pDev->mpNetDev->rx_urb_size = rx_urb_size; + } + + // 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; + DBG( "zpf RegisterQMIDevice\n" ); + 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 some device, 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, pDev->m_qmap_mode, NULL); + 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 ) +{ +#ifndef meig_no_for_each_process + struct inode * pOpenInode; + struct list_head * pInodeList; + struct task_struct * pEachTask; + struct fdtable * pFDT; + struct file * pFilp; + int count = 0; +#endif + unsigned long flags; + 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; + } + +#ifndef meig_no_for_each_process + // 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(); + } + } +#endif + + if (pDev->mpNetDev->udev->state) { + // 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++) { +#if (LINUX_VERSION_CODE < KERNEL_VERSION( 4,11,0 )) + int ref = atomic_read( &pDev->mQMIDev.mCdev.kobj.kref.refcount ); +#else + int ref = kref_read( &pDev->mQMIDev.mCdev.kobj.kref ); +#endif + if (ref > 1) { + DBG( "cdev in use by %d tasks\n", ref - 1 ); + if (tries > 10) + INFO( "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 +===========================================================================*/ +static bool QMIReady( + sGobiUSBNet * pDev, + u16 timeout ) +{ + int result; + void * pWriteBuffer; + u16 writeBufferSize; + void * pReadBuffer; + u16 readBufferSize; + u16 curTime; + unsigned long flags; + u8 transactionID; + u16 interval = 2000; + + 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 += interval) { + // Start read + struct MeigSem *readSem = kmalloc(sizeof(struct MeigSem ), GFP_KERNEL); + readSem->magic = MEIG_SEM_MAGIC; + sema_init( &readSem->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 ); + +#if 1 + if (down_timeout( &readSem->readSem, msecs_to_jiffies(interval) ) == 0) +#else + msleep( interval ); + if (down_trylock( &readSem->readSem ) == 0) +#endif + { + kfree(readSem); + // 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 { + readSem->magic = 0; + // 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 +===========================================================================*/ +static 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 +===========================================================================*/ +static 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 1 // add for "AT$QCRMCALL=1,1", be careful: donot enable these codes if use meig-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, + 0x81, + pDev, //add for net interface bind, by zhaopf@meigsmart.com + 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 +===========================================================================*/ +static 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 +===========================================================================*/ +static int QMIWDASetDataFormat( sGobiUSBNet * pDev, int qmap_mode, int *rx_urb_size ) +{ + 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(qmap_mode); + pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL ); + if (pWriteBuffer == NULL) { + return -ENOMEM; + } + + result = QMIWDASetDataFormatReq( pWriteBuffer, + writeBufferSize, pDev->mbRawIPMode, + qmap_mode, 32*1024, + 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; + + if (qmap_mode && rx_urb_size) { + int qmap_enabled = 0, rx_size = 0, tx_size = 0; + result = QMIWDASetDataFormatResp( pReadBuffer, + readBufferSize, pDev->mbRawIPMode, &qmap_enabled, &rx_size, &tx_size); + INFO( "qmap settings qmap_enabled=%d, rx_size=%d, tx_size=%d\n", + le32_to_cpu(qmap_enabled), le32_to_cpu(rx_size), le32_to_cpu(tx_size)); + + if (le32_to_cpu(qmap_enabled) == 5) { + *rx_urb_size = le32_to_cpu(rx_size); + } else { + *rx_urb_size = 0; + result = -EFAULT; + } + } else { + int qmap_enabled = 0, rx_size = 0, tx_size = 0; + result = QMIWDASetDataFormatResp( pReadBuffer, + readBufferSize, pDev->mbRawIPMode, &qmap_enabled, &rx_size, &tx_size); + } + + kfree( pReadBuffer ); + + if (result < 0) { + DBG( "Data Format Cannot be set\n" ); + } + + ReleaseClientID( pDev, WDAClientID ); + + // Success + return 0; +} + +int MeigQMIWDASetDataFormat( sGobiUSBNet * pDev, int qmap_mode, int *rx_urb_size ) +{ + return QMIWDASetDataFormat(pDev, qmap_mode, rx_urb_size); +} diff --git a/root/package/link4all/gobinet_srm815/src.old/QMIDevice.h b/root/package/link4all/gobinet_srm815/src.old/QMIDevice.h new file mode 100755 index 00000000..46eb11a4 --- /dev/null +++ b/root/package/link4all/gobinet_srm815/src.old/QMIDevice.h @@ -0,0 +1,368 @@ +/*=========================================================================== +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 +/*=========================================================================*/ + +#ifdef __MEIG_INTER__ + +// Basic test to see if device memory is valid +static bool IsDeviceValid( sGobiUSBNet * pDev ); + +/*=========================================================================*/ +// Driver level asynchronous read functions +/*=========================================================================*/ + +// Resubmit interrupt URB, re-using same values +static 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 )) +static void ReadCallback( struct urb * pReadURB ); +#else +static 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 )) +static void IntCallback( struct urb * pIntURB ); +#else +static void IntCallback(struct urb *pIntURB, struct pt_regs *regs); +#endif + +/*=========================================================================*/ +// Internal read/write functions +/*=========================================================================*/ + +// Start asynchronous read +// Reading client's data store, not device +static int ReadAsync( + sGobiUSBNet * pDev, + u16 clientID, + u16 transactionID, + void (*pCallback)(sGobiUSBNet *, u16, void *), + void * pData ); + +// Notification function for synchronous read +static void UpSem( + sGobiUSBNet * pDev, + u16 clientID, + void * pData ); + +// Start synchronous read +// Reading client's data store, not device +static int ReadSync( + sGobiUSBNet * pDev, + void ** ppOutBuffer, + u16 clientID, + u16 transactionID ); + +// Write callback +#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,18 )) +static void WriteSyncCallback( struct urb * pWriteURB ); +#else +static void WriteSyncCallback(struct urb *pWriteURB, struct pt_regs *regs); +#endif + +// Start synchronous write +static int WriteSync( + sGobiUSBNet * pDev, + char * pInWriteBuffer, + int size, + u16 clientID ); + +/*=========================================================================*/ +// Internal memory management functions +/*=========================================================================*/ + +// Create client and allocate memory +static int GetClientID( + sGobiUSBNet * pDev, + u8 serviceType ); + +// Release client and free memory +static void ReleaseClientID( + sGobiUSBNet * pDev, + u16 clientID ); + +// Find this client's memory +static sClientMemList * FindClientMem( + sGobiUSBNet * pDev, + u16 clientID ); + +// Add Data to this client's ReadMem list +static 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. +static bool PopFromReadMemList( + sGobiUSBNet * pDev, + u16 clientID, + u16 transactionID, + void ** ppData, + u16 * pDataSize ); + +// Add Notify entry to this client's notify List +static 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 +static bool NotifyAndPopNotifyList( + sGobiUSBNet * pDev, + u16 clientID, + u16 transactionID ); + +// Add URB to this client's URB list +static bool AddToURBList( + sGobiUSBNet * pDev, + u16 clientID, + struct urb * pURB ); + +// Remove URB from this client's URB list +static struct urb * PopFromURBList( + sGobiUSBNet * pDev, + u16 clientID ); + +/*=========================================================================*/ +// Internal userspace wrappers +/*=========================================================================*/ + +// Userspace unlocked ioctl +static long UserspaceunlockedIOCTL( + struct file * pFilp, + unsigned int cmd, + unsigned long arg ); + +/*=========================================================================*/ +// Userspace wrappers +/*=========================================================================*/ + +// Userspace open +static int UserspaceOpen( + struct inode * pInode, + struct file * pFilp ); + +#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,36 )) +// Userspace ioctl +static int UserspaceIOCTL( + struct inode * pUnusedInode, + struct file * pFilp, + unsigned int cmd, + unsigned long arg ); +#endif + +// Userspace close +#define meig_no_for_each_process +#ifdef meig_no_for_each_process +static int UserspaceClose( + struct inode * pInode, + struct file * pFilp ); +#else +#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,14 )) +static int UserspaceClose( + struct file * pFilp, + fl_owner_t unusedFileTable ); +#else +static int UserspaceClose( struct file * pFilp ); +#endif +#endif + +// Userspace read (synchronous) +static ssize_t UserspaceRead( + struct file * pFilp, + char __user * pBuf, + size_t size, + loff_t * pUnusedFpos ); + +// Userspace write (synchronous) +static ssize_t UserspaceWrite( + struct file * pFilp, + const char __user * pBuf, + size_t size, + loff_t * pUnusedFpos ); + +static unsigned int UserspacePoll( + struct file * pFilp, + struct poll_table_struct * pPollTable ); + +/*=========================================================================*/ +// Driver level client management +/*=========================================================================*/ + +// Check if QMI is ready for use +static bool QMIReady( + sGobiUSBNet * pDev, + u16 timeout ); + +// QMI WDS callback function +static void QMIWDSCallback( + sGobiUSBNet * pDev, + u16 clientID, + void * pData ); + +// Fire off reqests and start async read for QMI WDS callback +static int SetupQMIWDSCallback( sGobiUSBNet * pDev ); + +// Register client, send req and parse MEID response, release client +static int QMIDMSGetMEID( sGobiUSBNet * pDev ); + +// Register client, send req and parse Data format response, release client +static int QMIWDASetDataFormat( sGobiUSBNet * pDev, int qmap_mode, int *rx_urb_size ); +#endif + +// Print Hex data, for debug purposes +void MeigPrintHex( + void * pBuffer, + u16 bufSize ); + +// Sets mDownReason and turns carrier off +void MeigGobiSetDownReason( + sGobiUSBNet * pDev, + u8 reason ); + +// Clear mDownReason and may turn carrier on +void MeigGobiClearDownReason( + sGobiUSBNet * pDev, + u8 reason ); + +// Tests mDownReason and returns whether reason is set +bool MeigGobiTestDownReason( + sGobiUSBNet * pDev, + u8 reason ); + +// Start continuous read "thread" +int MeigStartRead( sGobiUSBNet * pDev ); + +// Kill continuous read "thread" +void MeigKillRead( sGobiUSBNet * pDev ); + +/*=========================================================================*/ +// Initializer and destructor +/*=========================================================================*/ + +// QMI Device initialization function +int MeigRegisterQMIDevice( sGobiUSBNet * pDev ); + +// QMI Device cleanup function +void MeigDeregisterQMIDevice( sGobiUSBNet * pDev ); + +int MeigQMIWDASetDataFormat( sGobiUSBNet * pDev, int qmap_mode, int *rx_urb_size ); + +#define PrintHex MeigPrintHex +#define GobiSetDownReason MeigGobiSetDownReason +#define GobiClearDownReason MeigGobiClearDownReason +#define GobiTestDownReason MeigGobiTestDownReason +#define StartRead MeigStartRead +#define KillRead MeigKillRead +#define RegisterQMIDevice MeigRegisterQMIDevice +#define DeregisterQMIDevice MeigDeregisterQMIDevice diff --git a/root/package/link4all/gobinet_srm815/src.old/Readme.txt b/root/package/link4all/gobinet_srm815/src.old/Readme.txt new file mode 100644 index 00000000..575d51c5 --- /dev/null +++ b/root/package/link4all/gobinet_srm815/src.old/Readme.txt @@ -0,0 +1,15 @@ +使用方法 +1.编译gobinet驱动 +PC端按如下方法: +make +insmod GobiNet.ko +嵌入å¼å¹³å°å¯ä¿®æ”¹makefileåŽç¼–译 +2.编译拨å·å·¥å…· +cd meig-cm/ +make +嵌入å¼å¹³å°åŒæ ·è¦ä¿®æ”¹MakefileåŽç¼–译 +3.æ‹¨å· +./meig-cm -s [APNåç§°] +å¦‚ç§»åŠ¨å¡æ‹¨å·: +./meig-cm -s cmnet + diff --git a/root/package/link4all/gobinet_srm815/src.old/Structs.h b/root/package/link4all/gobinet_srm815/src.old/Structs.h new file mode 100755 index 00000000..760b0554 --- /dev/null +++ b/root/package/link4all/gobinet_srm815/src.old/Structs.h @@ -0,0 +1,455 @@ +/*=========================================================================== +FILE: + Structs.h + +DESCRIPTION: + Declaration of structures used by the Qualcomm Linux USB Network driver + +FUNCTIONS: + none + +Copyright (c) 2011, Code Aurora Forum. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Code Aurora Forum nor + the names of its contributors may be used to endorse or promote + products derived from this software without specific prior written + permission. + + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +===========================================================================*/ + +//--------------------------------------------------------------------------- +// Pragmas +//--------------------------------------------------------------------------- +#pragma once + +//--------------------------------------------------------------------------- +// Include Files +//--------------------------------------------------------------------------- +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MEIG_WWAN_QMAP 8 +#ifdef MEIG_WWAN_QMAP +#define MEIG_QMAP_MUX_ID 0x81 +#endif + +#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,21 )) +static inline void skb_reset_mac_header(struct sk_buff *skb) +{ + skb->mac.raw = skb->data; +} +#endif + +#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,22 )) +#define bool u8 +#ifndef URB_FREE_BUFFER +#define URB_FREE_BUFFER_BY_SELF //usb_free_urb will not free, should free by self +#define URB_FREE_BUFFER 0x0100 /* Free transfer buffer with the URB */ +#endif + +/** + * usb_endpoint_type - get the endpoint's transfer type + * @epd: endpoint to be checked + * + * Returns one of USB_ENDPOINT_XFER_{CONTROL, ISOC, BULK, INT} according + * to @epd's transfer type. + */ +static inline int usb_endpoint_type(const struct usb_endpoint_descriptor *epd) +{ + return epd->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK; +} +#endif + +#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,18 )) +/** + * usb_endpoint_dir_in - check if the endpoint has IN direction + * @epd: endpoint to be checked + * + * Returns true if the endpoint is of type IN, otherwise it returns false. + */ +static inline int usb_endpoint_dir_in(const struct usb_endpoint_descriptor *epd) +{ + return ((epd->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN); +} + +/** + * usb_endpoint_dir_out - check if the endpoint has OUT direction + * @epd: endpoint to be checked + * + * Returns true if the endpoint is of type OUT, otherwise it returns false. + */ +static inline int usb_endpoint_dir_out( + const struct usb_endpoint_descriptor *epd) +{ + return ((epd->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_OUT); +} + +/** + * usb_endpoint_xfer_int - check if the endpoint has interrupt transfer type + * @epd: endpoint to be checked + * + * Returns true if the endpoint is of type interrupt, otherwise it returns + * false. + */ +static inline int usb_endpoint_xfer_int( + const struct usb_endpoint_descriptor *epd) +{ + return ((epd->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == + USB_ENDPOINT_XFER_INT); +} + +static inline int usb_autopm_set_interface(struct usb_interface *intf) +{ + return 0; +} + +static inline int usb_autopm_get_interface(struct usb_interface *intf) +{ + return 0; +} + +static inline int usb_autopm_get_interface_async(struct usb_interface *intf) +{ + return 0; +} + +static inline void usb_autopm_put_interface(struct usb_interface *intf) +{ } +static inline void usb_autopm_put_interface_async(struct usb_interface *intf) +{ } +static inline void usb_autopm_enable(struct usb_interface *intf) +{ } +static inline void usb_autopm_disable(struct usb_interface *intf) +{ } +static inline void usb_mark_last_busy(struct usb_device *udev) +{ } +#endif + +#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,24 )) +#include "usbnet.h" +#else +#include +#endif + +#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,25 )) +#include +#else +#include +#endif + +// Used in recursion, defined later below +struct sGobiUSBNet; + +/*=========================================================================*/ +// Struct sReadMemList +// +// Structure that defines an entry in a Read Memory linked list +/*=========================================================================*/ +typedef struct sReadMemList { + /* Data buffer */ + void * mpData; + + /* Transaction ID */ + u16 mTransactionID; + + /* Size of data buffer */ + u16 mDataSize; + + /* Next entry in linked list */ + struct sReadMemList * mpNext; + +} sReadMemList; + +/*=========================================================================*/ +// Struct sNotifyList +// +// Structure that defines an entry in a Notification linked list +/*=========================================================================*/ +typedef struct sNotifyList { + /* Function to be run when data becomes available */ + void (* mpNotifyFunct)(struct sGobiUSBNet *, u16, void *); + + /* Transaction ID */ + u16 mTransactionID; + + /* Data to provide as parameter to mpNotifyFunct */ + void * mpData; + + /* Next entry in linked list */ + struct sNotifyList * mpNext; + +} sNotifyList; + +/*=========================================================================*/ +// Struct sURBList +// +// Structure that defines an entry in a URB linked list +/*=========================================================================*/ +typedef struct sURBList { + /* The current URB */ + struct urb * mpURB; + + /* Next entry in linked list */ + struct sURBList * mpNext; + +} sURBList; + +/*=========================================================================*/ +// Struct sClientMemList +// +// Structure that defines an entry in a Client Memory linked list +// Stores data specific to a Service Type and Client ID +/*=========================================================================*/ +typedef struct sClientMemList { + /* Client ID for this Client */ + u16 mClientID; + + /* Linked list of Read entries */ + /* Stores data read from device before sending to client */ + sReadMemList * mpList; + + /* Linked list of Notification entries */ + /* Stores notification functions to be run as data becomes + available or the device is removed */ + sNotifyList * mpReadNotifyList; + + /* Linked list of URB entries */ + /* Stores pointers to outstanding URBs which need canceled + when the client is deregistered or the device is removed */ + sURBList * mpURBList; + + /* Next entry in linked list */ + struct sClientMemList * mpNext; + + /* Wait queue object for poll() */ + wait_queue_head_t mWaitQueue; + +} sClientMemList; + +/*=========================================================================*/ +// Struct sURBSetupPacket +// +// Structure that defines a USB Setup packet for Control URBs +// Taken from USB CDC specifications +/*=========================================================================*/ +typedef struct sURBSetupPacket { + /* Request type */ + u8 mRequestType; + + /* Request code */ + u8 mRequestCode; + + /* Value */ + u16 mValue; + + /* Index */ + u16 mIndex; + + /* Length of Control URB */ + u16 mLength; + +} sURBSetupPacket; + +// Common value for sURBSetupPacket.mLength +#define DEFAULT_READ_URB_LENGTH 0x1000 + +#ifdef CONFIG_PM +#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,29 )) +/*=========================================================================*/ +// Struct sAutoPM +// +// Structure used to manage AutoPM thread which determines whether the +// device is in use or may enter autosuspend. Also submits net +// transmissions asynchronously. +/*=========================================================================*/ +typedef struct sAutoPM { + /* Thread for atomic autopm function */ + struct task_struct * mpThread; + + /* Signal for completion when it's time for the thread to work */ + struct completion mThreadDoWork; + + /* Time to exit? */ + bool mbExit; + + /* List of URB's queued to be sent to the device */ + sURBList * mpURBList; + + /* URB list lock (for adding and removing elements) */ + spinlock_t mURBListLock; + + /* Length of the URB list */ + atomic_t mURBListLen; + + /* Active URB */ + struct urb * mpActiveURB; + + /* Active URB lock (for adding and removing elements) */ + spinlock_t mActiveURBLock; + + /* Duplicate pointer to USB device interface */ + struct usb_interface * mpIntf; + +} sAutoPM; +#endif +#endif /* CONFIG_PM */ + +/*=========================================================================*/ +// Struct sQMIDev +// +// Structure that defines the data for the QMI device +/*=========================================================================*/ +typedef struct sQMIDev { + /* Device number */ + dev_t mDevNum; + + /* Device class */ + struct class * mpDevClass; + + /* cdev struct */ + struct cdev mCdev; + + /* is mCdev initialized? */ + bool mbCdevIsInitialized; + + /* Pointer to read URB */ + struct urb * mpReadURB; + +//#define READ_QMI_URB_ERROR +#ifdef READ_QMI_URB_ERROR + struct timer_list mReadUrbTimer; +#endif + + /* Read setup packet */ + sURBSetupPacket * mpReadSetupPacket; + + /* Read buffer attached to current read URB */ + void * mpReadBuffer; + + /* Inturrupt URB */ + /* Used to asynchronously notify when read data is available */ + struct urb * mpIntURB; + + /* Buffer used by Inturrupt URB */ + void * mpIntBuffer; + + /* Pointer to memory linked list for all clients */ + sClientMemList * mpClientMemList; + + /* Spinlock for client Memory entries */ + spinlock_t mClientMemLock; + + /* Transaction ID associated with QMICTL "client" */ + atomic_t mQMICTLTransactionID; + +} sQMIDev; + +/*=========================================================================*/ +// Struct sGobiUSBNet +// +// Structure that defines the data associated with the Qualcomm USB device +/*=========================================================================*/ +typedef struct sGobiUSBNet { + atomic_t refcount; + + /* Net device structure */ + struct usbnet * mpNetDev; +#ifdef MEIG_WWAN_QMAP + int m_qmap_mode; + struct net_device *mpQmapNetDev[MEIG_WWAN_QMAP]; +#ifdef CONFIG_BRIDGE + int m_qmap_bridge_mode[MEIG_WWAN_QMAP]; +#endif +#endif + +#if 1 //def DATA_MODE_RP + bool mbMdm9x07; + bool mbMdm9x06; //for BG96 + /* QMI "device" work in IP Mode or ETH Mode */ + bool mbRawIPMode; +#ifdef CONFIG_BRIDGE + int m_bridge_mode; + uint m_bridge_ipv4; + unsigned char mHostMAC[6]; +#endif + int m_qcrmcall_mode; +#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 --git a/root/package/link4all/gobinet_srm815/src/GobiUSBNet.c b/root/package/link4all/gobinet_srm815/src/GobiUSBNet.c new file mode 100755 index 00000000..83a1e344 --- /dev/null +++ b/root/package/link4all/gobinet_srm815/src/GobiUSBNet.c @@ -0,0 +1,2171 @@ +/*=========================================================================== +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 +#include +#include +#include +#include + +#include +#include +#include + +#include "Structs.h" +#include "QMIDevice.h" +#include "QMI.h" + +//----------------------------------------------------------------------------- +// Definitions +//----------------------------------------------------------------------------- + +// Version Information +//add new module or new feature, increase major version. fix bug, increase minor version +#define DRIVER_VERSION "Meig_GobiNet_Driver_V1.4.1" +#define DRIVER_AUTHOR "Qualcomm Innovation Center" +#define DRIVER_DESC "GobiNet" + +// Debug flag +int meig_debug = 0; + +// Allow user interrupts +//int interruptible = 1; + +// Number of IP packets which may be queued up for transmit +static int txQueueLength = 100; + +// Class should be created during module init, so needs to be global +static struct class * gpClass; + +static const unsigned char meig_mac[ETH_ALEN] = {0x02, 0x50, 0xf3, 0x00, 0x00, 0x00}; +//static const u8 broadcast_addr[ETH_ALEN] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; + +//setup data call by "AT$QCRMCALL=1,1" +static uint __read_mostly qcrmcall_mode = 0; +module_param( qcrmcall_mode, uint, S_IRUGO | S_IWUSR ); + +static struct sk_buff * ether_to_ip_fixup(struct net_device *dev, struct sk_buff *skb) +{ + const struct ethhdr *ehdr; + + skb_reset_mac_header(skb); + ehdr = eth_hdr(skb); + + if (ehdr->h_proto == htons(ETH_P_IP)) { + if (unlikely(skb->len <= (sizeof(struct ethhdr) + sizeof(struct iphdr)))) { + goto drop_skb; + } + } else if (ehdr->h_proto == htons(ETH_P_IPV6)) { + if (unlikely(skb->len <= (sizeof(struct ethhdr) + sizeof(struct ipv6hdr)))) { + goto drop_skb; + } + } else { + DBG("%s skb h_proto is %04x\n", dev->name, ntohs(ehdr->h_proto)); + goto drop_skb; + } + + if (unlikely(skb_pull(skb, ETH_HLEN))) + return skb; + +drop_skb: + return NULL; +} + +//#define MEIG_REMOVE_TX_ZLP +#define USB_CDC_SET_REMOVE_TX_ZLP_COMMAND 0x5D + +//#define MEIG_WWAN_MULTI_PACKAGES + +#ifdef MEIG_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 MEIG_NET_MSG_SPEC (0x80) +#define MEIG_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 meig_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; +module_param( bridge_mode, int, S_IRUGO | S_IWUSR ); + +static int bridge_arp_reply(sGobiUSBNet * pGobiDev, struct sk_buff *skb) +{ + struct net_device *dev = pGobiDev->mpNetDev->net; + struct arphdr *parp; + u8 *arpptr, *sha; + u8 sip[4], tip[4], ipv4[4]; + struct sk_buff *reply = NULL; + + ipv4[0] = (pGobiDev->m_bridge_ipv4 >> 24) & 0xFF; + ipv4[1] = (pGobiDev->m_bridge_ipv4 >> 16) & 0xFF; + ipv4[2] = (pGobiDev->m_bridge_ipv4 >> 8) & 0xFF; + ipv4[3] = (pGobiDev->m_bridge_ipv4 >> 0) & 0xFF; + + 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)); + + DBG("sip = %d.%d.%d.%d, tip=%d.%d.%d.%d, ipv4=%d.%d.%d.%d\n", + sip[0], sip[1], sip[2], sip[3], tip[0], tip[1], tip[2], tip[3], ipv4[0], ipv4[1], ipv4[2], ipv4[3]); + if (tip[0] == ipv4[0] && tip[1] == ipv4[1] && tip[2] == ipv4[2] && tip[3] != ipv4[3]) + reply = arp_create(ARPOP_REPLY, ETH_P_ARP, *((__be32 *)sip), dev, *((__be32 *)tip), sha, meig_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; +} + +static ssize_t bridge_mode_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct net_device *pNet = to_net_dev(dev); + struct usbnet * pDev = netdev_priv( pNet ); + sGobiUSBNet * pGobiDev = (sGobiUSBNet *)pDev->data[0]; + + return snprintf(buf, PAGE_SIZE, "%d\n", pGobiDev->m_bridge_mode); +} + +static ssize_t bridge_mode_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct net_device *pNet = to_net_dev(dev); + struct usbnet * pDev = netdev_priv( pNet ); + sGobiUSBNet * pGobiDev = (sGobiUSBNet *)pDev->data[0]; + + if (!GobiTestDownReason( pGobiDev, NET_IFACE_STOPPED )) { + INFO("please ifconfig %s down\n", pNet->name); + return -EPERM; + } + + pGobiDev->m_bridge_mode = !!simple_strtoul(buf, NULL, 10); + + return count; +} + +static ssize_t bridge_ipv4_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct net_device *pNet = to_net_dev(dev); + struct usbnet * pDev = netdev_priv( pNet ); + sGobiUSBNet * pGobiDev = (sGobiUSBNet *)pDev->data[0]; + unsigned char ipv4[4]; + + ipv4[0] = (pGobiDev->m_bridge_ipv4 >> 24) & 0xFF; + ipv4[1] = (pGobiDev->m_bridge_ipv4 >> 16) & 0xFF; + ipv4[2] = (pGobiDev->m_bridge_ipv4 >> 8) & 0xFF; + ipv4[3] = (pGobiDev->m_bridge_ipv4 >> 0) & 0xFF; + + return snprintf(buf, PAGE_SIZE, "%d.%d.%d.%d\n", ipv4[0], ipv4[1], ipv4[2], ipv4[3]); +} + +static ssize_t bridge_ipv4_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct net_device *pNet = to_net_dev(dev); + struct usbnet * pDev = netdev_priv( pNet ); + sGobiUSBNet * pGobiDev = (sGobiUSBNet *)pDev->data[0]; + + pGobiDev->m_bridge_ipv4 = simple_strtoul(buf, NULL, 16); + + return count; +} + + +static DEVICE_ATTR(bridge_mode, S_IWUSR | S_IRUGO, bridge_mode_show, bridge_mode_store); +static DEVICE_ATTR(bridge_ipv4, S_IWUSR | S_IRUGO, bridge_ipv4_show, bridge_ipv4_store); +#endif + +#ifdef MEIG_WWAN_QMAP +/* + Meig_WCDMA<E_Linux_USB_Driver_User_Guide_V1.9.pdf + 5.6. Test QMAP on GobiNet or QMI WWAN + 0 - no QMAP + 1 - QMAP (Aggregation protocol) + X - QMAP (Multiplexing and Aggregation protocol) +*/ +static uint __read_mostly qmap_mode = 0; +module_param( qmap_mode, uint, S_IRUGO | S_IWUSR ); + +struct qmap_hdr { + u8 cd_rsvd_pad; + u8 mux_id; + u16 pkt_len; +} __packed; + +struct qmap_priv { + struct net_device *real_dev; + u8 offset_id; +}; + +static int qmap_open(struct net_device *dev) +{ + struct qmap_priv *priv = netdev_priv(dev); + struct net_device *real_dev = priv->real_dev; + + if (!(priv->real_dev->flags & IFF_UP)) + return -ENETDOWN; + + if (netif_carrier_ok(real_dev)) + netif_carrier_on(dev); + return 0; +} + +static int qmap_stop(struct net_device *pNet) +{ + netif_carrier_off(pNet); + return 0; +} + +static int qmap_start_xmit(struct sk_buff *skb, struct net_device *pNet) +{ + int err; + struct qmap_priv *priv = netdev_priv(pNet); + unsigned int len; + struct qmap_hdr *hdr; + + if (ether_to_ip_fixup(pNet, skb) == NULL) { + dev_kfree_skb_any (skb); + return NETDEV_TX_OK; + } + + len = skb->len; + hdr = (struct qmap_hdr *)skb_push(skb, sizeof(struct qmap_hdr)); + hdr->cd_rsvd_pad = 0; + hdr->mux_id = MEIG_QMAP_MUX_ID + priv->offset_id; + hdr->pkt_len = cpu_to_be16(len); + + skb->dev = priv->real_dev; + err = dev_queue_xmit(skb); +#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,14 )) + if (err == NET_XMIT_SUCCESS) { + pNet->stats.tx_packets++; + pNet->stats.tx_bytes += skb->len; + } else { + pNet->stats.tx_errors++; + } +#endif + + return err; +} + +#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,29 )) +#else +static const struct net_device_ops qmap_netdev_ops = { + .ndo_open = qmap_open, + .ndo_stop = qmap_stop, + .ndo_start_xmit = qmap_start_xmit, +}; +#endif + +static int qmap_register_device(sGobiUSBNet * pDev, u8 offset_id) +{ + struct net_device *real_dev = pDev->mpNetDev->net; + struct net_device *qmap_net; + struct qmap_priv *priv; + int err; + + qmap_net = alloc_etherdev(sizeof(*priv)); + if (!qmap_net) + return -ENOBUFS; + + SET_NETDEV_DEV(qmap_net, &real_dev->dev); + priv = netdev_priv(qmap_net); + priv->offset_id = offset_id; + priv->real_dev = real_dev; + sprintf(qmap_net->name, "%s.%d", real_dev->name, offset_id + 1); +#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,29 )) + qmap_net->open = qmap_open; + qmap_net->stop = qmap_stop; + qmap_net->hard_start_xmit = qmap_start_xmit; +#else + qmap_net->netdev_ops = &qmap_netdev_ops; +#endif + memcpy (qmap_net->dev_addr, real_dev->dev_addr, ETH_ALEN); + + err = register_netdev(qmap_net); + if (err < 0) + goto out_free_newdev; + netif_device_attach (qmap_net); + + pDev->mpQmapNetDev[offset_id] = qmap_net; + qmap_net->flags |= IFF_NOARP; + + INFO("%s\n", qmap_net->name); + + return 0; + +out_free_newdev: + free_netdev(qmap_net); + return err; +} + +static void qmap_unregister_device(sGobiUSBNet * pDev, u8 offset_id) +{ + struct net_device *net = pDev->mpQmapNetDev[offset_id]; + if (net != NULL) { + netif_carrier_off( net ); + unregister_netdev (net); + free_netdev(net); + } +} + +static ssize_t qmap_mode_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct net_device *pNet = to_net_dev(dev); + struct usbnet * pDev = netdev_priv( pNet ); + sGobiUSBNet * pGobiDev = (sGobiUSBNet *)pDev->data[0]; + + return snprintf(buf, PAGE_SIZE, "%d\n", pGobiDev->m_qmap_mode); +} + +static ssize_t qmap_mode_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct net_device *pNet = to_net_dev(dev); + struct usbnet * pDev = netdev_priv( pNet ); + sGobiUSBNet * pGobiDev = (sGobiUSBNet *)pDev->data[0]; + int err; + unsigned int qmap_mode; + int rx_urb_size = 4096; + + if (!GobiTestDownReason( pGobiDev, NET_IFACE_STOPPED )) { + INFO("please ifconfig %s down\n", pNet->name); + return -EPERM; + } + + if (!pGobiDev->mbQMIReady) { + INFO("please wait qmi ready\n"); + return -EBUSY; + } + + qmap_mode = simple_strtoul(buf, NULL, 10); + + if (pGobiDev->m_qcrmcall_mode) { + INFO("AT$QCRMCALL MODE had enabled\n"); + return -EINVAL; + } + + if (qmap_mode <= 0 || qmap_mode > MEIG_WWAN_QMAP) { + INFO("qmap_mode = %d is Invalid argument, shoule be 1 ~ %d\n", qmap_mode, MEIG_WWAN_QMAP); + return -EINVAL; + } + + if (pGobiDev->m_qmap_mode) { + INFO("qmap_mode aleary set to %d, do not allow re-set again\n", pGobiDev->m_qmap_mode); + return -EPERM; + } + + // Setup Data Format + err = MeigQMIWDASetDataFormat (pGobiDev, + qmap_mode, + &rx_urb_size); + if (err != 0) { + return err; + } + + pDev->rx_urb_size = rx_urb_size; + pGobiDev->m_qmap_mode = qmap_mode; + + if (pGobiDev->m_qmap_mode > 1) { + unsigned i; + for (i = 0; i < pGobiDev->m_qmap_mode; i++) { + qmap_register_device(pGobiDev, i); + } + } +#if (LINUX_VERSION_CODE < KERNEL_VERSION( 5,0,0 )) +#ifdef FLAG_RX_ASSEMBLE + if (pGobiDev->m_qmap_mode) + pDev->driver_info->flags |= FLAG_RX_ASSEMBLE; +#endif +#endif + + return count; +} + +static DEVICE_ATTR(qmap_mode, S_IWUSR | S_IRUGO, qmap_mode_show, qmap_mode_store); +#endif + +static struct attribute *gobinet_sysfs_attrs[] = { +#ifdef CONFIG_BRIDGE + &dev_attr_bridge_mode.attr, + &dev_attr_bridge_ipv4.attr, +#endif +#ifdef MEIG_WWAN_QMAP + &dev_attr_qmap_mode.attr, +#endif + NULL, +}; + +static struct attribute_group gobinet_sysfs_attr_group = { + .attrs = gobinet_sysfs_attrs, +}; + +#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 +===========================================================================*/ +static 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 + if (pGobiDev->m_qcrmcall_mode) { + } else { + 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 ); +} +int MeigGobiNetSuspend(struct usb_interface *pIntf, pm_message_t powerEvent ) +{ + return GobiNetSuspend(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 +===========================================================================*/ +static 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 + if (pGobiDev->m_qcrmcall_mode) { + nRet = 0; + } else { + 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 + if ( !test_bit(pIntf->cur_altsetting->desc.bInterfaceNumber, &pDev->driver_info->data)) { + DBG( "invalid interface %d\n", + pIntf->cur_altsetting->desc.bInterfaceNumber ); + 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 ); + +#if defined(MEIG_WWAN_MULTI_PACKAGES) + if (rx_packets && pDev->udev->descriptor.idVendor == cpu_to_le16(0x05c6)) { + struct multi_package_config rx_config = { + .enable = cpu_to_le32(1), + .package_max_len = cpu_to_le32((1500 + sizeof(struct meig_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( "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 + + if (qcrmcall_mode == 0 && pDev->net->sysfs_groups[0] == NULL && gobinet_sysfs_attr_group.attrs[0] != NULL) { +#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,32)) //see commit 0c509a6c9393b27a8c5a01acd4a72616206cfc24 + pDev->net->sysfs_groups[1] = &gobinet_sysfs_attr_group; //see netdev_register_sysfs() +#else + pDev->net->sysfs_groups[0] = &gobinet_sysfs_attr_group; +#endif + } + + if (!pDev->rx_urb_size) { +//to advoid module report mtu 1460, but rx 1500 bytes IP packets, and cause the customer's system crash +//next setting can make usbnet.c:usbnet_change_mtu() do not modify rx_urb_size according to mtu + pDev->rx_urb_size = ETH_DATA_LEN + ETH_HLEN + 6; + } + + 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 ); + + if (pGobiDev->m_qcrmcall_mode) { + } else { + 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 + INFO("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 +===========================================================================*/ +static 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 MEIG_WWAN_QMAP + if (pGobiDev->m_qmap_mode) { + struct qmap_hdr *qhdr; + + if (pGobiDev->m_qmap_mode > 1) { + qhdr = (struct qmap_hdr *)skb->data; + if (qhdr->cd_rsvd_pad != 0) { + goto drop_skb; + } + if ((qhdr->mux_id&0xF0) != 0x80) { + goto drop_skb; + } + } else { + if (ether_to_ip_fixup(dev->net, skb) == NULL) + goto drop_skb; + qhdr = (struct qmap_hdr *)skb_push(skb, sizeof(struct qmap_hdr)); + qhdr->cd_rsvd_pad = 0; + qhdr->mux_id = MEIG_QMAP_MUX_ID; + qhdr->pkt_len = cpu_to_be16(skb->len - sizeof(struct qmap_hdr)); + } + + return skb; + } +#endif + +#ifdef CONFIG_BRIDGE + if (pGobiDev->m_bridge_mode) { + struct ethhdr *ehdr; + const struct iphdr *iph; + + if (unlikely(skb->len <= ETH_ALEN)) + goto drop_skb; + skb_reset_mac_header(skb); + ehdr = eth_hdr(skb); +//meig_debug = 1; +// DBG("ethhdr: "); +// PrintHex(ehdr, sizeof(struct ethhdr)); + + if (ehdr->h_proto == htons(ETH_P_ARP)) { + bridge_arp_reply(pGobiDev, skb); + goto drop_skb; + } + + 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 + { + int save_debug = meig_debug; + memcpy(pGobiDev->mHostMAC, ehdr->h_source, ETH_ALEN); + INFO("PC Mac Address: "); + meig_debug=1; + PrintHex(pGobiDev->mHostMAC, ETH_ALEN); + meig_debug=save_debug; + } + } + +#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, meig_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)); + goto drop_skb; + } + +//meig_debug = 0; + } +#endif + + // Skip Ethernet header from message + if (likely(ether_to_ip_fixup(dev->net, skb))) { + 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 + } + +drop_skb: +#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,24 )) && defined(CONFIG_X86_32) + INFO("dev_kfree_skb_any() will make kernel panic on CentOS!\n"); + meig_debug=1; + PrintHex(skb->data, 32); + meig_debug=0; +#else + // Filter the packet out, release it + dev_kfree_skb_any(skb); +#endif + return NULL; +} + +#if defined(MEIG_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 meig_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 != MEIG_NET_MSG_SPEC || package_header.msg_id != MEIG_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; +} +#else +#ifdef MEIG_WWAN_QMAP +static int GobiNetDriverRxQmapFixup(struct usbnet *dev, struct sk_buff *skb) +{ + sGobiUSBNet * pGobiDev = (sGobiUSBNet *)dev->data[0]; + static int debug_len = 0; + int debug_pkts = 0; + int update_len = skb->len; + + while (skb->len > sizeof(struct qmap_hdr)) { + struct qmap_hdr *qhdr = (struct qmap_hdr *)skb->data; + struct net_device *qmap_net; + struct sk_buff *qmap_skb; + __be16 proto; + int pkt_len; + u8 offset_id = 0; + int err; + unsigned len = (be16_to_cpu(qhdr->pkt_len) + sizeof(struct qmap_hdr)); + +#if 0 + meig_debug = 1; + DBG("rx: %d\n", skb->len); + PrintHex(skb->data, 16); + meig_debug = 0; +#endif + + if (skb->len < (be16_to_cpu(qhdr->pkt_len) + sizeof(struct qmap_hdr))) { + INFO("drop qmap unknow pkt, len=%d, pkt_len=%d\n", skb->len, be16_to_cpu(qhdr->pkt_len)); + meig_debug = 1; + PrintHex(skb->data, 16); + meig_debug = 0; + goto out; + } + + debug_pkts++; + + if (qhdr->cd_rsvd_pad & 0x80) { + INFO("drop qmap command packet %x\n", qhdr->cd_rsvd_pad); + goto skip_pkt;; + } + + offset_id = qhdr->mux_id - MEIG_QMAP_MUX_ID; + if (offset_id >= pGobiDev->m_qmap_mode) { + INFO("drop qmap unknow mux_id %x\n", qhdr->mux_id); + goto skip_pkt; + } + + if (pGobiDev->m_qmap_mode > 1) { + qmap_net = pGobiDev->mpQmapNetDev[offset_id]; + } else { + qmap_net = dev->net; + } + + if (qmap_net == NULL) { + INFO("drop qmap unknow mux_id %x\n", qhdr->mux_id); + goto skip_pkt; + } + + switch (skb->data[sizeof(struct qmap_hdr)] & 0xf0) { + case 0x40: + proto = htons(ETH_P_IP); + break; + case 0x60: + proto = htons(ETH_P_IPV6); + break; + default: + goto skip_pkt; + } + + pkt_len = be16_to_cpu(qhdr->pkt_len) - (qhdr->cd_rsvd_pad&0x3F); + qmap_skb = netdev_alloc_skb(qmap_net, ETH_HLEN + pkt_len); + + skb_reset_mac_header(qmap_skb); + memcpy(eth_hdr(qmap_skb)->h_source, meig_mac, ETH_ALEN); + memcpy(eth_hdr(qmap_skb)->h_dest, qmap_net->dev_addr, ETH_ALEN); + eth_hdr(qmap_skb)->h_proto = proto; + memcpy(skb_put(qmap_skb, ETH_HLEN + pkt_len) + ETH_HLEN, skb->data + sizeof(struct qmap_hdr), pkt_len); + + if (pGobiDev->m_qmap_mode > 1) { + qmap_skb->protocol = eth_type_trans (qmap_skb, qmap_net); + memset(qmap_skb->cb, 0, sizeof(struct skb_data)); + err = netif_rx(qmap_skb); +#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,14 )) + if (err == NET_RX_SUCCESS) { + qmap_net->stats.rx_packets++; + qmap_net->stats.rx_bytes += qmap_skb->len; + } else { + qmap_net->stats.rx_errors++; + } +#endif + } else { + usbnet_skb_return(dev, qmap_skb); + } + +skip_pkt: + skb_pull(skb, len); + } + +out: + if (update_len > debug_len) { + debug_len = update_len; + INFO("rx_pkts=%d, rx_len=%d\n", debug_pkts, debug_len); + } + return 0; +} +#endif +/*=========================================================================== +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; + +#ifdef MEIG_WWAN_QMAP + if (pGobiDev->m_qmap_mode) { + return GobiNetDriverRxQmapFixup(dev, skb); + } +#endif + + 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, meig_mac, ETH_ALEN); +fix_dest: +#ifdef CONFIG_BRIDGE + if (pGobiDev->m_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 (pGobiDev->m_bridge_mode) { + struct ethhdr *ehdr = eth_hdr(skb); + meig_debug = 1; + DBG(": "); + PrintHex(ehdr, sizeof(struct ethhdr)); + meig_debug = 0; + } +#endif +#endif + + return 1; +} +#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 )) { + // 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 )) { + // 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 +===========================================================================*/ +static 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 +===========================================================================*/ +static 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 struct driver_info GobiNetInfo = { + .description = "Meig 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 +#if defined(MEIG_WWAN_MULTI_PACKAGES) + .rx_fixup = GobiNetDriverRxPktsFixup, +#else + .rx_fixup = GobiNetDriverRxFixup, +#endif + .tx_fixup = GobiNetDriverTxFixup, +#endif + .data = (1 << 5), +}; + +/*=========================================================================*/ +// 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 MeigGobiVIDPIDTable [] = { + GOBI_FIXED_INTF( 0x05c6, 0xf601 ), // SLM750V + GOBI_FIXED_INTF( 0x2dee, 0x4d22 ), // SRM815 + //Terminating entry + { } +}; + +MODULE_DEVICE_TABLE( usb, MeigGobiVIDPIDTable ); + +/*=========================================================================== +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 +===========================================================================*/ +static 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 + 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 + 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(0x05c6)); + pGobiDev->mbMdm9x07 = true; //only support 9x07 chipset + pGobiDev->mbRawIPMode = pGobiDev->mbMdm9x07; + pGobiDev->mbRawIPMode = true; + if ( pGobiDev->mbRawIPMode) + pGobiDev->mpNetDev->net->flags |= IFF_NOARP; +#ifdef CONFIG_BRIDGE + memcpy(pGobiDev->mHostMAC, pDev->net->dev_addr, 6); + pGobiDev->m_bridge_mode = bridge_mode; +#endif + +#ifdef MEIG_REMOVE_TX_ZLP + { + struct remove_tx_zlp_config { + __le32 enable; + } __packed; + + struct remove_tx_zlp_config cfg; + cfg.enable = cpu_to_le32(1); //1-enable 0-disable + + usb_control_msg( + interface_to_usbdev(pIntf), + usb_sndctrlpipe(interface_to_usbdev(pIntf), 0), + USB_CDC_SET_REMOVE_TX_ZLP_COMMAND, + 0x21, //USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE + 0, + pIntf->cur_altsetting->desc.bInterfaceNumber, + &cfg, sizeof(cfg), 100); + } +#endif + + pGobiDev->m_qcrmcall_mode = qcrmcall_mode; + + if (pGobiDev->m_qcrmcall_mode) { + INFO("AT$QCRMCALL MODE!"); + + GobiClearDownReason( pGobiDev, NO_NDIS_CONNECTION ); + usb_control_msg( + interface_to_usbdev(pIntf), + usb_sndctrlpipe(interface_to_usbdev(pIntf), 0), + 0x22, //USB_CDC_REQ_SET_CONTROL_LINE_STATE + 0x21, //USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE + 1, //active CDC DTR + pIntf->cur_altsetting->desc.bInterfaceNumber, + NULL, 0, 100); + status = 0; + } else { + if (pGobiDev->mbRawIPMode) { + pGobiDev->m_qmap_mode = qmap_mode; + } + 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; + } + +#if defined(MEIG_WWAN_QMAP) + if (pGobiDev->m_qmap_mode > 1) { + unsigned i; + for (i = 0; i < pGobiDev->m_qmap_mode; i++) { + qmap_register_device(pGobiDev, i); + } + } +#endif + +#if (LINUX_VERSION_CODE < KERNEL_VERSION( 5,0,0 )) +#ifdef FLAG_RX_ASSEMBLE + if (pGobiDev->m_qmap_mode) + pDev->driver_info->flags |= FLAG_RX_ASSEMBLE; +#endif +#endif + // Success + return 0; +} + +static void GobiUSBNetDisconnect (struct usb_interface *intf) +{ +#if defined(MEIG_WWAN_QMAP) + struct usbnet *pDev = usb_get_intfdata(intf); + sGobiUSBNet * pGobiDev = (sGobiUSBNet *)pDev->data[0]; + unsigned i; + + for (i = 0; i < pGobiDev->m_qmap_mode; i++) { + qmap_unregister_device(pGobiDev, i); + } +#endif + + usbnet_disconnect(intf); +} + +static struct usb_driver GobiNet = { + .name = "GobiNet", + .id_table = MeigGobiVIDPIDTable, + .probe = GobiUSBNetProbe, + .disconnect = GobiUSBNetDisconnect, +#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_named( debug, meig_debug, int, S_IRUGO | S_IWUSR ); +MODULE_PARM_DESC( debug, "Debuging enabled or not" ); + +//module_param_named( interruptible, Meiginterruptible, 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 --git a/root/package/link4all/gobinet_srm815/src/QMI.c b/root/package/link4all/gobinet_srm815/src/QMI.c new file mode 100755 index 00000000..c7d68de5 --- /dev/null +++ b/root/package/link4all/gobinet_srm815/src/QMI.c @@ -0,0 +1,1446 @@ +#ifdef __MEIG_INCLUDE_QMI_C__ +/*=========================================================================== +FILE: + QMI.c + +DESCRIPTION: + Qualcomm QMI driver code + +FUNCTIONS: + Generic QMUX functions + ParseQMUX + FillQMUX + + Generic QMI functions + GetTLV + ValidQMIMessage + GetQMIMessageID + + Fill Buffers with QMI requests + QMICTLGetClientIDReq + QMICTLReleaseClientIDReq + QMICTLReadyReq + QMIWDSSetEventReportReq + QMIWDSGetPKGSRVCStatusReq + QMIDMSGetMEIDReq + QMIWDASetDataFormatReq + QMICTLSetDataFormatReq + QMICTLSyncReq + + Parse data from QMI responses + QMICTLGetClientIDResp + QMICTLReleaseClientIDResp + QMIWDSEventResp + QMIDMSGetMEIDResp + QMIWDASetDataFormatResp + QMICTLSyncResp + +Copyright (c) 2011, Code Aurora Forum. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Code Aurora Forum nor + the names of its contributors may be used to endorse or promote + products derived from this software without specific prior written + permission. + + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +===========================================================================*/ + +//--------------------------------------------------------------------------- +// Include Files +//--------------------------------------------------------------------------- +#include +#include +#include "Structs.h" +#include "QMI.h" + +/*=========================================================================*/ +// Get sizes of buffers needed by QMI requests +/*=========================================================================*/ + +/*=========================================================================== +METHOD: + QMUXHeaderSize (Public Method) + +DESCRIPTION: + Get size of buffer needed for QMUX + +RETURN VALUE: + u16 - size of buffer +===========================================================================*/ +static 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 +===========================================================================*/ +static 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 +===========================================================================*/ +static 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 +===========================================================================*/ +static 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 +===========================================================================*/ +static 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 +===========================================================================*/ +static u16 QMIWDSGetPKGSRVCStatusReqSize( void ) +{ + return sizeof( sQMUX ) + 7; +} + +/*=========================================================================== +METHOD: + QMIDMSGetMEIDReqSize (Public Method) + +DESCRIPTION: + Get size of buffer needed for QMUX + QMIDMSGetMEIDReq + +RETURN VALUE: + u16 - size of buffer +===========================================================================*/ +static u16 QMIDMSGetMEIDReqSize( void ) +{ + return sizeof( sQMUX ) + 7; +} + +#ifdef MEIG_WWAN_QMAP +struct QMIWDS_ADMIN_SET_DATA_FORMAT_TLV_QOS { + u8 TLVType; + u16 TLVLength; + u8 QOSSetting; +} __packed; + +struct QMIWDS_ADMIN_SET_DATA_FORMAT_TLV { + u8 TLVType; + u16 TLVLength; + u32 Value; +} __packed; + +struct QMIWDS_ENDPOINT_TLV { + u8 TLVType; + u16 TLVLength; + u32 ep_type; + u32 iface_id; +} __packed; + +struct QMIWDS_ADMIN_SET_DATA_FORMAT_REQ_MSG { + u8 CtlFlags; // 0: single QMUX Msg; 1: + u16 TransactionId; + u16 Type; + u16 Length; + struct QMIWDS_ADMIN_SET_DATA_FORMAT_TLV_QOS QosDataFormatTlv; + struct QMIWDS_ADMIN_SET_DATA_FORMAT_TLV UnderlyingLinkLayerProtocolTlv; + struct QMIWDS_ADMIN_SET_DATA_FORMAT_TLV UplinkDataAggregationProtocolTlv; + struct QMIWDS_ADMIN_SET_DATA_FORMAT_TLV DownlinkDataAggregationProtocolTlv; + struct QMIWDS_ADMIN_SET_DATA_FORMAT_TLV DownlinkDataAggregationMaxDatagramsTlv; + struct QMIWDS_ADMIN_SET_DATA_FORMAT_TLV DownlinkDataAggregationMaxSizeTlv; + struct QMIWDS_ENDPOINT_TLV epTlv; + //struct QMIWDS_ADMIN_SET_DATA_FORMAT_TLV dl_minimum_padding; +} __packed; +#endif + +/*=========================================================================== +METHOD: + QMIWDASetDataFormatReqSize (Public Method) + +DESCRIPTION: + Get size of buffer needed for QMUX + QMIWDASetDataFormatReq + +RETURN VALUE: + u16 - size of buffer +===========================================================================*/ +static u16 QMIWDASetDataFormatReqSize( int qmap_mode ) +{ + if (qmap_mode) + return sizeof( sQMUX ) + sizeof(struct QMIWDS_ADMIN_SET_DATA_FORMAT_REQ_MSG); + else + return sizeof( sQMUX ) + 18; +} + +/*=========================================================================== +METHOD: + QMICTLSyncReqSize (Public Method) + +DESCRIPTION: + Get size of buffer needed for QMUX + QMICTLSyncReq + +RETURN VALUE: + u16 - size of buffer +===========================================================================*/ +static 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 +===========================================================================*/ +static 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 +===========================================================================*/ +static 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 +===========================================================================*/ +static 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 +===========================================================================*/ +static 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 +===========================================================================*/ +static 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 +===========================================================================*/ +static 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 +===========================================================================*/ +static 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 +===========================================================================*/ +static 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 +===========================================================================*/ +static 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 +===========================================================================*/ +static 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; +} + +#if 1 //enable by zhaopf for net interface bond. +static u16 QMIWDSSetQMUXBindMuxDataPortSize( void ) +{ + return sizeof( sQMUX ) + 29; +} + +static u16 QMIWDSSetQMUXBindMuxDataPortReq( + void * pBuffer, + u16 buffSize, + u8 MuxId, + sGobiUSBNet * pDev, //add for net interface bond, by zhaopf + 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 + ////add for net interface bind, by zhaopf + put_unaligned(cpu_to_le32(pDev->mpIntf->cur_altsetting->desc.bInterfaceNumber), (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) = MuxId; // 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; +} +#endif + +/*=========================================================================== +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 +===========================================================================*/ +static 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 +===========================================================================*/ +static int QMIWDASetDataFormatReq( + void * pBuffer, + u16 buffSize, + bool bRawIPMode, int qmap_mode, u32 rx_size, + u16 transactionID ) +{ + if (qmap_mode) { + struct QMIWDS_ADMIN_SET_DATA_FORMAT_REQ_MSG *pMUXMsg = (struct QMIWDS_ADMIN_SET_DATA_FORMAT_REQ_MSG *)(pBuffer + sizeof( sQMUX )); + + pMUXMsg->CtlFlags = 0x00; + put_unaligned( cpu_to_le16(transactionID), &pMUXMsg->TransactionId); + put_unaligned( cpu_to_le16(0x0020), &pMUXMsg->Type); + put_unaligned( cpu_to_le16(sizeof( struct QMIWDS_ADMIN_SET_DATA_FORMAT_REQ_MSG) - 7), &pMUXMsg->Length); + +//Indicates whether the Quality of Service(QOS) data format is used by the client. + pMUXMsg->QosDataFormatTlv.TLVType = 0x10; + pMUXMsg->QosDataFormatTlv.TLVLength = cpu_to_le16(0x0001); + pMUXMsg->QosDataFormatTlv.QOSSetting = 0; /* no-QOS header */ +//Underlying Link Layer Protocol + pMUXMsg->UnderlyingLinkLayerProtocolTlv.TLVType = 0x11; + pMUXMsg->UnderlyingLinkLayerProtocolTlv.TLVLength = cpu_to_le16(4); + pMUXMsg->UnderlyingLinkLayerProtocolTlv.Value = cpu_to_le32(0x02); /* Set Ethernet mode */ +//Uplink (UL) data aggregation protocol to be used for uplink data transfer. + pMUXMsg->UplinkDataAggregationProtocolTlv.TLVType = 0x12; + pMUXMsg->UplinkDataAggregationProtocolTlv.TLVLength = cpu_to_le16(4); + pMUXMsg->UplinkDataAggregationProtocolTlv.Value = cpu_to_le32(0x05); //UL QMAP is enabled +//Downlink (DL) data aggregation protocol to be used for downlink data transfer + pMUXMsg->DownlinkDataAggregationProtocolTlv.TLVType = 0x13; + pMUXMsg->DownlinkDataAggregationProtocolTlv.TLVLength = cpu_to_le16(4); + pMUXMsg->DownlinkDataAggregationProtocolTlv.Value = cpu_to_le32(0x05); //UL QMAP is enabled +//Maximum number of datagrams in a single aggregated packet on downlink + pMUXMsg->DownlinkDataAggregationMaxDatagramsTlv.TLVType = 0x15; + pMUXMsg->DownlinkDataAggregationMaxDatagramsTlv.TLVLength = cpu_to_le16(4); + pMUXMsg->DownlinkDataAggregationMaxDatagramsTlv.Value = cpu_to_le32(rx_size/1024); +//Maximum size in bytes of a single aggregated packet allowed on downlink + pMUXMsg->DownlinkDataAggregationMaxSizeTlv.TLVType = 0x16; + pMUXMsg->DownlinkDataAggregationMaxSizeTlv.TLVLength = cpu_to_le16(4); + pMUXMsg->DownlinkDataAggregationMaxSizeTlv.Value = cpu_to_le32(rx_size); +//Peripheral End Point ID + pMUXMsg->epTlv.TLVType = 0x17; + pMUXMsg->epTlv.TLVLength = cpu_to_le16(8); + pMUXMsg->epTlv.ep_type = cpu_to_le32(0x02); // DATA_EP_TYPE_BAM_DMUX + pMUXMsg->epTlv.iface_id = cpu_to_le32(0x04); + +#if 0 +//Specifies the minimum padding bytes to be added in between aggregated downlink QMAP packets. + pMUXMsg->dl_minimum_padding.TLVType = 0x19; + pMUXMsg->dl_minimum_padding.TLVLength = cpu_to_le16(4); + pMUXMsg->dl_minimum_padding.Value = cpu_to_le32(0); +#endif + + } else { + if (pBuffer == 0 || buffSize < QMIWDASetDataFormatReqSize(qmap_mode) ) { + 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(0x000b), (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 + + } + + // success + return QMIWDASetDataFormatReqSize(qmap_mode); +} + +#if 0 +static int QMIWDASetDataQmapReq( + void * pBuffer, + u16 buffSize, + u16 transactionID ) +{ + // 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(0x002B), (u16 *)(pBuffer + sizeof( sQMUX ) + 3) ); + + // Size of TLV's + put_unaligned( cpu_to_le16(0x0004), (u16 *)(pBuffer + sizeof( sQMUX ) + 5)); + + /* TLVType QMAP In-Band Flow Control 1 byte */ + *(u8 *)(pBuffer + sizeof( sQMUX ) + 7) = 0x10; + put_unaligned( cpu_to_le16(0x0001), (u16 *)(pBuffer + sizeof( sQMUX ) + 8)); + *(u8 *)(pBuffer + sizeof( sQMUX ) + 10) = 0x01; + + // success + return ( sizeof( sQMUX ) + 11); +} +#endif + +#if 0 +/*=========================================================================== +METHOD: + QMICTLSetDataFormatReqSize (Public Method) + +DESCRIPTION: + Get size of buffer needed for QMUX + QMICTLSetDataFormatReq + +RETURN VALUE: + u16 - size of buffer +===========================================================================*/ +static u16 QMICTLSetDataFormatReqSize( void ) +{ + return sizeof( sQMUX ) + 15; +} + +/*=========================================================================== +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 +===========================================================================*/ +static 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; + +} +#endif + +/*=========================================================================== +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 +===========================================================================*/ +static 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 +===========================================================================*/ +static 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 +===========================================================================*/ +static 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 +===========================================================================*/ +static 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 +===========================================================================*/ +static 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 +===========================================================================*/ +static int QMIWDASetDataFormatResp( + void * pBuffer, + u16 buffSize, bool bRawIPMode, int *qmap_enabled, int *rx_size, int *tx_size) +{ + + 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 + + GetTLV( pBuffer, buffSize, 0x12, qmap_enabled, 4); + if (le32_to_cpu(*qmap_enabled) == 5) + GetTLV( pBuffer, buffSize, 0x13, qmap_enabled, 4); + + GetTLV( pBuffer, buffSize, 0x16, rx_size, 4); + GetTLV( pBuffer, buffSize, 0x18, tx_size, 4); + + 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 +===========================================================================*/ +static 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; +} +#endif diff --git a/root/package/link4all/gobinet_srm815/src/QMI.h b/root/package/link4all/gobinet_srm815/src/QMI.h new file mode 100755 index 00000000..8a634769 --- /dev/null +++ b/root/package/link4all/gobinet_srm815/src/QMI.h @@ -0,0 +1,337 @@ +/*=========================================================================== +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 meig_debug; +// DBG macro +#define DBG( format, arg... ) do { \ + if (meig_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; + +#if 0 +/*=========================================================================*/ +// 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( int qmap_mode ); + +// 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, + u8 MuxId, + sGobiUSBNet * pDev, //add for net interface bond by zhaopf + 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, int qmap_mode, u32 rx_size, + u16 transactionID ); + +#if 0 +int QMIWDASetDataQmapReq( + void * pBuffer, + u16 buffSize, + u16 transactionID ); +#endif + +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, int *qmap_enabled, int *rx_size, int *tx_size); + +// Pasre the QMI CTL Sync Response +int QMICTLSyncResp( + void *pBuffer, + u16 buffSize ); +#endif diff --git a/root/package/link4all/gobinet_srm815/src/QMIDevice.c b/root/package/link4all/gobinet_srm815/src/QMIDevice.c new file mode 100755 index 00000000..93790b1c --- /dev/null +++ b/root/package/link4all/gobinet_srm815/src/QMIDevice.c @@ -0,0 +1,3906 @@ +/*=========================================================================== +FILE: + QMIDevice.c + +DESCRIPTION: + Functions related to the QMI interface device + +FUNCTIONS: + Generic functions + IsDeviceValid + PrintHex + GobiSetDownReason + GobiClearDownReason + GobiTestDownReason + + Driver level asynchronous read functions + ResubmitIntURB + ReadCallback + IntCallback + StartRead + KillRead + + Internal read/write functions + ReadAsync + UpSem + ReadSync + WriteSyncCallback + WriteSync + + Internal memory management functions + GetClientID + ReleaseClientID + FindClientMem + AddToReadMemList + PopFromReadMemList + AddToNotifyList + NotifyAndPopNotifyList + AddToURBList + PopFromURBList + + Internal userspace wrapper functions + UserspaceunlockedIOCTL + + Userspace wrappers + UserspaceOpen + UserspaceIOCTL + UserspaceClose + UserspaceRead + UserspaceWrite + UserspacePoll + + Initializer and destructor + RegisterQMIDevice + DeregisterQMIDevice + + Driver level client management + QMIReady + QMIWDSCallback + SetupQMIWDSCallback + QMIDMSGetMEID + +Copyright (c) 2011, Code Aurora Forum. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Code Aurora Forum nor + the names of its contributors may be used to endorse or promote + products derived from this software without specific prior written + permission. + + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +===========================================================================*/ + +//--------------------------------------------------------------------------- +// Include Files +//--------------------------------------------------------------------------- +#include +#include +#include +#include + +//----------------------------------------------------------------------------- +// Definitions +//----------------------------------------------------------------------------- + +#define __MEIG_INCLUDE_QMI_C__ +#include "QMI.c" +#define __MEIG_INTER__ +#include "QMIDevice.h" + +#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,22 )) +static int s_interval; +#endif + +#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,14 )) +#include +static char devfs_name[32]; +static 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; +} + +static 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 MeigGobiNetSuspend( + 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 + +static 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 +/*=========================================================================*/ +static 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, +#ifdef meig_no_for_each_process + .release = UserspaceClose, +#else + .flush = UserspaceClose, +#endif + .poll = UserspacePoll, +}; + +/*=========================================================================*/ +// Generic functions +/*=========================================================================*/ +static 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 +===========================================================================*/ +static 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 MeigPrintHex( + void * pBuffer, + u16 bufSize ) +{ + char * pPrintBuf; + u16 pos; + int status; + + if (meig_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 MeigGobiSetDownReason( + sGobiUSBNet * pDev, + u8 reason ) +{ + DBG("%s reason=%d, mDownReason=%x\n", __func__, reason, (unsigned)pDev->mDownReason); + +#ifdef MEIG_WWAN_QMAP + if (reason == NO_NDIS_CONNECTION) + return; +#endif + + set_bit( reason, &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 MeigGobiClearDownReason( + 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 MeigGobiTestDownReason( + 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 +===========================================================================*/ +static 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 )) +static void ReadCallback( struct urb * pReadURB ) +#else +static 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 )) +static void IntCallback( struct urb * pIntURB ) +{ +#else +static void IntCallback(struct urb *pIntURB, struct pt_regs *regs) +{ +#endif + int status; + struct usb_cdc_notification *dr; + + sGobiUSBNet * pDev = (sGobiUSBNet *)pIntURB->context; + dr = (struct usb_cdc_notification *)pDev->mQMIDev.mpIntBuffer; + + 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 + + VDBG( "IntCallback: Encapsulated Response = 0x%llx\n", + (*(u64*)pIntURB->transfer_buffer)); + + switch (dr->bNotificationType) { + case USB_CDC_NOTIFY_RESPONSE_AVAILABLE: { //0x01 + // 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; + } + case USB_CDC_NOTIFY_SPEED_CHANGE: { //0x2a + 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" ); + } + } + default: { + 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 MeigStartRead( 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 MeigKillRead( 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 +===========================================================================*/ +static 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 +===========================================================================*/ +#define MEIG_SEM_MAGIC 0x12345678 +struct MeigSem { + struct semaphore readSem; + int magic; +}; + +static void UpSem( + sGobiUSBNet * pDev, + u16 clientID, + void * pData ) +{ + struct MeigSem *pSem = (struct MeigSem *)pData; + + VDBG( "0x%04X\n", clientID ); + + if (pSem->magic == MEIG_SEM_MAGIC) + up( &(pSem->readSem) ); + else + kfree(pSem); + 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 +===========================================================================*/ +static int ReadSync( + sGobiUSBNet * pDev, + void ** ppOutBuffer, + u16 clientID, + u16 transactionID ) +{ + int result; + sClientMemList * pClientMem; + sNotifyList ** ppNotifyList, * pDelNotifyListEntry; + struct MeigSem 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.readSem, 0 ); + readSem.magic = MEIG_SEM_MAGIC; + + // 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.readSem ); + if (result == -EINTR) { + result = down_timeout(&readSem.readSem, msecs_to_jiffies(200)); + } + if (result != 0) { + DBG( "Down Timeout %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 )) +static void WriteSyncCallback( struct urb * pWriteURB ) +#else +static 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 +===========================================================================*/ +static 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 = kmalloc(sizeof(sURBSetupPacket *), GFP_KERNEL); + 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 + MeigGobiNetSuspend( pDev->mpIntf, PMSG_SUSPEND ); +#endif /* CONFIG_PM */ + } + usb_free_urb( pWriteURB ); + kfree(writeSetup); + + return result; + } + + // Critical section + spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags ); + + if (AddToURBList( pDev, clientID, pWriteURB ) == false) { + usb_free_urb( pWriteURB ); + kfree(writeSetup); + + // 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 ); + kfree(writeSetup); + + // 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 (1 != 0) { //(interruptible != 0) + // Allow user interrupts + result = down_interruptible( &writeSem ); + if (result == -EINTR) { + result = down_timeout(&writeSem, msecs_to_jiffies(200)); + } + } 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 ); + kfree(writeSetup); + 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 ); + kfree(writeSetup); + 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 ); + kfree(writeSetup); + + 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 +===========================================================================*/ +static 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 +===========================================================================*/ +static 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 && pDev->mpNetDev->udev->state) { + // 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 + INFO("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 +===========================================================================*/ +static 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 +===========================================================================*/ +static 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 +===========================================================================*/ +static 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 +===========================================================================*/ +static 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 +===========================================================================*/ +static 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 +===========================================================================*/ +static 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 +===========================================================================*/ +static 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 +===========================================================================*/ +static 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 meig-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 +===========================================================================*/ +static 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; + pFilpData->mpDev = pDev; + atomic_inc(&pFilpData->mpDev->refcount); + + 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 +===========================================================================*/ +#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,36 )) +static 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 ); +} +#endif + +#ifdef meig_no_for_each_process +static int UserspaceClose( + struct inode * pInode, + struct file * pFilp ) +{ + sQMIFilpStorage * pFilpData = (sQMIFilpStorage *)pFilp->private_data; + + if (pFilpData == NULL) { + DBG( "bad file data\n" ); + return -EBADF; + } + + atomic_dec(&pFilpData->mpDev->refcount); + + if (IsDeviceValid( pFilpData->mpDev ) == false) { + 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 ); + } + + kfree( pFilpData ); + return 0; +} +#else +/*=========================================================================== +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; +} +#endif + +/*=========================================================================== +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 +===========================================================================*/ +static 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 +===========================================================================*/ +static 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 +===========================================================================*/ +static 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 +/*=========================================================================*/ +static 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" ); + } + + if (pDev->m_qmap_mode) { + // Setup Data Format + int rx_urb_size = 0; + result = QMIWDASetDataFormat (pDev, pDev->m_qmap_mode, &rx_urb_size); + if (result != 0) { + goto __qmi_sync_finished; + } + pDev->mpNetDev->rx_urb_size = rx_urb_size; + } + + // 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; + DBG( "zpf RegisterQMIDevice\n" ); + 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 some device, 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, pDev->m_qmap_mode, NULL); + 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 ) +{ +#ifndef meig_no_for_each_process + struct inode * pOpenInode; + struct list_head * pInodeList; + struct task_struct * pEachTask; + struct fdtable * pFDT; + struct file * pFilp; + int count = 0; +#endif + unsigned long flags; + 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; + } + +#ifndef meig_no_for_each_process + // 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(); + } + } +#endif + + if (pDev->mpNetDev->udev->state) { + // 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++) { +#if (LINUX_VERSION_CODE < KERNEL_VERSION( 4,11,0 )) + int ref = atomic_read( &pDev->mQMIDev.mCdev.kobj.kref.refcount ); +#else + int ref = kref_read( &pDev->mQMIDev.mCdev.kobj.kref ); +#endif + if (ref > 1) { + DBG( "cdev in use by %d tasks\n", ref - 1 ); + if (tries > 10) + INFO( "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 +===========================================================================*/ +static bool QMIReady( + sGobiUSBNet * pDev, + u16 timeout ) +{ + int result; + void * pWriteBuffer; + u16 writeBufferSize; + void * pReadBuffer; + u16 readBufferSize; + u16 curTime; + unsigned long flags; + u8 transactionID; + u16 interval = 2000; + + 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 += interval) { + // Start read + struct MeigSem *readSem = kmalloc(sizeof(struct MeigSem ), GFP_KERNEL); + readSem->magic = MEIG_SEM_MAGIC; + sema_init( &readSem->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 ); + +#if 1 + if (down_timeout( &readSem->readSem, msecs_to_jiffies(interval) ) == 0) +#else + msleep( interval ); + if (down_trylock( &readSem->readSem ) == 0) +#endif + { + kfree(readSem); + // 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 { + readSem->magic = 0; + // 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 +===========================================================================*/ +static 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 +===========================================================================*/ +static 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 1 // add for "AT$QCRMCALL=1,1", be careful: donot enable these codes if use meig-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, + 0x81, + pDev, //add for net interface bind, by zhaopf@meigsmart.com + 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 +===========================================================================*/ +static 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 +===========================================================================*/ +static int QMIWDASetDataFormat( sGobiUSBNet * pDev, int qmap_mode, int *rx_urb_size ) +{ + 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(qmap_mode); + pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL ); + if (pWriteBuffer == NULL) { + return -ENOMEM; + } + + result = QMIWDASetDataFormatReq( pWriteBuffer, + writeBufferSize, pDev->mbRawIPMode, + qmap_mode, 32*1024, + 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; + + if (qmap_mode && rx_urb_size) { + int qmap_enabled = 0, rx_size = 0, tx_size = 0; + result = QMIWDASetDataFormatResp( pReadBuffer, + readBufferSize, pDev->mbRawIPMode, &qmap_enabled, &rx_size, &tx_size); + INFO( "qmap settings qmap_enabled=%d, rx_size=%d, tx_size=%d\n", + le32_to_cpu(qmap_enabled), le32_to_cpu(rx_size), le32_to_cpu(tx_size)); + + if (le32_to_cpu(qmap_enabled) == 5) { + *rx_urb_size = le32_to_cpu(rx_size); + } else { + *rx_urb_size = 0; + result = -EFAULT; + } + } else { + int qmap_enabled = 0, rx_size = 0, tx_size = 0; + result = QMIWDASetDataFormatResp( pReadBuffer, + readBufferSize, pDev->mbRawIPMode, &qmap_enabled, &rx_size, &tx_size); + } + + kfree( pReadBuffer ); + + if (result < 0) { + DBG( "Data Format Cannot be set\n" ); + } + + ReleaseClientID( pDev, WDAClientID ); + + // Success + return 0; +} + +int MeigQMIWDASetDataFormat( sGobiUSBNet * pDev, int qmap_mode, int *rx_urb_size ) +{ + return QMIWDASetDataFormat(pDev, qmap_mode, rx_urb_size); +} diff --git a/root/package/link4all/gobinet_srm815/src/QMIDevice.h b/root/package/link4all/gobinet_srm815/src/QMIDevice.h new file mode 100755 index 00000000..46eb11a4 --- /dev/null +++ b/root/package/link4all/gobinet_srm815/src/QMIDevice.h @@ -0,0 +1,368 @@ +/*=========================================================================== +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 +/*=========================================================================*/ + +#ifdef __MEIG_INTER__ + +// Basic test to see if device memory is valid +static bool IsDeviceValid( sGobiUSBNet * pDev ); + +/*=========================================================================*/ +// Driver level asynchronous read functions +/*=========================================================================*/ + +// Resubmit interrupt URB, re-using same values +static 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 )) +static void ReadCallback( struct urb * pReadURB ); +#else +static 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 )) +static void IntCallback( struct urb * pIntURB ); +#else +static void IntCallback(struct urb *pIntURB, struct pt_regs *regs); +#endif + +/*=========================================================================*/ +// Internal read/write functions +/*=========================================================================*/ + +// Start asynchronous read +// Reading client's data store, not device +static int ReadAsync( + sGobiUSBNet * pDev, + u16 clientID, + u16 transactionID, + void (*pCallback)(sGobiUSBNet *, u16, void *), + void * pData ); + +// Notification function for synchronous read +static void UpSem( + sGobiUSBNet * pDev, + u16 clientID, + void * pData ); + +// Start synchronous read +// Reading client's data store, not device +static int ReadSync( + sGobiUSBNet * pDev, + void ** ppOutBuffer, + u16 clientID, + u16 transactionID ); + +// Write callback +#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,18 )) +static void WriteSyncCallback( struct urb * pWriteURB ); +#else +static void WriteSyncCallback(struct urb *pWriteURB, struct pt_regs *regs); +#endif + +// Start synchronous write +static int WriteSync( + sGobiUSBNet * pDev, + char * pInWriteBuffer, + int size, + u16 clientID ); + +/*=========================================================================*/ +// Internal memory management functions +/*=========================================================================*/ + +// Create client and allocate memory +static int GetClientID( + sGobiUSBNet * pDev, + u8 serviceType ); + +// Release client and free memory +static void ReleaseClientID( + sGobiUSBNet * pDev, + u16 clientID ); + +// Find this client's memory +static sClientMemList * FindClientMem( + sGobiUSBNet * pDev, + u16 clientID ); + +// Add Data to this client's ReadMem list +static 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. +static bool PopFromReadMemList( + sGobiUSBNet * pDev, + u16 clientID, + u16 transactionID, + void ** ppData, + u16 * pDataSize ); + +// Add Notify entry to this client's notify List +static 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 +static bool NotifyAndPopNotifyList( + sGobiUSBNet * pDev, + u16 clientID, + u16 transactionID ); + +// Add URB to this client's URB list +static bool AddToURBList( + sGobiUSBNet * pDev, + u16 clientID, + struct urb * pURB ); + +// Remove URB from this client's URB list +static struct urb * PopFromURBList( + sGobiUSBNet * pDev, + u16 clientID ); + +/*=========================================================================*/ +// Internal userspace wrappers +/*=========================================================================*/ + +// Userspace unlocked ioctl +static long UserspaceunlockedIOCTL( + struct file * pFilp, + unsigned int cmd, + unsigned long arg ); + +/*=========================================================================*/ +// Userspace wrappers +/*=========================================================================*/ + +// Userspace open +static int UserspaceOpen( + struct inode * pInode, + struct file * pFilp ); + +#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,36 )) +// Userspace ioctl +static int UserspaceIOCTL( + struct inode * pUnusedInode, + struct file * pFilp, + unsigned int cmd, + unsigned long arg ); +#endif + +// Userspace close +#define meig_no_for_each_process +#ifdef meig_no_for_each_process +static int UserspaceClose( + struct inode * pInode, + struct file * pFilp ); +#else +#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,14 )) +static int UserspaceClose( + struct file * pFilp, + fl_owner_t unusedFileTable ); +#else +static int UserspaceClose( struct file * pFilp ); +#endif +#endif + +// Userspace read (synchronous) +static ssize_t UserspaceRead( + struct file * pFilp, + char __user * pBuf, + size_t size, + loff_t * pUnusedFpos ); + +// Userspace write (synchronous) +static ssize_t UserspaceWrite( + struct file * pFilp, + const char __user * pBuf, + size_t size, + loff_t * pUnusedFpos ); + +static unsigned int UserspacePoll( + struct file * pFilp, + struct poll_table_struct * pPollTable ); + +/*=========================================================================*/ +// Driver level client management +/*=========================================================================*/ + +// Check if QMI is ready for use +static bool QMIReady( + sGobiUSBNet * pDev, + u16 timeout ); + +// QMI WDS callback function +static void QMIWDSCallback( + sGobiUSBNet * pDev, + u16 clientID, + void * pData ); + +// Fire off reqests and start async read for QMI WDS callback +static int SetupQMIWDSCallback( sGobiUSBNet * pDev ); + +// Register client, send req and parse MEID response, release client +static int QMIDMSGetMEID( sGobiUSBNet * pDev ); + +// Register client, send req and parse Data format response, release client +static int QMIWDASetDataFormat( sGobiUSBNet * pDev, int qmap_mode, int *rx_urb_size ); +#endif + +// Print Hex data, for debug purposes +void MeigPrintHex( + void * pBuffer, + u16 bufSize ); + +// Sets mDownReason and turns carrier off +void MeigGobiSetDownReason( + sGobiUSBNet * pDev, + u8 reason ); + +// Clear mDownReason and may turn carrier on +void MeigGobiClearDownReason( + sGobiUSBNet * pDev, + u8 reason ); + +// Tests mDownReason and returns whether reason is set +bool MeigGobiTestDownReason( + sGobiUSBNet * pDev, + u8 reason ); + +// Start continuous read "thread" +int MeigStartRead( sGobiUSBNet * pDev ); + +// Kill continuous read "thread" +void MeigKillRead( sGobiUSBNet * pDev ); + +/*=========================================================================*/ +// Initializer and destructor +/*=========================================================================*/ + +// QMI Device initialization function +int MeigRegisterQMIDevice( sGobiUSBNet * pDev ); + +// QMI Device cleanup function +void MeigDeregisterQMIDevice( sGobiUSBNet * pDev ); + +int MeigQMIWDASetDataFormat( sGobiUSBNet * pDev, int qmap_mode, int *rx_urb_size ); + +#define PrintHex MeigPrintHex +#define GobiSetDownReason MeigGobiSetDownReason +#define GobiClearDownReason MeigGobiClearDownReason +#define GobiTestDownReason MeigGobiTestDownReason +#define StartRead MeigStartRead +#define KillRead MeigKillRead +#define RegisterQMIDevice MeigRegisterQMIDevice +#define DeregisterQMIDevice MeigDeregisterQMIDevice diff --git a/root/package/link4all/gobinet_srm815/src/Readme.txt b/root/package/link4all/gobinet_srm815/src/Readme.txt new file mode 100644 index 00000000..575d51c5 --- /dev/null +++ b/root/package/link4all/gobinet_srm815/src/Readme.txt @@ -0,0 +1,15 @@ +使用方法 +1.编译gobinet驱动 +PC端按如下方法: +make +insmod GobiNet.ko +嵌入å¼å¹³å°å¯ä¿®æ”¹makefileåŽç¼–译 +2.编译拨å·å·¥å…· +cd meig-cm/ +make +嵌入å¼å¹³å°åŒæ ·è¦ä¿®æ”¹MakefileåŽç¼–译 +3.æ‹¨å· +./meig-cm -s [APNåç§°] +å¦‚ç§»åŠ¨å¡æ‹¨å·: +./meig-cm -s cmnet + diff --git a/root/package/link4all/gobinet_srm815/src/Structs.h b/root/package/link4all/gobinet_srm815/src/Structs.h new file mode 100755 index 00000000..760b0554 --- /dev/null +++ b/root/package/link4all/gobinet_srm815/src/Structs.h @@ -0,0 +1,455 @@ +/*=========================================================================== +FILE: + Structs.h + +DESCRIPTION: + Declaration of structures used by the Qualcomm Linux USB Network driver + +FUNCTIONS: + none + +Copyright (c) 2011, Code Aurora Forum. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Code Aurora Forum nor + the names of its contributors may be used to endorse or promote + products derived from this software without specific prior written + permission. + + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +===========================================================================*/ + +//--------------------------------------------------------------------------- +// Pragmas +//--------------------------------------------------------------------------- +#pragma once + +//--------------------------------------------------------------------------- +// Include Files +//--------------------------------------------------------------------------- +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MEIG_WWAN_QMAP 8 +#ifdef MEIG_WWAN_QMAP +#define MEIG_QMAP_MUX_ID 0x81 +#endif + +#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,21 )) +static inline void skb_reset_mac_header(struct sk_buff *skb) +{ + skb->mac.raw = skb->data; +} +#endif + +#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,22 )) +#define bool u8 +#ifndef URB_FREE_BUFFER +#define URB_FREE_BUFFER_BY_SELF //usb_free_urb will not free, should free by self +#define URB_FREE_BUFFER 0x0100 /* Free transfer buffer with the URB */ +#endif + +/** + * usb_endpoint_type - get the endpoint's transfer type + * @epd: endpoint to be checked + * + * Returns one of USB_ENDPOINT_XFER_{CONTROL, ISOC, BULK, INT} according + * to @epd's transfer type. + */ +static inline int usb_endpoint_type(const struct usb_endpoint_descriptor *epd) +{ + return epd->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK; +} +#endif + +#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,18 )) +/** + * usb_endpoint_dir_in - check if the endpoint has IN direction + * @epd: endpoint to be checked + * + * Returns true if the endpoint is of type IN, otherwise it returns false. + */ +static inline int usb_endpoint_dir_in(const struct usb_endpoint_descriptor *epd) +{ + return ((epd->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN); +} + +/** + * usb_endpoint_dir_out - check if the endpoint has OUT direction + * @epd: endpoint to be checked + * + * Returns true if the endpoint is of type OUT, otherwise it returns false. + */ +static inline int usb_endpoint_dir_out( + const struct usb_endpoint_descriptor *epd) +{ + return ((epd->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_OUT); +} + +/** + * usb_endpoint_xfer_int - check if the endpoint has interrupt transfer type + * @epd: endpoint to be checked + * + * Returns true if the endpoint is of type interrupt, otherwise it returns + * false. + */ +static inline int usb_endpoint_xfer_int( + const struct usb_endpoint_descriptor *epd) +{ + return ((epd->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == + USB_ENDPOINT_XFER_INT); +} + +static inline int usb_autopm_set_interface(struct usb_interface *intf) +{ + return 0; +} + +static inline int usb_autopm_get_interface(struct usb_interface *intf) +{ + return 0; +} + +static inline int usb_autopm_get_interface_async(struct usb_interface *intf) +{ + return 0; +} + +static inline void usb_autopm_put_interface(struct usb_interface *intf) +{ } +static inline void usb_autopm_put_interface_async(struct usb_interface *intf) +{ } +static inline void usb_autopm_enable(struct usb_interface *intf) +{ } +static inline void usb_autopm_disable(struct usb_interface *intf) +{ } +static inline void usb_mark_last_busy(struct usb_device *udev) +{ } +#endif + +#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,24 )) +#include "usbnet.h" +#else +#include +#endif + +#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,25 )) +#include +#else +#include +#endif + +// Used in recursion, defined later below +struct sGobiUSBNet; + +/*=========================================================================*/ +// Struct sReadMemList +// +// Structure that defines an entry in a Read Memory linked list +/*=========================================================================*/ +typedef struct sReadMemList { + /* Data buffer */ + void * mpData; + + /* Transaction ID */ + u16 mTransactionID; + + /* Size of data buffer */ + u16 mDataSize; + + /* Next entry in linked list */ + struct sReadMemList * mpNext; + +} sReadMemList; + +/*=========================================================================*/ +// Struct sNotifyList +// +// Structure that defines an entry in a Notification linked list +/*=========================================================================*/ +typedef struct sNotifyList { + /* Function to be run when data becomes available */ + void (* mpNotifyFunct)(struct sGobiUSBNet *, u16, void *); + + /* Transaction ID */ + u16 mTransactionID; + + /* Data to provide as parameter to mpNotifyFunct */ + void * mpData; + + /* Next entry in linked list */ + struct sNotifyList * mpNext; + +} sNotifyList; + +/*=========================================================================*/ +// Struct sURBList +// +// Structure that defines an entry in a URB linked list +/*=========================================================================*/ +typedef struct sURBList { + /* The current URB */ + struct urb * mpURB; + + /* Next entry in linked list */ + struct sURBList * mpNext; + +} sURBList; + +/*=========================================================================*/ +// Struct sClientMemList +// +// Structure that defines an entry in a Client Memory linked list +// Stores data specific to a Service Type and Client ID +/*=========================================================================*/ +typedef struct sClientMemList { + /* Client ID for this Client */ + u16 mClientID; + + /* Linked list of Read entries */ + /* Stores data read from device before sending to client */ + sReadMemList * mpList; + + /* Linked list of Notification entries */ + /* Stores notification functions to be run as data becomes + available or the device is removed */ + sNotifyList * mpReadNotifyList; + + /* Linked list of URB entries */ + /* Stores pointers to outstanding URBs which need canceled + when the client is deregistered or the device is removed */ + sURBList * mpURBList; + + /* Next entry in linked list */ + struct sClientMemList * mpNext; + + /* Wait queue object for poll() */ + wait_queue_head_t mWaitQueue; + +} sClientMemList; + +/*=========================================================================*/ +// Struct sURBSetupPacket +// +// Structure that defines a USB Setup packet for Control URBs +// Taken from USB CDC specifications +/*=========================================================================*/ +typedef struct sURBSetupPacket { + /* Request type */ + u8 mRequestType; + + /* Request code */ + u8 mRequestCode; + + /* Value */ + u16 mValue; + + /* Index */ + u16 mIndex; + + /* Length of Control URB */ + u16 mLength; + +} sURBSetupPacket; + +// Common value for sURBSetupPacket.mLength +#define DEFAULT_READ_URB_LENGTH 0x1000 + +#ifdef CONFIG_PM +#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,29 )) +/*=========================================================================*/ +// Struct sAutoPM +// +// Structure used to manage AutoPM thread which determines whether the +// device is in use or may enter autosuspend. Also submits net +// transmissions asynchronously. +/*=========================================================================*/ +typedef struct sAutoPM { + /* Thread for atomic autopm function */ + struct task_struct * mpThread; + + /* Signal for completion when it's time for the thread to work */ + struct completion mThreadDoWork; + + /* Time to exit? */ + bool mbExit; + + /* List of URB's queued to be sent to the device */ + sURBList * mpURBList; + + /* URB list lock (for adding and removing elements) */ + spinlock_t mURBListLock; + + /* Length of the URB list */ + atomic_t mURBListLen; + + /* Active URB */ + struct urb * mpActiveURB; + + /* Active URB lock (for adding and removing elements) */ + spinlock_t mActiveURBLock; + + /* Duplicate pointer to USB device interface */ + struct usb_interface * mpIntf; + +} sAutoPM; +#endif +#endif /* CONFIG_PM */ + +/*=========================================================================*/ +// Struct sQMIDev +// +// Structure that defines the data for the QMI device +/*=========================================================================*/ +typedef struct sQMIDev { + /* Device number */ + dev_t mDevNum; + + /* Device class */ + struct class * mpDevClass; + + /* cdev struct */ + struct cdev mCdev; + + /* is mCdev initialized? */ + bool mbCdevIsInitialized; + + /* Pointer to read URB */ + struct urb * mpReadURB; + +//#define READ_QMI_URB_ERROR +#ifdef READ_QMI_URB_ERROR + struct timer_list mReadUrbTimer; +#endif + + /* Read setup packet */ + sURBSetupPacket * mpReadSetupPacket; + + /* Read buffer attached to current read URB */ + void * mpReadBuffer; + + /* Inturrupt URB */ + /* Used to asynchronously notify when read data is available */ + struct urb * mpIntURB; + + /* Buffer used by Inturrupt URB */ + void * mpIntBuffer; + + /* Pointer to memory linked list for all clients */ + sClientMemList * mpClientMemList; + + /* Spinlock for client Memory entries */ + spinlock_t mClientMemLock; + + /* Transaction ID associated with QMICTL "client" */ + atomic_t mQMICTLTransactionID; + +} sQMIDev; + +/*=========================================================================*/ +// Struct sGobiUSBNet +// +// Structure that defines the data associated with the Qualcomm USB device +/*=========================================================================*/ +typedef struct sGobiUSBNet { + atomic_t refcount; + + /* Net device structure */ + struct usbnet * mpNetDev; +#ifdef MEIG_WWAN_QMAP + int m_qmap_mode; + struct net_device *mpQmapNetDev[MEIG_WWAN_QMAP]; +#ifdef CONFIG_BRIDGE + int m_qmap_bridge_mode[MEIG_WWAN_QMAP]; +#endif +#endif + +#if 1 //def DATA_MODE_RP + bool mbMdm9x07; + bool mbMdm9x06; //for BG96 + /* QMI "device" work in IP Mode or ETH Mode */ + bool mbRawIPMode; +#ifdef CONFIG_BRIDGE + int m_bridge_mode; + uint m_bridge_ipv4; + unsigned char mHostMAC[6]; +#endif + int m_qcrmcall_mode; +#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 --git a/root/package/link4all/quectel-CM/Makefile b/root/package/link4all/quectel-CM/Makefile new file mode 100755 index 00000000..cc60d5cd --- /dev/null +++ b/root/package/link4all/quectel-CM/Makefile @@ -0,0 +1,84 @@ +############################################## +# OpenWrt Makefile for HelloWorld program +# +# +# Most of the variables used here are defined in +# the include directives below. We just need to +# specify a basic description of the package, +# where to build our program, where to find +# the source files, and where to install the +# compiled program on the router. +# +# Be very careful of spacing in this file. +# Indents should be tabs, not spaces, and +# there should be no trailing whitespace in +# lines that are not commented. +# +############################################## +include $(TOPDIR)/rules.mk + +# Name and release number of this package +PKG_NAME:=quectel-CM +PKG_RELEASE:=1 + + +# This specifies the directory where we're going to build the program. +# The root build directory, $(BUILD_DIR), is by default the build_mipsel +# directory in your OpenWrt SDK directory +PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME) + + +include $(INCLUDE_DIR)/package.mk + +# Specify package information for this program. +# The variables defined here should be self explanatory. +# If you are running Kamikaze, delete the DESCRIPTION +# variable below and uncomment the Kamikaze define +# directive for the description below +define Package/quectel-CM + SECTION:=utils + CATEGORY:=LINK4ALL + DEPENDS:=+libpthread +libuci + TITLE:=quectel-CM 4g dialer +endef + + +# Uncomment portion below for Kamikaze and delete DESCRIPTION variable above +define Package/quectel-CM/description + quectel-CM 4g dialer. +endef + +# Specify what needs to be done to prepare for building the package. +# In our case, we need to copy the source files to the build directory. +# This is NOT the default. The default uses the PKG_SOURCE_URL and the +# PKG_SOURCE which is not defined here to download the source from the web. +# In order to just build a simple program that we have just written, it is +# much easier to do it this way. +define Build/Prepare + mkdir -p $(PKG_BUILD_DIR) + $(CP) ./src/* $(PKG_BUILD_DIR)/ +endef + + +# We do not need to define Build/Configure or Build/Compile directives +# The defaults are appropriate for compiling a simple program such as this one + + +# Specify where and how to install the program. Since we only have one file, +# the HelloWorld executable, install it by copying it to the /bin directory on +# the router. The $(1) variable represents the root directory on the router running +# OpenWrt. The $(INSTALL_DIR) variable contains a command to prepare the install +# directory if it does not already exist. Likewise $(INSTALL_BIN) contains the +# command to copy the binary file from its current location (in our case the build +# directory) to the install directory. +define Package/quectel-CM/install + $(INSTALL_DIR) $(1)/bin + $(INSTALL_BIN) $(PKG_BUILD_DIR)/quectel-CM $(1)/bin/ +endef + + +# This line executes the necessary commands to compile our program. +# The above define directives specify all the information needed, but this +# line calls BuildPackage which in turn actually uses this information to +# build a package. +$(eval $(call BuildPackage,quectel-CM)) diff --git a/root/package/link4all/quectel-CM/quectel-CM.tar.gz b/root/package/link4all/quectel-CM/quectel-CM.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..36f7b8f005c3641fcb01e2be4a17cd00658795c2 GIT binary patch literal 160619 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�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 Z$K$X6@9FdC^D_jtxTVVgY8DOZe*nqYWqSYs literal 0 HcmV?d00001 diff --git a/root/package/link4all/quectel-CM/quectel1.6.0.15_src/GobiNetCM.c b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/GobiNetCM.c new file mode 100644 index 00000000..3e4a3c41 --- /dev/null +++ b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/GobiNetCM.c @@ -0,0 +1,242 @@ +/****************************************************************************** + @file GobiNetCM.c + @brief GobiNet driver. + + DESCRIPTION + Connectivity Management Tool for USB network adapter of Quectel wireless cellular modules. + + INITIALIZATION AND SEQUENCING REQUIREMENTS + None. + + --------------------------------------------------------------------------- + Copyright (c) 2016 - 2020 Quectel Wireless Solution, Co., Ltd. All Rights Reserved. + Quectel Wireless Solution Proprietary and Confidential. + --------------------------------------------------------------------------- +******************************************************************************/ +#include +#include +#include +#include +#include +#include "QMIThread.h" + +#ifdef CONFIG_GOBINET + +// 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 + +static int GobiNetSendQMI(PQCQMIMSG pRequest) { + int ret, fd; + + fd = qmiclientId[pRequest->QMIHdr.QMIType]; + + if (fd <= 0) { + dbg_time("%s QMIType: %d has no clientID", __func__, pRequest->QMIHdr.QMIType); + return -ENODEV; + } + + // Always ready to write + if (1 == 1) { + ssize_t nwrites = le16_to_cpu(pRequest->QMIHdr.Length) + 1 - sizeof(QCQMI_HDR); + ret = write(fd, &pRequest->MUXMsg, nwrites); + if (ret == nwrites) { + ret = 0; + } else { + dbg_time("%s write=%d, errno: %d (%s)", __func__, ret, errno, strerror(errno)); + } + } else { + dbg_time("%s poll=%d, errno: %d (%s)", __func__, ret, errno, strerror(errno)); + } + + return ret; +} + +static int GobiNetGetClientID(const char *qcqmi, UCHAR QMIType) { + int ClientId; + ClientId = open(qcqmi, O_RDWR | O_NONBLOCK | O_NOCTTY); + if (ClientId == -1) { + dbg_time("failed to open %s, errno: %d (%s)", qcqmi, errno, strerror(errno)); + return -1; + } + + if (ioctl(ClientId, IOCTL_QMI_GET_SERVICE_FILE, QMIType) != 0) { + dbg_time("failed to get ClientID for 0x%02x errno: %d (%s)", QMIType, errno, strerror(errno)); + close(ClientId); + ClientId = 0; + } + + switch (QMIType) { + case QMUX_TYPE_WDS: dbg_time("Get clientWDS = %d", ClientId); break; + case QMUX_TYPE_DMS: dbg_time("Get clientDMS = %d", ClientId); break; + case QMUX_TYPE_NAS: dbg_time("Get clientNAS = %d", ClientId); break; + case QMUX_TYPE_QOS: dbg_time("Get clientQOS = %d", ClientId); break; + case QMUX_TYPE_WMS: dbg_time("Get clientWMS = %d", ClientId); break; + case QMUX_TYPE_PDS: dbg_time("Get clientPDS = %d", ClientId); break; + case QMUX_TYPE_UIM: dbg_time("Get clientUIM = %d", ClientId); break; + case QMUX_TYPE_WDS_ADMIN: dbg_time("Get clientWDA = %d", ClientId); + break; + default: break; + } + + return ClientId; +} + +static int GobiNetDeInit(void) { + unsigned int i; + for (i = 0; i < sizeof(qmiclientId)/sizeof(qmiclientId[0]); i++) + { + if (qmiclientId[i] != 0) + { + close(qmiclientId[i]); + qmiclientId[i] = 0; + } + } + + return 0; +} + +static void * GobiNetThread(void *pData) { + PROFILE_T *profile = (PROFILE_T *)pData; + const char *qcqmi = (const char *)profile->qmichannel; + int wait_for_request_quit = 0; + + qmiclientId[QMUX_TYPE_WDS] = GobiNetGetClientID(qcqmi, QMUX_TYPE_WDS); + if (profile->enable_ipv6) + qmiclientId[QMUX_TYPE_WDS_IPV6] = GobiNetGetClientID(qcqmi, QMUX_TYPE_WDS); + qmiclientId[QMUX_TYPE_DMS] = GobiNetGetClientID(qcqmi, QMUX_TYPE_DMS); + qmiclientId[QMUX_TYPE_NAS] = GobiNetGetClientID(qcqmi, QMUX_TYPE_NAS); + qmiclientId[QMUX_TYPE_UIM] = GobiNetGetClientID(qcqmi, QMUX_TYPE_UIM); + if (profile->qmap_mode == 0 || profile->loopback_state) //when QMAP enabled, set data format in GobiNet Driver + qmiclientId[QMUX_TYPE_WDS_ADMIN] = GobiNetGetClientID(qcqmi, QMUX_TYPE_WDS_ADMIN); + + //donot check clientWDA, there is only one client for WDA, if quectel-CM is killed by SIGKILL, i cannot get client ID for WDA again! + if (qmiclientId[QMUX_TYPE_WDS] == 0) /*|| (clientWDA == -1)*/ { + GobiNetDeInit(); + dbg_time("%s Failed to open %s, errno: %d (%s)", __func__, qcqmi, errno, strerror(errno)); + qmidevice_send_event_to_main(RIL_INDICATE_DEVICE_DISCONNECTED); + pthread_exit(NULL); + return NULL; + } + + qmidevice_send_event_to_main(RIL_INDICATE_DEVICE_CONNECTED); + + while (1) { + struct pollfd pollfds[16] = {{qmidevice_control_fd[1], POLLIN, 0}}; + int ne, ret, nevents = 1; + unsigned int i; + + for (i = 0; i < sizeof(qmiclientId)/sizeof(qmiclientId[0]); i++) + { + if (qmiclientId[i] != 0) + { + pollfds[nevents].fd = qmiclientId[i]; + pollfds[nevents].events = POLLIN; + pollfds[nevents].revents = 0; + nevents++; + } + } + + do { + ret = poll(pollfds, nevents, wait_for_request_quit ? 1000: -1); + } while ((ret < 0) && (errno == EINTR)); + + if (ret == 0 && wait_for_request_quit) { + QmiThreadRecvQMI(NULL); //main thread may pending on QmiThreadSendQMI() + continue; + } + + if (ret <= 0) { + dbg_time("%s poll=%d, errno: %d (%s)", __func__, ret, errno, strerror(errno)); + break; + } + + for (ne = 0; ne < nevents; ne++) { + int fd = pollfds[ne].fd; + short revents = pollfds[ne].revents; + + if (revents & (POLLERR | POLLHUP | POLLNVAL)) { + dbg_time("%s poll err/hup/inval", __func__); + dbg_time("epoll fd = %d, events = 0x%04x", fd, revents); + if (fd == qmidevice_control_fd[1]) { + } else { + } + if (revents & (POLLERR | POLLHUP | POLLNVAL)) + goto __GobiNetThread_quit; + } + + if ((revents & POLLIN) == 0) + continue; + + if (fd == qmidevice_control_fd[1]) { + int triger_event; + if (read(fd, &triger_event, sizeof(triger_event)) == sizeof(triger_event)) { + //DBG("triger_event = 0x%x", triger_event); + switch (triger_event) { + case RIL_REQUEST_QUIT: + goto __GobiNetThread_quit; + break; + case SIG_EVENT_STOP: + wait_for_request_quit = 1; + break; + default: + break; + } + } + continue; + } + + { + ssize_t nreads; + static UCHAR QMIBuf[4096]; + PQCQMIMSG pResponse = (PQCQMIMSG)QMIBuf; + + nreads = read(fd, &pResponse->MUXMsg, sizeof(QMIBuf) - sizeof(QCQMI_HDR)); + if (nreads <= 0) + { + dbg_time("%s read=%d errno: %d (%s)", __func__, (int)nreads, errno, strerror(errno)); + break; + } + + for (i = 0; i < sizeof(qmiclientId)/sizeof(qmiclientId[0]); i++) + { + if (qmiclientId[i] == fd) + { + pResponse->QMIHdr.QMIType = i; + } + } + + pResponse->QMIHdr.IFType = USB_CTL_MSG_TYPE_QMI; + pResponse->QMIHdr.Length = cpu_to_le16(nreads + sizeof(QCQMI_HDR) - 1); + pResponse->QMIHdr.CtlFlags = 0x00; + pResponse->QMIHdr.ClientId = fd & 0xFF; + + QmiThreadRecvQMI(pResponse); + } + } + } + +__GobiNetThread_quit: + GobiNetDeInit(); + qmidevice_send_event_to_main(RIL_INDICATE_DEVICE_DISCONNECTED); + QmiThreadRecvQMI(NULL); //main thread may pending on QmiThreadSendQMI() + dbg_time("%s exit", __func__); + pthread_exit(NULL); + return NULL; +} + +#else +static int GobiNetSendQMI(PQCQMIMSG pRequest) {return -1;} +static void * GobiNetThread(void *pData) {dbg_time("please set CONFIG_GOBINET"); return NULL;} +#endif + +const struct qmi_device_ops gobi_qmidev_ops = { + .deinit = GobiNetDeInit, + .send = GobiNetSendQMI, + .read = GobiNetThread, +}; \ No newline at end of file diff --git a/root/package/link4all/quectel-CM/quectel1.6.0.15_src/MPQCTL.h b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/MPQCTL.h new file mode 100644 index 00000000..c88bdc32 --- /dev/null +++ b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/MPQCTL.h @@ -0,0 +1,377 @@ +/*=========================================================================== + + M P Q C T L. H +DESCRIPTION: + + This module contains QMI QCTL module. + +INITIALIZATION AND SEQUENCING REQUIREMENTS: + +Copyright (C) 2011 by Qualcomm Technologies, Incorporated. All Rights Reserved. +===========================================================================*/ + +#ifndef MPQCTL_H +#define MPQCTL_H + +#include "MPQMI.h" + +#pragma pack(push, 1) + +// ================= QMICTL ================== + +// QMICTL Control Flags +#define QMICTL_CTL_FLAG_CMD 0x00 +#define QMICTL_CTL_FLAG_RSP 0x01 +#define QMICTL_CTL_FLAG_IND 0x02 + +#if 0 +typedef struct _QMICTL_TRANSACTION_ITEM +{ + LIST_ENTRY List; + UCHAR TransactionId; // QMICTL transaction id + PVOID Context; // Adapter or IocDev + PIRP Irp; +} QMICTL_TRANSACTION_ITEM, *PQMICTL_TRANSACTION_ITEM; +#endif + +typedef struct _QCQMICTL_MSG_HDR +{ + UCHAR CtlFlags; // 00-cmd, 01-rsp, 10-ind + UCHAR TransactionId; + USHORT QMICTLType; + USHORT Length; +} __attribute__ ((packed)) QCQMICTL_MSG_HDR, *PQCQMICTL_MSG_HDR; + +#define QCQMICTL_MSG_HDR_SIZE sizeof(QCQMICTL_MSG_HDR) + +typedef struct _QCQMICTL_MSG_HDR_RESP +{ + UCHAR CtlFlags; // 00-cmd, 01-rsp, 10-ind + UCHAR TransactionId; + USHORT QMICTLType; + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} __attribute__ ((packed)) QCQMICTL_MSG_HDR_RESP, *PQCQMICTL_MSG_HDR_RESP; + +typedef struct _QCQMICTL_MSG +{ + UCHAR CtlFlags; // 00-cmd, 01-rsp, 10-ind + UCHAR TransactionId; + USHORT QMICTLType; + USHORT Length; + UCHAR Payload; +} __attribute__ ((packed)) QCQMICTL_MSG, *PQCQMICTL_MSG; + +// TLV Header +typedef struct _QCQMICTL_TLV_HDR +{ + UCHAR TLVType; + USHORT TLVLength; +} __attribute__ ((packed)) QCQMICTL_TLV_HDR, *PQCQMICTL_TLV_HDR; + +#define QCQMICTL_TLV_HDR_SIZE sizeof(QCQMICTL_TLV_HDR) + +// QMICTL Type +#define QMICTL_SET_INSTANCE_ID_REQ 0x0020 +#define QMICTL_SET_INSTANCE_ID_RESP 0x0020 +#define QMICTL_GET_VERSION_REQ 0x0021 +#define QMICTL_GET_VERSION_RESP 0x0021 +#define QMICTL_GET_CLIENT_ID_REQ 0x0022 +#define QMICTL_GET_CLIENT_ID_RESP 0x0022 +#define QMICTL_RELEASE_CLIENT_ID_REQ 0x0023 +#define QMICTL_RELEASE_CLIENT_ID_RESP 0x0023 +#define QMICTL_REVOKE_CLIENT_ID_IND 0x0024 +#define QMICTL_INVALID_CLIENT_ID_IND 0x0025 +#define QMICTL_SET_DATA_FORMAT_REQ 0x0026 +#define QMICTL_SET_DATA_FORMAT_RESP 0x0026 +#define QMICTL_SYNC_REQ 0x0027 +#define QMICTL_SYNC_RESP 0x0027 +#define QMICTL_SYNC_IND 0x0027 + +#define QMICTL_FLAG_REQUEST 0x00 +#define QMICTL_FLAG_RESPONSE 0x01 +#define QMICTL_FLAG_INDICATION 0x02 + +// QMICTL Message Definitions + +typedef struct _QMICTL_SET_INSTANCE_ID_REQ_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_REQUEST + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_SET_INSTANCE_ID_REQ + USHORT Length; // 4 + UCHAR TLVType; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLVLength; // 1 + UCHAR Value; // Host-unique QMI instance for this device driver +} __attribute__ ((packed)) QMICTL_SET_INSTANCE_ID_REQ_MSG, *PQMICTL_SET_INSTANCE_ID_REQ_MSG; + +typedef struct _QMICTL_SET_INSTANCE_ID_RESP_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_RESPONSE + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_SET_INSTANCE_ID_RESP + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_RESULT_CODE + USHORT TLVLength; // 0x0004 + USHORT QMIResult; + USHORT QMIError; + UCHAR TLV2Type; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLV2Length; // 0x0002 + USHORT QMI_ID; // Upper byte is assigned by MSM, + // lower assigned by host +} __attribute__ ((packed)) QMICTL_SET_INSTANCE_ID_RESP_MSG, *PQMICTL_SET_INSTANCE_ID_RESP_MSG; + +typedef struct _QMICTL_GET_VERSION_REQ_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_REQUEST + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_GET_VERSION_REQ + USHORT Length; // 0 + UCHAR TLVType; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLVLength; // var + UCHAR QMUXTypes; // List of one byte QMUX_TYPE values + // 0xFF returns a list of versions for all + // QMUX_TYPEs implemented on the device +} __attribute__ ((packed)) QMICTL_GET_VERSION_REQ_MSG, *PQMICTL_GET_VERSION_REQ_MSG; + +typedef struct _QMUX_TYPE_VERSION_STRUCT +{ + UCHAR QMUXType; + USHORT MajorVersion; + USHORT MinorVersion; +} __attribute__ ((packed)) QMUX_TYPE_VERSION_STRUCT, *PQMUX_TYPE_VERSION_STRUCT; + +typedef struct _ADDENDUM_VERSION_PREAMBLE +{ + UCHAR LabelLength; + UCHAR Label; +} __attribute__ ((packed)) ADDENDUM_VERSION_PREAMBLE, *PADDENDUM_VERSION_PREAMBLE; + +#define QMICTL_GET_VERSION_RSP_TLV_TYPE_VERSION 0x01 +#define QMICTL_GET_VERSION_RSP_TLV_TYPE_ADD_VERSION 0x10 + +typedef struct _QMICTL_GET_VERSION_RESP_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_RESPONSE + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_GET_VERSION_RESP + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_RESULT_CODE + USHORT TLVLength; // 0x0004 + USHORT QMIResult; + USHORT QMIError; + UCHAR TLV2Type; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLV2Length; // var + UCHAR NumElements; // Num of QMUX_TYPE_VERSION_STRUCT + QMUX_TYPE_VERSION_STRUCT TypeVersion[0]; +} __attribute__ ((packed)) QMICTL_GET_VERSION_RESP_MSG, *PQMICTL_GET_VERSION_RESP_MSG; + +typedef struct _QMICTL_GET_CLIENT_ID_REQ_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_REQUEST + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_GET_CLIENT_ID_REQ + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLVLength; // 1 + UCHAR QMIType; // QMUX type +} __attribute__ ((packed)) QMICTL_GET_CLIENT_ID_REQ_MSG, *PQMICTL_GET_CLIENT_ID_REQ_MSG; + +typedef struct _QMICTL_GET_CLIENT_ID_RESP_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_RESPONSE + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_GET_CLIENT_ID_RESP + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_RESULT_CODE + USHORT TLVLength; // 0x0004 + USHORT QMIResult; // result code + USHORT QMIError; // error code + UCHAR TLV2Type; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLV2Length; // 2 + UCHAR QMIType; + UCHAR ClientId; +} __attribute__ ((packed)) QMICTL_GET_CLIENT_ID_RESP_MSG, *PQMICTL_GET_CLIENT_ID_RESP_MSG; + +typedef struct _QMICTL_RELEASE_CLIENT_ID_REQ_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_REQUEST + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_RELEASE_CLIENT_ID_REQ + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLVLength; // 0x0002 + UCHAR QMIType; + UCHAR ClientId; +} __attribute__ ((packed)) QMICTL_RELEASE_CLIENT_ID_REQ_MSG, *PQMICTL_RELEASE_CLIENT_ID_REQ_MSG; + +typedef struct _QMICTL_RELEASE_CLIENT_ID_RESP_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_RESPONSE + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_RELEASE_CLIENT_ID_RESP + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_RESULT_CODE + USHORT TLVLength; // 0x0004 + USHORT QMIResult; // result code + USHORT QMIError; // error code + UCHAR TLV2Type; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLV2Length; // 2 + UCHAR QMIType; + UCHAR ClientId; +} __attribute__ ((packed)) QMICTL_RELEASE_CLIENT_ID_RESP_MSG, *PQMICTL_RELEASE_CLIENT_ID_RESP_MSG; + +typedef struct _QMICTL_REVOKE_CLIENT_ID_IND_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_INDICATION + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_REVOKE_CLIENT_ID_IND + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLVLength; // 0x0002 + UCHAR QMIType; + UCHAR ClientId; +} __attribute__ ((packed)) QMICTL_REVOKE_CLIENT_ID_IND_MSG, *PQMICTL_REVOKE_CLIENT_ID_IND_MSG; + +typedef struct _QMICTL_INVALID_CLIENT_ID_IND_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_INDICATION + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_REVOKE_CLIENT_ID_IND + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLVLength; // 0x0002 + UCHAR QMIType; + UCHAR ClientId; +} __attribute__ ((packed)) QMICTL_INVALID_CLIENT_ID_IND_MSG, *PQMICTL_INVALID_CLIENT_ID_IND_MSG; + +typedef struct _QMICTL_SET_DATA_FORMAT_REQ_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_REQUEST + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_SET_DATA_FORMAT_REQ + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLVLength; // 1 + UCHAR DataFormat; // 0-default; 1-QoS hdr present +} __attribute__ ((packed)) QMICTL_SET_DATA_FORMAT_REQ_MSG, *PQMICTL_SET_DATA_FORMAT_REQ_MSG; + +#ifdef QC_IP_MODE +#define SET_DATA_FORMAT_TLV_TYPE_LINK_PROTO 0x10 +#define SET_DATA_FORMAT_LINK_PROTO_ETH 0x0001 +#define SET_DATA_FORMAT_LINK_PROTO_IP 0x0002 +typedef struct _QMICTL_SET_DATA_FORMAT_TLV_LINK_PROT +{ + UCHAR TLVType; // Link-Layer Protocol + USHORT TLVLength; // 2 + USHORT LinkProt; // 0x1: ETH; 0x2: IP +} QMICTL_SET_DATA_FORMAT_TLV_LINK_PROT, *PQMICTL_SET_DATA_FORMAT_TLV_LINK_PROT; + +#ifdef QCMP_UL_TLP +#define SET_DATA_FORMAT_TLV_TYPE_UL_TLP 0x11 +typedef struct _QMICTL_SET_DATA_FORMAT_TLV_UL_TLP +{ + UCHAR TLVType; // 0x11, Uplink TLP Setting + USHORT TLVLength; // 1 + UCHAR UlTlpSetting; // 0x0: Disable; 0x01: Enable +} QMICTL_SET_DATA_FORMAT_TLV_UL_TLP, *PQMICTL_SET_DATA_FORMAT_TLV_UL_TLP; +#endif // QCMP_UL_TLP + +#ifdef QCMP_DL_TLP +#define SET_DATA_FORMAT_TLV_TYPE_DL_TLP 0x13 +typedef struct _QMICTL_SET_DATA_FORMAT_TLV_DL_TLP +{ + UCHAR TLVType; // 0x11, Uplink TLP Setting + USHORT TLVLength; // 1 + UCHAR DlTlpSetting; // 0x0: Disable; 0x01: Enable +} QMICTL_SET_DATA_FORMAT_TLV_DL_TLP, *PQMICTL_SET_DATA_FORMAT_TLV_DL_TLP; +#endif // QCMP_DL_TLP + +#endif // QC_IP_MODE + +#ifdef MP_QCQOS_ENABLED +#define SET_DATA_FORMAT_TLV_TYPE_QOS_SETTING 0x12 +typedef struct _QMICTL_SET_DATA_FORMAT_TLV_QOS_SETTING +{ + UCHAR TLVType; // 0x12, QoS setting + USHORT TLVLength; // 1 + UCHAR QosSetting; // 0x0: Disable; 0x01: Enable +} QMICTL_SET_DATA_FORMAT_TLV_QOS_SETTING, *PQMICTL_SET_DATA_FORMAT_TLV_QOS_SETTING; +#endif // MP_QCQOS_ENABLED + +typedef struct _QMICTL_SET_DATA_FORMAT_RESP_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_RESPONSE + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_SET_DATA_FORMAT_RESP + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_RESULT_CODE + USHORT TLVLength; // 0x0004 + USHORT QMIResult; // result code + USHORT QMIError; // error code +} __attribute__ ((packed)) QMICTL_SET_DATA_FORMAT_RESP_MSG, *PQMICTL_SET_DATA_FORMAT_RESP_MSG; + +typedef struct _QMICTL_SYNC_REQ_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_REQUEST + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_CTL_SYNC_REQ + USHORT Length; // 0 +} __attribute__ ((packed)) QMICTL_SYNC_REQ_MSG, *PQMICTL_SYNC_REQ_MSG; + +typedef struct _QMICTL_SYNC_RESP_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_RESPONSE + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_CTL_SYNC_RESP + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_RESULT_CODE + USHORT TLVLength; // 0x0004 + USHORT QMIResult; + USHORT QMIError; +} __attribute__ ((packed)) QMICTL_SYNC_RESP_MSG, *PQMICTL_SYNC_RESP_MSG; + +typedef struct _QMICTL_SYNC_IND_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_INDICATION + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_REVOKE_CLIENT_ID_IND + USHORT Length; +} __attribute__ ((packed)) QMICTL_SYNC_IND_MSG, *PQMICTL_SYNC_IND_MSG; + +typedef struct _QMICTL_MSG +{ + union + { + // Message Header + QCQMICTL_MSG_HDR QMICTLMsgHdr; + QCQMICTL_MSG_HDR_RESP QMICTLMsgHdrRsp; + + // QMICTL Message + QMICTL_SET_INSTANCE_ID_REQ_MSG SetInstanceIdReq; + QMICTL_SET_INSTANCE_ID_RESP_MSG SetInstanceIdRsp; + QMICTL_GET_VERSION_REQ_MSG GetVersionReq; + QMICTL_GET_VERSION_RESP_MSG GetVersionRsp; + QMICTL_GET_CLIENT_ID_REQ_MSG GetClientIdReq; + QMICTL_GET_CLIENT_ID_RESP_MSG GetClientIdRsp; + QMICTL_RELEASE_CLIENT_ID_REQ_MSG ReleaseClientIdReq; + QMICTL_RELEASE_CLIENT_ID_RESP_MSG ReleaseClientIdRsp; + QMICTL_REVOKE_CLIENT_ID_IND_MSG RevokeClientIdInd; + QMICTL_INVALID_CLIENT_ID_IND_MSG InvalidClientIdInd; + QMICTL_SET_DATA_FORMAT_REQ_MSG SetDataFormatReq; + QMICTL_SET_DATA_FORMAT_RESP_MSG SetDataFormatRsp; + QMICTL_SYNC_REQ_MSG SyncReq; + QMICTL_SYNC_RESP_MSG SyncRsp; + QMICTL_SYNC_IND_MSG SyncInd; + }; +} __attribute__ ((packed)) QMICTL_MSG, *PQMICTL_MSG; +#pragma pack(pop) + +#endif // MPQCTL_H diff --git a/root/package/link4all/quectel-CM/quectel1.6.0.15_src/MPQMI.h b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/MPQMI.h new file mode 100644 index 00000000..0092a47d --- /dev/null +++ b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/MPQMI.h @@ -0,0 +1,302 @@ +/*=========================================================================== + + M P Q M I. H +DESCRIPTION: + + This module contains forward references to the QMI module. + +INITIALIZATION AND SEQUENCING REQUIREMENTS: + +Copyright (C) 2011 by Qualcomm Technologies, Incorporated. All Rights Reserved. +===========================================================================*/ +/*=========================================================================== + + EDIT HISTORY FOR FILE + $Header: //depot/QMI/win/qcdrivers/ndis/MPQMI.h#3 $ + +when who what, where, why +-------- --- ---------------------------------------------------------- +11/20/04 hg Initial version. +===========================================================================*/ + +#ifndef USBQMI_H +#define USBQMI_H + +typedef char CHAR; +typedef unsigned char UCHAR; +typedef short SHORT; +typedef unsigned short USHORT; +typedef int INT; +typedef unsigned int UINT; +typedef long LONG; +typedef unsigned int ULONG; +typedef unsigned long long ULONG64; +typedef char *PCHAR; +typedef unsigned char *PUCHAR; +typedef int *PINT; +typedef int BOOL; + +#define TRUE (1 == 1) +#define FALSE (1 != 1) + +#define QMICTL_SUPPORTED_MAJOR_VERSION 1 +#define QMICTL_SUPPORTED_MINOR_VERSION 0 + +#pragma pack(push, 1) + +// ========= USB Control Message ========== + +#define USB_CTL_MSG_TYPE_QMI 0x01 + +// USB Control Message +typedef struct _QCUSB_CTL_MSG_HDR +{ + UCHAR IFType; +} __attribute__ ((packed)) QCUSB_CTL_MSG_HDR, *PQCUSB_CTL_MSG_HDR; + +#define QCUSB_CTL_MSG_HDR_SIZE sizeof(QCUSB_CTL_MSG_HDR) + +typedef struct _QCUSB_CTL_MSG +{ + UCHAR IFType; + UCHAR Message; +} __attribute__ ((packed)) QCUSB_CTL_MSG, *PQCUSB_CTL_MSG; + +#define QCTLV_TYPE_REQUIRED_PARAMETER 0x01 +#define QCTLV_TYPE_RESULT_CODE 0x02 + +// ================= QMI ================== + +// Define QMI Type +typedef enum _QMI_SERVICE_TYPE +{ + QMUX_TYPE_CTL = 0x00, + QMUX_TYPE_WDS = 0x01, + QMUX_TYPE_DMS = 0x02, + QMUX_TYPE_NAS = 0x03, + QMUX_TYPE_QOS = 0x04, + QMUX_TYPE_WMS = 0x05, + QMUX_TYPE_PDS = 0x06, + QMUX_TYPE_UIM = 0x0B, + QMUX_TYPE_WDS_IPV6 = 0x11, + QMUX_TYPE_WDS_ADMIN = 0x1A, + QMUX_TYPE_MAX = 0xFF, + QMUX_TYPE_ALL = 0xFF +} QMI_SERVICE_TYPE; + +typedef enum _QMI_RESULT_CODE_TYPE +{ + QMI_RESULT_SUCCESS = 0x0000, + QMI_RESULT_FAILURE = 0x0001 +} QMI_RESULT_CODE_TYPE; + +typedef enum _QMI_ERROR_CODE_TYPE +{ + QMI_ERR_NONE = 0x0000 + ,QMI_ERR_MALFORMED_MSG = 0x0001 + ,QMI_ERR_NO_MEMORY = 0x0002 + ,QMI_ERR_INTERNAL = 0x0003 + ,QMI_ERR_ABORTED = 0x0004 + ,QMI_ERR_CLIENT_IDS_EXHAUSTED = 0x0005 + ,QMI_ERR_UNABORTABLE_TRANSACTION = 0x0006 + ,QMI_ERR_INVALID_CLIENT_ID = 0x0007 + ,QMI_ERR_NO_THRESHOLDS = 0x0008 + ,QMI_ERR_INVALID_HANDLE = 0x0009 + ,QMI_ERR_INVALID_PROFILE = 0x000A + ,QMI_ERR_INVALID_PINID = 0x000B + ,QMI_ERR_INCORRECT_PIN = 0x000C + ,QMI_ERR_NO_NETWORK_FOUND = 0x000D + ,QMI_ERR_CALL_FAILED = 0x000E + ,QMI_ERR_OUT_OF_CALL = 0x000F + ,QMI_ERR_NOT_PROVISIONED = 0x0010 + ,QMI_ERR_MISSING_ARG = 0x0011 + ,QMI_ERR_ARG_TOO_LONG = 0x0013 + ,QMI_ERR_INVALID_TX_ID = 0x0016 + ,QMI_ERR_DEVICE_IN_USE = 0x0017 + ,QMI_ERR_OP_NETWORK_UNSUPPORTED = 0x0018 + ,QMI_ERR_OP_DEVICE_UNSUPPORTED = 0x0019 + ,QMI_ERR_NO_EFFECT = 0x001A + ,QMI_ERR_NO_FREE_PROFILE = 0x001B + ,QMI_ERR_INVALID_PDP_TYPE = 0x001C + ,QMI_ERR_INVALID_TECH_PREF = 0x001D + ,QMI_ERR_INVALID_PROFILE_TYPE = 0x001E + ,QMI_ERR_INVALID_SERVICE_TYPE = 0x001F + ,QMI_ERR_INVALID_REGISTER_ACTION = 0x0020 + ,QMI_ERR_INVALID_PS_ATTACH_ACTION = 0x0021 + ,QMI_ERR_AUTHENTICATION_FAILED = 0x0022 + ,QMI_ERR_PIN_BLOCKED = 0x0023 + ,QMI_ERR_PIN_PERM_BLOCKED = 0x0024 + ,QMI_ERR_SIM_NOT_INITIALIZED = 0x0025 + ,QMI_ERR_MAX_QOS_REQUESTS_IN_USE = 0x0026 + ,QMI_ERR_INCORRECT_FLOW_FILTER = 0x0027 + ,QMI_ERR_NETWORK_QOS_UNAWARE = 0x0028 + ,QMI_ERR_INVALID_QOS_ID = 0x0029 + ,QMI_ERR_INVALID_ID = 0x0029 + ,QMI_ERR_REQUESTED_NUM_UNSUPPORTED = 0x002A + ,QMI_ERR_INTERFACE_NOT_FOUND = 0x002B + ,QMI_ERR_FLOW_SUSPENDED = 0x002C + ,QMI_ERR_INVALID_DATA_FORMAT = 0x002D + ,QMI_ERR_GENERAL = 0x002E + ,QMI_ERR_UNKNOWN = 0x002F + ,QMI_ERR_INVALID_ARG = 0x0030 + ,QMI_ERR_INVALID_INDEX = 0x0031 + ,QMI_ERR_NO_ENTRY = 0x0032 + ,QMI_ERR_DEVICE_STORAGE_FULL = 0x0033 + ,QMI_ERR_DEVICE_NOT_READY = 0x0034 + ,QMI_ERR_NETWORK_NOT_READY = 0x0035 + ,QMI_ERR_CAUSE_CODE = 0x0036 + ,QMI_ERR_MESSAGE_NOT_SENT = 0x0037 + ,QMI_ERR_MESSAGE_DELIVERY_FAILURE = 0x0038 + ,QMI_ERR_INVALID_MESSAGE_ID = 0x0039 + ,QMI_ERR_ENCODING = 0x003A + ,QMI_ERR_AUTHENTICATION_LOCK = 0x003B + ,QMI_ERR_INVALID_TRANSITION = 0x003C + ,QMI_ERR_NOT_A_MCAST_IFACE = 0x003D + ,QMI_ERR_MAX_MCAST_REQUESTS_IN_USE = 0x003E + ,QMI_ERR_INVALID_MCAST_HANDLE = 0x003F + ,QMI_ERR_INVALID_IP_FAMILY_PREF = 0x0040 + ,QMI_ERR_SESSION_INACTIVE = 0x0041 + ,QMI_ERR_SESSION_INVALID = 0x0042 + ,QMI_ERR_SESSION_OWNERSHIP = 0x0043 + ,QMI_ERR_INSUFFICIENT_RESOURCES = 0x0044 + ,QMI_ERR_DISABLED = 0x0045 + ,QMI_ERR_INVALID_OPERATION = 0x0046 + ,QMI_ERR_INVALID_QMI_CMD = 0x0047 + ,QMI_ERR_TPDU_TYPE = 0x0048 + ,QMI_ERR_SMSC_ADDR = 0x0049 + ,QMI_ERR_INFO_UNAVAILABLE = 0x004A + ,QMI_ERR_SEGMENT_TOO_LONG = 0x004B + ,QMI_ERR_SEGMENT_ORDER = 0x004C + ,QMI_ERR_BUNDLING_NOT_SUPPORTED = 0x004D + ,QMI_ERR_OP_PARTIAL_FAILURE = 0x004E + ,QMI_ERR_POLICY_MISMATCH = 0x004F + ,QMI_ERR_SIM_FILE_NOT_FOUND = 0x0050 + ,QMI_ERR_EXTENDED_INTERNAL = 0x0051 + ,QMI_ERR_ACCESS_DENIED = 0x0052 + ,QMI_ERR_HARDWARE_RESTRICTED = 0x0053 + ,QMI_ERR_ACK_NOT_SENT = 0x0054 + ,QMI_ERR_INJECT_TIMEOUT = 0x0055 + ,QMI_ERR_INCOMPATIBLE_STATE = 0x005A + ,QMI_ERR_FDN_RESTRICT = 0x005B + ,QMI_ERR_SUPS_FAILURE_CAUSE = 0x005C + ,QMI_ERR_NO_RADIO = 0x005D + ,QMI_ERR_NOT_SUPPORTED = 0x005E + ,QMI_ERR_NO_SUBSCRIPTION = 0x005F + ,QMI_ERR_CARD_CALL_CONTROL_FAILED = 0x0060 + ,QMI_ERR_NETWORK_ABORTED = 0x0061 + ,QMI_ERR_MSG_BLOCKED = 0x0062 + ,QMI_ERR_INVALID_SESSION_TYPE = 0x0064 + ,QMI_ERR_INVALID_PB_TYPE = 0x0065 + ,QMI_ERR_NO_SIM = 0x0066 + ,QMI_ERR_PB_NOT_READY = 0x0067 + ,QMI_ERR_PIN_RESTRICTION = 0x0068 + ,QMI_ERR_PIN2_RESTRICTION = 0x0069 + ,QMI_ERR_PUK_RESTRICTION = 0x006A + ,QMI_ERR_PUK2_RESTRICTION = 0x006B + ,QMI_ERR_PB_ACCESS_RESTRICTED = 0x006C + ,QMI_ERR_PB_DELETE_IN_PROG = 0x006D + ,QMI_ERR_PB_TEXT_TOO_LONG = 0x006E + ,QMI_ERR_PB_NUMBER_TOO_LONG = 0x006F + ,QMI_ERR_PB_HIDDEN_KEY_RESTRICTION = 0x0070 +} QMI_ERROR_CODE_TYPE; + +#define QCQMI_CTL_FLAG_SERVICE 0x80 +#define QCQMI_CTL_FLAG_CTL_POINT 0x00 + +typedef struct _QCQMI_HDR +{ + UCHAR IFType; + USHORT Length; + UCHAR CtlFlags; // reserved + UCHAR QMIType; + UCHAR ClientId; +} __attribute__ ((packed)) QCQMI_HDR, *PQCQMI_HDR; + +#define QCQMI_HDR_SIZE (sizeof(QCQMI_HDR)-1) + +typedef struct _QCQMI +{ + UCHAR IFType; + USHORT Length; + UCHAR CtlFlags; // reserved + UCHAR QMIType; + UCHAR ClientId; + UCHAR SDU; +} __attribute__ ((packed)) QCQMI, *PQCQMI; + +typedef struct _QMI_SERVICE_VERSION +{ + USHORT Major; + USHORT Minor; + USHORT AddendumMajor; + USHORT AddendumMinor; +} __attribute__ ((packed)) QMI_SERVICE_VERSION, *PQMI_SERVICE_VERSION; + +// ================= QMUX ================== + +#define QMUX_MSG_OVERHEAD_BYTES 4 // Type(USHORT) Length(USHORT) -- header + +#define QMUX_BROADCAST_CID 0xFF + +typedef struct _QCQMUX_HDR +{ + UCHAR CtlFlags; // 0: single QMUX Msg; 1: + USHORT TransactionId; +} __attribute__ ((packed)) QCQMUX_HDR, *PQCQMUX_HDR; + +typedef struct _QCQMUX +{ + UCHAR CtlFlags; // 0: single QMUX Msg; 1: + USHORT TransactionId; + UCHAR Message; // Type(2), Length(2), Value +} __attribute__ ((packed)) QCQMUX, *PQCQMUX; + +#define QCQMUX_HDR_SIZE sizeof(QCQMUX_HDR) + +typedef struct _QCQMUX_MSG_HDR +{ + USHORT Type; + USHORT Length; +} __attribute__ ((packed)) QCQMUX_MSG_HDR, *PQCQMUX_MSG_HDR; + +#define QCQMUX_MSG_HDR_SIZE sizeof(QCQMUX_MSG_HDR) + +typedef struct _QCQMUX_MSG_HDR_RESP +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} __attribute__ ((packed)) QCQMUX_MSG_HDR_RESP, *PQCQMUX_MSG_HDR_RESP; + +typedef struct _QCQMUX_TLV +{ + UCHAR Type; + USHORT Length; + UCHAR Value; +} __attribute__ ((packed)) QCQMUX_TLV, *PQCQMUX_TLV; + +typedef struct _QMI_TLV_HDR +{ + UCHAR TLVType; + USHORT TLVLength; +} __attribute__ ((packed)) QMI_TLV_HDR, *PQMI_TLV_HDR; + +// QMUX Message Definitions -- QMI SDU +#define QMUX_CTL_FLAG_SINGLE_MSG 0x00 +#define QMUX_CTL_FLAG_COMPOUND_MSG 0x01 +#define QMUX_CTL_FLAG_TYPE_CMD 0x00 +#define QMUX_CTL_FLAG_TYPE_RSP 0x02 +#define QMUX_CTL_FLAG_TYPE_IND 0x04 +#define QMUX_CTL_FLAG_MASK_COMPOUND 0x01 +#define QMUX_CTL_FLAG_MASK_TYPE 0x06 // 00-cmd, 01-rsp, 10-ind + +#pragma pack(pop) + +#endif // USBQMI_H diff --git a/root/package/link4all/quectel-CM/quectel1.6.0.15_src/MPQMUX.c b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/MPQMUX.c new file mode 100644 index 00000000..b12922dc --- /dev/null +++ b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/MPQMUX.c @@ -0,0 +1,446 @@ +/****************************************************************************** + @file MPQMUX.c + @brief QMI mux. + + DESCRIPTION + Connectivity Management Tool for USB network adapter of Quectel wireless cellular modules. + + INITIALIZATION AND SEQUENCING REQUIREMENTS + None. + + --------------------------------------------------------------------------- + Copyright (c) 2016 - 2020 Quectel Wireless Solution, Co., Ltd. All Rights Reserved. + Quectel Wireless Solution Proprietary and Confidential. + --------------------------------------------------------------------------- +******************************************************************************/ + +#include "QMIThread.h" +static char line[1024]; +static pthread_mutex_t dumpQMIMutex = PTHREAD_MUTEX_INITIALIZER; +#undef dbg +#define dbg( format, arg... ) do {if (strlen(line) < sizeof(line)) snprintf(&line[strlen(line)], sizeof(line) - strlen(line), format, ## arg);} while (0) + +PQMI_TLV_HDR GetTLV (PQCQMUX_MSG_HDR pQMUXMsgHdr, int TLVType); + +typedef struct { + UINT type; + const char *name; +} QMI_NAME_T; + +#define qmi_name_item(type) {type, #type} + +#if 0 +static const QMI_NAME_T qmi_IFType[] = { +{USB_CTL_MSG_TYPE_QMI, "USB_CTL_MSG_TYPE_QMI"}, +}; + +static const QMI_NAME_T qmi_CtlFlags[] = { +qmi_name_item(QMICTL_CTL_FLAG_CMD), +qmi_name_item(QCQMI_CTL_FLAG_SERVICE), +}; + +static const QMI_NAME_T qmi_QMIType[] = { +qmi_name_item(QMUX_TYPE_CTL), +qmi_name_item(QMUX_TYPE_WDS), +qmi_name_item(QMUX_TYPE_DMS), +qmi_name_item(QMUX_TYPE_NAS), +qmi_name_item(QMUX_TYPE_QOS), +qmi_name_item(QMUX_TYPE_WMS), +qmi_name_item(QMUX_TYPE_PDS), +qmi_name_item(QMUX_TYPE_WDS_ADMIN), +}; + +static const QMI_NAME_T qmi_ctl_CtlFlags[] = { +qmi_name_item(QMICTL_FLAG_REQUEST), +qmi_name_item(QMICTL_FLAG_RESPONSE), +qmi_name_item(QMICTL_FLAG_INDICATION), +}; +#endif + +static const QMI_NAME_T qmux_ctl_QMICTLType[] = { +// QMICTL Type +qmi_name_item(QMICTL_SET_INSTANCE_ID_REQ), // 0x0020 +qmi_name_item(QMICTL_SET_INSTANCE_ID_RESP), // 0x0020 +qmi_name_item(QMICTL_GET_VERSION_REQ), // 0x0021 +qmi_name_item(QMICTL_GET_VERSION_RESP), // 0x0021 +qmi_name_item(QMICTL_GET_CLIENT_ID_REQ), // 0x0022 +qmi_name_item(QMICTL_GET_CLIENT_ID_RESP), // 0x0022 +qmi_name_item(QMICTL_RELEASE_CLIENT_ID_REQ), // 0x0023 +qmi_name_item(QMICTL_RELEASE_CLIENT_ID_RESP), // 0x0023 +qmi_name_item(QMICTL_REVOKE_CLIENT_ID_IND), // 0x0024 +qmi_name_item(QMICTL_INVALID_CLIENT_ID_IND), // 0x0025 +qmi_name_item(QMICTL_SET_DATA_FORMAT_REQ), // 0x0026 +qmi_name_item(QMICTL_SET_DATA_FORMAT_RESP), // 0x0026 +qmi_name_item(QMICTL_SYNC_REQ), // 0x0027 +qmi_name_item(QMICTL_SYNC_RESP), // 0x0027 +qmi_name_item(QMICTL_SYNC_IND), // 0x0027 +}; + +static const QMI_NAME_T qmux_CtlFlags[] = { +qmi_name_item(QMUX_CTL_FLAG_TYPE_CMD), +qmi_name_item(QMUX_CTL_FLAG_TYPE_RSP), +qmi_name_item(QMUX_CTL_FLAG_TYPE_IND), +}; + + +static const QMI_NAME_T qmux_wds_Type[] = { +qmi_name_item(QMIWDS_SET_EVENT_REPORT_REQ), // 0x0001 +qmi_name_item(QMIWDS_SET_EVENT_REPORT_RESP), // 0x0001 +qmi_name_item(QMIWDS_EVENT_REPORT_IND), // 0x0001 +qmi_name_item(QMIWDS_START_NETWORK_INTERFACE_REQ), // 0x0020 +qmi_name_item(QMIWDS_START_NETWORK_INTERFACE_RESP), // 0x0020 +qmi_name_item(QMIWDS_STOP_NETWORK_INTERFACE_REQ), // 0x0021 +qmi_name_item(QMIWDS_STOP_NETWORK_INTERFACE_RESP), // 0x0021 +qmi_name_item(QMIWDS_GET_PKT_SRVC_STATUS_REQ), // 0x0022 +qmi_name_item(QMIWDS_GET_PKT_SRVC_STATUS_RESP), // 0x0022 +qmi_name_item(QMIWDS_GET_PKT_SRVC_STATUS_IND), // 0x0022 +qmi_name_item(QMIWDS_GET_CURRENT_CHANNEL_RATE_REQ), // 0x0023 +qmi_name_item(QMIWDS_GET_CURRENT_CHANNEL_RATE_RESP), // 0x0023 +qmi_name_item(QMIWDS_GET_PKT_STATISTICS_REQ), // 0x0024 +qmi_name_item(QMIWDS_GET_PKT_STATISTICS_RESP), // 0x0024 +qmi_name_item(QMIWDS_MODIFY_PROFILE_SETTINGS_REQ), // 0x0028 +qmi_name_item(QMIWDS_MODIFY_PROFILE_SETTINGS_RESP), // 0x0028 +qmi_name_item(QMIWDS_GET_PROFILE_SETTINGS_REQ), // 0x002B +qmi_name_item(QMIWDS_GET_PROFILE_SETTINGS_RESP), // 0x002BD +qmi_name_item(QMIWDS_GET_DEFAULT_SETTINGS_REQ), // 0x002C +qmi_name_item(QMIWDS_GET_DEFAULT_SETTINGS_RESP), // 0x002C +qmi_name_item(QMIWDS_GET_RUNTIME_SETTINGS_REQ), // 0x002D +qmi_name_item(QMIWDS_GET_RUNTIME_SETTINGS_RESP), // 0x002D +qmi_name_item(QMIWDS_GET_MIP_MODE_REQ), // 0x002F +qmi_name_item(QMIWDS_GET_MIP_MODE_RESP), // 0x002F +qmi_name_item(QMIWDS_GET_DATA_BEARER_REQ), // 0x0037 +qmi_name_item(QMIWDS_GET_DATA_BEARER_RESP), // 0x0037 +qmi_name_item(QMIWDS_DUN_CALL_INFO_REQ), // 0x0038 +qmi_name_item(QMIWDS_DUN_CALL_INFO_RESP), // 0x0038 +qmi_name_item(QMIWDS_DUN_CALL_INFO_IND), // 0x0038 +qmi_name_item(QMIWDS_SET_CLIENT_IP_FAMILY_PREF_REQ), // 0x004D +qmi_name_item(QMIWDS_SET_CLIENT_IP_FAMILY_PREF_RESP), // 0x004D +qmi_name_item(QMIWDS_SET_AUTO_CONNECT_REQ), // 0x0051 +qmi_name_item(QMIWDS_SET_AUTO_CONNECT_RESP), // 0x0051 +qmi_name_item(QMIWDS_BIND_MUX_DATA_PORT_REQ), // 0x00A2 +qmi_name_item(QMIWDS_BIND_MUX_DATA_PORT_RESP), // 0x00A2 +}; + +static const QMI_NAME_T qmux_dms_Type[] = { +// ======================= DMS ============================== +qmi_name_item(QMIDMS_SET_EVENT_REPORT_REQ), // 0x0001 +qmi_name_item(QMIDMS_SET_EVENT_REPORT_RESP), // 0x0001 +qmi_name_item(QMIDMS_EVENT_REPORT_IND), // 0x0001 +qmi_name_item(QMIDMS_GET_DEVICE_CAP_REQ), // 0x0020 +qmi_name_item(QMIDMS_GET_DEVICE_CAP_RESP), // 0x0020 +qmi_name_item(QMIDMS_GET_DEVICE_MFR_REQ), // 0x0021 +qmi_name_item(QMIDMS_GET_DEVICE_MFR_RESP), // 0x0021 +qmi_name_item(QMIDMS_GET_DEVICE_MODEL_ID_REQ), // 0x0022 +qmi_name_item(QMIDMS_GET_DEVICE_MODEL_ID_RESP), // 0x0022 +qmi_name_item(QMIDMS_GET_DEVICE_REV_ID_REQ), // 0x0023 +qmi_name_item(QMIDMS_GET_DEVICE_REV_ID_RESP), // 0x0023 +qmi_name_item(QMIDMS_GET_MSISDN_REQ), // 0x0024 +qmi_name_item(QMIDMS_GET_MSISDN_RESP), // 0x0024 +qmi_name_item(QMIDMS_GET_DEVICE_SERIAL_NUMBERS_REQ), // 0x0025 +qmi_name_item(QMIDMS_GET_DEVICE_SERIAL_NUMBERS_RESP), // 0x0025 +qmi_name_item(QMIDMS_UIM_SET_PIN_PROTECTION_REQ), // 0x0027 +qmi_name_item(QMIDMS_UIM_SET_PIN_PROTECTION_RESP), // 0x0027 +qmi_name_item(QMIDMS_UIM_VERIFY_PIN_REQ), // 0x0028 +qmi_name_item(QMIDMS_UIM_VERIFY_PIN_RESP), // 0x0028 +qmi_name_item(QMIDMS_UIM_UNBLOCK_PIN_REQ), // 0x0029 +qmi_name_item(QMIDMS_UIM_UNBLOCK_PIN_RESP), // 0x0029 +qmi_name_item(QMIDMS_UIM_CHANGE_PIN_REQ), // 0x002A +qmi_name_item(QMIDMS_UIM_CHANGE_PIN_RESP), // 0x002A +qmi_name_item(QMIDMS_UIM_GET_PIN_STATUS_REQ), // 0x002B +qmi_name_item(QMIDMS_UIM_GET_PIN_STATUS_RESP), // 0x002B +qmi_name_item(QMIDMS_GET_DEVICE_HARDWARE_REV_REQ), // 0x002C +qmi_name_item(QMIDMS_GET_DEVICE_HARDWARE_REV_RESP), // 0x002C +qmi_name_item(QMIDMS_GET_OPERATING_MODE_REQ), // 0x002D +qmi_name_item(QMIDMS_GET_OPERATING_MODE_RESP), // 0x002D +qmi_name_item(QMIDMS_SET_OPERATING_MODE_REQ), // 0x002E +qmi_name_item(QMIDMS_SET_OPERATING_MODE_RESP), // 0x002E +qmi_name_item(QMIDMS_GET_ACTIVATED_STATUS_REQ), // 0x0031 +qmi_name_item(QMIDMS_GET_ACTIVATED_STATUS_RESP), // 0x0031 +qmi_name_item(QMIDMS_ACTIVATE_AUTOMATIC_REQ), // 0x0032 +qmi_name_item(QMIDMS_ACTIVATE_AUTOMATIC_RESP), // 0x0032 +qmi_name_item(QMIDMS_ACTIVATE_MANUAL_REQ), // 0x0033 +qmi_name_item(QMIDMS_ACTIVATE_MANUAL_RESP), // 0x0033 +qmi_name_item(QMIDMS_UIM_GET_ICCID_REQ), // 0x003C +qmi_name_item(QMIDMS_UIM_GET_ICCID_RESP), // 0x003C +qmi_name_item(QMIDMS_UIM_GET_CK_STATUS_REQ), // 0x0040 +qmi_name_item(QMIDMS_UIM_GET_CK_STATUS_RESP), // 0x0040 +qmi_name_item(QMIDMS_UIM_SET_CK_PROTECTION_REQ), // 0x0041 +qmi_name_item(QMIDMS_UIM_SET_CK_PROTECTION_RESP), // 0x0041 +qmi_name_item(QMIDMS_UIM_UNBLOCK_CK_REQ), // 0x0042 +qmi_name_item(QMIDMS_UIM_UNBLOCK_CK_RESP), // 0x0042 +qmi_name_item(QMIDMS_UIM_GET_IMSI_REQ), // 0x0043 +qmi_name_item(QMIDMS_UIM_GET_IMSI_RESP), // 0x0043 +qmi_name_item(QMIDMS_UIM_GET_STATE_REQ), // 0x0044 +qmi_name_item(QMIDMS_UIM_GET_STATE_RESP), // 0x0044 +qmi_name_item(QMIDMS_GET_BAND_CAP_REQ), // 0x0045 +qmi_name_item(QMIDMS_GET_BAND_CAP_RESP), // 0x0045 +}; + +static const QMI_NAME_T qmux_nas_Type[] = { +// ======================= NAS ============================== +qmi_name_item(QMINAS_SET_EVENT_REPORT_REQ), // 0x0002 +qmi_name_item(QMINAS_SET_EVENT_REPORT_RESP), // 0x0002 +qmi_name_item(QMINAS_EVENT_REPORT_IND), // 0x0002 +qmi_name_item(QMINAS_GET_SIGNAL_STRENGTH_REQ), // 0x0020 +qmi_name_item(QMINAS_GET_SIGNAL_STRENGTH_RESP), // 0x0020 +qmi_name_item(QMINAS_PERFORM_NETWORK_SCAN_REQ), // 0x0021 +qmi_name_item(QMINAS_PERFORM_NETWORK_SCAN_RESP), // 0x0021 +qmi_name_item(QMINAS_INITIATE_NW_REGISTER_REQ), // 0x0022 +qmi_name_item(QMINAS_INITIATE_NW_REGISTER_RESP), // 0x0022 +qmi_name_item(QMINAS_INITIATE_ATTACH_REQ), // 0x0023 +qmi_name_item(QMINAS_INITIATE_ATTACH_RESP), // 0x0023 +qmi_name_item(QMINAS_GET_SERVING_SYSTEM_REQ), // 0x0024 +qmi_name_item(QMINAS_GET_SERVING_SYSTEM_RESP), // 0x0024 +qmi_name_item(QMINAS_SERVING_SYSTEM_IND), // 0x0024 +qmi_name_item(QMINAS_GET_HOME_NETWORK_REQ), // 0x0025 +qmi_name_item(QMINAS_GET_HOME_NETWORK_RESP), // 0x0025 +qmi_name_item(QMINAS_GET_PREFERRED_NETWORK_REQ), // 0x0026 +qmi_name_item(QMINAS_GET_PREFERRED_NETWORK_RESP), // 0x0026 +qmi_name_item(QMINAS_SET_PREFERRED_NETWORK_REQ), // 0x0027 +qmi_name_item(QMINAS_SET_PREFERRED_NETWORK_RESP), // 0x0027 +qmi_name_item(QMINAS_GET_FORBIDDEN_NETWORK_REQ), // 0x0028 +qmi_name_item(QMINAS_GET_FORBIDDEN_NETWORK_RESP), // 0x0028 +qmi_name_item(QMINAS_SET_FORBIDDEN_NETWORK_REQ), // 0x0029 +qmi_name_item(QMINAS_SET_FORBIDDEN_NETWORK_RESP), // 0x0029 +qmi_name_item(QMINAS_SET_TECHNOLOGY_PREF_REQ), // 0x002A +qmi_name_item(QMINAS_SET_TECHNOLOGY_PREF_RESP), // 0x002A +qmi_name_item(QMINAS_GET_RF_BAND_INFO_REQ), // 0x0031 +qmi_name_item(QMINAS_GET_RF_BAND_INFO_RESP), // 0x0031 +qmi_name_item(QMINAS_GET_PLMN_NAME_REQ), // 0x0044 +qmi_name_item(QMINAS_GET_PLMN_NAME_RESP), // 0x0044 +qmi_name_item(QUECTEL_PACKET_TRANSFER_START_IND), // 0X100 +qmi_name_item(QUECTEL_PACKET_TRANSFER_END_IND), // 0X101 +qmi_name_item(QMINAS_GET_SYS_INFO_REQ), // 0x004D +qmi_name_item(QMINAS_GET_SYS_INFO_RESP), // 0x004D +qmi_name_item(QMINAS_SYS_INFO_IND), // 0x004D +}; + +static const QMI_NAME_T qmux_wms_Type[] = { +// ======================= WMS ============================== +qmi_name_item(QMIWMS_SET_EVENT_REPORT_REQ), // 0x0001 +qmi_name_item(QMIWMS_SET_EVENT_REPORT_RESP), // 0x0001 +qmi_name_item(QMIWMS_EVENT_REPORT_IND), // 0x0001 +qmi_name_item(QMIWMS_RAW_SEND_REQ), // 0x0020 +qmi_name_item(QMIWMS_RAW_SEND_RESP), // 0x0020 +qmi_name_item(QMIWMS_RAW_WRITE_REQ), // 0x0021 +qmi_name_item(QMIWMS_RAW_WRITE_RESP), // 0x0021 +qmi_name_item(QMIWMS_RAW_READ_REQ), // 0x0022 +qmi_name_item(QMIWMS_RAW_READ_RESP), // 0x0022 +qmi_name_item(QMIWMS_MODIFY_TAG_REQ), // 0x0023 +qmi_name_item(QMIWMS_MODIFY_TAG_RESP), // 0x0023 +qmi_name_item(QMIWMS_DELETE_REQ), // 0x0024 +qmi_name_item(QMIWMS_DELETE_RESP), // 0x0024 +qmi_name_item(QMIWMS_GET_MESSAGE_PROTOCOL_REQ), // 0x0030 +qmi_name_item(QMIWMS_GET_MESSAGE_PROTOCOL_RESP), // 0x0030 +qmi_name_item(QMIWMS_LIST_MESSAGES_REQ), // 0x0031 +qmi_name_item(QMIWMS_LIST_MESSAGES_RESP), // 0x0031 +qmi_name_item(QMIWMS_GET_SMSC_ADDRESS_REQ), // 0x0034 +qmi_name_item(QMIWMS_GET_SMSC_ADDRESS_RESP), // 0x0034 +qmi_name_item(QMIWMS_SET_SMSC_ADDRESS_REQ), // 0x0035 +qmi_name_item(QMIWMS_SET_SMSC_ADDRESS_RESP), // 0x0035 +qmi_name_item(QMIWMS_GET_STORE_MAX_SIZE_REQ), // 0x0036 +qmi_name_item(QMIWMS_GET_STORE_MAX_SIZE_RESP), // 0x0036 +}; + +static const QMI_NAME_T qmux_wds_admin_Type[] = { +qmi_name_item(QMIWDS_ADMIN_SET_DATA_FORMAT_REQ), // 0x0020 +qmi_name_item(QMIWDS_ADMIN_SET_DATA_FORMAT_RESP), // 0x0020 +qmi_name_item(QMIWDS_ADMIN_GET_DATA_FORMAT_REQ), // 0x0021 +qmi_name_item(QMIWDS_ADMIN_GET_DATA_FORMAT_RESP), // 0x0021 +qmi_name_item(QMIWDS_ADMIN_SET_QMAP_SETTINGS_REQ), // 0x002B +qmi_name_item(QMIWDS_ADMIN_SET_QMAP_SETTINGS_RESP), // 0x002B +qmi_name_item(QMIWDS_ADMIN_GET_QMAP_SETTINGS_REQ), // 0x002C +qmi_name_item(QMIWDS_ADMIN_GET_QMAP_SETTINGS_RESP), // 0x002C +qmi_name_item(QMI_WDA_SET_LOOPBACK_CONFIG_REQ), // 0x002F +qmi_name_item(QMI_WDA_SET_LOOPBACK_CONFIG_RESP), // 0x002F +qmi_name_item(QMI_WDA_SET_LOOPBACK_CONFIG_IND), // 0x002F +}; + +static const QMI_NAME_T qmux_uim_Type[] = { +qmi_name_item( QMIUIM_READ_TRANSPARENT_REQ), // 0x0020 +qmi_name_item( QMIUIM_READ_TRANSPARENT_RESP), // 0x0020 +qmi_name_item( QMIUIM_READ_TRANSPARENT_IND), // 0x0020 +qmi_name_item( QMIUIM_READ_RECORD_REQ), // 0x0021 +qmi_name_item( QMIUIM_READ_RECORD_RESP), // 0x0021 +qmi_name_item( QMIUIM_READ_RECORD_IND), // 0x0021 +qmi_name_item( QMIUIM_WRITE_TRANSPARENT_REQ), // 0x0022 +qmi_name_item( QMIUIM_WRITE_TRANSPARENT_RESP), // 0x0022 +qmi_name_item( QMIUIM_WRITE_TRANSPARENT_IND), // 0x0022 +qmi_name_item( QMIUIM_WRITE_RECORD_REQ), // 0x0023 +qmi_name_item( QMIUIM_WRITE_RECORD_RESP), // 0x0023 +qmi_name_item( QMIUIM_WRITE_RECORD_IND), // 0x0023 +qmi_name_item( QMIUIM_SET_PIN_PROTECTION_REQ), // 0x0025 +qmi_name_item( QMIUIM_SET_PIN_PROTECTION_RESP), // 0x0025 +qmi_name_item( QMIUIM_SET_PIN_PROTECTION_IND), // 0x0025 +qmi_name_item( QMIUIM_VERIFY_PIN_REQ), // 0x0026 +qmi_name_item( QMIUIM_VERIFY_PIN_RESP), // 0x0026 +qmi_name_item( QMIUIM_VERIFY_PIN_IND), // 0x0026 +qmi_name_item( QMIUIM_UNBLOCK_PIN_REQ), // 0x0027 +qmi_name_item( QMIUIM_UNBLOCK_PIN_RESP), // 0x0027 +qmi_name_item( QMIUIM_UNBLOCK_PIN_IND), // 0x0027 +qmi_name_item( QMIUIM_CHANGE_PIN_REQ), // 0x0028 +qmi_name_item( QMIUIM_CHANGE_PIN_RESP), // 0x0028 +qmi_name_item( QMIUIM_CHANGE_PIN_IND), // 0x0028 +qmi_name_item( QMIUIM_DEPERSONALIZATION_REQ), // 0x0029 +qmi_name_item( QMIUIM_DEPERSONALIZATION_RESP), // 0x0029 +qmi_name_item( QMIUIM_EVENT_REG_REQ), // 0x002E +qmi_name_item( QMIUIM_EVENT_REG_RESP), // 0x002E +qmi_name_item( QMIUIM_GET_CARD_STATUS_REQ), // 0x002F +qmi_name_item( QMIUIM_GET_CARD_STATUS_RESP), // 0x002F +qmi_name_item( QMIUIM_STATUS_CHANGE_IND), // 0x0032 +}; + +static const char * qmi_name_get(const QMI_NAME_T *table, size_t size, int type, const char *tag) { + static char unknow[40]; + size_t i; + + if (qmux_CtlFlags == table) { + if (!strcmp(tag, "_REQ")) + tag = "_CMD"; + else if (!strcmp(tag, "_RESP")) + tag = "_RSP"; + } + + for (i = 0; i < size; i++) { + if (table[i].type == (UINT)type) { + if (!tag || (strstr(table[i].name, tag))) + return table[i].name; + } + } + sprintf(unknow, "unknow_%x", type); + return unknow; +} + +#define QMI_NAME(table, type) qmi_name_get(table, sizeof(table) / sizeof(table[0]), type, 0) +#define QMUX_NAME(table, type, tag) qmi_name_get(table, sizeof(table) / sizeof(table[0]), type, tag) + +void dump_tlv(PQCQMUX_MSG_HDR pQMUXMsgHdr) { + int TLVFind = 0; + int i; + //dbg("QCQMUX_TLV-----------------------------------\n"); + //dbg("{Type,\tLength,\tValue}\n"); + + while (1) { + PQMI_TLV_HDR TLVHdr = GetTLV(pQMUXMsgHdr, 0x1000 + (++TLVFind)); + if (TLVHdr == NULL) + break; + + //if ((TLVHdr->TLVType == 0x02) && ((USHORT *)(TLVHdr+1))[0]) + { + dbg("{%02x,\t%04x,\t", TLVHdr->TLVType, le16_to_cpu(TLVHdr->TLVLength)); + for (i = 0; i < le16_to_cpu(TLVHdr->TLVLength); i++) { + dbg("%02x ", ((UCHAR *)(TLVHdr+1))[i]); + } + dbg("}\n"); + } + } // while +} + +void dump_ctl(PQCQMICTL_MSG_HDR CTLHdr) { + const char *tag; + + //dbg("QCQMICTL_MSG--------------------------------------------\n"); + //dbg("CtlFlags: %02x\t\t%s\n", CTLHdr->CtlFlags, QMI_NAME(qmi_ctl_CtlFlags, CTLHdr->CtlFlags)); + dbg("TransactionId: %02x\n", CTLHdr->TransactionId); + switch (CTLHdr->CtlFlags) { + case QMICTL_FLAG_REQUEST: tag = "_REQ"; break; + case QMICTL_FLAG_RESPONSE: tag = "_RESP"; break; + case QMICTL_FLAG_INDICATION: tag = "_IND"; break; + default: tag = 0; break; + } + dbg("QMICTLType: %04x\t%s\n", le16_to_cpu(CTLHdr->QMICTLType), + QMUX_NAME(qmux_ctl_QMICTLType, le16_to_cpu(CTLHdr->QMICTLType), tag)); + dbg("Length: %04x\n", le16_to_cpu(CTLHdr->Length)); + + dump_tlv((PQCQMUX_MSG_HDR)(&CTLHdr->QMICTLType)); +} + +int dump_qmux(QMI_SERVICE_TYPE serviceType, PQCQMUX_HDR QMUXHdr) { + PQCQMUX_MSG_HDR QMUXMsgHdr = (PQCQMUX_MSG_HDR) (QMUXHdr + 1); + CHAR *tag; + + //dbg("QCQMUX--------------------------------------------\n"); + switch (QMUXHdr->CtlFlags&QMUX_CTL_FLAG_MASK_TYPE) { + case QMUX_CTL_FLAG_TYPE_CMD: tag = "_REQ"; break; + case QMUX_CTL_FLAG_TYPE_RSP: tag = "_RESP"; break; + case QMUX_CTL_FLAG_TYPE_IND: tag = "_IND"; break; + default: tag = 0; break; + } + //dbg("CtlFlags: %02x\t\t%s\n", QMUXHdr->CtlFlags, QMUX_NAME(qmux_CtlFlags, QMUXHdr->CtlFlags, tag)); + dbg("TransactionId: %04x\n", le16_to_cpu(QMUXHdr->TransactionId)); + + //dbg("QCQMUX_MSG_HDR-----------------------------------\n"); + switch (serviceType) { + case QMUX_TYPE_DMS: + dbg("Type: %04x\t%s\n", le16_to_cpu(QMUXMsgHdr->Type), + QMUX_NAME(qmux_dms_Type, le16_to_cpu(QMUXMsgHdr->Type), tag)); + break; + case QMUX_TYPE_NAS: + dbg("Type: %04x\t%s\n", le16_to_cpu(QMUXMsgHdr->Type), + QMUX_NAME(qmux_nas_Type, le16_to_cpu(QMUXMsgHdr->Type), tag)); + break; + case QMUX_TYPE_WDS: + case QMUX_TYPE_WDS_IPV6: + dbg("Type: %04x\t%s\n", le16_to_cpu(QMUXMsgHdr->Type), + QMUX_NAME(qmux_wds_Type, le16_to_cpu(QMUXMsgHdr->Type), tag)); + break; + case QMUX_TYPE_WMS: + dbg("Type: %04x\t%s\n", le16_to_cpu(QMUXMsgHdr->Type), + QMUX_NAME(qmux_wms_Type, le16_to_cpu(QMUXMsgHdr->Type), tag)); + break; + case QMUX_TYPE_WDS_ADMIN: + dbg("Type: %04x\t%s\n", le16_to_cpu(QMUXMsgHdr->Type), + QMUX_NAME(qmux_wds_admin_Type, le16_to_cpu(QMUXMsgHdr->Type), tag)); + break; + case QMUX_TYPE_UIM: + dbg("Type: %04x\t%s\n", le16_to_cpu(QMUXMsgHdr->Type), + QMUX_NAME(qmux_uim_Type, le16_to_cpu(QMUXMsgHdr->Type), tag)); + break; + case QMUX_TYPE_PDS: + case QMUX_TYPE_QOS: + case QMUX_TYPE_CTL: + default: + dbg("Type: %04x\t%s\n", le16_to_cpu(QMUXMsgHdr->Type), "PDS/QOS/CTL/unknown!"); + break; + } + dbg("Length: %04x\n", le16_to_cpu(QMUXMsgHdr->Length)); + + dump_tlv(QMUXMsgHdr); + + return 0; +} + +void dump_qmi(void *dataBuffer, int dataLen) +{ + PQCQMI_HDR QMIHdr = (PQCQMI_HDR)dataBuffer; + PQCQMUX_HDR QMUXHdr = (PQCQMUX_HDR) (QMIHdr + 1); + PQCQMICTL_MSG_HDR CTLHdr = (PQCQMICTL_MSG_HDR) (QMIHdr + 1); + + int i; + + if (!debug_qmi) + return; + + pthread_mutex_lock(&dumpQMIMutex); + line[0] = 0; + for (i = 0; i < dataLen; i++) { + dbg("%02x ", ((unsigned char *)dataBuffer)[i]); + } + dbg_time("%s", line); + line[0] = 0; + + //dbg("QCQMI_HDR-----------------------------------------"); + //dbg("IFType: %02x\t\t%s", QMIHdr->IFType, QMI_NAME(qmi_IFType, QMIHdr->IFType)); + //dbg("Length: %04x", le16_to_cpu(QMIHdr->Length)); + //dbg("CtlFlags: %02x\t\t%s", QMIHdr->CtlFlags, QMI_NAME(qmi_CtlFlags, QMIHdr->CtlFlags)); + //dbg("QMIType: %02x\t\t%s", QMIHdr->QMIType, QMI_NAME(qmi_QMIType, QMIHdr->QMIType)); + //dbg("ClientId: %02x", QMIHdr->ClientId); + + if (QMIHdr->QMIType == QMUX_TYPE_CTL) { + dump_ctl(CTLHdr); + } else { + dump_qmux(QMIHdr->QMIType, QMUXHdr); + } + dbg_time("%s", line); + pthread_mutex_unlock(&dumpQMIMutex); +} diff --git a/root/package/link4all/quectel-CM/quectel1.6.0.15_src/MPQMUX.h b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/MPQMUX.h new file mode 100644 index 00000000..3c63b6ae --- /dev/null +++ b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/MPQMUX.h @@ -0,0 +1,3485 @@ +/*=========================================================================== + + M P Q M U X. H +DESCRIPTION: + + This file provides support for QMUX. + +INITIALIZATION AND SEQUENCING REQUIREMENTS: + +Copyright (C) 2011 by Qualcomm Technologies, Incorporated. All Rights Reserved. +===========================================================================*/ + +#ifndef MPQMUX_H +#define MPQMUX_H + +#include "MPQMI.h" + +#pragma pack(push, 1) + +#define QMIWDS_SET_EVENT_REPORT_REQ 0x0001 +#define QMIWDS_SET_EVENT_REPORT_RESP 0x0001 +#define QMIWDS_EVENT_REPORT_IND 0x0001 +#define QMIWDS_START_NETWORK_INTERFACE_REQ 0x0020 +#define QMIWDS_START_NETWORK_INTERFACE_RESP 0x0020 +#define QMIWDS_STOP_NETWORK_INTERFACE_REQ 0x0021 +#define QMIWDS_STOP_NETWORK_INTERFACE_RESP 0x0021 +#define QMIWDS_GET_PKT_SRVC_STATUS_REQ 0x0022 +#define QMIWDS_GET_PKT_SRVC_STATUS_RESP 0x0022 +#define QMIWDS_GET_PKT_SRVC_STATUS_IND 0x0022 +#define QMIWDS_GET_CURRENT_CHANNEL_RATE_REQ 0x0023 +#define QMIWDS_GET_CURRENT_CHANNEL_RATE_RESP 0x0023 +#define QMIWDS_GET_PKT_STATISTICS_REQ 0x0024 +#define QMIWDS_GET_PKT_STATISTICS_RESP 0x0024 +#define QMIWDS_MODIFY_PROFILE_SETTINGS_REQ 0x0028 +#define QMIWDS_MODIFY_PROFILE_SETTINGS_RESP 0x0028 +#define QMIWDS_GET_PROFILE_SETTINGS_REQ 0x002B +#define QMIWDS_GET_PROFILE_SETTINGS_RESP 0x002B +#define QMIWDS_GET_DEFAULT_SETTINGS_REQ 0x002C +#define QMIWDS_GET_DEFAULT_SETTINGS_RESP 0x002C +#define QMIWDS_GET_RUNTIME_SETTINGS_REQ 0x002D +#define QMIWDS_GET_RUNTIME_SETTINGS_RESP 0x002D +#define QMIWDS_GET_MIP_MODE_REQ 0x002F +#define QMIWDS_GET_MIP_MODE_RESP 0x002F +#define QMIWDS_GET_DATA_BEARER_REQ 0x0037 +#define QMIWDS_GET_DATA_BEARER_RESP 0x0037 +#define QMIWDS_DUN_CALL_INFO_REQ 0x0038 +#define QMIWDS_DUN_CALL_INFO_RESP 0x0038 +#define QMIWDS_DUN_CALL_INFO_IND 0x0038 +#define QMIWDS_SET_CLIENT_IP_FAMILY_PREF_REQ 0x004D +#define QMIWDS_SET_CLIENT_IP_FAMILY_PREF_RESP 0x004D +#define QMIWDS_SET_AUTO_CONNECT_REQ 0x0051 +#define QMIWDS_SET_AUTO_CONNECT_RESP 0x0051 +#define QMIWDS_BIND_MUX_DATA_PORT_REQ 0x00A2 +#define QMIWDS_BIND_MUX_DATA_PORT_RESP 0x00A2 + + +// Stats masks +#define QWDS_STAT_MASK_TX_PKT_OK 0x00000001 +#define QWDS_STAT_MASK_RX_PKT_OK 0x00000002 +#define QWDS_STAT_MASK_TX_PKT_ER 0x00000004 +#define QWDS_STAT_MASK_RX_PKT_ER 0x00000008 +#define QWDS_STAT_MASK_TX_PKT_OF 0x00000010 +#define QWDS_STAT_MASK_RX_PKT_OF 0x00000020 + +// TLV Types for xfer statistics +#define TLV_WDS_TX_GOOD_PKTS 0x10 +#define TLV_WDS_RX_GOOD_PKTS 0x11 +#define TLV_WDS_TX_ERROR 0x12 +#define TLV_WDS_RX_ERROR 0x13 +#define TLV_WDS_TX_OVERFLOW 0x14 +#define TLV_WDS_RX_OVERFLOW 0x15 +#define TLV_WDS_CHANNEL_RATE 0x16 +#define TLV_WDS_DATA_BEARER 0x17 +#define TLV_WDS_DORMANCY_STATUS 0x18 + +#define QWDS_PKT_DATA_UNKNOW 0x00 +#define QWDS_PKT_DATA_DISCONNECTED 0x01 +#define QWDS_PKT_DATA_CONNECTED 0x02 +#define QWDS_PKT_DATA_SUSPENDED 0x03 +#define QWDS_PKT_DATA_AUTHENTICATING 0x04 + +#define QMIWDS_ADMIN_SET_DATA_FORMAT_REQ 0x0020 +#define QMIWDS_ADMIN_SET_DATA_FORMAT_RESP 0x0020 +#define QMIWDS_ADMIN_GET_DATA_FORMAT_REQ 0x0021 +#define QMIWDS_ADMIN_GET_DATA_FORMAT_RESP 0x0021 +#define QMIWDS_ADMIN_SET_QMAP_SETTINGS_REQ 0x002B +#define QMIWDS_ADMIN_SET_QMAP_SETTINGS_RESP 0x002B +#define QMIWDS_ADMIN_GET_QMAP_SETTINGS_REQ 0x002C +#define QMIWDS_ADMIN_GET_QMAP_SETTINGS_RESP 0x002C +#define QMI_WDA_SET_LOOPBACK_CONFIG_REQ 0x002F +#define QMI_WDA_SET_LOOPBACK_CONFIG_RESP 0x002F +#define QMI_WDA_SET_LOOPBACK_CONFIG_IND 0x002F + +#define NETWORK_DESC_ENCODING_OCTET 0x00 +#define NETWORK_DESC_ENCODING_EXTPROTOCOL 0x01 +#define NETWORK_DESC_ENCODING_7BITASCII 0x02 +#define NETWORK_DESC_ENCODING_IA5 0x03 +#define NETWORK_DESC_ENCODING_UNICODE 0x04 +#define NETWORK_DESC_ENCODING_SHIFTJIS 0x05 +#define NETWORK_DESC_ENCODING_KOREAN 0x06 +#define NETWORK_DESC_ENCODING_LATINH 0x07 +#define NETWORK_DESC_ENCODING_LATIN 0x08 +#define NETWORK_DESC_ENCODING_GSM7BIT 0x09 +#define NETWORK_DESC_ENCODING_GSMDATA 0x0A +#define NETWORK_DESC_ENCODING_UNKNOWN 0xFF + +typedef struct _QMIWDS_ADMIN_SET_DATA_FORMAT +{ + USHORT Type; // QMUX type 0x0000 + USHORT Length; +} __attribute__ ((packed)) QMIWDS_ADMIN_SET_DATA_FORMAT, *PQMIWDS_ADMIN_SET_DATA_FORMAT; + +typedef struct _QMIWDS_ADMIN_SET_DATA_FORMAT_TLV_QOS +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR QOSSetting; +} __attribute__ ((packed)) QMIWDS_ADMIN_SET_DATA_FORMAT_TLV_QOS, *PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV_QOS; + +typedef struct _QMIWDS_ADMIN_SET_DATA_FORMAT_TLV +{ + UCHAR TLVType; + USHORT TLVLength; + ULONG Value; +} __attribute__ ((packed)) QMIWDS_ADMIN_SET_DATA_FORMAT_TLV, *PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV; + +typedef struct _QMIWDS_ENDPOINT_TLV +{ + UCHAR TLVType; + USHORT TLVLength; + ULONG ep_type; + ULONG iface_id; +} __attribute__ ((packed)) QMIWDS_ENDPOINT_TLV, *PQMIWDS_ENDPOINT_TLV; + +typedef struct _QMIWDS_ADMIN_SET_DATA_FORMAT_REQ_MSG +{ + USHORT Type; + USHORT Length; + QMIWDS_ADMIN_SET_DATA_FORMAT_TLV_QOS QosDataFormatTlv; + QMIWDS_ADMIN_SET_DATA_FORMAT_TLV UnderlyingLinkLayerProtocolTlv; + QMIWDS_ADMIN_SET_DATA_FORMAT_TLV UplinkDataAggregationProtocolTlv; + QMIWDS_ADMIN_SET_DATA_FORMAT_TLV DownlinkDataAggregationProtocolTlv; + QMIWDS_ADMIN_SET_DATA_FORMAT_TLV DownlinkDataAggregationMaxDatagramsTlv; + QMIWDS_ADMIN_SET_DATA_FORMAT_TLV DownlinkDataAggregationMaxSizeTlv; + QMIWDS_ENDPOINT_TLV epTlv; +#ifdef QUECTEL_UL_DATA_AGG + QMIWDS_ADMIN_SET_DATA_FORMAT_TLV DlMinimumPassingTlv; + QMIWDS_ADMIN_SET_DATA_FORMAT_TLV UplinkDataAggregationMaxDatagramsTlv; + QMIWDS_ADMIN_SET_DATA_FORMAT_TLV UplinkDataAggregationMaxSizeTlv; +#endif +} __attribute__ ((packed)) QMIWDS_ADMIN_SET_DATA_FORMAT_REQ_MSG, *PQMIWDS_ADMIN_SET_DATA_FORMAT_REQ_MSG; + +typedef struct _QMI_U8_TLV +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR TLVVaule; +} __attribute__ ((packed)) QMI_U8_TLV, *PQMI_U8_TLV; + +typedef struct _QMI_U32_TLV +{ + UCHAR TLVType; + USHORT TLVLength; + ULONG TLVVaule; +} __attribute__ ((packed)) QMI_U32_TLV, *PQMI_U32_TLV; + +typedef struct _QMI_WDA_SET_LOOPBACK_CONFIG_REQ_MSG { + USHORT Type; + USHORT Length; + QMI_U8_TLV loopback_state; //0x01 + QMI_U32_TLV replication_factor; //0x10 +} __attribute__ ((packed)) QMI_WDA_SET_LOOPBACK_CONFIG_REQ_MSG, *PQMI_WDA_SET_LOOPBACK_CONFIG_REQ_MSG; + +typedef struct _QMI_WDA_SET_LOOPBACK_CONFIG_IND_MSG +{ + USHORT Type; + USHORT Length; + QMI_U8_TLV loopback_state; //0x01 + QMI_U32_TLV replication_factor; //0x10 +} __attribute__ ((packed)) QMI_WDA_SET_LOOPBACK_CONFIG_IND_MSG, *PQMI_WDA_SET_LOOPBACK_CONFIG_IND_MSG; + +#if 0 +typedef enum _QMI_RETURN_CODES { + QMI_SUCCESS = 0, + QMI_SUCCESS_NOT_COMPLETE, + QMI_FAILURE +}QMI_RETURN_CODES; + +typedef struct _QMIWDS_GET_PKT_SRVC_STATUS_REQ_MSG +{ + USHORT Type; // 0x0022 + USHORT Length; // 0x0000 +} QMIWDS_GET_PKT_SRVC_STATUS_REQ_MSG, *PQMIWDS_GET_PKT_SRVC_STATUS_REQ_MSG; + +typedef struct _QMIWDS_GET_PKT_SRVC_STATUS_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLVType2; + USHORT TLVLength2; + UCHAR ConnectionStatus; // 0x01: QWDS_PKT_DATAC_DISCONNECTED + // 0x02: QWDS_PKT_DATA_CONNECTED + // 0x03: QWDS_PKT_DATA_SUSPENDED + // 0x04: QWDS_PKT_DATA_AUTHENTICATING +} QMIWDS_GET_PKT_SRVC_STATUS_RESP_MSG, *PQMIWDS_GET_PKT_SRVC_STATUS_RESP_MSG; + +typedef struct _QMIWDS_GET_PKT_SRVC_STATUS_IND_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR ConnectionStatus; // 0x01: QWDS_PKT_DATAC_DISCONNECTED + // 0x02: QWDS_PKT_DATA_CONNECTED + // 0x03: QWDS_PKT_DATA_SUSPENDED + UCHAR ReconfigRequired; // 0x00: No need to reconfigure + // 0x01: Reconfiguration required +} QMIWDS_GET_PKT_SRVC_STATUS_IND_MSG, *PQMIWDS_GET_PKT_SRVC_STATUS_IND_MSG; + +typedef struct _WDS_PKT_SRVC_IP_FAMILY_TLV +{ + UCHAR TLVType; // 0x12 + USHORT TLVLength; // 1 + UCHAR IpFamily; // IPV4-0x04, IPV6-0x06 +} WDS_PKT_SRVC_IP_FAMILY_TLV, *PWDS_PKT_SRVC_IP_FAMILY_TLV; + +typedef struct _QMIWDS_DUN_CALL_INFO_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + ULONG Mask; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR ReportConnectionStatus; +} QMIWDS_DUN_CALL_INFO_REQ_MSG, *PQMIWDS_DUN_CALL_INFO_REQ_MSG; + +typedef struct _QMIWDS_DUN_CALL_INFO_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} QMIWDS_DUN_CALL_INFO_RESP_MSG, *PQMIWDS_DUN_CALL_INFO_RESP_MSG; + +typedef struct _QMIWDS_DUN_CALL_INFO_IND_MSG +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR ConnectionStatus; +} QMIWDS_DUN_CALL_INFO_IND_MSG, *PQMIWDS_DUN_CALL_INFO_IND_MSG; + +typedef struct _QMIWDS_GET_CURRENT_CHANNEL_RATE_REQ_MSG +{ + USHORT Type; // QMUX type 0x0040 + USHORT Length; +} QMIWDS_GET_CURRENT_CHANNEL_RATE_REQ_MSG, *PQMIWDS_GET_CURRENT_CHANNEL_RATE_REQ_MSG; + +typedef struct _QMIWDS_GET_CURRENT_CHANNEL_RATE_RESP_MSG +{ + USHORT Type; // QMUX type 0x0040 + USHORT Length; + UCHAR TLVType; // 0x02 + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + + UCHAR TLV2Type; // 0x01 + USHORT TLV2Length; // 16 + //ULONG CallHandle; // Context corresponding to reported channel + ULONG CurrentTxRate; // bps + ULONG CurrentRxRate; // bps + ULONG ServingSystemTxRate; // bps + ULONG ServingSystemRxRate; // bps + +} QMIWDS_GET_CURRENT_CHANNEL_RATE_RESP_MSG, *PQMIWDS_GET_CURRENT_CHANNEL_RATE_RESP; + +#define QWDS_EVENT_REPORT_MASK_RATES 0x01 +#define QWDS_EVENT_REPORT_MASK_STATS 0x02 + +#ifdef QCUSB_MUX_PROTOCOL +#error code not present +#endif // QCUSB_MUX_PROTOCOL + +typedef struct _QMIWDS_SET_EVENT_REPORT_REQ_MSG +{ + USHORT Type; // QMUX type 0x0042 + USHORT Length; + + UCHAR TLVType; // 0x10 -- current channel rate indicator + USHORT TLVLength; // 1 + UCHAR Mode; // 0-do not report; 1-report when rate changes + + UCHAR TLV2Type; // 0x11 + USHORT TLV2Length; // 5 + UCHAR StatsPeriod; // seconds between reports; 0-do not report + ULONG StatsMask; // + + UCHAR TLV3Type; // 0x12 -- current data bearer indicator + USHORT TLV3Length; // 1 + UCHAR Mode3; // 0-do not report; 1-report when changes + + UCHAR TLV4Type; // 0x13 -- dormancy status indicator + USHORT TLV4Length; // 1 + UCHAR DormancyStatus; // 0-do not report; 1-report when changes +} QMIWDS_SET_EVENT_REPORT_REQ_MSG, *PQMIWDS_SET_EVENT_REPORT_REQ_MSG; + +typedef struct _QMIWDS_SET_EVENT_REPORT_RESP_MSG +{ + USHORT Type; // QMUX type 0x0042 + USHORT Length; + + UCHAR TLVType; // 0x02 result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_NO_BATTERY + // QMI_ERR_FAULT +} QMIWDS_SET_EVENT_REPORT_RESP_MSG, *PQMIWDS_SET_EVENT_REPORT_RESP_MSG; + +typedef struct _QMIWDS_EVENT_REPORT_IND_MSG +{ + USHORT Type; // QMUX type 0x0001 + USHORT Length; +} QMIWDS_EVENT_REPORT_IND_MSG, *PQMIWDS_EVENT_REPORT_IND_MSG; + +// PQCTLV_PKT_STATISTICS + +typedef struct _QMIWDS_EVENT_REPORT_IND_CHAN_RATE_TLV +{ + UCHAR Type; + USHORT Length; // 8 + ULONG TxRate; + ULONG RxRate; +} QMIWDS_EVENT_REPORT_IND_CHAN_RATE_TLV, *PQMIWDS_EVENT_REPORT_IND_CHAN_RATE_TLV; + +#ifdef QCUSB_MUX_PROTOCOL +#error code not present +#endif // QCUSB_MUX_PROTOCOL + +typedef struct _QMIWDS_GET_PKT_STATISTICS_REQ_MSG +{ + USHORT Type; // QMUX type 0x0041 + USHORT Length; + UCHAR TLVType; // 0x01 + USHORT TLVLength; // 4 + ULONG StateMask; // 0x00000001 tx success packets + // 0x00000002 rx success packets + // 0x00000004 rx packet errors (checksum) + // 0x00000008 rx packets dropped (memory) + +} QMIWDS_GET_PKT_STATISTICS_REQ_MSG, *PQMIWDS_GET_PKT_STATISTICS_REQ_MSG; + +typedef struct _QMIWDS_GET_PKT_STATISTICS_RESP_MSG +{ + USHORT Type; // QMUX type 0x0041 + USHORT Length; + UCHAR TLVType; // 0x02 + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} QMIWDS_GET_PKT_STATISTICS_RESP_MSG, *PQMIWDS_GET_PKT_STATISTICS_RESP_MSG; + +// optional TLV for stats +typedef struct _QCTLV_PKT_STATISTICS +{ + UCHAR TLVType; // see above definitions for TLV types + USHORT TLVLength; // 4 + ULONG Count; +} QCTLV_PKT_STATISTICS, *PQCTLV_PKT_STATISTICS; +#endif + +//#ifdef QC_IP_MODE + +/* + • Bit 0 – Profile identifier + • Bit 1 – Profile name + • Bit 2 – PDP type + • Bit 3 – APN name + • Bit 4 – DNS address + • Bit 5 – UMTS/GPRS granted QoS + • Bit 6 – Username + • Bit 7 – Authentication Protocol + • Bit 8 – IP address + • Bit 9 – Gateway information (address and subnet mask) + • Bit 10 – PCSCF address using a PCO flag + • Bit 11 – PCSCF server address list + • Bit 12 – PCSCF domain name list + • Bit 13 – MTU + • Bit 14 – Domain name list + • Bit 15 – IP family + • Bit 16 – IM_CM flag + • Bit 17 – Technology name + • Bit 18 – Operator reserved PCO +*/ +#define QMIWDS_GET_RUNTIME_SETTINGS_MASK_IPV4DNS_ADDR (1 << 4) +#define QMIWDS_GET_RUNTIME_SETTINGS_MASK_IPV4_ADDR (1 << 8) +#define QMIWDS_GET_RUNTIME_SETTINGS_MASK_IPV4GATEWAY_ADDR (1 << 9) +#define QMIWDS_GET_RUNTIME_SETTINGS_MASK_MTU (1 << 13) +#define QMIWDS_GET_RUNTIME_SETTINGS_MASK_PCSCF_SV_ADDR (1 << 11) +#define QMIWDS_GET_RUNTIME_SETTINGS_MASK_PCSCF_DOM_NAME (1 << 14) + +typedef struct _QMIWDS_GET_RUNTIME_SETTINGS_REQ_MSG +{ + USHORT Type; // QMIWDS_GET_RUNTIME_SETTINGS_REQ + USHORT Length; + UCHAR TLVType; // 0x10 + USHORT TLVLength; // 0x0004 + ULONG Mask; // mask, bit 8: IP addr -- 0x0100 +} __attribute__ ((packed)) QMIWDS_GET_RUNTIME_SETTINGS_REQ_MSG, *PQMIWDS_GET_RUNTIME_SETTINGS_REQ_MSG; + +typedef struct _QMIWDS_BIND_MUX_DATA_PORT_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + ULONG ep_type; + ULONG iface_id; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR MuxId; + UCHAR TLV3Type; + USHORT TLV3Length; + ULONG client_type; +} __attribute__ ((packed)) QMIWDS_BIND_MUX_DATA_PORT_REQ_MSG, *PQMIWDS_BIND_MUX_DATA_PORT_REQ_MSG; + +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4PRIMARYDNS 0x15 +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4SECONDARYDNS 0x16 +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4 0x1E +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4GATEWAY 0x20 +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4SUBNET 0x21 + +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6 0x25 +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6GATEWAY 0x26 +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6PRIMARYDNS 0x27 +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6SECONDARYDNS 0x28 +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_MTU 0x29 + +typedef struct _QMIWDS_GET_RUNTIME_SETTINGS_TLV_MTU +{ + UCHAR TLVType; // QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_MTU + USHORT TLVLength; // 4 + ULONG Mtu; // MTU +} __attribute__ ((packed)) QMIWDS_GET_RUNTIME_SETTINGS_TLV_MTU, *PQMIWDS_GET_RUNTIME_SETTINGS_TLV_MTU; + +typedef struct _QMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR +{ + UCHAR TLVType; // QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4 + USHORT TLVLength; // 4 + ULONG IPV4Address; // address +} __attribute__ ((packed)) QMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR, *PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR; + +typedef struct _QMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV6_ADDR +{ + UCHAR TLVType; // QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6 + USHORT TLVLength; // 16 + UCHAR IPV6Address[16]; // address + UCHAR PrefixLength; // prefix length +} __attribute__ ((packed)) QMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV6_ADDR, *PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV6_ADDR; + +typedef struct _QMIWDS_GET_RUNNING_SETTINGS_PCSCF_IPV6_ADDR +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR PCSCFNumber; +} __attribute__ ((packed)) QMIWDS_GET_RUNNING_SETTINGS_PCSCF_IPV6_ADDR, *PQMIWDS_GET_RUNNING_SETTINGS_PCSCF_IPV6_ADDR; + +typedef struct _QMIWDS_GET_RUNNING_SETTINGS_PCSCF_IPV4_ADDR +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR PCSCFNumber; +} __attribute__ ((packed)) QMIWDS_GET_RUNNING_SETTINGS_PCSCF_IPV4_ADDR, *PQMIWDS_GET_RUNNING_SETTINGS_PCSCF_IPV4_ADDR; + +typedef struct _QMIWDS_GET_RUNTIME_SETTINGS_RESP_MSG +{ + USHORT Type; // QMIWDS_GET_RUNTIME_SETTINGS_RESP + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_RESULT_CODE + USHORT TLVLength; // 0x0004 + USHORT QMUXResult; // result code + USHORT QMUXError; // error code +} __attribute__ ((packed)) QMIWDS_GET_RUNTIME_SETTINGS_RESP_MSG, *PQMIWDS_GET_RUNTIME_SETTINGS_RESP_MSG; + +//#endif // QC_IP_MODE + +typedef struct _QMIWDS_IP_FAMILY_TLV +{ + UCHAR TLVType; // 0x12 + USHORT TLVLength; // 1 + UCHAR IpFamily; // IPV4-0x04, IPV6-0x06 +} __attribute__ ((packed)) QMIWDS_IP_FAMILY_TLV, *PQMIWDS_IP_FAMILY_TLV; + +typedef struct _QMIWDS_PKT_SRVC_TLV +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR ConnectionStatus; + UCHAR ReconfigReqd; +} __attribute__ ((packed)) QMIWDS_PKT_SRVC_TLV, *PQMIWDS_PKT_SRVC_TLV; + +typedef struct _QMIWDS_CALL_END_REASON_TLV +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT CallEndReason; +} __attribute__ ((packed)) QMIWDS_CALL_END_REASON_TLV, *PQMIWDS_CALL_END_REASON_TLV; + +typedef struct _QMIWDS_CALL_END_REASON_V_TLV +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT CallEndReasonType; + USHORT CallEndReason; +} __attribute__ ((packed)) QMIWDS_CALL_END_REASON_V_TLV, *PQMIWDS_CALL_END_REASON_V_TLV; + +typedef struct _QMIWDS_SET_CLIENT_IP_FAMILY_PREF_REQ_MSG +{ + USHORT Type; // QMUX type 0x004D + USHORT Length; + UCHAR TLVType; // 0x01 + USHORT TLVLength; // 1 + UCHAR IpPreference; // IPV4-0x04, IPV6-0x06 +} __attribute__ ((packed)) QMIWDS_SET_CLIENT_IP_FAMILY_PREF_REQ_MSG, *PQMIWDS_SET_CLIENT_IP_FAMILY_PREF_REQ_MSG; + +typedef struct _QMIWDS_SET_CLIENT_IP_FAMILY_PREF_RESP_MSG +{ + USHORT Type; // QMUX type 0x0037 + USHORT Length; + UCHAR TLVType; // 0x02 + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS, QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INTERNAL, QMI_ERR_MALFORMED_MSG, QMI_ERR_INVALID_ARG +} __attribute__ ((packed)) QMIWDS_SET_CLIENT_IP_FAMILY_PREF_RESP_MSG, *PQMIWDS_SET_CLIENT_IP_FAMILY_PREF_RESP_MSG; + +typedef struct _QMIWDS_SET_AUTO_CONNECT_REQ_MSG +{ + USHORT Type; // QMUX type 0x0051 + USHORT Length; + UCHAR TLVType; // 0x01 + USHORT TLVLength; // 1 + UCHAR autoconnect_setting; // 0x00 ?C Disabled, 0x01 ?C Enabled, 0x02 ?C Paused (resume on power cycle) +} __attribute__ ((packed)) QMIWDS_SET_AUTO_CONNECT_REQ_MSG, *PQMIWDS_SET_AUTO_CONNECT_REQ_MSG; + +#if 0 +typedef struct _QMIWDS_GET_MIP_MODE_REQ_MSG +{ + USHORT Type; // QMUX type 0x0040 + USHORT Length; +} QMIWDS_GET_MIP_MODE_REQ_MSG, *PQMIWDS_GET_MIP_MODE_REQ_MSG; + +typedef struct _QMIWDS_GET_MIP_MODE_RESP_MSG +{ + USHORT Type; // QMUX type 0x0040 + USHORT Length; + UCHAR TLVType; // 0x02 + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + + UCHAR TLV2Type; // 0x01 + USHORT TLV2Length; // 20 + UCHAR MipMode; // +} QMIWDS_GET_MIP_MODE_RESP_MSG, *PQMIWDS_GET_MIP_MODE_RESP_MSG; +#endif + +typedef struct _QMIWDS_TECHNOLOGY_PREFERECE +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR TechPreference; +} __attribute__ ((packed)) QMIWDS_TECHNOLOGY_PREFERECE, *PQMIWDS_TECHNOLOGY_PREFERECE; + +typedef struct _QMIWDS_PROFILE_IDENTIFIER +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR ProfileIndex; +} __attribute__ ((packed)) QMIWDS_PROFILE_IDENTIFIER, *PQMIWDS_PROFILE_IDENTIFIER; + +#if 0 +typedef struct _QMIWDS_IPADDRESS +{ + UCHAR TLVType; + USHORT TLVLength; + ULONG IPv4Address; +}QMIWDS_IPADDRESS, *PQMIWDS_IPADDRESS; + +/* +typedef struct _QMIWDS_UMTS_QOS +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR TrafficClass; + ULONG MaxUplinkBitRate; + ULONG MaxDownlinkBitRate; + ULONG GuarUplinkBitRate; + ULONG GuarDownlinkBitRate; + UCHAR QOSDevOrder; + ULONG MAXSDUSize; + UCHAR SDUErrorRatio; + UCHAR ResidualBerRatio; + UCHAR DeliveryErrorSDUs; + ULONG TransferDelay; + ULONG TrafficHndPri; +}QMIWDS_UMTS_QOS, *PQMIWDS_UMTS_QOS; + +typedef struct _QMIWDS_GPRS_QOS +{ + UCHAR TLVType; + USHORT TLVLength; + ULONG PrecedenceClass; + ULONG DelayClass; + ULONG ReliabilityClass; + ULONG PeekThroClass; + ULONG MeanThroClass; +}QMIWDS_GPRS_QOS, *PQMIWDS_GPRS_QOS; +*/ +#endif + +typedef struct _QMIWDS_PROFILENAME +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR ProfileName; +} __attribute__ ((packed)) QMIWDS_PROFILENAME, *PQMIWDS_PROFILENAME; + +typedef struct _QMIWDS_PDPTYPE +{ + UCHAR TLVType; + USHORT TLVLength; +// 0 ?C PDP-IP (IPv4) +// 1 ?C PDP-PPP +// 2 ?C PDP-IPv6 +// 3 ?C PDP-IPv4v6 + UCHAR PdpType; +} __attribute__ ((packed)) QMIWDS_PDPTYPE, *PQMIWDS_PDPTYPE; + +typedef struct _QMIWDS_USERNAME +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR UserName; +} __attribute__ ((packed)) QMIWDS_USERNAME, *PQMIWDS_USERNAME; + +typedef struct _QMIWDS_PASSWD +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR Passwd; +} __attribute__ ((packed)) QMIWDS_PASSWD, *PQMIWDS_PASSWD; + +typedef struct _QMIWDS_AUTH_PREFERENCE +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR AuthPreference; +} __attribute__ ((packed)) QMIWDS_AUTH_PREFERENCE, *PQMIWDS_AUTH_PREFERENCE; + +typedef struct _QMIWDS_APNNAME +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR ApnName; +} __attribute__ ((packed)) QMIWDS_APNNAME, *PQMIWDS_APNNAME; + +typedef struct _QMIWDS_AUTOCONNECT +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR AutoConnect; +} __attribute__ ((packed)) QMIWDS_AUTOCONNECT, *PQMIWDS_AUTOCONNECT; + +typedef struct _QMIWDS_START_NETWORK_INTERFACE_REQ_MSG +{ + USHORT Type; + USHORT Length; +} __attribute__ ((packed)) QMIWDS_START_NETWORK_INTERFACE_REQ_MSG, *PQMIWDS_START_NETWORK_INTERFACE_REQ_MSG; + +typedef struct _QMIWDS_CALLENDREASON +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT Reason; +}__attribute__ ((packed)) QMIWDS_CALLENDREASON, *PQMIWDS_CALLENDREASON; + +typedef struct _QMIWDS_START_NETWORK_INTERFACE_RESP_MSG +{ + USHORT Type; // QMUX type 0x0040 + USHORT Length; + UCHAR TLVType; // 0x02 + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + + UCHAR TLV2Type; // 0x01 + USHORT TLV2Length; // 20 + ULONG Handle; // +} __attribute__ ((packed)) QMIWDS_START_NETWORK_INTERFACE_RESP_MSG, *PQMIWDS_START_NETWORK_INTERFACE_RESP_MSG; + +typedef struct _QMIWDS_STOP_NETWORK_INTERFACE_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + ULONG Handle; +} __attribute__ ((packed)) QMIWDS_STOP_NETWORK_INTERFACE_REQ_MSG, *PQMIWDS_STOP_NETWORK_INTERFACE_REQ_MSG; + +typedef struct _QMIWDS_STOP_NETWORK_INTERFACE_RESP_MSG +{ + USHORT Type; // QMUX type 0x0040 + USHORT Length; + UCHAR TLVType; // 0x02 + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + +} __attribute__ ((packed)) QMIWDS_STOP_NETWORK_INTERFACE_RESP_MSG, *PQMIWDS_STOP_NETWORK_INTERFACE_RESP_MSG; + +typedef struct _QMIWDS_GET_DEFAULT_SETTINGS_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR ProfileType; +} __attribute__ ((packed)) QMIWDS_GET_DEFAULT_SETTINGS_REQ_MSG, *PQMIWDS_GET_DEFAULT_SETTINGS_REQ_MSG; + +typedef struct _QMIWDS_GET_DEFAULT_SETTINGS_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} __attribute__ ((packed)) QMIWDS_GET_DEFAULT_SETTINGS_RESP_MSG, *PQMIWDS_GET_DEFAULT_SETTINGS_RESP_MSG; + +typedef struct _QMIWDS_MODIFY_PROFILE_SETTINGS_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR ProfileType; + UCHAR ProfileIndex; +} __attribute__ ((packed)) QMIWDS_MODIFY_PROFILE_SETTINGS_REQ_MSG, *PQMIWDS_MODIFY_PROFILE_SETTINGS_REQ_MSG; + +typedef struct _QMIWDS_MODIFY_PROFILE_SETTINGS_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} __attribute__ ((packed)) QMIWDS_MODIFY_PROFILE_SETTINGS_RESP_MSG, *PQMIWDS_MODIFY_PROFILE_SETTINGS_RESP_MSG; + +typedef struct _QMIWDS_GET_PROFILE_SETTINGS_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR ProfileType; + UCHAR ProfileIndex; +} __attribute__ ((packed)) QMIWDS_GET_PROFILE_SETTINGS_REQ_MSG, *PQMIWDS_GET_PROFILE_SETTINGS_REQ_MSG; + +#if 0 +typedef struct _QMIWDS_EVENT_REPORT_IND_DATA_BEARER_TLV +{ + UCHAR Type; + USHORT Length; + UCHAR DataBearer; +} QMIWDS_EVENT_REPORT_IND_DATA_BEARER_TLV, *PQMIWDS_EVENT_REPORT_IND_DATA_BEARER_TLV; + +typedef struct _QMIWDS_EVENT_REPORT_IND_DORMANCY_STATUS_TLV +{ + UCHAR Type; + USHORT Length; + UCHAR DormancyStatus; +} QMIWDS_EVENT_REPORT_IND_DORMANCY_STATUS_TLV, *PQMIWDS_EVENT_REPORT_IND_DORMANCY_STATUS_TLV; + + +typedef struct _QMIWDS_GET_DATA_BEARER_REQ_MSG +{ + USHORT Type; // QMUX type 0x0037 + USHORT Length; +} QMIWDS_GET_DATA_BEARER_REQ_MSG, *PQMIWDS_GET_DATA_BEARER_REQ_MSG; + +typedef struct _QMIWDS_GET_DATA_BEARER_RESP_MSG +{ + USHORT Type; // QMUX type 0x0037 + USHORT Length; + UCHAR TLVType; // 0x02 + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INTERNAL + // QMI_ERR_MALFORMED_MSG + // QMI_ERR_NO_MEMORY + // QMI_ERR_OUT_OF_CALL + // QMI_ERR_INFO_UNAVAILABLE + UCHAR TLV2Type; // 0x01 + USHORT TLV2Length; // + UCHAR Technology; // +} QMIWDS_GET_DATA_BEARER_RESP_MSG, *PQMIWDS_GET_DATA_BEARER_RESP_MSG; +#endif + +// ======================= DMS ============================== +#define QMIDMS_SET_EVENT_REPORT_REQ 0x0001 +#define QMIDMS_SET_EVENT_REPORT_RESP 0x0001 +#define QMIDMS_EVENT_REPORT_IND 0x0001 +#define QMIDMS_GET_DEVICE_CAP_REQ 0x0020 +#define QMIDMS_GET_DEVICE_CAP_RESP 0x0020 +#define QMIDMS_GET_DEVICE_MFR_REQ 0x0021 +#define QMIDMS_GET_DEVICE_MFR_RESP 0x0021 +#define QMIDMS_GET_DEVICE_MODEL_ID_REQ 0x0022 +#define QMIDMS_GET_DEVICE_MODEL_ID_RESP 0x0022 +#define QMIDMS_GET_DEVICE_REV_ID_REQ 0x0023 +#define QMIDMS_GET_DEVICE_REV_ID_RESP 0x0023 +#define QMIDMS_GET_MSISDN_REQ 0x0024 +#define QMIDMS_GET_MSISDN_RESP 0x0024 +#define QMIDMS_GET_DEVICE_SERIAL_NUMBERS_REQ 0x0025 +#define QMIDMS_GET_DEVICE_SERIAL_NUMBERS_RESP 0x0025 +#define QMIDMS_UIM_SET_PIN_PROTECTION_REQ 0x0027 +#define QMIDMS_UIM_SET_PIN_PROTECTION_RESP 0x0027 +#define QMIDMS_UIM_VERIFY_PIN_REQ 0x0028 +#define QMIDMS_UIM_VERIFY_PIN_RESP 0x0028 +#define QMIDMS_UIM_UNBLOCK_PIN_REQ 0x0029 +#define QMIDMS_UIM_UNBLOCK_PIN_RESP 0x0029 +#define QMIDMS_UIM_CHANGE_PIN_REQ 0x002A +#define QMIDMS_UIM_CHANGE_PIN_RESP 0x002A +#define QMIDMS_UIM_GET_PIN_STATUS_REQ 0x002B +#define QMIDMS_UIM_GET_PIN_STATUS_RESP 0x002B +#define QMIDMS_GET_DEVICE_HARDWARE_REV_REQ 0x002C +#define QMIDMS_GET_DEVICE_HARDWARE_REV_RESP 0x002C +#define QMIDMS_GET_OPERATING_MODE_REQ 0x002D +#define QMIDMS_GET_OPERATING_MODE_RESP 0x002D +#define QMIDMS_SET_OPERATING_MODE_REQ 0x002E +#define QMIDMS_SET_OPERATING_MODE_RESP 0x002E +#define QMIDMS_GET_ACTIVATED_STATUS_REQ 0x0031 +#define QMIDMS_GET_ACTIVATED_STATUS_RESP 0x0031 +#define QMIDMS_ACTIVATE_AUTOMATIC_REQ 0x0032 +#define QMIDMS_ACTIVATE_AUTOMATIC_RESP 0x0032 +#define QMIDMS_ACTIVATE_MANUAL_REQ 0x0033 +#define QMIDMS_ACTIVATE_MANUAL_RESP 0x0033 +#define QMIDMS_UIM_GET_ICCID_REQ 0x003C +#define QMIDMS_UIM_GET_ICCID_RESP 0x003C +#define QMIDMS_UIM_GET_CK_STATUS_REQ 0x0040 +#define QMIDMS_UIM_GET_CK_STATUS_RESP 0x0040 +#define QMIDMS_UIM_SET_CK_PROTECTION_REQ 0x0041 +#define QMIDMS_UIM_SET_CK_PROTECTION_RESP 0x0041 +#define QMIDMS_UIM_UNBLOCK_CK_REQ 0x0042 +#define QMIDMS_UIM_UNBLOCK_CK_RESP 0x0042 +#define QMIDMS_UIM_GET_IMSI_REQ 0x0043 +#define QMIDMS_UIM_GET_IMSI_RESP 0x0043 +#define QMIDMS_UIM_GET_STATE_REQ 0x0044 +#define QMIDMS_UIM_GET_STATE_RESP 0x0044 +#define QMIDMS_GET_BAND_CAP_REQ 0x0045 +#define QMIDMS_GET_BAND_CAP_RESP 0x0045 + +#if 0 +typedef struct _QMIDMS_GET_DEVICE_MFR_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; +} QMIDMS_GET_DEVICE_MFR_REQ_MSG, *PQMIDMS_GET_DEVICE_MFR_REQ_MSG; + +typedef struct _QMIDMS_GET_DEVICE_MFR_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR TLV2Type; // 0x01 - required parameter + USHORT TLV2Length; // length of the mfr string + UCHAR DeviceManufacturer; // first byte of string +} QMIDMS_GET_DEVICE_MFR_RESP_MSG, *PQMIDMS_GET_DEVICE_MFR_RESP_MSG; + +typedef struct _QMIDMS_GET_DEVICE_MODEL_ID_REQ_MSG +{ + USHORT Type; // QMUX type 0x0004 + USHORT Length; +} QMIDMS_GET_DEVICE_MODEL_ID_REQ_MSG, *PQMIDMS_GET_DEVICE_MODEL_ID_REQ_MSG; + +typedef struct _QMIDMS_GET_DEVICE_MODEL_ID_RESP_MSG +{ + USHORT Type; // QMUX type 0x0004 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR TLV2Type; // 0x01 - required parameter + USHORT TLV2Length; // length of the modem id string + UCHAR DeviceModelID; // device model id +} QMIDMS_GET_DEVICE_MODEL_ID_RESP_MSG, *PQMIDMS_GET_DEVICE_MODEL_ID_RESP_MSG; +#endif + +typedef struct _QMIDMS_GET_DEVICE_REV_ID_REQ_MSG +{ + USHORT Type; // QMUX type 0x0005 + USHORT Length; +} __attribute__ ((packed)) QMIDMS_GET_DEVICE_REV_ID_REQ_MSG, *PQMIDMS_GET_DEVICE_REV_ID_REQ_MSG; + +typedef struct _DEVICE_REV_ID +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR RevisionID; +} __attribute__ ((packed)) DEVICE_REV_ID, *PDEVICE_REV_ID; + +#if 0 +typedef struct _QMIDMS_GET_DEVICE_REV_ID_RESP_MSG +{ + USHORT Type; // QMUX type 0x0023 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} QMIDMS_GET_DEVICE_REV_ID_RESP_MSG, *PQMIDMS_GET_DEVICE_REV_ID_RESP_MSG; + +typedef struct _QMIDMS_GET_MSISDN_REQ_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; +} QMIDMS_GET_MSISDN_REQ_MSG, *PQMIDMS_GET_MSISDN_REQ_MSG; + +typedef struct _QCTLV_DEVICE_VOICE_NUMBERS +{ + UCHAR TLVType; // as defined above + USHORT TLVLength; // 4/7/7 + UCHAR VoideNumberString; // ESN, IMEI, or MEID + +} QCTLV_DEVICE_VOICE_NUMBERS, *PQCTLV_DEVICE_VOICE_NUMBERS; + + +typedef struct _QMIDMS_GET_MSISDN_RESP_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG +} QMIDMS_GET_MSISDN_RESP_MSG, *PQMIDMS_GET_MSISDN_RESP_MSG; +#endif + +typedef struct _QMIDMS_UIM_GET_IMSI_REQ_MSG +{ + USHORT Type; + USHORT Length; +} __attribute__ ((packed)) QMIDMS_UIM_GET_IMSI_REQ_MSG, *PQMIDMS_UIM_GET_IMSI_REQ_MSG; + +typedef struct _QMIDMS_UIM_GET_IMSI_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR IMSI; +} __attribute__ ((packed)) QMIDMS_UIM_GET_IMSI_RESP_MSG, *PQMIDMS_UIM_GET_IMSI_RESP_MSG; + +#if 0 +typedef struct _QMIDMS_GET_DEVICE_SERIAL_NUMBERS_REQ_MSG +{ + USHORT Type; // QMUX type 0x0007 + USHORT Length; +} QMIDMS_GET_DEVICE_SERIAL_NUMBERS_REQ_MSG, *PQMIDMS_GET_DEVICE_SERIAL_NUMBERS_REQ_MSG; + +#define QCTLV_TYPE_SER_NUM_ESN 0x10 +#define QCTLV_TYPE_SER_NUM_IMEI 0x11 +#define QCTLV_TYPE_SER_NUM_MEID 0x12 + +typedef struct _QCTLV_DEVICE_SERIAL_NUMBER +{ + UCHAR TLVType; // as defined above + USHORT TLVLength; // 4/7/7 + UCHAR SerialNumberString; // ESN, IMEI, or MEID + +} QCTLV_DEVICE_SERIAL_NUMBER, *PQCTLV_DEVICE_SERIAL_NUMBER; + +typedef struct _QMIDMS_GET_DEVICE_SERIAL_NUMBERS_RESP_MSG +{ + USHORT Type; // QMUX type 0x0007 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + // followed by optional TLV +} QMIDMS_GET_DEVICE_SERIAL_NUMBERS_RESP_MSG, *PQMIDMS_GET_DEVICE_SERIAL_NUMBERS_RESP; + +typedef struct _QMIDMS_GET_DMS_BAND_CAP +{ + USHORT Type; + USHORT Length; +} QMIDMS_GET_BAND_CAP_REQ_MSG, *PQMIDMS_GET_BAND_CAP_REQ_MSG; + +typedef struct _QMIDMS_GET_BAND_CAP_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_NONE + // QMI_ERR_INTERNAL + // QMI_ERR_MALFORMED_MSG + // QMI_ERR_NO_MEMORY + + UCHAR TLV2Type; // 0x01 + USHORT TLV2Length; // 2 + ULONG64 BandCap; +} QMIDMS_GET_BAND_CAP_RESP_MSG, *PQMIDMS_GET_BAND_CAP_RESP; + +typedef struct _QMIDMS_GET_DEVICE_CAP_REQ_MSG +{ + USHORT Type; // QMUX type 0x0002 + USHORT Length; +} QMIDMS_GET_DEVICE_CAP_REQ_MSG, *PQMIDMS_GET_DEVICE_CAP_REQ_MSG; + +typedef struct _QMIDMS_GET_DEVICE_CAP_RESP_MSG +{ + USHORT Type; // QMUX type 0x0002 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMUX_RESULT_SUCCESS + // QMUX_RESULT_FAILURE + USHORT QMUXError; // QMUX_ERR_INVALID_ARG + // QMUX_ERR_NO_MEMORY + // QMUX_ERR_INTERNAL + // QMUX_ERR_FAULT + UCHAR TLV2Type; // 0x01 + USHORT TLV2Length; // 2 + + ULONG MaxTxChannelRate; + ULONG MaxRxChannelRate; + UCHAR VoiceCap; + UCHAR SimCap; + + UCHAR RadioIfListCnt; // #elements in radio interface list + UCHAR RadioIfList; // N 1-byte elements +} QMIDMS_GET_DEVICE_CAP_RESP_MSG, *PQMIDMS_GET_DEVICE_CAP_RESP_MSG; + +typedef struct _QMIDMS_GET_ACTIVATED_STATUS_REQ_MSG +{ + USHORT Type; // QMUX type 0x0002 + USHORT Length; +} QMIDMS_GET_ACTIVATED_STATUS_REQ_MSG, *PQMIDMS_GET_ACTIVATES_STATUD_REQ_MSG; + +typedef struct _QMIDMS_GET_ACTIVATED_STATUS_RESP_MSG +{ + USHORT Type; // QMUX type 0x0002 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMUX_RESULT_SUCCESS + // QMUX_RESULT_FAILURE + USHORT QMUXError; // QMUX_ERR_INVALID_ARG + // QMUX_ERR_NO_MEMORY + // QMUX_ERR_INTERNAL + // QMUX_ERR_FAULT + UCHAR TLV2Type; // 0x01 + USHORT TLV2Length; // 2 + + USHORT ActivatedStatus; +} QMIDMS_GET_ACTIVATED_STATUS_RESP_MSG, *PQMIDMS_GET_ACTIVATED_STATUS_RESP_MSG; + +typedef struct _QMIDMS_GET_OPERATING_MODE_REQ_MSG +{ + USHORT Type; // QMUX type 0x0002 + USHORT Length; +} QMIDMS_GET_OPERATING_MODE_REQ_MSG, *PQMIDMS_GET_OPERATING_MODE_REQ_MSG; + +typedef struct _OFFLINE_REASON +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT OfflineReason; +} OFFLINE_REASON, *POFFLINE_REASON; + +typedef struct _HARDWARE_RESTRICTED_MODE +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR HardwareControlledMode; +} HARDWARE_RESTRICTED_MODE, *PHARDWARE_RESTRICTED_MODE; + +typedef struct _QMIDMS_GET_OPERATING_MODE_RESP_MSG +{ + USHORT Type; // QMUX type 0x0002 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMUX_RESULT_SUCCESS + // QMUX_RESULT_FAILURE + USHORT QMUXError; // QMUX_ERR_INVALID_ARG + // QMUX_ERR_NO_MEMORY + // QMUX_ERR_INTERNAL + // QMUX_ERR_FAULT + UCHAR TLV2Type; // 0x01 + USHORT TLV2Length; // 2 + + UCHAR OperatingMode; +} QMIDMS_GET_OPERATING_MODE_RESP_MSG, *PQMIDMS_GET_OPERATING_MODE_RESP_MSG; + +typedef struct _QMIDMS_UIM_GET_ICCID_REQ_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; +} QMIDMS_UIM_GET_ICCID_REQ_MSG, *PQMIDMS_UIM_GET_ICCID_REQ_MSG; + +typedef struct _QMIDMS_UIM_GET_ICCID_RESP_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR TLV2Type; // 0x01 - required parameter + USHORT TLV2Length; // var + UCHAR ICCID; // String of voice number +} QMIDMS_UIM_GET_ICCID_RESP_MSG, *PQMIDMS_UIM_GET_ICCID_RESP_MSG; +#endif + +typedef struct _QMIDMS_SET_OPERATING_MODE_REQ_MSG +{ + USHORT Type; // QMUX type 0x0002 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + UCHAR OperatingMode; +} __attribute__ ((packed)) QMIDMS_SET_OPERATING_MODE_REQ_MSG, *PQMIDMS_SET_OPERATING_MODE_REQ_MSG; + +typedef struct _QMIDMS_SET_OPERATING_MODE_RESP_MSG +{ + USHORT Type; // QMUX type 0x0002 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMUX_RESULT_SUCCESS + // QMUX_RESULT_FAILURE + USHORT QMUXError; // QMUX_ERR_INVALID_ARG + // QMUX_ERR_NO_MEMORY + // QMUX_ERR_INTERNAL + // QMUX_ERR_FAULT +} __attribute__ ((packed)) QMIDMS_SET_OPERATING_MODE_RESP_MSG, *PQMIDMS_SET_OPERATING_MODE_RESP_MSG; + +#if 0 +typedef struct _QMIDMS_ACTIVATE_AUTOMATIC_REQ_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // + UCHAR ActivateCodelen; + UCHAR ActivateCode; +} QMIDMS_ACTIVATE_AUTOMATIC_REQ_MSG, *PQMIDMS_ACTIVATE_AUTOMATIC_REQ_MSG; + +typedef struct _QMIDMS_ACTIVATE_AUTOMATIC_RESP_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} QMIDMS_ACTIVATE_AUTOMATIC_RESP_MSG, *PQMIDMS_ACTIVATE_AUTOMATIC_RESP_MSG; + + +typedef struct _SPC_MSG +{ + UCHAR SPC[6]; + USHORT SID; +} SPC_MSG, *PSPC_MSG; + +typedef struct _MDN_MSG +{ + UCHAR MDNLEN; + UCHAR MDN; +} MDN_MSG, *PMDN_MSG; + +typedef struct _MIN_MSG +{ + UCHAR MINLEN; + UCHAR MIN; +} MIN_MSG, *PMIN_MSG; + +typedef struct _PRL_MSG +{ + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // + USHORT PRLLEN; + UCHAR PRL; +} PRL_MSG, *PPRL_MSG; + +typedef struct _MN_HA_KEY_MSG +{ + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // + UCHAR MN_HA_KEY_LEN; + UCHAR MN_HA_KEY; +} MN_HA_KEY_MSG, *PMN_HA_KEY_MSG; + +typedef struct _MN_AAA_KEY_MSG +{ + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // + UCHAR MN_AAA_KEY_LEN; + UCHAR MN_AAA_KEY; +} MN_AAA_KEY_MSG, *PMN_AAA_KEY_MSG; + +typedef struct _QMIDMS_ACTIVATE_MANUAL_REQ_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // + UCHAR Value; +} QMIDMS_ACTIVATE_MANUAL_REQ_MSG, *PQMIDMS_ACTIVATE_MANUAL_REQ_MSG; + +typedef struct _QMIDMS_ACTIVATE_MANUAL_RESP_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} QMIDMS_ACTIVATE_MANUAL_RESP_MSG, *PQMIDMS_ACTIVATE_MANUAL_RESP_MSG; +#endif + +typedef struct _QMIDMS_UIM_GET_STATE_REQ_MSG +{ + USHORT Type; + USHORT Length; +} __attribute__ ((packed)) QMIDMS_UIM_GET_STATE_REQ_MSG, *PQMIDMS_UIM_GET_STATE_REQ_MSG; + +typedef struct _QMIDMS_UIM_GET_STATE_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR UIMState; +} __attribute__ ((packed)) QMIDMS_UIM_GET_STATE_RESP_MSG, *PQMIDMS_UIM_GET_STATE_RESP_MSG; + +typedef struct _QMIDMS_UIM_GET_PIN_STATUS_REQ_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; +} __attribute__ ((packed)) QMIDMS_UIM_GET_PIN_STATUS_REQ_MSG, *PQMIDMS_UIM_GET_PIN_STATUS_REQ_MSG; + +typedef struct _QMIDMS_UIM_PIN_STATUS +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR PINStatus; + UCHAR PINVerifyRetriesLeft; + UCHAR PINUnblockRetriesLeft; +} __attribute__ ((packed)) QMIDMS_UIM_PIN_STATUS, *PQMIDMS_UIM_PIN_STATUS; + +#define QMI_PIN_STATUS_NOT_INIT 0 +#define QMI_PIN_STATUS_NOT_VERIF 1 +#define QMI_PIN_STATUS_VERIFIED 2 +#define QMI_PIN_STATUS_DISABLED 3 +#define QMI_PIN_STATUS_BLOCKED 4 +#define QMI_PIN_STATUS_PERM_BLOCKED 5 +#define QMI_PIN_STATUS_UNBLOCKED 6 +#define QMI_PIN_STATUS_CHANGED 7 + + +typedef struct _QMIDMS_UIM_GET_PIN_STATUS_RESP_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR PinStatus; +} __attribute__ ((packed)) QMIDMS_UIM_GET_PIN_STATUS_RESP_MSG, *PQMIDMS_UIM_GET_PIN_STATUS_RESP_MSG; + +#if 0 +typedef struct _QMIDMS_UIM_GET_CK_STATUS_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR Facility; +} QMIDMS_UIM_GET_CK_STATUS_REQ_MSG, *PQMIDMS_UIM_GET_CK_STATUS_REQ_MSG; + + +typedef struct _QMIDMS_UIM_CK_STATUS +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR FacilityStatus; + UCHAR FacilityVerifyRetriesLeft; + UCHAR FacilityUnblockRetriesLeft; +} QMIDMS_UIM_CK_STATUS, *PQMIDMS_UIM_CK_STATUS; + +typedef struct _QMIDMS_UIM_CK_OPERATION_STATUS +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR OperationBlocking; +} QMIDMS_UIM_CK_OPERATION_STATUS, *PQMIDMS_UIM_CK_OPERATION_STATUS; + +typedef struct _QMIDMS_UIM_GET_CK_STATUS_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR CkStatus; +} QMIDMS_UIM_GET_CK_STATUS_RESP_MSG, *PQMIDMS_UIM_GET_CK_STATUS_RESP_MSG; +#endif + +typedef struct _QMIDMS_UIM_VERIFY_PIN_REQ_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + UCHAR PINID; + UCHAR PINLen; + UCHAR PINValue; +} __attribute__ ((packed)) QMIDMS_UIM_VERIFY_PIN_REQ_MSG, *PQMIDMS_UIM_VERIFY_PIN_REQ_MSG; + +typedef struct _QMIDMS_UIM_VERIFY_PIN_RESP_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR PINVerifyRetriesLeft; + UCHAR PINUnblockRetriesLeft; +} __attribute__ ((packed)) QMIDMS_UIM_VERIFY_PIN_RESP_MSG, *PQMIDMS_UIM_VERIFY_PIN_RESP_MSG; + +#if 0 +typedef struct _QMIDMS_UIM_SET_PIN_PROTECTION_REQ_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + UCHAR PINID; + UCHAR ProtectionSetting; + UCHAR PINLen; + UCHAR PINValue; +} QMIDMS_UIM_SET_PIN_PROTECTION_REQ_MSG, *PQMIDMS_UIM_SET_PIN_PROTECTION_REQ_MSG; + +typedef struct _QMIDMS_UIM_SET_PIN_PROTECTION_RESP_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR PINVerifyRetriesLeft; + UCHAR PINUnblockRetriesLeft; +} QMIDMS_UIM_SET_PIN_PROTECTION_RESP_MSG, *PQMIDMS_UIM_SET_PIN_PROTECTION_RESP_MSG; + +typedef struct _QMIDMS_UIM_SET_CK_PROTECTION_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR Facility; + UCHAR FacilityState; + UCHAR FacliltyLen; + UCHAR FacliltyValue; +} QMIDMS_UIM_SET_CK_PROTECTION_REQ_MSG, *PQMIDMS_UIM_SET_CK_PROTECTION_REQ_MSG; + +typedef struct _QMIDMS_UIM_SET_CK_PROTECTION_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR FacilityRetriesLeft; +} QMIDMS_UIM_SET_CK_PROTECTION_RESP_MSG, *PQMIDMS_UIM_SET_CK_PROTECTION_RESP_MSG; + + +typedef struct _UIM_PIN +{ + UCHAR PinLength; + UCHAR PinValue; +} UIM_PIN, *PUIM_PIN; + +typedef struct _QMIDMS_UIM_CHANGE_PIN_REQ_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + UCHAR PINID; + UCHAR PinDetails; +} QMIDMS_UIM_CHANGE_PIN_REQ_MSG, *PQMIDMS_UIM_CHANGE_PIN_REQ_MSG; + +typedef struct QMIDMS_UIM_CHANGE_PIN_RESP_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR PINVerifyRetriesLeft; + UCHAR PINUnblockRetriesLeft; +} QMIDMS_UIM_CHANGE_PIN_RESP_MSG, *PQMIDMS_UIM_CHANGE_PIN_RESP_MSG; + +typedef struct _UIM_PUK +{ + UCHAR PukLength; + UCHAR PukValue; +} UIM_PUK, *PUIM_PUK; + +typedef struct _QMIDMS_UIM_UNBLOCK_PIN_REQ_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + UCHAR PINID; + UCHAR PinDetails; +} QMIDMS_UIM_UNBLOCK_PIN_REQ_MSG, *PQMIDMS_UIM_BLOCK_PIN_REQ_MSG; + +typedef struct QMIDMS_UIM_UNBLOCK_PIN_RESP_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR PINVerifyRetriesLeft; + UCHAR PINUnblockRetriesLeft; +} QMIDMS_UIM_UNBLOCK_PIN_RESP_MSG, *PQMIDMS_UIM_UNBLOCK_PIN_RESP_MSG; + +typedef struct _QMIDMS_UIM_UNBLOCK_CK_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR Facility; + UCHAR FacliltyUnblockLen; + UCHAR FacliltyUnblockValue; +} QMIDMS_UIM_UNBLOCK_CK_REQ_MSG, *PQMIDMS_UIM_BLOCK_CK_REQ_MSG; + +typedef struct QMIDMS_UIM_UNBLOCK_CK_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR FacilityUnblockRetriesLeft; +} QMIDMS_UIM_UNBLOCK_CK_RESP_MSG, *PQMIDMS_UIM_UNBLOCK_CK_RESP_MSG; + +typedef struct _QMIDMS_SET_EVENT_REPORT_REQ_MSG +{ + USHORT Type; + USHORT Length; +} QMIDMS_SET_EVENT_REPORT_REQ_MSG, *PQMIDMS_SET_EVENT_REPORT_REQ_MSG; + +typedef struct _QMIDMS_SET_EVENT_REPORT_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG +} QMIDMS_SET_EVENT_REPORT_RESP_MSG, *PQMIDMS_SET_EVENT_REPORT_RESP_MSG; + +typedef struct _PIN_STATUS +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR ReportPinState; +} PIN_STATUS, *PPIN_STATUS; + +typedef struct _POWER_STATUS +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR PowerStatus; + UCHAR BatteryLvl; +} POWER_STATUS, *PPOWER_STATUS; + +typedef struct _ACTIVATION_STATE +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT ActivationState; +} ACTIVATION_STATE, *PACTIVATION_STATE; + +typedef struct _ACTIVATION_STATE_REQ +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR ActivationState; +} ACTIVATION_STATE_REQ, *PACTIVATION_STATE_REQ; + +typedef struct _OPERATING_MODE +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR OperatingMode; +} OPERATING_MODE, *POPERATING_MODE; + +typedef struct _UIM_STATE +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR UIMState; +} UIM_STATE, *PUIM_STATE; + +typedef struct _WIRELESS_DISABLE_STATE +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR WirelessDisableState; +} WIRELESS_DISABLE_STATE, *PWIRELESS_DISABLE_STATE; + +typedef struct _QMIDMS_EVENT_REPORT_IND_MSG +{ + USHORT Type; + USHORT Length; +} QMIDMS_EVENT_REPORT_IND_MSG, *PQMIDMS_EVENT_REPORT_IND_MSG; +#endif + +// ============================ END OF DMS =============================== + +// ======================= QOS ============================== +typedef struct _MPIOC_DEV_INFO MPIOC_DEV_INFO, *PMPIOC_DEV_INFO; + +#define QMI_QOS_SET_EVENT_REPORT_REQ 0x0001 +#define QMI_QOS_SET_EVENT_REPORT_RESP 0x0001 +#define QMI_QOS_EVENT_REPORT_IND 0x0001 + +#if 0 +typedef struct _QMI_QOS_SET_EVENT_REPORT_REQ_MSG +{ + USHORT Type; // QMUX type 0x0001 + USHORT Length; + // UCHAR TLVType; // 0x01 - physical link state + // USHORT TLVLength; // 1 + // UCHAR PhyLinkStatusRpt; // 0-enable; 1-disable + UCHAR TLVType2; // 0x02 = global flow reporting + USHORT TLVLength2; // 1 + UCHAR GlobalFlowRpt; // 1-enable; 0-disable +} QMI_QOS_SET_EVENT_REPORT_REQ_MSG, *PQMI_QOS_SET_EVENT_REPORT_REQ_MSG; + +typedef struct _QMI_QOS_SET_EVENT_REPORT_RESP_MSG +{ + USHORT Type; // QMUX type 0x0010 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMUX_RESULT_SUCCESS + // QMUX_RESULT_FAILURE + USHORT QMUXError; // QMUX_ERR_INVALID_ARG + // QMUX_ERR_NO_MEMORY + // QMUX_ERR_INTERNAL + // QMUX_ERR_FAULT +} QMI_QOS_SET_EVENT_REPORT_RESP_MSG, *PQMI_QOS_SET_EVENT_REPORT_RESP_MSG; + +typedef struct _QMI_QOS_EVENT_REPORT_IND_MSG +{ + USHORT Type; // QMUX type 0x0001 + USHORT Length; + UCHAR TLVs; +} QMI_QOS_EVENT_REPORT_IND_MSG, *PQMI_QOS_EVENT_REPORT_IND_MSG; + +#define QOS_EVENT_RPT_IND_FLOW_ACTIVATED 0x01 +#define QOS_EVENT_RPT_IND_FLOW_MODIFIED 0x02 +#define QOS_EVENT_RPT_IND_FLOW_DELETED 0x03 +#define QOS_EVENT_RPT_IND_FLOW_SUSPENDED 0x04 +#define QOS_EVENT_RPT_IND_FLOW_ENABLED 0x05 +#define QOS_EVENT_RPT_IND_FLOW_DISABLED 0x06 + +#define QOS_EVENT_RPT_IND_TLV_PHY_LINK_STATE_TYPE 0x01 +#define QOS_EVENT_RPT_IND_TLV_GLOBAL_FL_RPT_STATE 0x10 +#define QOS_EVENT_RPT_IND_TLV_GLOBAL_FL_RPT_TYPE 0x10 +#define QOS_EVENT_RPT_IND_TLV_TX_FLOW_TYPE 0x11 +#define QOS_EVENT_RPT_IND_TLV_RX_FLOW_TYPE 0x12 +#define QOS_EVENT_RPT_IND_TLV_TX_FILTER_TYPE 0x13 +#define QOS_EVENT_RPT_IND_TLV_RX_FILTER_TYPE 0x14 +#define QOS_EVENT_RPT_IND_TLV_FLOW_SPEC 0x10 +#define QOS_EVENT_RPT_IND_TLV_FILTER_SPEC 0x10 + +typedef struct _QOS_EVENT_RPT_IND_TLV_PHY_LINK_STATE +{ + UCHAR TLVType; // 0x01 + USHORT TLVLength; // 1 + UCHAR PhyLinkState; // 0-dormant, 1-active +} QOS_EVENT_RPT_IND_TLV_PHY_LINK_STATE, *PQOS_EVENT_RPT_IND_TLV_PHY_LINK_STATE; + +typedef struct _QOS_EVENT_RPT_IND_TLV_GLOBAL_FL_RPT +{ + UCHAR TLVType; // 0x10 + USHORT TLVLength; // 6 + ULONG QosId; + UCHAR NewFlow; // 1: newly added flow; 0: existing flow + UCHAR StateChange; // 1: activated; 2: modified; 3: deleted; + // 4: suspended(delete); 5: enabled; 6: disabled +} QOS_EVENT_RPT_IND_TLV_GLOBAL_FL_RPT, *PQOS_EVENT_RPT_IND_TLV_GLOBAL_FL_RPT; + +// QOS Flow + +typedef struct _QOS_EVENT_RPT_IND_TLV_FLOW +{ + UCHAR TLVType; // 0x10-TX flow; 0x11-RX flow + USHORT TLVLength; // var + // embedded TLV's +} QOS_EVENT_RPT_IND_TLV_TX_FLOW, *PQOS_EVENT_RPT_IND_TLV_TX_FLOW; + +#define QOS_FLOW_TLV_IP_FLOW_IDX_TYPE 0x10 +#define QOS_FLOW_TLV_IP_FLOW_TRAFFIC_CLASS_TYPE 0x11 +#define QOS_FLOW_TLV_IP_FLOW_DATA_RATE_MIN_MAX_TYPE 0x12 +#define QOS_FLOW_TLV_IP_FLOW_DATA_RATE_TOKEN_BUCKET_TYPE 0x13 +#define QOS_FLOW_TLV_IP_FLOW_LATENCY_TYPE 0x14 +#define QOS_FLOW_TLV_IP_FLOW_JITTER_TYPE 0x15 +#define QOS_FLOW_TLV_IP_FLOW_PKT_ERR_RATE_TYPE 0x16 +#define QOS_FLOW_TLV_IP_FLOW_MIN_PKT_SIZE_TYPE 0x17 +#define QOS_FLOW_TLV_IP_FLOW_MAX_PKT_SIZE_TYPE 0x18 +#define QOS_FLOW_TLV_IP_FLOW_3GPP_BIT_ERR_RATE_TYPE 0x19 +#define QOS_FLOW_TLV_IP_FLOW_3GPP_TRAF_PRIORITY_TYPE 0x1A +#define QOS_FLOW_TLV_IP_FLOW_3GPP2_PROFILE_ID_TYPE 0x1B + +typedef struct _QOS_FLOW_TLV_IP_FLOW_IDX +{ + UCHAR TLVType; // 0x10 + USHORT TLVLength; // 1 + UCHAR IpFlowIndex; +} QOS_FLOW_TLV_IP_FLOW_IDX, *PQOS_FLOW_TLV_IP_FLOW_IDX; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_TRAFFIC_CLASS +{ + UCHAR TLVType; // 0x11 + USHORT TLVLength; // 1 + UCHAR TrafficClass; +} QOS_FLOW_TLV_IP_FLOW_TRAFFIC_CLASS, *PQOS_FLOW_TLV_IP_FLOW_TRAFFIC_CLASS; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_DATA_RATE_MIN_MAX +{ + UCHAR TLVType; // 0x12 + USHORT TLVLength; // 8 + ULONG DataRateMax; + ULONG GuaranteedRate; +} QOS_FLOW_TLV_IP_FLOW_DATA_RATE_MIN_MAX, *PQOS_FLOW_TLV_IP_FLOW_DATA_RATE_MIN_MAX; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_DATA_RATE_TOKEN_BUCKET +{ + UCHAR TLVType; // 0x13 + USHORT TLVLength; // 12 + ULONG PeakRate; + ULONG TokenRate; + ULONG BucketSize; +} QOS_FLOW_TLV_IP_FLOW_DATA_RATE_TOKEN_BUCKET, *PQOS_FLOW_TLV_IP_FLOW_DATA_RATE_TOKEN_BUCKET; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_LATENCY +{ + UCHAR TLVType; // 0x14 + USHORT TLVLength; // 4 + ULONG IpFlowLatency; +} QOS_FLOW_TLV_IP_FLOW_LATENCY, *PQOS_FLOW_TLV_IP_FLOW_LATENCY; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_JITTER +{ + UCHAR TLVType; // 0x15 + USHORT TLVLength; // 4 + ULONG IpFlowJitter; +} QOS_FLOW_TLV_IP_FLOW_JITTER, *PQOS_FLOW_TLV_IP_FLOW_JITTER; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_PKT_ERR_RATE +{ + UCHAR TLVType; // 0x16 + USHORT TLVLength; // 4 + USHORT ErrRateMultiplier; + USHORT ErrRateExponent; +} QOS_FLOW_TLV_IP_FLOW_PKT_ERR_RATE, *PQOS_FLOW_TLV_IP_FLOW_PKT_ERR_RATE; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_MIN_PKT_SIZE +{ + UCHAR TLVType; // 0x17 + USHORT TLVLength; // 4 + ULONG MinPolicedPktSize; +} QOS_FLOW_TLV_IP_FLOW_MIN_PKT_SIZE, *PQOS_FLOW_TLV_IP_FLOW_MIN_PKT_SIZE; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_MAX_PKT_SIZE +{ + UCHAR TLVType; // 0x18 + USHORT TLVLength; // 4 + ULONG MaxAllowedPktSize; +} QOS_FLOW_TLV_IP_FLOW_MAX_PKT_SIZE, *PQOS_FLOW_TLV_IP_FLOW_MAX_PKT_SIZE; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_3GPP_BIT_ERR_RATE +{ + UCHAR TLVType; // 0x19 + USHORT TLVLength; // 1 + UCHAR ResidualBitErrorRate; +} QOS_FLOW_TLV_IP_FLOW_3GPP_BIT_ERR_RATE, *PQOS_FLOW_TLV_IP_FLOW_3GPP_BIT_ERR_RATE; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_3GPP_TRAF_PRIORITY +{ + UCHAR TLVType; // 0x1A + USHORT TLVLength; // 1 + UCHAR TrafficHandlingPriority; +} QOS_FLOW_TLV_IP_FLOW_3GPP_TRAF_PRIORITY, *PQOS_FLOW_TLV_IP_FLOW_3GPP_TRAF_PRIORITY; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_3GPP2_PROFILE_ID +{ + UCHAR TLVType; // 0x1B + USHORT TLVLength; // 2 + USHORT ProfileId; +} QOS_FLOW_TLV_IP_FLOW_3GPP2_PROFILE_ID, *PQOS_FLOW_TLV_IP_FLOW_3GPP2_PROFILE_ID; + +// QOS Filter + +#define QOS_FILTER_TLV_IP_FILTER_IDX_TYPE 0x10 +#define QOS_FILTER_TLV_IP_VERSION_TYPE 0x11 +#define QOS_FILTER_TLV_IPV4_SRC_ADDR_TYPE 0x12 +#define QOS_FILTER_TLV_IPV4_DEST_ADDR_TYPE 0x13 +#define QOS_FILTER_TLV_NEXT_HDR_PROTOCOL_TYPE 0x14 +#define QOS_FILTER_TLV_IPV4_TYPE_OF_SERVICE_TYPE 0x15 +#define QOS_FILTER_TLV_TCP_UDP_PORT_SRC_TCP_TYPE 0x1B +#define QOS_FILTER_TLV_TCP_UDP_PORT_DEST_TCP_TYPE 0x1C +#define QOS_FILTER_TLV_TCP_UDP_PORT_SRC_UDP_TYPE 0x1D +#define QOS_FILTER_TLV_TCP_UDP_PORT_DEST_UDP_TYPE 0x1E +#define QOS_FILTER_TLV_ICMP_FILTER_MSG_TYPE_TYPE 0x1F +#define QOS_FILTER_TLV_ICMP_FILTER_MSG_CODE_TYPE 0x20 +#define QOS_FILTER_TLV_TCP_UDP_PORT_SRC_TYPE 0x24 +#define QOS_FILTER_TLV_TCP_UDP_PORT_DEST_TYPE 0x25 + +typedef struct _QOS_EVENT_RPT_IND_TLV_FILTER +{ + UCHAR TLVType; // 0x12-TX filter; 0x13-RX filter + USHORT TLVLength; // var + // embedded TLV's +} QOS_EVENT_RPT_IND_TLV_RX_FILTER, *PQOS_EVENT_RPT_IND_TLV_RX_FILTER; + +typedef struct _QOS_FILTER_TLV_IP_FILTER_IDX +{ + UCHAR TLVType; // 0x10 + USHORT TLVLength; // 1 + UCHAR IpFilterIndex; +} QOS_FILTER_TLV_IP_FILTER_IDX, *PQOS_FILTER_TLV_IP_FILTER_IDX; + +typedef struct _QOS_FILTER_TLV_IP_VERSION +{ + UCHAR TLVType; // 0x11 + USHORT TLVLength; // 1 + UCHAR IpVersion; +} QOS_FILTER_TLV_IP_VERSION, *PQOS_FILTER_TLV_IP_VERSION; + +typedef struct _QOS_FILTER_TLV_IPV4_SRC_ADDR +{ + UCHAR TLVType; // 0x12 + USHORT TLVLength; // 8 + ULONG IpSrcAddr; + ULONG IpSrcSubnetMask; +} QOS_FILTER_TLV_IPV4_SRC_ADDR, *PQOS_FILTER_TLV_IPV4_SRC_ADDR; + +typedef struct _QOS_FILTER_TLV_IPV4_DEST_ADDR +{ + UCHAR TLVType; // 0x13 + USHORT TLVLength; // 8 + ULONG IpDestAddr; + ULONG IpDestSubnetMask; +} QOS_FILTER_TLV_IPV4_DEST_ADDR, *PQOS_FILTER_TLV_IPV4_DEST_ADDR; + +typedef struct _QOS_FILTER_TLV_NEXT_HDR_PROTOCOL +{ + UCHAR TLVType; // 0x14 + USHORT TLVLength; // 1 + UCHAR NextHdrProtocol; +} QOS_FILTER_TLV_NEXT_HDR_PROTOCOL, *PQOS_FILTER_TLV_NEXT_HDR_PROTOCOL; + +typedef struct _QOS_FILTER_TLV_IPV4_TYPE_OF_SERVICE +{ + UCHAR TLVType; // 0x15 + USHORT TLVLength; // 2 + UCHAR Ipv4TypeOfService; + UCHAR Ipv4TypeOfServiceMask; +} QOS_FILTER_TLV_IPV4_TYPE_OF_SERVICE, *PQOS_FILTER_TLV_IPV4_TYPE_OF_SERVICE; + +typedef struct _QOS_FILTER_TLV_TCP_UDP_PORT +{ + UCHAR TLVType; // source port: 0x1B-TCP; 0x1D-UDP + // dest port: 0x1C-TCP; 0x1E-UDP + USHORT TLVLength; // 4 + USHORT FilterPort; + USHORT FilterPortRange; +} QOS_FILTER_TLV_TCP_UDP_PORT, *PQOS_FILTER_TLV_TCP_UDP_PORT; + +typedef struct _QOS_FILTER_TLV_ICMP_FILTER_MSG_TYPE +{ + UCHAR TLVType; // 0x1F + USHORT TLVLength; // 1 + UCHAR IcmpFilterMsgType; +} QOS_FILTER_TLV_ICMP_FILTER_MSG_TYPE, *PQOS_FILTER_TLV_ICMP_FILTER_MSG_TYPE; + +typedef struct _QOS_FILTER_TLV_ICMP_FILTER_MSG_CODE +{ + UCHAR TLVType; // 0x20 + USHORT TLVLength; // 1 + UCHAR IcmpFilterMsgCode; +} QOS_FILTER_TLV_ICMP_FILTER_MSG_CODE, *PQOS_FILTER_TLV_ICMP_FILTER_MSG_CODE; + +#define QOS_FILTER_PRECEDENCE_INVALID 256 +#define QOS_FILTER_TLV_PRECEDENCE_TYPE 0x22 +#define QOS_FILTER_TLV_ID_TYPE 0x23 + +typedef struct _QOS_FILTER_TLV_PRECEDENCE +{ + UCHAR TLVType; // 0x22 + USHORT TLVLength; // 2 + USHORT Precedence; // precedence of the filter +} QOS_FILTER_TLV_PRECEDENCE, *PQOS_FILTER_TLV_PRECEDENCE; + +typedef struct _QOS_FILTER_TLV_ID +{ + UCHAR TLVType; // 0x23 + USHORT TLVLength; // 2 + USHORT FilterId; // filter ID +} QOS_FILTER_TLV_ID, *PQOS_FILTER_TLV_ID; + +#ifdef QCQOS_IPV6 + +#define QOS_FILTER_TLV_IPV6_SRC_ADDR_TYPE 0x16 +#define QOS_FILTER_TLV_IPV6_DEST_ADDR_TYPE 0x17 +#define QOS_FILTER_TLV_IPV6_NEXT_HDR_PROTOCOL_TYPE 0x14 // same as IPV4 +#define QOS_FILTER_TLV_IPV6_TRAFFIC_CLASS_TYPE 0x19 +#define QOS_FILTER_TLV_IPV6_FLOW_LABEL_TYPE 0x1A + +typedef struct _QOS_FILTER_TLV_IPV6_SRC_ADDR +{ + UCHAR TLVType; // 0x16 + USHORT TLVLength; // 17 + UCHAR IpSrcAddr[16]; + UCHAR IpSrcAddrPrefixLen; // [0..128] +} QOS_FILTER_TLV_IPV6_SRC_ADDR, *PQOS_FILTER_TLV_IPV6_SRC_ADDR; + +typedef struct _QOS_FILTER_TLV_IPV6_DEST_ADDR +{ + UCHAR TLVType; // 0x17 + USHORT TLVLength; // 17 + UCHAR IpDestAddr[16]; + UCHAR IpDestAddrPrefixLen; // [0..128] +} QOS_FILTER_TLV_IPV6_DEST_ADDR, *PQOS_FILTER_TLV_IPV6_DEST_ADDR; + +#define QOS_FILTER_IPV6_NEXT_HDR_PROTOCOL_TCP 0x06 +#define QOS_FILTER_IPV6_NEXT_HDR_PROTOCOL_UDP 0x11 + +typedef struct _QOS_FILTER_TLV_IPV6_TRAFFIC_CLASS +{ + UCHAR TLVType; // 0x19 + USHORT TLVLength; // 2 + UCHAR TrafficClass; + UCHAR TrafficClassMask; // compare the first 6 bits only +} QOS_FILTER_TLV_IPV6_TRAFFIC_CLASS, *PQOS_FILTER_TLV_IPV6_TRAFFIC_CLASS; + +typedef struct _QOS_FILTER_TLV_IPV6_FLOW_LABEL +{ + UCHAR TLVType; // 0x1A + USHORT TLVLength; // 4 + ULONG FlowLabel; +} QOS_FILTER_TLV_IPV6_FLOW_LABEL, *PQOS_FILTER_TLV_IPV6_FLOW_LABEL; + +#endif // QCQOS_IPV6 +#endif + +// ======================= WMS ============================== +#define QMIWMS_SET_EVENT_REPORT_REQ 0x0001 +#define QMIWMS_SET_EVENT_REPORT_RESP 0x0001 +#define QMIWMS_EVENT_REPORT_IND 0x0001 +#define QMIWMS_RAW_SEND_REQ 0x0020 +#define QMIWMS_RAW_SEND_RESP 0x0020 +#define QMIWMS_RAW_WRITE_REQ 0x0021 +#define QMIWMS_RAW_WRITE_RESP 0x0021 +#define QMIWMS_RAW_READ_REQ 0x0022 +#define QMIWMS_RAW_READ_RESP 0x0022 +#define QMIWMS_MODIFY_TAG_REQ 0x0023 +#define QMIWMS_MODIFY_TAG_RESP 0x0023 +#define QMIWMS_DELETE_REQ 0x0024 +#define QMIWMS_DELETE_RESP 0x0024 +#define QMIWMS_GET_MESSAGE_PROTOCOL_REQ 0x0030 +#define QMIWMS_GET_MESSAGE_PROTOCOL_RESP 0x0030 +#define QMIWMS_LIST_MESSAGES_REQ 0x0031 +#define QMIWMS_LIST_MESSAGES_RESP 0x0031 +#define QMIWMS_GET_SMSC_ADDRESS_REQ 0x0034 +#define QMIWMS_GET_SMSC_ADDRESS_RESP 0x0034 +#define QMIWMS_SET_SMSC_ADDRESS_REQ 0x0035 +#define QMIWMS_SET_SMSC_ADDRESS_RESP 0x0035 +#define QMIWMS_GET_STORE_MAX_SIZE_REQ 0x0036 +#define QMIWMS_GET_STORE_MAX_SIZE_RESP 0x0036 + + +#define WMS_MESSAGE_PROTOCOL_CDMA 0x00 +#define WMS_MESSAGE_PROTOCOL_WCDMA 0x01 + +#if 0 +typedef struct _QMIWMS_GET_MESSAGE_PROTOCOL_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; +} QMIWMS_GET_MESSAGE_PROTOCOL_REQ_MSG, *PQMIWMS_GET_MESSAGE_PROTOCOL_REQ_MSG; + +typedef struct _QMIWMS_GET_MESSAGE_PROTOCOL_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR MessageProtocol; +} QMIWMS_GET_MESSAGE_PROTOCOL_RESP_MSG, *PQMIWMS_GET_MESSAGE_PROTOCOL_RESP_MSG; + +typedef struct _QMIWMS_GET_STORE_MAX_SIZE_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR StorageType; +} QMIWMS_GET_STORE_MAX_SIZE_REQ_MSG, *PQMIWMS_GET_STORE_MAX_SIZE_REQ_MSG; + +typedef struct _QMIWMS_GET_STORE_MAX_SIZE_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLV2Type; + USHORT TLV2Length; + ULONG MemStoreMaxSize; +} QMIWMS_GET_STORE_MAX_SIZE_RESP_MSG, *PQMIWMS_GET_STORE_MAX_SIZE_RESP_MSG; + +typedef struct _REQUEST_TAG +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR TagType; +} REQUEST_TAG, *PREQUEST_TAG; + +typedef struct _QMIWMS_LIST_MESSAGES_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR StorageType; +} QMIWMS_LIST_MESSAGES_REQ_MSG, *PQMIWMS_LIST_MESSAGES_REQ_MSG; + +typedef struct _QMIWMS_MESSAGE +{ + ULONG MessageIndex; + UCHAR TagType; +} QMIWMS_MESSAGE, *PQMIWMS_MESSAGE; + +typedef struct _QMIWMS_LIST_MESSAGES_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLV2Type; + USHORT TLV2Length; + ULONG NumMessages; +} QMIWMS_LIST_MESSAGES_RESP_MSG, *PQMIWMS_LIST_MESSAGES_RESP_MSG; + +typedef struct _QMIWMS_RAW_READ_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR StorageType; + ULONG MemoryIndex; +} QMIWMS_RAW_READ_REQ_MSG, *PQMIWMS_RAW_READ_REQ_MSG; + +typedef struct _QMIWMS_RAW_READ_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR TagType; + UCHAR Format; + USHORT MessageLength; + UCHAR Message; +} QMIWMS_RAW_READ_RESP_MSG, *PQMIWMS_RAW_READ_RESP_MSG; + +typedef struct _QMIWMS_MODIFY_TAG_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR StorageType; + ULONG MemoryIndex; + UCHAR TagType; +} QMIWMS_MODIFY_TAG_REQ_MSG, *PQMIWMS_MODIFY_TAG_REQ_MSG; + +typedef struct _QMIWMS_MODIFY_TAG_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} QMIWMS_MODIFY_TAG_RESP_MSG, *PQMIWMS_MODIFY_TAG_RESP_MSG; + +typedef struct _QMIWMS_RAW_SEND_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR SmsFormat; + USHORT SmsLength; + UCHAR SmsMessage; +} QMIWMS_RAW_SEND_REQ_MSG, *PQMIWMS_RAW_SEND_REQ_MSG; + +typedef struct _RAW_SEND_CAUSE_CODE +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT CauseCode; +} RAW_SEND_CAUSE_CODE, *PRAW_SEND_CAUSE_CODE; + + +typedef struct _QMIWMS_RAW_SEND_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} QMIWMS_RAW_SEND_RESP_MSG, *PQMIWMS_RAW_SEND_RESP_MSG; + + +typedef struct _WMS_DELETE_MESSAGE_INDEX +{ + UCHAR TLVType; + USHORT TLVLength; + ULONG MemoryIndex; +} WMS_DELETE_MESSAGE_INDEX, *PWMS_DELETE_MESSAGE_INDEX; + +typedef struct _WMS_DELETE_MESSAGE_TAG +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR MessageTag; +} WMS_DELETE_MESSAGE_TAG, *PWMS_DELETE_MESSAGE_TAG; + +typedef struct _QMIWMS_DELETE_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR StorageType; +} QMIWMS_DELETE_REQ_MSG, *PQMIWMS_DELETE_REQ_MSG; + +typedef struct _QMIWMS_DELETE_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} QMIWMS_DELETE_RESP_MSG, *PQMIWMS_DELETE_RESP_MSG; + + +typedef struct _QMIWMS_GET_SMSC_ADDRESS_REQ_MSG +{ + USHORT Type; + USHORT Length; +} QMIWMS_GET_SMSC_ADDRESS_REQ_MSG, *PQMIWMS_GET_SMSC_ADDRESS_REQ_MSG; + +typedef struct _QMIWMS_SMSC_ADDRESS +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR SMSCAddressType[3]; + UCHAR SMSCAddressLength; + UCHAR SMSCAddressDigits; +} QMIWMS_SMSC_ADDRESS, *PQMIWMS_SMSC_ADDRESS; + + +typedef struct _QMIWMS_GET_SMSC_ADDRESS_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR SMSCAddress; +} QMIWMS_GET_SMSC_ADDRESS_RESP_MSG, *PQMIWMS_GET_SMSC_ADDRESS_RESP_MSG; + +typedef struct _QMIWMS_SET_SMSC_ADDRESS_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR SMSCAddress; +} QMIWMS_SET_SMSC_ADDRESS_REQ_MSG, *PQMIWMS_SET_SMSC_ADDRESS_REQ_MSG; + +typedef struct _QMIWMS_SET_SMSC_ADDRESS_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} QMIWMS_SET_SMSC_ADDRESS_RESP_MSG, *PQMIWMS_SET_SMSC_ADDRESS_RESP_MSG; + +typedef struct _QMIWMS_SET_EVENT_REPORT_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR ReportNewMessage; +} QMIWMS_SET_EVENT_REPORT_REQ_MSG, *PQMIWMS_SET_EVENT_REPORT_REQ_MSG; + +typedef struct _QMIWMS_SET_EVENT_REPORT_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} QMIWMS_SET_EVENT_REPORT_RESP_MSG, *PQMIWMS_SET_EVENT_REPORT_RESP_MSG; + +typedef struct _QMIWMS_EVENT_REPORT_IND_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR StorageType; + ULONG StorageIndex; +} QMIWMS_EVENT_REPORT_IND_MSG, *PQMIWMS_EVENT_REPORT_IND_MSG; +#endif + +// ======================= End of WMS ============================== + + +// ======================= NAS ============================== +#define QMINAS_SET_EVENT_REPORT_REQ 0x0002 +#define QMINAS_SET_EVENT_REPORT_RESP 0x0002 +#define QMINAS_EVENT_REPORT_IND 0x0002 +#define QMINAS_GET_SIGNAL_STRENGTH_REQ 0x0020 +#define QMINAS_GET_SIGNAL_STRENGTH_RESP 0x0020 +#define QMINAS_PERFORM_NETWORK_SCAN_REQ 0x0021 +#define QMINAS_PERFORM_NETWORK_SCAN_RESP 0x0021 +#define QMINAS_INITIATE_NW_REGISTER_REQ 0x0022 +#define QMINAS_INITIATE_NW_REGISTER_RESP 0x0022 +#define QMINAS_INITIATE_ATTACH_REQ 0x0023 +#define QMINAS_INITIATE_ATTACH_RESP 0x0023 +#define QMINAS_GET_SERVING_SYSTEM_REQ 0x0024 +#define QMINAS_GET_SERVING_SYSTEM_RESP 0x0024 +#define QMINAS_SERVING_SYSTEM_IND 0x0024 +#define QMINAS_GET_HOME_NETWORK_REQ 0x0025 +#define QMINAS_GET_HOME_NETWORK_RESP 0x0025 +#define QMINAS_GET_PREFERRED_NETWORK_REQ 0x0026 +#define QMINAS_GET_PREFERRED_NETWORK_RESP 0x0026 +#define QMINAS_SET_PREFERRED_NETWORK_REQ 0x0027 +#define QMINAS_SET_PREFERRED_NETWORK_RESP 0x0027 +#define QMINAS_GET_FORBIDDEN_NETWORK_REQ 0x0028 +#define QMINAS_GET_FORBIDDEN_NETWORK_RESP 0x0028 +#define QMINAS_SET_FORBIDDEN_NETWORK_REQ 0x0029 +#define QMINAS_SET_FORBIDDEN_NETWORK_RESP 0x0029 +#define QMINAS_SET_TECHNOLOGY_PREF_REQ 0x002A +#define QMINAS_SET_TECHNOLOGY_PREF_RESP 0x002A +#define QMINAS_GET_RF_BAND_INFO_REQ 0x0031 +#define QMINAS_GET_RF_BAND_INFO_RESP 0x0031 +#define QMINAS_GET_PLMN_NAME_REQ 0x0044 +#define QMINAS_GET_PLMN_NAME_RESP 0x0044 +#define QUECTEL_PACKET_TRANSFER_START_IND 0X100 +#define QUECTEL_PACKET_TRANSFER_END_IND 0X101 +#define QMINAS_GET_SYS_INFO_REQ 0x004D +#define QMINAS_GET_SYS_INFO_RESP 0x004D +#define QMINAS_SYS_INFO_IND 0x004D +#define QMINAS_GET_SIG_INFO_REQ 0x004F +#define QMINAS_GET_SIG_INFO_RESP 0x004F + +typedef struct _QMINAS_GET_HOME_NETWORK_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; +} __attribute__ ((packed)) QMINAS_GET_HOME_NETWORK_REQ_MSG, *PQMINAS_GET_HOME_NETWORK_REQ_MSG; + +typedef struct _HOME_NETWORK_SYSTEMID +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT SystemID; + USHORT NetworkID; +} __attribute__ ((packed)) HOME_NETWORK_SYSTEMID, *PHOME_NETWORK_SYSTEMID; + +typedef struct _HOME_NETWORK +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT MobileCountryCode; + USHORT MobileNetworkCode; + UCHAR NetworkDesclen; + UCHAR NetworkDesc; +} __attribute__ ((packed)) HOME_NETWORK, *PHOME_NETWORK; + +#if 0 +typedef struct _HOME_NETWORK_EXT +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT MobileCountryCode; + USHORT MobileNetworkCode; + UCHAR NetworkDescDisp; + UCHAR NetworkDescEncoding; + UCHAR NetworkDesclen; + UCHAR NetworkDesc; +} HOME_NETWORK_EXT, *PHOME_NETWORK_EXT; + +typedef struct _QMINAS_GET_HOME_NETWORK_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} QMINAS_GET_HOME_NETWORK_RESP_MSG, *PQMINAS_GET_HOME_NETWORK_RESP_MSG; + +typedef struct _QMINAS_GET_PREFERRED_NETWORK_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; +} QMINAS_GET_PREFERRED_NETWORK_REQ_MSG, *PQMINAS_GET_PREFERRED_NETWORK_REQ_MSG; + + +typedef struct _PREFERRED_NETWORK +{ + USHORT MobileCountryCode; + USHORT MobileNetworkCode; + USHORT RadioAccess; +} PREFERRED_NETWORK, *PPREFERRED_NETWORK; + +typedef struct _QMINAS_GET_PREFERRED_NETWORK_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR TLV2Type; // 0x01 - required parameter + USHORT TLV2Length; // length of the mfr string + USHORT NumPreferredNetwork; +} QMINAS_GET_PREFERRED_NETWORK_RESP_MSG, *PQMINAS_GET_PREFERRED_NETWORK_RESP_MSG; + +typedef struct _QMINAS_GET_FORBIDDEN_NETWORK_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; +} QMINAS_GET_FORBIDDEN_NETWORK_REQ_MSG, *PQMINAS_GET_FORBIDDEN_NETWORK_REQ_MSG; + +typedef struct _FORBIDDEN_NETWORK +{ + USHORT MobileCountryCode; + USHORT MobileNetworkCode; +} FORBIDDEN_NETWORK, *PFORBIDDEN_NETWORK; + +typedef struct _QMINAS_GET_FORBIDDEN_NETWORK_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR TLV2Type; // 0x01 - required parameter + USHORT TLV2Length; // length of the mfr string + USHORT NumForbiddenNetwork; +} QMINAS_GET_FORBIDDEN_NETWORK_RESP_MSG, *PQMINAS_GET_FORBIDDEN_NETWORK_RESP_MSG; + +typedef struct _QMINAS_GET_SERVING_SYSTEM_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; +} QMINAS_GET_SERVING_SYSTEM_REQ_MSG, *PQMINAS_GET_SERVING_SYSTEM_REQ_MSG; + +typedef struct _QMINAS_ROAMING_INDICATOR_MSG +{ + UCHAR TLVType; // 0x01 - required parameter + USHORT TLVLength; // length of the mfr string + UCHAR RoamingIndicator; +} QMINAS_ROAMING_INDICATOR_MSG, *PQMINAS_ROAMING_INDICATOR_MSG; +#endif + +typedef struct _QMINAS_DATA_CAP +{ + UCHAR TLVType; // 0x01 - required parameter + USHORT TLVLength; // length of the mfr string + UCHAR DataCapListLen; + UCHAR DataCap; +} __attribute__ ((packed)) QMINAS_DATA_CAP, *PQMINAS_DATA_CAP; + +typedef struct _QMINAS_CURRENT_PLMN_MSG +{ + UCHAR TLVType; // 0x01 - required parameter + USHORT TLVLength; // length of the mfr string + USHORT MobileCountryCode; + USHORT MobileNetworkCode; + UCHAR NetworkDesclen; + UCHAR NetworkDesc; +} __attribute__ ((packed)) QMINAS_CURRENT_PLMN_MSG, *PQMINAS_CURRENT_PLMN_MSG; + +typedef struct _QMINAS_GET_SERVING_SYSTEM_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} __attribute__ ((packed)) QMINAS_GET_SERVING_SYSTEM_RESP_MSG, *PQMINAS_GET_SERVING_SYSTEM_RESP_MSG; + +typedef struct _SERVING_SYSTEM +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR RegistrationState; + UCHAR CSAttachedState; + UCHAR PSAttachedState; + UCHAR RegistredNetwork; + UCHAR InUseRadioIF; + UCHAR RadioIF; +} __attribute__ ((packed)) SERVING_SYSTEM, *PSERVING_SYSTEM; + +typedef struct _QMINAS_GET_SYS_INFO_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} __attribute__ ((packed)) QMINAS_GET_SYS_INFO_RESP_MSG, *PQMINAS_GET_SYS_INFO_RESP_MSG; + +typedef struct _QMINAS_SYS_INFO_IND_MSG +{ + USHORT Type; + USHORT Length; +} __attribute__ ((packed)) QMINAS_SYS_INFO_IND_MSG, *PQMINAS_SYS_INFO_IND_MSG; + +typedef struct _SERVICE_STATUS_INFO +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR SrvStatus; + UCHAR true_srv_status; + UCHAR IsPrefDataPath; +} __attribute__ ((packed)) SERVICE_STATUS_INFO, *PSERVICE_STATUS_INFO; + +typedef struct _CDMA_SYSTEM_INFO +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR SrvDomainValid; + UCHAR SrvDomain; + UCHAR SrvCapabilityValid; + UCHAR SrvCapability; + UCHAR RoamStatusValid; + UCHAR RoamStatus; + UCHAR IsSysForbiddenValid; + UCHAR IsSysForbidden; + UCHAR IsSysPrlMatchValid; + UCHAR IsSysPrlMatch; + UCHAR PRevInUseValid; + UCHAR PRevInUse; + UCHAR BSPRevValid; + UCHAR BSPRev; + UCHAR CCSSupportedValid; + UCHAR CCSSupported; + UCHAR CDMASysIdValid; + USHORT SID; + USHORT NID; + UCHAR BSInfoValid; + USHORT BaseID; + ULONG BaseLAT; + ULONG BaseLONG; + UCHAR PacketZoneValid; + USHORT PacketZone; + UCHAR NetworkIdValid; + UCHAR MCC[3]; + UCHAR MNC[3]; +} __attribute__ ((packed)) CDMA_SYSTEM_INFO, *PCDMA_SYSTEM_INFO; + +typedef struct _HDR_SYSTEM_INFO +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR SrvDomainValid; + UCHAR SrvDomain; + UCHAR SrvCapabilityValid; + UCHAR SrvCapability; + UCHAR RoamStatusValid; + UCHAR RoamStatus; + UCHAR IsSysForbiddenValid; + UCHAR IsSysForbidden; + UCHAR IsSysPrlMatchValid; + UCHAR IsSysPrlMatch; + UCHAR HdrPersonalityValid; + UCHAR HdrPersonality; + UCHAR HdrActiveProtValid; + UCHAR HdrActiveProt; + UCHAR is856SysIdValid; + UCHAR is856SysId[16]; +} __attribute__ ((packed)) HDR_SYSTEM_INFO, *PHDR_SYSTEM_INFO; + +typedef struct _GSM_SYSTEM_INFO +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR SrvDomainValid; + UCHAR SrvDomain; + UCHAR SrvCapabilityValid; + UCHAR SrvCapability; + UCHAR RoamStatusValid; + UCHAR RoamStatus; + UCHAR IsSysForbiddenValid; + UCHAR IsSysForbidden; + UCHAR LacValid; + USHORT Lac; + UCHAR CellIdValid; + ULONG CellId; + UCHAR RegRejectInfoValid; + UCHAR RejectSrvDomain; + UCHAR RejCause; + UCHAR NetworkIdValid; + UCHAR MCC[3]; + UCHAR MNC[3]; + UCHAR EgprsSuppValid; + UCHAR EgprsSupp; + UCHAR DtmSuppValid; + UCHAR DtmSupp; +} __attribute__ ((packed)) GSM_SYSTEM_INFO, *PGSM_SYSTEM_INFO; + +typedef struct _WCDMA_SYSTEM_INFO +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR SrvDomainValid; + UCHAR SrvDomain; + UCHAR SrvCapabilityValid; + UCHAR SrvCapability; + UCHAR RoamStatusValid; + UCHAR RoamStatus; + UCHAR IsSysForbiddenValid; + UCHAR IsSysForbidden; + UCHAR LacValid; + USHORT Lac; + UCHAR CellIdValid; + ULONG CellId; + UCHAR RegRejectInfoValid; + UCHAR RejectSrvDomain; + UCHAR RejCause; + UCHAR NetworkIdValid; + UCHAR MCC[3]; + UCHAR MNC[3]; + UCHAR HsCallStatusValid; + UCHAR HsCallStatus; + UCHAR HsIndValid; + UCHAR HsInd; + UCHAR PscValid; + UCHAR Psc; +} __attribute__ ((packed)) WCDMA_SYSTEM_INFO, *PWCDMA_SYSTEM_INFO; + +typedef struct _LTE_SYSTEM_INFO +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR SrvDomainValid; + UCHAR SrvDomain; + UCHAR SrvCapabilityValid; + UCHAR SrvCapability; + UCHAR RoamStatusValid; + UCHAR RoamStatus; + UCHAR IsSysForbiddenValid; + UCHAR IsSysForbidden; + UCHAR LacValid; + USHORT Lac; + UCHAR CellIdValid; + ULONG CellId; + UCHAR RegRejectInfoValid; + UCHAR RejectSrvDomain; + UCHAR RejCause; + UCHAR NetworkIdValid; + UCHAR MCC[3]; + UCHAR MNC[3]; + UCHAR TacValid; + USHORT Tac; +} __attribute__ ((packed)) LTE_SYSTEM_INFO, *PLTE_SYSTEM_INFO; + +typedef struct _TDSCDMA_SYSTEM_INFO +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR SrvDomainValid; + UCHAR SrvDomain; + UCHAR SrvCapabilityValid; + UCHAR SrvCapability; + UCHAR RoamStatusValid; + UCHAR RoamStatus; + UCHAR IsSysForbiddenValid; + UCHAR IsSysForbidden; + UCHAR LacValid; + USHORT Lac; + UCHAR CellIdValid; + ULONG CellId; + UCHAR RegRejectInfoValid; + UCHAR RejectSrvDomain; + UCHAR RejCause; + UCHAR NetworkIdValid; + UCHAR MCC[3]; + UCHAR MNC[3]; + UCHAR HsCallStatusValid; + UCHAR HsCallStatus; + UCHAR HsIndValid; + UCHAR HsInd; + UCHAR CellParameterIdValid; + USHORT CellParameterId; + UCHAR CellBroadcastCapValid; + ULONG CellBroadcastCap; + UCHAR CsBarStatusValid; + ULONG CsBarStatus; + UCHAR PsBarStatusValid; + ULONG PsBarStatus; + UCHAR CipherDomainValid; + UCHAR CipherDomain; +} __attribute__ ((packed)) TDSCDMA_SYSTEM_INFO, *PTDSCDMA_SYSTEM_INFO; + +typedef enum { + NAS_SYS_SRV_STATUS_NO_SRV_V01 = 0, + NAS_SYS_SRV_STATUS_LIMITED_V01 = 1, + NAS_SYS_SRV_STATUS_SRV_V01 = 2, + NAS_SYS_SRV_STATUS_LIMITED_REGIONAL_V01 = 3, + NAS_SYS_SRV_STATUS_PWR_SAVE_V01 = 4, +}nas_service_status_enum_type_v01; + +typedef enum { + SYS_SRV_DOMAIN_NO_SRV_V01 = 0, + SYS_SRV_DOMAIN_CS_ONLY_V01 = 1, + SYS_SRV_DOMAIN_PS_ONLY_V01 = 2, + SYS_SRV_DOMAIN_CS_PS_V01 = 3, + SYS_SRV_DOMAIN_CAMPED_V01 = 4, +}nas_service_domain_enum_type_v01; + +typedef struct { + UCHAR TLVType; + USHORT TLVLength; + + uint8_t srv_domain_valid; + uint8_t srv_domain; + uint8_t srv_capability_valid; + uint8_t srv_capability; + uint8_t roam_status_valid; + uint8_t roam_status; + uint8_t is_sys_forbidden_valid; + uint8_t is_sys_forbidden; + + uint8_t lac_valid; + uint16_t lac; + uint8_t cell_id_valid; + uint32_t cell_id; + uint8_t reg_reject_info_valid; + uint8_t reject_srv_domain; + uint8_t rej_cause; + uint8_t network_id_valid; + UCHAR MCC[3]; + UCHAR MNC[3]; + + uint8_t tac_valid; + uint16_t tac; +} __attribute__ ((packed)) NR5G_SYSTEM_INFO, *PNR5G_SYSTEM_INFO; + +#if 0 +typedef struct _QMINAS_SERVING_SYSTEM_IND_MSG +{ + USHORT Type; + USHORT Length; +} QMINAS_SERVING_SYSTEM_IND_MSG, *PQMINAS_SERVING_SYSTEM_IND_MSG; + +typedef struct _QMINAS_SET_PREFERRED_NETWORK_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT NumPreferredNetwork; + USHORT MobileCountryCode; + USHORT MobileNetworkCode; + USHORT RadioAccess; +} QMINAS_SET_PREFERRED_NETWORK_REQ_MSG, *PQMINAS_SET_PREFERRED_NETWORK_REQ_MSG; + +typedef struct _QMINAS_SET_PREFERRED_NETWORK_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} QMINAS_SET_PREFERRED_NETWORK_RESP_MSG, *PQMINAS_SET_PREFERRED_NETWORK_RESP_MSG; + +typedef struct _QMINAS_SET_FORBIDDEN_NETWORK_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT NumForbiddenNetwork; + USHORT MobileCountryCode; + USHORT MobileNetworkCode; +} QMINAS_SET_FORBIDDEN_NETWORK_REQ_MSG, *PQMINAS_SET_FORBIDDEN_NETWORK_REQ_MSG; + +typedef struct _QMINAS_SET_FORBIDDEN_NETWORK_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} QMINAS_SET_FORBIDDEN_NETWORK_RESP_MSG, *PQMINAS_SET_FORBIDDEN_NETWORK_RESP_MSG; + +typedef struct _QMINAS_PERFORM_NETWORK_SCAN_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; +} QMINAS_PERFORM_NETWORK_SCAN_REQ_MSG, *PQMINAS_PERFORM_NETWORK_SCAN_REQ_MSG; + +typedef struct _VISIBLE_NETWORK +{ + USHORT MobileCountryCode; + USHORT MobileNetworkCode; + UCHAR NetworkStatus; + UCHAR NetworkDesclen; +} VISIBLE_NETWORK, *PVISIBLE_NETWORK; + +typedef struct _QMINAS_PERFORM_NETWORK_SCAN_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} QMINAS_PERFORM_NETWORK_SCAN_RESP_MSG, *PQMINAS_PERFORM_NETWORK_SCAN_RESP_MSG; + +typedef struct _QMINAS_PERFORM_NETWORK_SCAN_NETWORK_INFO +{ + UCHAR TLVType; // 0x010 - required parameter + USHORT TLVLength; // length + USHORT NumNetworkInstances; +} QMINAS_PERFORM_NETWORK_SCAN_NETWORK_INFO, *PQMINAS_PERFORM_NETWORK_SCAN_NETWORK_INFO; + +typedef struct _QMINAS_PERFORM_NETWORK_SCAN_RAT_INFO +{ + UCHAR TLVType; // 0x011 - required parameter + USHORT TLVLength; // length + USHORT NumInst; +} QMINAS_PERFORM_NETWORK_SCAN_RAT_INFO, *PQMINAS_PERFORM_NETWORK_SCAN_RAT_INFO; + +typedef struct _QMINAS_PERFORM_NETWORK_SCAN_RAT +{ + USHORT MCC; + USHORT MNC; + UCHAR RAT; +} QMINAS_PERFORM_NETWORK_SCAN_RAT, *PQMINAS_PERFORM_NETWORK_SCAN_RAT; + + +typedef struct _QMINAS_MANUAL_NW_REGISTER +{ + UCHAR TLV2Type; // 0x02 - result code + USHORT TLV2Length; // 4 + USHORT MobileCountryCode; + USHORT MobileNetworkCode; + UCHAR RadioAccess; +} QMINAS_MANUAL_NW_REGISTER, *PQMINAS_MANUAL_NW_REGISTER; + +typedef struct _QMINAS_INITIATE_NW_REGISTER_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + UCHAR RegisterAction; +} QMINAS_INITIATE_NW_REGISTER_REQ_MSG, *PQMINAS_INITIATE_NW_REGISTER_REQ_MSG; + +typedef struct _QMINAS_INITIATE_NW_REGISTER_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} QMINAS_INITIATE_NW_REGISTER_RESP_MSG, *PQMINAS_INITIATE_NW_REGISTER_RESP_MSG; + +typedef struct _QMINAS_SET_TECHNOLOGY_PREF_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT TechPref; + UCHAR Duration; +} QMINAS_SET_TECHNOLOGY_PREF_REQ_MSG, *PQMINAS_SET_TECHNOLOGY_PREF_REQ_MSG; + +typedef struct _QMINAS_SET_TECHNOLOGY_PREF_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} QMINAS_SET_TECHNOLOGY_PREF_RESP_MSG, *PQMINAS_SET_TECHNOLOGY_PREF_RESP_MSG; + +typedef struct _QMINAS_GET_SIGNAL_STRENGTH_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; +} QMINAS_GET_SIGNAL_STRENGTH_REQ_MSG, *PQMINAS_GET_SIGNAL_STRENGTH_REQ_MSG; + +typedef struct _QMINAS_SIGNAL_STRENGTH +{ + CHAR SigStrength; + UCHAR RadioIf; +} QMINAS_SIGNAL_STRENGTH, *PQMINAS_SIGNAL_STRENGTH; + +typedef struct _QMINAS_SIGNAL_STRENGTH_LIST +{ + UCHAR TLV3Type; + USHORT TLV3Length; + USHORT NumInstance; +} QMINAS_SIGNAL_STRENGTH_LIST, *PQMINAS_SIGNAL_STRENGTH_LIST; + + +typedef struct _QMINAS_GET_SIGNAL_STRENGTH_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR TLV2Type; + USHORT TLV2Length; + CHAR SignalStrength; + UCHAR RadioIf; +} QMINAS_GET_SIGNAL_STRENGTH_RESP_MSG, *PQMINAS_GET_SIGNAL_STRENGTH_RESP_MSG; + + +typedef struct _QMINAS_SET_EVENT_REPORT_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR ReportSigStrength; + UCHAR NumTresholds; + CHAR TresholdList[2]; +} QMINAS_SET_EVENT_REPORT_REQ_MSG, *PQMINAS_SET_EVENT_REPORT_REQ_MSG; + +typedef struct _QMINAS_SET_EVENT_REPORT_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} QMINAS_SET_EVENT_REPORT_RESP_MSG, *PQMINAS_SET_EVENT_REPORT_RESP_MSG; + +typedef struct _QMINAS_SIGNAL_STRENGTH_TLV +{ + UCHAR TLVType; + USHORT TLVLength; + CHAR SigStrength; + UCHAR RadioIf; +} QMINAS_SIGNAL_STRENGTH_TLV, *PQMINAS_SIGNAL_STRENGTH_TLV; + +typedef struct _QMINAS_REJECT_CAUSE_TLV +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR ServiceDomain; + USHORT RejectCause; +} QMINAS_REJECT_CAUSE_TLV, *PQMINAS_REJECT_CAUSE_TLV; + +typedef struct _QMINAS_EVENT_REPORT_IND_MSG +{ + USHORT Type; + USHORT Length; +} QMINAS_EVENT_REPORT_IND_MSG, *PQMINAS_EVENT_REPORT_IND_MSG; + +typedef struct _QMINAS_GET_RF_BAND_INFO_REQ_MSG +{ + USHORT Type; + USHORT Length; +} QMINAS_GET_RF_BAND_INFO_REQ_MSG, *PQMINAS_GET_RF_BAND_INFO_REQ_MSG; + +typedef struct _QMINASRF_BAND_INFO +{ + UCHAR RadioIf; + USHORT ActiveBand; + USHORT ActiveChannel; +} QMINASRF_BAND_INFO, *PQMINASRF_BAND_INFO; + +typedef struct _QMINAS_GET_RF_BAND_INFO_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR NumInstances; +} QMINAS_GET_RF_BAND_INFO_RESP_MSG, *PQMINAS_GET_RF_BAND_INFO_RESP_MSG; + + +typedef struct _QMINAS_GET_PLMN_NAME_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT MCC; + USHORT MNC; +} QMINAS_GET_PLMN_NAME_REQ_MSG, *PQMINAS_GET_PLMN_NAME_REQ_MSG; + +typedef struct _QMINAS_GET_PLMN_NAME_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} QMINAS_GET_PLMN_NAME_RESP_MSG, *PQMINAS_GET_PLMN_NAME_RESP_MSG; + +typedef struct _QMINAS_GET_PLMN_NAME_SPN +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR SPN_Enc; + UCHAR SPN_Len; +} QMINAS_GET_PLMN_NAME_SPN, *PQMINAS_GET_PLMN_NAME_SPN; + +typedef struct _QMINAS_GET_PLMN_NAME_PLMN +{ + UCHAR PLMN_Enc; + UCHAR PLMN_Ci; + UCHAR PLMN_SpareBits; + UCHAR PLMN_Len; +} QMINAS_GET_PLMN_NAME_PLMN, *PQMINAS_GET_PLMN_NAME_PLMN; + +typedef struct _QMINAS_INITIATE_ATTACH_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR PsAttachAction; +} QMINAS_INITIATE_ATTACH_REQ_MSG, *PQMINAS_INITIATE_ATTACH_REQ_MSG; + +typedef struct _QMINAS_INITIATE_ATTACH_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} QMINAS_INITIATE_ATTACH_RESP_MSG, *PQMINAS_INITIATE_ATTACH_RESP_MSG; +#endif +typedef struct { + UCHAR TLVType; + USHORT TLVLength; + CHAR rssi; + SHORT ecio; +} __attribute__ ((packed)) QMINAS_SIG_INFO_CDMA_TLV_MSG, *PQMINAS_SIG_INFO_CDMA_TLV_MSG; + +typedef struct { + UCHAR TLVType; + USHORT TLVLength; + CHAR rssi; + SHORT ecio; + CHAR sinr; + INT io; +} __attribute__ ((packed)) QMINAS_SIG_INFO_HDR_TLV_MSG, *PQMINAS_SIG_INFO_HDR_TLV_MSG; + +typedef struct { + UCHAR TLVType; + USHORT TLVLength; + CHAR rssi; +} __attribute__ ((packed)) QMINAS_SIG_INFO_GSM_TLV_MSG, *PQMINAS_SIG_INFO_GSM_TLV_MSG; + +typedef struct { + UCHAR TLVType; + USHORT TLVLength; + CHAR rssi; + SHORT ecio; +} __attribute__ ((packed)) QMINAS_SIG_INFO_WCDMA_TLV_MSG, *PQMINAS_SIG_INFO_WCDMA_TLV_MSG; + +typedef struct { + UCHAR TLVType; + USHORT TLVLength; + CHAR rssi; + CHAR rsrq; + SHORT rsrp; + SHORT snr; +} __attribute__ ((packed)) QMINAS_SIG_INFO_LTE_TLV_MSG, *PQMINAS_SIG_INFO_LTE_TLV_MSG; + +typedef struct { + UCHAR TLVType; + USHORT TLVLength; + CHAR rscp; +} __attribute__ ((packed)) QMINAS_SIG_INFO_TDSCDMA_TLV_MSG, *PQMINAS_SIG_INFO_TDSCDMA_TLV_MSG; +// ======================= End of NAS ============================== + +// ======================= UIM ============================== +#define QMIUIM_READ_TRANSPARENT_REQ 0x0020 +#define QMIUIM_READ_TRANSPARENT_RESP 0x0020 +#define QMIUIM_READ_TRANSPARENT_IND 0x0020 +#define QMIUIM_READ_RECORD_REQ 0x0021 +#define QMIUIM_READ_RECORD_RESP 0x0021 +#define QMIUIM_READ_RECORD_IND 0x0021 +#define QMIUIM_WRITE_TRANSPARENT_REQ 0x0022 +#define QMIUIM_WRITE_TRANSPARENT_RESP 0x0022 +#define QMIUIM_WRITE_TRANSPARENT_IND 0x0022 +#define QMIUIM_WRITE_RECORD_REQ 0x0023 +#define QMIUIM_WRITE_RECORD_RESP 0x0023 +#define QMIUIM_WRITE_RECORD_IND 0x0023 +#define QMIUIM_SET_PIN_PROTECTION_REQ 0x0025 +#define QMIUIM_SET_PIN_PROTECTION_RESP 0x0025 +#define QMIUIM_SET_PIN_PROTECTION_IND 0x0025 +#define QMIUIM_VERIFY_PIN_REQ 0x0026 +#define QMIUIM_VERIFY_PIN_RESP 0x0026 +#define QMIUIM_VERIFY_PIN_IND 0x0026 +#define QMIUIM_UNBLOCK_PIN_REQ 0x0027 +#define QMIUIM_UNBLOCK_PIN_RESP 0x0027 +#define QMIUIM_UNBLOCK_PIN_IND 0x0027 +#define QMIUIM_CHANGE_PIN_REQ 0x0028 +#define QMIUIM_CHANGE_PIN_RESP 0x0028 +#define QMIUIM_CHANGE_PIN_IND 0x0028 +#define QMIUIM_DEPERSONALIZATION_REQ 0x0029 +#define QMIUIM_DEPERSONALIZATION_RESP 0x0029 +#define QMIUIM_EVENT_REG_REQ 0x002E +#define QMIUIM_EVENT_REG_RESP 0x002E +#define QMIUIM_GET_CARD_STATUS_REQ 0x002F +#define QMIUIM_GET_CARD_STATUS_RESP 0x002F +#define QMIUIM_STATUS_CHANGE_IND 0x0032 + + +typedef struct _QMIUIM_GET_CARD_STATUS_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} __attribute__ ((packed)) QMIUIM_GET_CARD_STATUS_RESP_MSG, *PQMIUIM_GET_CARD_STATUS_RESP_MSG; + +#define UIM_CARD_STATE_ABSENT 0x00 +#define UIM_CARD_STATE_PRESENT 0x01 +#define UIM_CARD_STATE_ERROR 0x02 + +typedef struct _QMIUIM_CARD_STATUS +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT IndexGWPri; + USHORT Index1XPri; + USHORT IndexGWSec; + USHORT Index1XSec; + UCHAR NumSlot; + UCHAR CardState; + UCHAR UPINState; + UCHAR UPINRetries; + UCHAR UPUKRetries; + UCHAR ErrorCode; + UCHAR NumApp; + UCHAR AppType; + UCHAR AppState; + UCHAR PersoState; + UCHAR PersoFeature; + UCHAR PersoRetries; + UCHAR PersoUnblockRetries; + UCHAR AIDLength; +} __attribute__ ((packed)) QMIUIM_CARD_STATUS, *PQMIUIM_CARD_STATUS; + +typedef struct _QMIUIM_PIN_STATE +{ + UCHAR UnivPIN; + UCHAR PIN1State; + UCHAR PIN1Retries; + UCHAR PUK1Retries; + UCHAR PIN2State; + UCHAR PIN2Retries; + UCHAR PUK2Retries; +} __attribute__ ((packed)) QMIUIM_PIN_STATE, *PQMIUIM_PIN_STATE; + +typedef struct _QMIUIM_VERIFY_PIN_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR Session_Type; + UCHAR Aid_Len; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR PINID; + UCHAR PINLen; + UCHAR PINValue; +} __attribute__ ((packed)) QMIUIM_VERIFY_PIN_REQ_MSG, *PQMIUIM_VERIFY_PIN_REQ_MSG; + +typedef struct _QMIUIM_VERIFY_PIN_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR PINVerifyRetriesLeft; + UCHAR PINUnblockRetriesLeft; +} __attribute__ ((packed)) QMIUIM_VERIFY_PIN_RESP_MSG, *PQMIUIM_VERIFY_PIN_RESP_MSG; + +typedef struct _QMIUIM_READ_TRANSPARENT_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR Session_Type; + UCHAR Aid_Len; + UCHAR TLV2Type; + USHORT TLV2Length; + USHORT file_id; + UCHAR path_len; + UCHAR path[]; +} __attribute__ ((packed)) QMIUIM_READ_TRANSPARENT_REQ_MSG, *PQMIUIM_READ_TRANSPARENT_REQ_MSG; + +typedef struct _READ_TRANSPARENT_TLV +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT Offset; + USHORT Length; +} __attribute__ ((packed)) READ_TRANSPARENT_TLV, *PREAD_TRANSPARENT_TLV; + +typedef struct _QMIUIM_CONTENT +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT content_len; + UCHAR content[]; +} __attribute__ ((packed)) QMIUIM_CONTENT, *PQMIUIM_CONTENT; + +typedef struct _QMIUIM_READ_TRANSPARENT_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} __attribute__ ((packed)) QMIUIM_READ_TRANSPARENT_RESP_MSG, *PQMIUIM_READ_TRANSPARENT_RESP_MSG; + +typedef struct _QMUX_MSG +{ + QCQMUX_HDR QMUXHdr; + union + { + // Message Header + QCQMUX_MSG_HDR QMUXMsgHdr; + QCQMUX_MSG_HDR_RESP QMUXMsgHdrResp; + + // QMIWDS Message +#if 0 + QMIWDS_GET_PKT_SRVC_STATUS_REQ_MSG PacketServiceStatusReq; + QMIWDS_GET_PKT_SRVC_STATUS_RESP_MSG PacketServiceStatusRsp; + QMIWDS_GET_PKT_SRVC_STATUS_IND_MSG PacketServiceStatusInd; + QMIWDS_EVENT_REPORT_IND_MSG EventReportInd; + QMIWDS_GET_CURRENT_CHANNEL_RATE_REQ_MSG GetCurrChannelRateReq; + QMIWDS_GET_CURRENT_CHANNEL_RATE_RESP_MSG GetCurrChannelRateRsp; + QMIWDS_GET_PKT_STATISTICS_REQ_MSG GetPktStatsReq; + QMIWDS_GET_PKT_STATISTICS_RESP_MSG GetPktStatsRsp; + QMIWDS_SET_EVENT_REPORT_REQ_MSG EventReportReq; + QMIWDS_SET_EVENT_REPORT_RESP_MSG EventReportRsp; +#endif + //#ifdef QC_IP_MODE + QMIWDS_GET_RUNTIME_SETTINGS_REQ_MSG GetRuntimeSettingsReq; + QMIWDS_GET_RUNTIME_SETTINGS_RESP_MSG GetRuntimeSettingsRsp; + //#endif // QC_IP_MODE + QMIWDS_SET_CLIENT_IP_FAMILY_PREF_REQ_MSG SetClientIpFamilyPrefReq; + QMIWDS_SET_CLIENT_IP_FAMILY_PREF_RESP_MSG SetClientIpFamilyPrefResp; + QMIWDS_SET_AUTO_CONNECT_REQ_MSG SetAutoConnectReq; +#if 0 + QMIWDS_GET_MIP_MODE_REQ_MSG GetMipModeReq; + QMIWDS_GET_MIP_MODE_RESP_MSG GetMipModeResp; +#endif + QMIWDS_START_NETWORK_INTERFACE_REQ_MSG StartNwInterfaceReq; + QMIWDS_START_NETWORK_INTERFACE_RESP_MSG StartNwInterfaceResp; + QMIWDS_STOP_NETWORK_INTERFACE_REQ_MSG StopNwInterfaceReq; + QMIWDS_STOP_NETWORK_INTERFACE_RESP_MSG StopNwInterfaceResp; + QMIWDS_GET_DEFAULT_SETTINGS_REQ_MSG GetDefaultSettingsReq; + QMIWDS_GET_DEFAULT_SETTINGS_RESP_MSG GetDefaultSettingsResp; + QMIWDS_MODIFY_PROFILE_SETTINGS_REQ_MSG ModifyProfileSettingsReq; + QMIWDS_MODIFY_PROFILE_SETTINGS_RESP_MSG ModifyProfileSettingsResp; + QMIWDS_GET_PROFILE_SETTINGS_REQ_MSG GetProfileSettingsReq; +#if 0 + QMIWDS_GET_DATA_BEARER_REQ_MSG GetDataBearerReq; + QMIWDS_GET_DATA_BEARER_RESP_MSG GetDataBearerResp; + QMIWDS_DUN_CALL_INFO_REQ_MSG DunCallInfoReq; + QMIWDS_DUN_CALL_INFO_RESP_MSG DunCallInfoResp; +#endif + QMIWDS_BIND_MUX_DATA_PORT_REQ_MSG BindMuxDataPortReq; + + // QMIDMS Messages +#if 0 + QMIDMS_GET_DEVICE_MFR_REQ_MSG GetDeviceMfrReq; + QMIDMS_GET_DEVICE_MFR_RESP_MSG GetDeviceMfrRsp; + QMIDMS_GET_DEVICE_MODEL_ID_REQ_MSG GetDeviceModeIdReq; + QMIDMS_GET_DEVICE_MODEL_ID_RESP_MSG GetDeviceModeIdRsp; + QMIDMS_GET_DEVICE_REV_ID_REQ_MSG GetDeviceRevIdReq; + QMIDMS_GET_DEVICE_REV_ID_RESP_MSG GetDeviceRevIdRsp; + QMIDMS_GET_MSISDN_REQ_MSG GetMsisdnReq; + QMIDMS_GET_MSISDN_RESP_MSG GetMsisdnRsp; + QMIDMS_GET_DEVICE_SERIAL_NUMBERS_REQ_MSG GetDeviceSerialNumReq; + QMIDMS_GET_DEVICE_SERIAL_NUMBERS_RESP_MSG GetDeviceSerialNumRsp; + QMIDMS_GET_DEVICE_CAP_REQ_MSG GetDeviceCapReq; + QMIDMS_GET_DEVICE_CAP_RESP_MSG GetDeviceCapResp; + QMIDMS_GET_BAND_CAP_REQ_MSG GetBandCapReq; + QMIDMS_GET_BAND_CAP_RESP_MSG GetBandCapRsp; + QMIDMS_GET_ACTIVATED_STATUS_REQ_MSG GetActivatedStatusReq; + QMIDMS_GET_ACTIVATED_STATUS_RESP_MSG GetActivatedStatusResp; + QMIDMS_GET_OPERATING_MODE_REQ_MSG GetOperatingModeReq; + QMIDMS_GET_OPERATING_MODE_RESP_MSG GetOperatingModeResp; +#endif + QMIDMS_SET_OPERATING_MODE_REQ_MSG SetOperatingModeReq; + QMIDMS_SET_OPERATING_MODE_RESP_MSG SetOperatingModeResp; +#if 0 + QMIDMS_UIM_GET_ICCID_REQ_MSG GetICCIDReq; + QMIDMS_UIM_GET_ICCID_RESP_MSG GetICCIDResp; + QMIDMS_ACTIVATE_AUTOMATIC_REQ_MSG ActivateAutomaticReq; + QMIDMS_ACTIVATE_AUTOMATIC_RESP_MSG ActivateAutomaticResp; + QMIDMS_ACTIVATE_MANUAL_REQ_MSG ActivateManualReq; + QMIDMS_ACTIVATE_MANUAL_RESP_MSG ActivateManualResp; +#endif + QMIDMS_UIM_GET_PIN_STATUS_REQ_MSG UIMGetPinStatusReq; + QMIDMS_UIM_GET_PIN_STATUS_RESP_MSG UIMGetPinStatusResp; + QMIDMS_UIM_VERIFY_PIN_REQ_MSG UIMVerifyPinReq; + QMIDMS_UIM_VERIFY_PIN_RESP_MSG UIMVerifyPinResp; +#if 0 + QMIDMS_UIM_SET_PIN_PROTECTION_REQ_MSG UIMSetPinProtectionReq; + QMIDMS_UIM_SET_PIN_PROTECTION_RESP_MSG UIMSetPinProtectionResp; + QMIDMS_UIM_CHANGE_PIN_REQ_MSG UIMChangePinReq; + QMIDMS_UIM_CHANGE_PIN_RESP_MSG UIMChangePinResp; + QMIDMS_UIM_UNBLOCK_PIN_REQ_MSG UIMUnblockPinReq; + QMIDMS_UIM_UNBLOCK_PIN_RESP_MSG UIMUnblockPinResp; + QMIDMS_SET_EVENT_REPORT_REQ_MSG DmsSetEventReportReq; + QMIDMS_SET_EVENT_REPORT_RESP_MSG DmsSetEventReportResp; + QMIDMS_EVENT_REPORT_IND_MSG DmsEventReportInd; +#endif + QMIDMS_UIM_GET_STATE_REQ_MSG UIMGetStateReq; + QMIDMS_UIM_GET_STATE_RESP_MSG UIMGetStateResp; + QMIDMS_UIM_GET_IMSI_REQ_MSG UIMGetIMSIReq; + QMIDMS_UIM_GET_IMSI_RESP_MSG UIMGetIMSIResp; +#if 0 + QMIDMS_UIM_GET_CK_STATUS_REQ_MSG UIMGetCkStatusReq; + QMIDMS_UIM_GET_CK_STATUS_RESP_MSG UIMGetCkStatusResp; + QMIDMS_UIM_SET_CK_PROTECTION_REQ_MSG UIMSetCkProtectionReq; + QMIDMS_UIM_SET_CK_PROTECTION_RESP_MSG UIMSetCkProtectionResp; + QMIDMS_UIM_UNBLOCK_CK_REQ_MSG UIMUnblockCkReq; + QMIDMS_UIM_UNBLOCK_CK_RESP_MSG UIMUnblockCkResp; +#endif + + // QMIQOS Messages +#if 0 + QMI_QOS_SET_EVENT_REPORT_REQ_MSG QosSetEventReportReq; + QMI_QOS_SET_EVENT_REPORT_RESP_MSG QosSetEventReportRsp; + QMI_QOS_EVENT_REPORT_IND_MSG QosEventReportInd; +#endif + + // QMIWMS Messages +#if 0 + QMIWMS_GET_MESSAGE_PROTOCOL_REQ_MSG GetMessageProtocolReq; + QMIWMS_GET_MESSAGE_PROTOCOL_RESP_MSG GetMessageProtocolResp; + QMIWMS_GET_SMSC_ADDRESS_REQ_MSG GetSMSCAddressReq; + QMIWMS_GET_SMSC_ADDRESS_RESP_MSG GetSMSCAddressResp; + QMIWMS_SET_SMSC_ADDRESS_REQ_MSG SetSMSCAddressReq; + QMIWMS_SET_SMSC_ADDRESS_RESP_MSG SetSMSCAddressResp; + QMIWMS_GET_STORE_MAX_SIZE_REQ_MSG GetStoreMaxSizeReq; + QMIWMS_GET_STORE_MAX_SIZE_RESP_MSG GetStoreMaxSizeResp; + QMIWMS_LIST_MESSAGES_REQ_MSG ListMessagesReq; + QMIWMS_LIST_MESSAGES_RESP_MSG ListMessagesResp; + QMIWMS_RAW_READ_REQ_MSG RawReadMessagesReq; + QMIWMS_RAW_READ_RESP_MSG RawReadMessagesResp; + QMIWMS_SET_EVENT_REPORT_REQ_MSG WmsSetEventReportReq; + QMIWMS_SET_EVENT_REPORT_RESP_MSG WmsSetEventReportResp; + QMIWMS_EVENT_REPORT_IND_MSG WmsEventReportInd; + QMIWMS_DELETE_REQ_MSG WmsDeleteReq; + QMIWMS_DELETE_RESP_MSG WmsDeleteResp; + QMIWMS_RAW_SEND_REQ_MSG RawSendMessagesReq; + QMIWMS_RAW_SEND_RESP_MSG RawSendMessagesResp; + QMIWMS_MODIFY_TAG_REQ_MSG WmsModifyTagReq; + QMIWMS_MODIFY_TAG_RESP_MSG WmsModifyTagResp; +#endif + + // QMINAS Messages +#if 0 + QMINAS_GET_HOME_NETWORK_REQ_MSG GetHomeNetworkReq; + QMINAS_GET_HOME_NETWORK_RESP_MSG GetHomeNetworkResp; + QMINAS_GET_PREFERRED_NETWORK_REQ_MSG GetPreferredNetworkReq; + QMINAS_GET_PREFERRED_NETWORK_RESP_MSG GetPreferredNetworkResp; + QMINAS_GET_FORBIDDEN_NETWORK_REQ_MSG GetForbiddenNetworkReq; + QMINAS_GET_FORBIDDEN_NETWORK_RESP_MSG GetForbiddenNetworkResp; + QMINAS_GET_SERVING_SYSTEM_REQ_MSG GetServingSystemReq; +#endif + QMINAS_GET_SERVING_SYSTEM_RESP_MSG GetServingSystemResp; + QMINAS_GET_SYS_INFO_RESP_MSG GetSysInfoResp; + QMINAS_SYS_INFO_IND_MSG NasSysInfoInd; +#if 0 + QMINAS_SERVING_SYSTEM_IND_MSG NasServingSystemInd; + QMINAS_SET_PREFERRED_NETWORK_REQ_MSG SetPreferredNetworkReq; + QMINAS_SET_PREFERRED_NETWORK_RESP_MSG SetPreferredNetworkResp; + QMINAS_SET_FORBIDDEN_NETWORK_REQ_MSG SetForbiddenNetworkReq; + QMINAS_SET_FORBIDDEN_NETWORK_RESP_MSG SetForbiddenNetworkResp; + QMINAS_PERFORM_NETWORK_SCAN_REQ_MSG PerformNetworkScanReq; + QMINAS_PERFORM_NETWORK_SCAN_RESP_MSG PerformNetworkScanResp; + QMINAS_INITIATE_NW_REGISTER_REQ_MSG InitiateNwRegisterReq; + QMINAS_INITIATE_NW_REGISTER_RESP_MSG InitiateNwRegisterResp; + QMINAS_SET_TECHNOLOGY_PREF_REQ_MSG SetTechnologyPrefReq; + QMINAS_SET_TECHNOLOGY_PREF_RESP_MSG SetTechnologyPrefResp; + QMINAS_GET_SIGNAL_STRENGTH_REQ_MSG GetSignalStrengthReq; + QMINAS_GET_SIGNAL_STRENGTH_RESP_MSG GetSignalStrengthResp; + QMINAS_SET_EVENT_REPORT_REQ_MSG SetEventReportReq; + QMINAS_SET_EVENT_REPORT_RESP_MSG SetEventReportResp; + QMINAS_EVENT_REPORT_IND_MSG NasEventReportInd; + QMINAS_GET_RF_BAND_INFO_REQ_MSG GetRFBandInfoReq; + QMINAS_GET_RF_BAND_INFO_RESP_MSG GetRFBandInfoResp; + QMINAS_INITIATE_ATTACH_REQ_MSG InitiateAttachReq; + QMINAS_INITIATE_ATTACH_RESP_MSG InitiateAttachResp; + QMINAS_GET_PLMN_NAME_REQ_MSG GetPLMNNameReq; + QMINAS_GET_PLMN_NAME_RESP_MSG GetPLMNNameResp; +#endif + + // QMIUIM Messages + QMIUIM_GET_CARD_STATUS_RESP_MSG UIMGetCardStatus; + QMIUIM_VERIFY_PIN_REQ_MSG UIMUIMVerifyPinReq; + QMIUIM_VERIFY_PIN_RESP_MSG UIMUIMVerifyPinResp; +#if 0 + QMIUIM_SET_PIN_PROTECTION_REQ_MSG UIMUIMSetPinProtectionReq; + QMIUIM_SET_PIN_PROTECTION_RESP_MSG UIMUIMSetPinProtectionResp; + QMIUIM_CHANGE_PIN_REQ_MSG UIMUIMChangePinReq; + QMIUIM_CHANGE_PIN_RESP_MSG UIMUIMChangePinResp; + QMIUIM_UNBLOCK_PIN_REQ_MSG UIMUIMUnblockPinReq; + QMIUIM_UNBLOCK_PIN_RESP_MSG UIMUIMUnblockPinResp; +#endif + QMIUIM_READ_TRANSPARENT_REQ_MSG UIMUIMReadTransparentReq; + QMIUIM_READ_TRANSPARENT_RESP_MSG UIMUIMReadTransparentResp; + + QMIWDS_ADMIN_SET_DATA_FORMAT_REQ_MSG SetDataFormatReq; + QMI_WDA_SET_LOOPBACK_CONFIG_REQ_MSG SetLoopBackReq; + QMI_WDA_SET_LOOPBACK_CONFIG_IND_MSG SetLoopBackInd; + }; +} __attribute__ ((packed)) QMUX_MSG, *PQMUX_MSG; + +#pragma pack(pop) + +#endif // MPQMUX_H diff --git a/root/package/link4all/quectel-CM/quectel1.6.0.15_src/Makefile b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/Makefile new file mode 100644 index 00000000..86adfd07 --- /dev/null +++ b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/Makefile @@ -0,0 +1,24 @@ +#CROSS_COMPILE=arm-hisiv100nptl-linux- +# ifneq ($(CROSS_COMPILE),) +# CROSS-COMPILE:=$(CROSS_COMPILE) +# endif + +# CC:=$(CROSS-COMPILE)gcc +# LD:=$(CROSS-COMPILE)ld + +# release: clean +# $(CC) -Wall -s QmiWwanCM.c GobiNetCM.c main.c MPQMUX.c QMIThread.c util.c udhcpc.c -o simcom-cm -lpthread + +# debug: clean +# $(CC) -Wall -g QmiWwanCM.c GobiNetCM.c main.c MPQMUX.c QMIThread.c util.c udhcpc.c -o simcom-cm -lpthread + +# clean: +# rm -rf simcom-cm *~ + + +quectel-CM:clean + $(CC) -Wall -s QmiWwanCM.c GobiNetCM.c main.c MPQMUX.c QMIThread.c util.c udhcpc.c qmap_bridge_mode.c device.c mbim-cm.c -o quectel-CM -lpthread + +clean: + rm -rf quectel-CM *~ + diff --git a/root/package/link4all/quectel-CM/quectel1.6.0.15_src/NOTICE b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/NOTICE new file mode 100644 index 00000000..0a062cf0 --- /dev/null +++ b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/NOTICE @@ -0,0 +1,7 @@ +This program is totally open souce code, and public domain software for customers of Quectel company. + +The APIs of QMI WWAMN interfaces are defined by Qualcomm. And this program complies with Qualcomm QMI WWAN interfaces specification. + +Customers are free to modify the source codes and redistribute them. + +For those who is not Quectel's customer, all rights are closed, and any copying and commercial development over this progrma is not allowed. diff --git a/root/package/link4all/quectel-CM/quectel1.6.0.15_src/QMIThread.c b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/QMIThread.c new file mode 100644 index 00000000..e8893035 --- /dev/null +++ b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/QMIThread.c @@ -0,0 +1,2204 @@ +/****************************************************************************** + @file QMIThread.c + @brief QMI WWAN connectivity manager. + + DESCRIPTION + Connectivity Management Tool for USB network adapter of Quectel wireless cellular modules. + + INITIALIZATION AND SEQUENCING REQUIREMENTS + None. + + --------------------------------------------------------------------------- + Copyright (c) 2016 - 2020 Quectel Wireless Solution, Co., Ltd. All Rights Reserved. + Quectel Wireless Solution Proprietary and Confidential. + --------------------------------------------------------------------------- +******************************************************************************/ +#include "QMIThread.h" +#ifndef MIN +#define MIN(a, b) ((a) < (b)? (a): (b)) +#endif + +extern char *strndup (const char *__string, size_t __n); + +#define qmi_rsp_check_and_return() do { \ + if (err < 0 || pResponse == NULL) { \ + dbg_time("%s err = %d", __func__, err); \ + return err; \ + } \ + pMUXMsg = &pResponse->MUXMsg; \ + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { \ + USHORT QMUXError = le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); \ + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, \ + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), QMUXError); \ + free(pResponse); \ + return QMUXError; \ + } \ +} while(0) + +#define qmi_rsp_check() do { \ + if (err < 0 || pResponse == NULL) { \ + dbg_time("%s err = %d", __func__, err); \ + return err; \ + } \ + pMUXMsg = &pResponse->MUXMsg; \ + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { \ + USHORT QMUXError = le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); \ + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, \ + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), QMUXError); \ + } \ +} while(0) + +int qmiclientId[QMUX_TYPE_WDS_ADMIN + 1]; //GobiNet use fd to indicate client ID, so type of qmiclientId must be int +static uint32_t WdsConnectionIPv4Handle = 0; +static uint32_t WdsConnectionIPv6Handle = 0; +static int s_is_cdma = 0; +static int s_hdr_personality = 0; // 0x01-HRPD, 0x02-eHRPD +static char *qstrcpy(char *to, const char *from) { //no __strcpy_chk + char *save = to; + for (; (*to = *from) != '\0'; ++from, ++to); + return(save); +} + +static int s_9x07 = -1; + +typedef USHORT (*CUSTOMQMUX)(PQMUX_MSG pMUXMsg, void *arg); + +// To retrieve the ith (Index) TLV +PQMI_TLV_HDR GetTLV (PQCQMUX_MSG_HDR pQMUXMsgHdr, int TLVType) { + int TLVFind = 0; + USHORT Length = le16_to_cpu(pQMUXMsgHdr->Length); + PQMI_TLV_HDR pTLVHdr = (PQMI_TLV_HDR)(pQMUXMsgHdr + 1); + + while (Length >= sizeof(QMI_TLV_HDR)) { + TLVFind++; + if (TLVType > 0x1000) { + if ((TLVFind + 0x1000) == TLVType) + return pTLVHdr; + } else if (pTLVHdr->TLVType == TLVType) { + return pTLVHdr; + } + + Length -= (le16_to_cpu((pTLVHdr->TLVLength)) + sizeof(QMI_TLV_HDR)); + pTLVHdr = (PQMI_TLV_HDR)(((UCHAR *)pTLVHdr) + le16_to_cpu(pTLVHdr->TLVLength) + sizeof(QMI_TLV_HDR)); + } + + return NULL; +} + +static USHORT GetQMUXTransactionId(void) { + static int TransactionId = 0; + if (++TransactionId > 0xFFFF) + TransactionId = 1; + return TransactionId; +} + +static PQCQMIMSG ComposeQMUXMsg(UCHAR QMIType, USHORT Type, CUSTOMQMUX customQmuxMsgFunction, void *arg) { + UCHAR QMIBuf[WDM_DEFAULT_BUFSIZE]; + PQCQMIMSG pRequest = (PQCQMIMSG)QMIBuf; + int Length; + + memset(QMIBuf, 0x00, sizeof(QMIBuf)); + pRequest->QMIHdr.IFType = USB_CTL_MSG_TYPE_QMI; + pRequest->QMIHdr.CtlFlags = 0x00; + pRequest->QMIHdr.QMIType = QMIType; + pRequest->QMIHdr.ClientId = qmiclientId[QMIType] & 0xFF; + + if (qmiclientId[QMIType] == 0) { + dbg_time("QMIType %d has no clientID", QMIType); + return NULL; + } + + pRequest->MUXMsg.QMUXHdr.CtlFlags = QMUX_CTL_FLAG_SINGLE_MSG | QMUX_CTL_FLAG_TYPE_CMD; + pRequest->MUXMsg.QMUXHdr.TransactionId = cpu_to_le16(GetQMUXTransactionId()); + pRequest->MUXMsg.QMUXMsgHdr.Type = cpu_to_le16(Type); + if (customQmuxMsgFunction) + pRequest->MUXMsg.QMUXMsgHdr.Length = cpu_to_le16(customQmuxMsgFunction(&pRequest->MUXMsg, arg) - sizeof(QCQMUX_MSG_HDR)); + else + pRequest->MUXMsg.QMUXMsgHdr.Length = cpu_to_le16(0x0000); + + pRequest->QMIHdr.Length = cpu_to_le16(le16_to_cpu(pRequest->MUXMsg.QMUXMsgHdr.Length) + sizeof(QCQMUX_MSG_HDR) + sizeof(QCQMUX_HDR) + + sizeof(QCQMI_HDR) - 1); + Length = le16_to_cpu(pRequest->QMIHdr.Length) + 1; + + pRequest = (PQCQMIMSG)malloc(Length); + if (pRequest == NULL) { + dbg_time("%s fail to malloc", __func__); + } else { + memcpy(pRequest, QMIBuf, Length); + } + + return pRequest; +} + +#if 0 +static USHORT NasSetEventReportReq(PQMUX_MSG pMUXMsg, void *arg) { + pMUXMsg->SetEventReportReq.TLVType = 0x10; + pMUXMsg->SetEventReportReq.TLVLength = 0x04; + pMUXMsg->SetEventReportReq.ReportSigStrength = 0x00; + pMUXMsg->SetEventReportReq.NumTresholds = 2; + pMUXMsg->SetEventReportReq.TresholdList[0] = -113; + pMUXMsg->SetEventReportReq.TresholdList[1] = -50; + return sizeof(QMINAS_SET_EVENT_REPORT_REQ_MSG); +} + +static USHORT WdsSetEventReportReq(PQMUX_MSG pMUXMsg, void *arg) { + pMUXMsg->EventReportReq.TLVType = 0x10; // 0x10 -- current channel rate indicator + pMUXMsg->EventReportReq.TLVLength = 0x0001; // 1 + pMUXMsg->EventReportReq.Mode = 0x00; // 0-do not report; 1-report when rate changes + + pMUXMsg->EventReportReq.TLV2Type = 0x11; // 0x11 + pMUXMsg->EventReportReq.TLV2Length = 0x0005; // 5 + pMUXMsg->EventReportReq.StatsPeriod = 0x00; // seconds between reports; 0-do not report + pMUXMsg->EventReportReq.StatsMask = 0x000000ff; // + + pMUXMsg->EventReportReq.TLV3Type = 0x12; // 0x12 -- current data bearer indicator + pMUXMsg->EventReportReq.TLV3Length = 0x0001; // 1 + pMUXMsg->EventReportReq.Mode3 = 0x01; // 0-do not report; 1-report when changes + + pMUXMsg->EventReportReq.TLV4Type = 0x13; // 0x13 -- dormancy status indicator + pMUXMsg->EventReportReq.TLV4Length = 0x0001; // 1 + pMUXMsg->EventReportReq.DormancyStatus = 0x00; // 0-do not report; 1-report when changes + return sizeof(QMIWDS_SET_EVENT_REPORT_REQ_MSG); +} + +static USHORT DmsSetEventReportReq(PQMUX_MSG pMUXMsg) { + PPIN_STATUS pPinState = (PPIN_STATUS)(&pMUXMsg->DmsSetEventReportReq + 1); + PUIM_STATE pUimState = (PUIM_STATE)(pPinState + 1); + // Pin State + pPinState->TLVType = 0x12; + pPinState->TLVLength = 0x01; + pPinState->ReportPinState = 0x01; + // UIM State + pUimState->TLVType = 0x15; + pUimState->TLVLength = 0x01; + pUimState->UIMState = 0x01; + return sizeof(QMIDMS_SET_EVENT_REPORT_REQ_MSG) + sizeof(PIN_STATUS) + sizeof(UIM_STATE); +} +#endif + +static USHORT WdsStartNwInterfaceReq(PQMUX_MSG pMUXMsg, void *arg) { + PQMIWDS_TECHNOLOGY_PREFERECE pTechPref; + PQMIWDS_AUTH_PREFERENCE pAuthPref; + PQMIWDS_USERNAME pUserName; + PQMIWDS_PASSWD pPasswd; + PQMIWDS_APNNAME pApnName; + PQMIWDS_IP_FAMILY_TLV pIpFamily; + USHORT TLVLength = 0; + UCHAR *pTLV; + PROFILE_T *profile = (PROFILE_T *)arg; + const char *profile_user = profile->user; + const char *profile_password = profile->password; + int profile_auth = profile->auth; + + if (s_is_cdma && (profile_user == NULL || profile_user[0] == '\0') && (profile_password == NULL || profile_password[0] == '\0')) { + profile_user = "ctnet@mycdma.cn"; + profile_password = "vnet.mobi"; + profile_auth = 2; //chap + } + + pTLV = (UCHAR *)(&pMUXMsg->StartNwInterfaceReq + 1); + pMUXMsg->StartNwInterfaceReq.Length = 0; + + // Set technology Preferece + pTechPref = (PQMIWDS_TECHNOLOGY_PREFERECE)(pTLV + TLVLength); + pTechPref->TLVType = 0x30; + pTechPref->TLVLength = cpu_to_le16(0x01); + if (s_is_cdma == 0) + pTechPref->TechPreference = 0x01; + else + pTechPref->TechPreference = 0x02; + TLVLength +=(le16_to_cpu(pTechPref->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + + // Set APN Name + if (profile->apn && !s_is_cdma) { //cdma no apn + pApnName = (PQMIWDS_APNNAME)(pTLV + TLVLength); + pApnName->TLVType = 0x14; + pApnName->TLVLength = cpu_to_le16(strlen(profile->apn)); + qstrcpy((char *)&pApnName->ApnName, profile->apn); + TLVLength +=(le16_to_cpu(pApnName->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + // Set User Name + if (profile_user) { + pUserName = (PQMIWDS_USERNAME)(pTLV + TLVLength); + pUserName->TLVType = 0x17; + pUserName->TLVLength = cpu_to_le16(strlen(profile_user)); + qstrcpy((char *)&pUserName->UserName, profile_user); + TLVLength += (le16_to_cpu(pUserName->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + // Set Password + if (profile_password) { + pPasswd = (PQMIWDS_PASSWD)(pTLV + TLVLength); + pPasswd->TLVType = 0x18; + pPasswd->TLVLength = cpu_to_le16(strlen(profile_password)); + qstrcpy((char *)&pPasswd->Passwd, profile_password); + TLVLength += (le16_to_cpu(pPasswd->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + // Set Auth Protocol + if (profile_user && profile_password) { + pAuthPref = (PQMIWDS_AUTH_PREFERENCE)(pTLV + TLVLength); + pAuthPref->TLVType = 0x16; + pAuthPref->TLVLength = cpu_to_le16(0x01); + pAuthPref->AuthPreference = profile_auth; // 0 ~ None, 1 ~ Pap, 2 ~ Chap, 3 ~ MsChapV2 + TLVLength += (le16_to_cpu(pAuthPref->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + // Add IP Family Preference + pIpFamily = (PQMIWDS_IP_FAMILY_TLV)(pTLV + TLVLength); + pIpFamily->TLVType = 0x19; + pIpFamily->TLVLength = cpu_to_le16(0x01); + pIpFamily->IpFamily = profile->curIpFamily; + TLVLength += (le16_to_cpu(pIpFamily->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + + //Set Profile Index + if (profile->pdp && !s_is_cdma) { //cdma only support one pdp, so no need to set profile index + PQMIWDS_PROFILE_IDENTIFIER pProfileIndex = (PQMIWDS_PROFILE_IDENTIFIER)(pTLV + TLVLength); + pProfileIndex->TLVLength = cpu_to_le16(0x01); + pProfileIndex->TLVType = 0x31; + pProfileIndex->ProfileIndex = profile->pdp; + if (s_is_cdma && s_hdr_personality == 0x02) { + pProfileIndex->TLVType = 0x32; //profile_index_3gpp2 + pProfileIndex->ProfileIndex = 101; + } + TLVLength += (le16_to_cpu(pProfileIndex->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + return sizeof(QMIWDS_START_NETWORK_INTERFACE_REQ_MSG) + TLVLength; +} + +static USHORT WdsStopNwInterfaceReq(PQMUX_MSG pMUXMsg, void *arg) { + pMUXMsg->StopNwInterfaceReq.TLVType = 0x01; + pMUXMsg->StopNwInterfaceReq.TLVLength = cpu_to_le16(0x04); + if (*((int *)arg) == IpFamilyV4) + pMUXMsg->StopNwInterfaceReq.Handle = cpu_to_le32(WdsConnectionIPv4Handle); + else + pMUXMsg->StopNwInterfaceReq.Handle = cpu_to_le32(WdsConnectionIPv6Handle); + return sizeof(QMIWDS_STOP_NETWORK_INTERFACE_REQ_MSG); +} + +static USHORT WdsSetClientIPFamilyPref(PQMUX_MSG pMUXMsg, void *arg) { + pMUXMsg->SetClientIpFamilyPrefReq.TLVType = 0x01; + pMUXMsg->SetClientIpFamilyPrefReq.TLVLength = cpu_to_le16(0x01); + pMUXMsg->SetClientIpFamilyPrefReq.IpPreference = *((UCHAR *)arg); + return sizeof(QMIWDS_SET_CLIENT_IP_FAMILY_PREF_REQ_MSG); +} + +static USHORT WdsSetAutoConnect(PQMUX_MSG pMUXMsg, void *arg) { + pMUXMsg->SetAutoConnectReq.TLVType = 0x01; + pMUXMsg->SetAutoConnectReq.TLVLength = cpu_to_le16(0x01); + pMUXMsg->SetAutoConnectReq.autoconnect_setting = *((UCHAR *)arg); + return sizeof(QMIWDS_SET_AUTO_CONNECT_REQ_MSG); +} + +enum peripheral_ep_type { + DATA_EP_TYPE_RESERVED = 0x0, + DATA_EP_TYPE_HSIC = 0x1, + DATA_EP_TYPE_HSUSB = 0x2, + DATA_EP_TYPE_PCIE = 0x3, + DATA_EP_TYPE_EMBEDDED = 0x4, + DATA_EP_TYPE_BAM_DMUX = 0x5, +}; + +static USHORT WdsSetQMUXBindMuxDataPort(PQMUX_MSG pMUXMsg, void *arg) { + QMAP_SETTING *qmap_settings = (QMAP_SETTING *)arg; + + pMUXMsg->BindMuxDataPortReq.TLVType = 0x10; + pMUXMsg->BindMuxDataPortReq.TLVLength = cpu_to_le16(0x08); + pMUXMsg->BindMuxDataPortReq.ep_type = cpu_to_le32(qmap_settings->ep_type); + pMUXMsg->BindMuxDataPortReq.iface_id = cpu_to_le32(qmap_settings->iface_id); + pMUXMsg->BindMuxDataPortReq.TLV2Type = 0x11; + pMUXMsg->BindMuxDataPortReq.TLV2Length = cpu_to_le16(0x01); + pMUXMsg->BindMuxDataPortReq.MuxId = qmap_settings->MuxId; + pMUXMsg->BindMuxDataPortReq.TLV3Type = 0x13; + pMUXMsg->BindMuxDataPortReq.TLV3Length = cpu_to_le16(0x04); + pMUXMsg->BindMuxDataPortReq.client_type = cpu_to_le32(1); //WDS_CLIENT_TYPE_TETHERED + + return sizeof(QMIWDS_BIND_MUX_DATA_PORT_REQ_MSG); +} + +static int qmap_version = 0x05; +static USHORT WdaSetDataFormat(PQMUX_MSG pMUXMsg, void *arg) { + QMAP_SETTING *qmap_settings = (QMAP_SETTING *)arg; + + if (qmap_settings->rx_urb_size == 0) { + PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV_QOS pWdsAdminQosTlv; + PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV linkProto; + PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV dlTlp; + + pWdsAdminQosTlv = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV_QOS)(&pMUXMsg->QMUXMsgHdr + 1); + pWdsAdminQosTlv->TLVType = 0x10; + pWdsAdminQosTlv->TLVLength = cpu_to_le16(0x0001); + pWdsAdminQosTlv->QOSSetting = 0; /* no-QOS header */ + + linkProto = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV)(pWdsAdminQosTlv + 1); + linkProto->TLVType = 0x11; + linkProto->TLVLength = cpu_to_le16(4); + linkProto->Value = cpu_to_le32(0x01); /* Set Ethernet mode */ + + dlTlp = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV)(linkProto + 1);; + dlTlp->TLVType = 0x13; + dlTlp->TLVLength = cpu_to_le16(4); + dlTlp->Value = cpu_to_le32(0x00); + + if (sizeof(*linkProto) != 7 ) + dbg_time("%s sizeof(*linkProto) = %zu, is not 7!", __func__, sizeof(*linkProto) ); + + return sizeof(QCQMUX_MSG_HDR) + sizeof(*pWdsAdminQosTlv) + sizeof(*linkProto) + sizeof(*dlTlp); + } + else { + //Indicates whether the Quality of Service(QOS) data format is used by the client. + pMUXMsg->SetDataFormatReq.QosDataFormatTlv.TLVType = 0x10; + pMUXMsg->SetDataFormatReq.QosDataFormatTlv.TLVLength = cpu_to_le16(0x0001); + pMUXMsg->SetDataFormatReq.QosDataFormatTlv.QOSSetting = 0; /* no-QOS header */ + + //Underlying Link Layer Protocol + pMUXMsg->SetDataFormatReq.UnderlyingLinkLayerProtocolTlv.TLVType = 0x11; + pMUXMsg->SetDataFormatReq.UnderlyingLinkLayerProtocolTlv.TLVLength = cpu_to_le16(4); + pMUXMsg->SetDataFormatReq.UnderlyingLinkLayerProtocolTlv.Value = cpu_to_le32(0x02); /* Set IP mode */ + + //Uplink (UL) data aggregation protocol to be used for uplink data transfer. + pMUXMsg->SetDataFormatReq.UplinkDataAggregationProtocolTlv.TLVType = 0x12; + pMUXMsg->SetDataFormatReq.UplinkDataAggregationProtocolTlv.TLVLength = cpu_to_le16(4); + pMUXMsg->SetDataFormatReq.UplinkDataAggregationProtocolTlv.Value = cpu_to_le32(qmap_version); //UL QMAP is enabled + + //Downlink (DL) data aggregation protocol to be used for downlink data transfer + pMUXMsg->SetDataFormatReq.DownlinkDataAggregationProtocolTlv.TLVType = 0x13; + pMUXMsg->SetDataFormatReq.DownlinkDataAggregationProtocolTlv.TLVLength = cpu_to_le16(4); + pMUXMsg->SetDataFormatReq.DownlinkDataAggregationProtocolTlv.Value = cpu_to_le32(qmap_version); //DL QMAP is enabled + + //Maximum number of datagrams in a single aggregated packet on downlink + pMUXMsg->SetDataFormatReq.DownlinkDataAggregationMaxDatagramsTlv.TLVType = 0x15; + pMUXMsg->SetDataFormatReq.DownlinkDataAggregationMaxDatagramsTlv.TLVLength = cpu_to_le16(4); + pMUXMsg->SetDataFormatReq.DownlinkDataAggregationMaxDatagramsTlv.Value = cpu_to_le32(qmap_settings->rx_urb_size/512); + + //Maximum size in bytes of a single aggregated packet allowed on downlink + pMUXMsg->SetDataFormatReq.DownlinkDataAggregationMaxSizeTlv.TLVType = 0x16; + pMUXMsg->SetDataFormatReq.DownlinkDataAggregationMaxSizeTlv.TLVLength = cpu_to_le16(4); + pMUXMsg->SetDataFormatReq.DownlinkDataAggregationMaxSizeTlv.Value = cpu_to_le32(qmap_settings->rx_urb_size); + + //Peripheral End Point ID + pMUXMsg->SetDataFormatReq.epTlv.TLVType = 0x17; + pMUXMsg->SetDataFormatReq.epTlv.TLVLength = cpu_to_le16(8); + pMUXMsg->SetDataFormatReq.epTlv.ep_type = cpu_to_le32(qmap_settings->ep_type); + pMUXMsg->SetDataFormatReq.epTlv.iface_id = cpu_to_le32(qmap_settings->iface_id); + +#ifdef QUECTEL_UL_DATA_AGG + if (!qmap_settings->ul_data_aggregation_max_datagrams) { + return ((size_t)&((QMIWDS_ADMIN_SET_DATA_FORMAT_REQ_MSG *)0)->DlMinimumPassingTlv); + } + + //Maximum number of datagrams in a single aggregated packet on uplink + pMUXMsg->SetDataFormatReq.DlMinimumPassingTlv.TLVType = 0x19; + pMUXMsg->SetDataFormatReq.DlMinimumPassingTlv.TLVLength = cpu_to_le16(4); + pMUXMsg->SetDataFormatReq.DlMinimumPassingTlv.Value = cpu_to_le32(qmap_settings->dl_minimum_padding); + + //Maximum number of datagrams in a single aggregated packet on uplink + pMUXMsg->SetDataFormatReq.UplinkDataAggregationMaxDatagramsTlv.TLVType = 0x1B; + pMUXMsg->SetDataFormatReq.UplinkDataAggregationMaxDatagramsTlv.TLVLength = cpu_to_le16(4); + pMUXMsg->SetDataFormatReq.UplinkDataAggregationMaxDatagramsTlv.Value = cpu_to_le32(qmap_settings->ul_data_aggregation_max_datagrams); + + //Maximum size in bytes of a single aggregated packet allowed on downlink + pMUXMsg->SetDataFormatReq.UplinkDataAggregationMaxSizeTlv.TLVType = 0x1C; + pMUXMsg->SetDataFormatReq.UplinkDataAggregationMaxSizeTlv.TLVLength = cpu_to_le16(4); + pMUXMsg->SetDataFormatReq.UplinkDataAggregationMaxSizeTlv.Value = cpu_to_le32(qmap_settings->ul_data_aggregation_max_size); +#endif + + return sizeof(QMIWDS_ADMIN_SET_DATA_FORMAT_REQ_MSG); + } +} + +#ifdef CONFIG_SIM +static USHORT DmsUIMVerifyPinReqSend(PQMUX_MSG pMUXMsg, void *arg) { + pMUXMsg->UIMVerifyPinReq.TLVType = 0x01; + pMUXMsg->UIMVerifyPinReq.PINID = 0x01; //Pin1, not Puk + pMUXMsg->UIMVerifyPinReq.PINLen = strlen((const char *)arg); + qstrcpy((PCHAR)&pMUXMsg->UIMVerifyPinReq.PINValue, ((const char *)arg)); + pMUXMsg->UIMVerifyPinReq.TLVLength = cpu_to_le16(2 + strlen((const char *)arg)); + return sizeof(QMIDMS_UIM_VERIFY_PIN_REQ_MSG) + (strlen((const char *)arg) - 1); +} + +static USHORT UimVerifyPinReqSend(PQMUX_MSG pMUXMsg, void *arg) +{ + pMUXMsg->UIMUIMVerifyPinReq.TLVType = 0x01; + pMUXMsg->UIMUIMVerifyPinReq.TLVLength = cpu_to_le16(0x02); + pMUXMsg->UIMUIMVerifyPinReq.Session_Type = 0x00; + pMUXMsg->UIMUIMVerifyPinReq.Aid_Len = 0x00; + pMUXMsg->UIMUIMVerifyPinReq.TLV2Type = 0x02; + pMUXMsg->UIMUIMVerifyPinReq.TLV2Length = cpu_to_le16(2 + strlen((const char *)arg)); + pMUXMsg->UIMUIMVerifyPinReq.PINID = 0x01; //Pin1, not Puk + pMUXMsg->UIMUIMVerifyPinReq.PINLen= strlen((const char *)arg); + qstrcpy((PCHAR)&pMUXMsg->UIMUIMVerifyPinReq.PINValue, ((const char *)arg)); + return sizeof(QMIUIM_VERIFY_PIN_REQ_MSG) + (strlen((const char *)arg) - 1); +} + +#ifdef CONFIG_IMSI_ICCID +static USHORT UimReadTransparentIMSIReqSend(PQMUX_MSG pMUXMsg, void *arg) { + PREAD_TRANSPARENT_TLV pReadTransparent; + + pMUXMsg->UIMUIMReadTransparentReq.TLVType = 0x01; + pMUXMsg->UIMUIMReadTransparentReq.TLVLength = cpu_to_le16(0x02); + if (!strcmp((char *)arg, "EF_ICCID")) { + pMUXMsg->UIMUIMReadTransparentReq.Session_Type = 0x06; + pMUXMsg->UIMUIMReadTransparentReq.Aid_Len = 0x00; + + pMUXMsg->UIMUIMReadTransparentReq.TLV2Type = 0x02; + pMUXMsg->UIMUIMReadTransparentReq.file_id = cpu_to_le16(0x2FE2); + pMUXMsg->UIMUIMReadTransparentReq.path_len = 0x02; + pMUXMsg->UIMUIMReadTransparentReq.path[0] = 0x00; + pMUXMsg->UIMUIMReadTransparentReq.path[1] = 0x3F; + } + else if(!strcmp((char *)arg, "EF_IMSI")) { + pMUXMsg->UIMUIMReadTransparentReq.Session_Type = 0x00; + pMUXMsg->UIMUIMReadTransparentReq.Aid_Len = 0x00; + + pMUXMsg->UIMUIMReadTransparentReq.TLV2Type = 0x02; + pMUXMsg->UIMUIMReadTransparentReq.file_id = cpu_to_le16(0x6F07); + pMUXMsg->UIMUIMReadTransparentReq.path_len = 0x04; + pMUXMsg->UIMUIMReadTransparentReq.path[0] = 0x00; + pMUXMsg->UIMUIMReadTransparentReq.path[1] = 0x3F; + pMUXMsg->UIMUIMReadTransparentReq.path[2] = 0xFF; + pMUXMsg->UIMUIMReadTransparentReq.path[3] = 0x7F; + } + + pMUXMsg->UIMUIMReadTransparentReq.TLV2Length = cpu_to_le16(3 + pMUXMsg->UIMUIMReadTransparentReq.path_len); + + pReadTransparent = (PREAD_TRANSPARENT_TLV)(&pMUXMsg->UIMUIMReadTransparentReq.path[pMUXMsg->UIMUIMReadTransparentReq.path_len]); + pReadTransparent->TLVType = 0x03; + pReadTransparent->TLVLength = cpu_to_le16(0x04); + pReadTransparent->Offset = cpu_to_le16(0x00); + pReadTransparent->Length = cpu_to_le16(0x00); + + return (sizeof(QMIUIM_READ_TRANSPARENT_REQ_MSG) + pMUXMsg->UIMUIMReadTransparentReq.path_len + sizeof(READ_TRANSPARENT_TLV)); +} +#endif +#endif + +#ifdef CONFIG_APN +static USHORT WdsGetProfileSettingsReqSend(PQMUX_MSG pMUXMsg, void *arg) { + PROFILE_T *profile = (PROFILE_T *)arg; + pMUXMsg->GetProfileSettingsReq.Length = cpu_to_le16(sizeof(QMIWDS_GET_PROFILE_SETTINGS_REQ_MSG) - 4); + pMUXMsg->GetProfileSettingsReq.TLVType = 0x01; + pMUXMsg->GetProfileSettingsReq.TLVLength = cpu_to_le16(0x02); + pMUXMsg->GetProfileSettingsReq.ProfileType = 0x00; // 0 ~ 3GPP, 1 ~ 3GPP2 + pMUXMsg->GetProfileSettingsReq.ProfileIndex = profile->pdp; + return sizeof(QMIWDS_GET_PROFILE_SETTINGS_REQ_MSG); +} + +static USHORT WdsModifyProfileSettingsReq(PQMUX_MSG pMUXMsg, void *arg) { + USHORT TLVLength = 0; + UCHAR *pTLV; + PROFILE_T *profile = (PROFILE_T *)arg; + PQMIWDS_PDPTYPE pPdpType; + + pMUXMsg->ModifyProfileSettingsReq.Length = cpu_to_le16(sizeof(QMIWDS_MODIFY_PROFILE_SETTINGS_REQ_MSG) - 4); + pMUXMsg->ModifyProfileSettingsReq.TLVType = 0x01; + pMUXMsg->ModifyProfileSettingsReq.TLVLength = cpu_to_le16(0x02); + pMUXMsg->ModifyProfileSettingsReq.ProfileType = 0x00; // 0 ~ 3GPP, 1 ~ 3GPP2 + pMUXMsg->ModifyProfileSettingsReq.ProfileIndex = profile->pdp; + + pTLV = (UCHAR *)(&pMUXMsg->ModifyProfileSettingsReq + 1); + + pPdpType = (PQMIWDS_PDPTYPE)(pTLV + TLVLength); + pPdpType->TLVType = 0x11; + pPdpType->TLVLength = cpu_to_le16(0x01); +// 0 ?C PDP-IP (IPv4) +// 1 ?C PDP-PPP +// 2 ?C PDP-IPv6 +// 3 ?C PDP-IPv4v6 + if (profile->enable_ipv4 && profile->enable_ipv6) + pPdpType->PdpType = 3; + else if (profile->enable_ipv6) + pPdpType->PdpType = 2; + else + pPdpType->PdpType = 0; + TLVLength +=(le16_to_cpu(pPdpType->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + + // Set APN Name + if (profile->apn) { + PQMIWDS_APNNAME pApnName = (PQMIWDS_APNNAME)(pTLV + TLVLength); + pApnName->TLVType = 0x14; + pApnName->TLVLength = cpu_to_le16(strlen(profile->apn)); + qstrcpy((char *)&pApnName->ApnName, profile->apn); + TLVLength +=(le16_to_cpu(pApnName->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + // Set User Name + if (profile->user) { + PQMIWDS_USERNAME pUserName = (PQMIWDS_USERNAME)(pTLV + TLVLength); + pUserName->TLVType = 0x1B; + pUserName->TLVLength = cpu_to_le16(strlen(profile->user)); + qstrcpy((char *)&pUserName->UserName, profile->user); + TLVLength += (le16_to_cpu(pUserName->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + // Set Password + if (profile->password) { + PQMIWDS_PASSWD pPasswd = (PQMIWDS_PASSWD)(pTLV + TLVLength); + pPasswd->TLVType = 0x1C; + pPasswd->TLVLength = cpu_to_le16(strlen(profile->password)); + qstrcpy((char *)&pPasswd->Passwd, profile->password); + TLVLength +=(le16_to_cpu(pPasswd->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + // Set Auth Protocol + if (profile->user && profile->password) { + PQMIWDS_AUTH_PREFERENCE pAuthPref = (PQMIWDS_AUTH_PREFERENCE)(pTLV + TLVLength); + pAuthPref->TLVType = 0x1D; + pAuthPref->TLVLength = cpu_to_le16(0x01); + pAuthPref->AuthPreference = profile->auth; // 0 ~ None, 1 ~ Pap, 2 ~ Chap, 3 ~ MsChapV2 + TLVLength += (le16_to_cpu(pAuthPref->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + return sizeof(QMIWDS_MODIFY_PROFILE_SETTINGS_REQ_MSG) + TLVLength; +} +#endif + +static USHORT WdsGetRuntimeSettingReq(PQMUX_MSG pMUXMsg, void *arg) { + pMUXMsg->GetRuntimeSettingsReq.TLVType = 0x10; + pMUXMsg->GetRuntimeSettingsReq.TLVLength = cpu_to_le16(0x04); + // the following mask also applies to IPV6 + pMUXMsg->GetRuntimeSettingsReq.Mask = cpu_to_le32(QMIWDS_GET_RUNTIME_SETTINGS_MASK_IPV4DNS_ADDR | + QMIWDS_GET_RUNTIME_SETTINGS_MASK_IPV4_ADDR | + QMIWDS_GET_RUNTIME_SETTINGS_MASK_MTU | + QMIWDS_GET_RUNTIME_SETTINGS_MASK_IPV4GATEWAY_ADDR) | + QMIWDS_GET_RUNTIME_SETTINGS_MASK_PCSCF_SV_ADDR | + QMIWDS_GET_RUNTIME_SETTINGS_MASK_PCSCF_DOM_NAME; + + return sizeof(QMIWDS_GET_RUNTIME_SETTINGS_REQ_MSG); +} + +static PQCQMIMSG s_pRequest; +static PQCQMIMSG s_pResponse; +static pthread_mutex_t s_commandmutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t s_commandcond = PTHREAD_COND_INITIALIZER; + +static int is_response(const PQCQMIMSG pRequest, const PQCQMIMSG pResponse) { + if ((pRequest->QMIHdr.QMIType == pResponse->QMIHdr.QMIType) + && (pRequest->QMIHdr.ClientId == pResponse->QMIHdr.ClientId)) { + USHORT requestTID, responseTID; + if (pRequest->QMIHdr.QMIType == QMUX_TYPE_CTL) { + requestTID = pRequest->CTLMsg.QMICTLMsgHdr.TransactionId; + responseTID = pResponse->CTLMsg.QMICTLMsgHdr.TransactionId; + } else { + requestTID = le16_to_cpu(pRequest->MUXMsg.QMUXHdr.TransactionId); + responseTID = le16_to_cpu(pResponse->MUXMsg.QMUXHdr.TransactionId); + } + return (requestTID == responseTID); + } + return 0; +} + + +int (*qmidev_send)(PQCQMIMSG pRequest); + +int QmiThreadSendQMITimeout(PQCQMIMSG pRequest, PQCQMIMSG *ppResponse, unsigned msecs) { + int ret; + + static int flag = 0; + if (!flag) { + cond_setclock_attr(&s_commandcond, CLOCK_MONOTONIC); + flag = 1; + } + + if (!pRequest) + return -EINVAL; + + pthread_mutex_lock(&s_commandmutex); + + if (ppResponse) + *ppResponse = NULL; + + dump_qmi(pRequest, le16_to_cpu(pRequest->QMIHdr.Length) + 1); + + s_pRequest = pRequest; + s_pResponse = NULL; + + ret = qmidev_send(pRequest); + + if (ret == 0) { + ret = pthread_cond_timeout_np(&s_commandcond, &s_commandmutex, msecs); + if (!ret) { + if (s_pResponse && ppResponse) { + *ppResponse = s_pResponse; + } else { + if (s_pResponse) { + free(s_pResponse); + s_pResponse = NULL; + } + } + } else { + dbg_time("%s pthread_cond_timeout_np timeout", __func__); + } + } + + pthread_mutex_unlock(&s_commandmutex); + + return ret; +} + +int QmiThreadSendQMI(PQCQMIMSG pRequest, PQCQMIMSG *ppResponse) { + return QmiThreadSendQMITimeout(pRequest, ppResponse, 30 * 1000); +} + +void QmiThreadRecvQMI(PQCQMIMSG pResponse) { + pthread_mutex_lock(&s_commandmutex); + if (pResponse == NULL) { + if (s_pRequest) { + free(s_pRequest); + s_pRequest = NULL; + s_pResponse = NULL; + pthread_cond_signal(&s_commandcond); + } + pthread_mutex_unlock(&s_commandmutex); + return; + } + dump_qmi(pResponse, le16_to_cpu(pResponse->QMIHdr.Length) + 1); + if (s_pRequest && is_response(s_pRequest, pResponse)) { + free(s_pRequest); + s_pRequest = NULL; + s_pResponse = malloc(le16_to_cpu(pResponse->QMIHdr.Length) + 1); + if (s_pResponse != NULL) { + memcpy(s_pResponse, pResponse, le16_to_cpu(pResponse->QMIHdr.Length) + 1); + } + pthread_cond_signal(&s_commandcond); + } else if ((pResponse->QMIHdr.QMIType == QMUX_TYPE_CTL) + && (le16_to_cpu(pResponse->CTLMsg.QMICTLMsgHdrRsp.QMICTLType == QMICTL_REVOKE_CLIENT_ID_IND))) { + qmidevice_send_event_to_main(MODEM_REPORT_RESET_EVENT); + } else if ((pResponse->QMIHdr.QMIType == QMUX_TYPE_NAS) + && (le16_to_cpu(pResponse->MUXMsg.QMUXMsgHdrResp.Type) == QMINAS_SERVING_SYSTEM_IND)) { + qmidevice_send_event_to_main(RIL_UNSOL_RESPONSE_VOICE_NETWORK_STATE_CHANGED); + } else if ((pResponse->QMIHdr.QMIType == QMUX_TYPE_WDS) + && (le16_to_cpu(pResponse->MUXMsg.QMUXMsgHdrResp.Type) == QMIWDS_GET_PKT_SRVC_STATUS_IND)) { + qmidevice_send_event_to_main(RIL_UNSOL_DATA_CALL_LIST_CHANGED); + } else if ((pResponse->QMIHdr.QMIType == QMUX_TYPE_NAS) + && (le16_to_cpu(pResponse->MUXMsg.QMUXMsgHdrResp.Type) == QMINAS_SYS_INFO_IND)) { + qmidevice_send_event_to_main(RIL_UNSOL_RESPONSE_VOICE_NETWORK_STATE_CHANGED); + } else if ((pResponse->QMIHdr.QMIType == QMUX_TYPE_WDS_ADMIN) + && (le16_to_cpu(pResponse->MUXMsg.QMUXMsgHdrResp.Type) == QMI_WDA_SET_LOOPBACK_CONFIG_IND)) { + qmidevice_send_event_to_main_ext(RIL_UNSOL_LOOPBACK_CONFIG_IND, + &pResponse->MUXMsg.SetLoopBackInd, sizeof(pResponse->MUXMsg.SetLoopBackInd)); + } else { + if (debug_qmi) + dbg_time("nobody care this qmi msg!!"); + } + pthread_mutex_unlock(&s_commandmutex); +} + +int requestSetEthMode(PROFILE_T *profile) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse = NULL; + PQMUX_MSG pMUXMsg; + int err; + PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV linkProto; + UCHAR IpPreference; + UCHAR autoconnect_setting = 0; + QMAP_SETTING qmap_settings = {0}; + + qmap_settings.size = sizeof(qmap_settings); + + if (profile->qmap_mode) { + profile->rawIP = 1; + s_9x07 = profile->rawIP; + + qmap_settings.MuxId = profile->muxid; + + if (qmidev_is_pciemhi(profile->qmichannel)) { //SDX20_PCIE + qmap_settings.rx_urb_size = profile->qmap_size; //SDX24&SDX55 support 32KB + qmap_settings.ep_type = DATA_EP_TYPE_PCIE; + qmap_settings.iface_id = 0x04; + } + else { // for MDM9x07&MDM9x40&SDX20 USB + qmap_settings.rx_urb_size = profile->qmap_size; //SDX24&SDX55 support 32KB + qmap_settings.ep_type = DATA_EP_TYPE_HSUSB; + qmap_settings.iface_id = 0x04; + } + + qmap_settings.ul_data_aggregation_max_datagrams = 11; //by test result, 11 can get best TPUT + qmap_settings.ul_data_aggregation_max_size = 8*1024; + qmap_settings.dl_minimum_padding = 16; + if(profile->qmap_version != 0x09) + profile->qmap_version = 0x05; + + qmap_version = profile->qmap_version; + if (profile->rmnet_info.size) { + qmap_settings.rx_urb_size = profile->rmnet_info.rx_urb_size; + qmap_settings.ep_type = profile->rmnet_info.ep_type; + qmap_settings.iface_id = profile->rmnet_info.iface_id; + qmap_settings.dl_minimum_padding = profile->rmnet_info.dl_minimum_padding; + qmap_version = profile->rmnet_info.qmap_version; + + if (profile->loopback_state) { + //if loopbakck enabed by '/nv/item_files/modem/data/3gpp/ps/loopback_config.txt', dl_minimum_padding donot take effect + qmap_settings.dl_minimum_padding = 0; + } + else { + qmap_settings.dl_minimum_padding = 0; //no effect when register to real netowrk + } + } + + if (qmidev_is_gobinet(profile->qmichannel)) { //GobiNet set data format in GobiNet driver + goto skip_WdaSetDataFormat; + } else if (profile->qmap_mode > 1) {//QMAP MUX enabled, set data format in quectel-qmi-proxy + goto skip_WdaSetDataFormat; + } + } + + pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS_ADMIN, QMIWDS_ADMIN_SET_DATA_FORMAT_REQ, WdaSetDataFormat, (void *)&qmap_settings); + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + + linkProto = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x11); + if (linkProto != NULL) { + profile->rawIP = (le32_to_cpu(linkProto->Value) == 2); + s_9x07 = profile->rawIP; + } + + linkProto = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x16); + if (linkProto != NULL && profile->qmap_mode) { + qmap_settings.rx_urb_size = le32_to_cpu(linkProto->Value); + dbg_time("qmap_settings.rx_urb_size = %u", qmap_settings.rx_urb_size); //must same as rx_urb_size defined in GobiNet&qmi_wwan driver + } + +#ifdef QUECTEL_UL_DATA_AGG + if (qmap_settings.ul_data_aggregation_max_datagrams) + { + linkProto = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x17); + if (linkProto != NULL) { + qmap_settings.ul_data_aggregation_max_datagrams = MIN(qmap_settings.ul_data_aggregation_max_datagrams, le32_to_cpu(linkProto->Value)); + dbg_time("qmap_settings.ul_data_aggregation_max_datagrams = %u", qmap_settings.ul_data_aggregation_max_datagrams); + } + + linkProto = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x18); + if (linkProto != NULL) { + qmap_settings.ul_data_aggregation_max_size = MIN(qmap_settings.ul_data_aggregation_max_size, le32_to_cpu(linkProto->Value)); + dbg_time("qmap_settings.ul_data_aggregation_max_size = %u", qmap_settings.ul_data_aggregation_max_size); + } + + linkProto = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x1A); + if (linkProto != NULL) { + qmap_settings.dl_minimum_padding = le32_to_cpu(linkProto->Value); + dbg_time("qmap_settings.dl_minimum_padding = %u", qmap_settings.dl_minimum_padding); + } + + if (qmap_settings.ul_data_aggregation_max_datagrams > 1) { + ql_set_driver_qmap_setting(profile, &qmap_settings); + } + } +#endif + + free(pResponse); + +skip_WdaSetDataFormat: + if (profile->enable_ipv4) { + if (profile->qmapnet_adapter) { + // bind wds mux data port + pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS, QMIWDS_BIND_MUX_DATA_PORT_REQ , WdsSetQMUXBindMuxDataPort, (void *)&qmap_settings); + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + if (pResponse) free(pResponse); + } + + // set ipv4 + IpPreference = IpFamilyV4; + pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS, QMIWDS_SET_CLIENT_IP_FAMILY_PREF_REQ, WdsSetClientIPFamilyPref, (void *)&IpPreference); + err = QmiThreadSendQMI(pRequest, &pResponse); + if (pResponse) free(pResponse); + } + + if (profile->enable_ipv6) { + if (profile->qmapnet_adapter) { + // bind wds ipv6 mux data port + pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS_IPV6, QMIWDS_BIND_MUX_DATA_PORT_REQ , WdsSetQMUXBindMuxDataPort, (void *)&qmap_settings); + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + if (pResponse) free(pResponse); + } + + // set ipv6 + IpPreference = IpFamilyV6; + pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS_IPV6, QMIWDS_SET_CLIENT_IP_FAMILY_PREF_REQ, WdsSetClientIPFamilyPref, (void *)&IpPreference); + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + if (pResponse) free(pResponse); + } + + pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS, QMIWDS_SET_AUTO_CONNECT_REQ , WdsSetAutoConnect, (void *)&autoconnect_setting); + QmiThreadSendQMI(pRequest, &pResponse); + if (pResponse) free(pResponse); + + return 0; +} + +#ifdef CONFIG_SIM +int requestGetPINStatus(SIM_Status *pSIMStatus) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + PQMIDMS_UIM_PIN_STATUS pPin1Status = NULL; + //PQMIDMS_UIM_PIN_STATUS pPin2Status = NULL; + + if (s_9x07 && qmiclientId[QMUX_TYPE_UIM]) + pRequest = ComposeQMUXMsg(QMUX_TYPE_UIM, QMIUIM_GET_CARD_STATUS_REQ, NULL, NULL); + else + pRequest = ComposeQMUXMsg(QMUX_TYPE_DMS, QMIDMS_UIM_GET_PIN_STATUS_REQ, NULL, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + + pPin1Status = (PQMIDMS_UIM_PIN_STATUS)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x11); + //pPin2Status = (PQMIDMS_UIM_PIN_STATUS)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x12); + + if (pPin1Status != NULL) { + if (pPin1Status->PINStatus == QMI_PIN_STATUS_NOT_VERIF) { + *pSIMStatus = SIM_PIN; + } else if (pPin1Status->PINStatus == QMI_PIN_STATUS_BLOCKED) { + *pSIMStatus = SIM_PUK; + } else if (pPin1Status->PINStatus == QMI_PIN_STATUS_PERM_BLOCKED) { + *pSIMStatus = SIM_BAD; + } + } + + free(pResponse); + return 0; +} + +int requestGetSIMStatus(SIM_Status *pSIMStatus) { //RIL_REQUEST_GET_SIM_STATUS + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + const char * SIM_Status_String[] = { + "SIM_ABSENT", + "SIM_NOT_READY", + "SIM_READY", /* SIM_READY means the radio state is RADIO_STATE_SIM_READY */ + "SIM_PIN", + "SIM_PUK", + "SIM_NETWORK_PERSONALIZATION" + }; + + if (s_9x07 && qmiclientId[QMUX_TYPE_UIM]) + pRequest = ComposeQMUXMsg(QMUX_TYPE_UIM, QMIUIM_GET_CARD_STATUS_REQ, NULL, NULL); + else + pRequest = ComposeQMUXMsg(QMUX_TYPE_DMS, QMIDMS_UIM_GET_STATE_REQ, NULL, NULL); + + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + + *pSIMStatus = SIM_ABSENT; + if (s_9x07 && qmiclientId[QMUX_TYPE_UIM]) + { + PQMIUIM_CARD_STATUS pCardStatus = NULL; + PQMIUIM_PIN_STATE pPINState = NULL; + UCHAR CardState = 0x01; + UCHAR PIN1State = QMI_PIN_STATUS_NOT_VERIF; + //UCHAR PIN1Retries; + //UCHAR PUK1Retries; + //UCHAR PIN2State; + //UCHAR PIN2Retries; + //UCHAR PUK2Retries; + + pCardStatus = (PQMIUIM_CARD_STATUS)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x10); + if (pCardStatus != NULL) + { + pPINState = (PQMIUIM_PIN_STATE)((PUCHAR)pCardStatus + sizeof(QMIUIM_CARD_STATUS) + pCardStatus->AIDLength); + CardState = pCardStatus->CardState; + if (CardState == UIM_CARD_STATE_PRESENT) { + if (pPINState->UnivPIN == 1) + { + PIN1State = pCardStatus->UPINState; + //PIN1Retries = pCardStatus->UPINRetries; + //PUK1Retries = pCardStatus->UPUKRetries; + } + else + { + PIN1State = pPINState->PIN1State; + //PIN1Retries = pPINState->PIN1Retries; + //PUK1Retries = pPINState->PUK1Retries; + } + //PIN2State = pPINState->PIN2State; + //PIN2Retries = pPINState->PIN2Retries; + //PUK2Retries = pPINState->PUK2Retries; + } + } + + *pSIMStatus = SIM_ABSENT; + if ((CardState == 0x01) && ((PIN1State == QMI_PIN_STATUS_VERIFIED)|| (PIN1State == QMI_PIN_STATUS_DISABLED))) + { + *pSIMStatus = SIM_READY; + } + else if (CardState == 0x01) + { + if (PIN1State == QMI_PIN_STATUS_NOT_VERIF) + { + *pSIMStatus = SIM_PIN; + } + if ( PIN1State == QMI_PIN_STATUS_BLOCKED) + { + *pSIMStatus = SIM_PUK; + } + else if (PIN1State == QMI_PIN_STATUS_PERM_BLOCKED) + { + *pSIMStatus = SIM_BAD; + } + else if (PIN1State == QMI_PIN_STATUS_NOT_INIT || PIN1State == QMI_PIN_STATUS_VERIFIED || PIN1State == QMI_PIN_STATUS_DISABLED) + { + *pSIMStatus = SIM_READY; + } + } + else if (CardState == 0x00 || CardState == 0x02) + { + } + else + { + } + } + else + { + //UIM state. Values: + // 0x00 UIM initialization completed + // 0x01 UIM is locked or the UIM failed + // 0x02 UIM is not present + // 0x03 Reserved + // 0xFF UIM state is currently + //unavailable + if (pResponse->MUXMsg.UIMGetStateResp.UIMState == 0x00) { + *pSIMStatus = SIM_READY; + } else if (pResponse->MUXMsg.UIMGetStateResp.UIMState == 0x01) { + *pSIMStatus = SIM_ABSENT; + err = requestGetPINStatus(pSIMStatus); + } else if ((pResponse->MUXMsg.UIMGetStateResp.UIMState == 0x02) || (pResponse->MUXMsg.UIMGetStateResp.UIMState == 0xFF)) { + *pSIMStatus = SIM_ABSENT; + } else { + *pSIMStatus = SIM_ABSENT; + } + } + dbg_time("%s SIMStatus: %s", __func__, SIM_Status_String[*pSIMStatus]); + + free(pResponse); + + return 0; +} + +int requestEnterSimPin(const CHAR *pPinCode) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + + if (s_9x07 && qmiclientId[QMUX_TYPE_UIM]) + pRequest = ComposeQMUXMsg(QMUX_TYPE_UIM, QMIUIM_VERIFY_PIN_REQ, UimVerifyPinReqSend, (void *)pPinCode); + else + pRequest = ComposeQMUXMsg(QMUX_TYPE_DMS, QMIDMS_UIM_VERIFY_PIN_REQ, DmsUIMVerifyPinReqSend, (void *)pPinCode); + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + + free(pResponse); + return 0; +} + +#ifdef CONFIG_IMSI_ICCID +int requestGetICCID(void) { //RIL_REQUEST_GET_IMSI + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + PQMIUIM_CONTENT pUimContent; + int err; + + if (s_9x07 && qmiclientId[QMUX_TYPE_UIM]) { + pRequest = ComposeQMUXMsg(QMUX_TYPE_UIM, QMIUIM_READ_TRANSPARENT_REQ, UimReadTransparentIMSIReqSend, (void *)"EF_ICCID"); + err = QmiThreadSendQMI(pRequest, &pResponse); + } else { + return 0; + } + qmi_rsp_check_and_return(); + + pUimContent = (PQMIUIM_CONTENT)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x11); + if (pUimContent != NULL) { + static char DeviceICCID[32] = {'\0'}; + int i = 0, j = 0; + + for (i = 0, j = 0; i < le16_to_cpu(pUimContent->content_len); ++i) { + char charmaps[] = "0123456789ABCDEF"; + + DeviceICCID[j++] = charmaps[(pUimContent->content[i] & 0x0F)]; + DeviceICCID[j++] = charmaps[((pUimContent->content[i] & 0xF0) >> 0x04)]; + } + DeviceICCID[j] = '\0'; + + dbg_time("%s DeviceICCID: %s", __func__, DeviceICCID); + } + + free(pResponse); + return 0; +} + +int requestGetIMSI(void) { //RIL_REQUEST_GET_IMSI + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + PQMIUIM_CONTENT pUimContent; + int err; + + if (s_9x07 && qmiclientId[QMUX_TYPE_UIM]) { + pRequest = ComposeQMUXMsg(QMUX_TYPE_UIM, QMIUIM_READ_TRANSPARENT_REQ, UimReadTransparentIMSIReqSend, (void *)"EF_IMSI"); + err = QmiThreadSendQMI(pRequest, &pResponse); + } else { + return 0; + } + qmi_rsp_check_and_return(); + + pUimContent = (PQMIUIM_CONTENT)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x11); + if (pUimContent != NULL) { + static char DeviceIMSI[32] = {'\0'}; + int i = 0, j = 0; + + for (i = 0, j = 0; i < le16_to_cpu(pUimContent->content[0]); ++i) { + if (i != 0) + DeviceIMSI[j++] = (pUimContent->content[i+1] & 0x0F) + '0'; + DeviceIMSI[j++] = ((pUimContent->content[i+1] & 0xF0) >> 0x04) + '0'; + } + DeviceIMSI[j] = '\0'; + + dbg_time("%s DeviceIMSI: %s", __func__, DeviceIMSI); + } + + free(pResponse); + return 0; +} +#endif +#endif + +#if 1 +static void quectel_convert_cdma_mcc_2_ascii_mcc( USHORT *p_mcc, USHORT mcc ) +{ + unsigned int d1, d2, d3, buf = mcc + 111; + + if ( mcc == 0x3FF ) // wildcard + { + *p_mcc = 3; + } + else + { + d3 = buf % 10; + buf = ( d3 == 0 ) ? (buf-10)/10 : buf/10; + + d2 = buf % 10; + buf = ( d2 == 0 ) ? (buf-10)/10 : buf/10; + + d1 = ( buf == 10 ) ? 0 : buf; + +//dbg_time("d1:%d, d2:%d,d3:%d",d1,d2,d3); + if ( d1<10 && d2<10 && d3<10 ) + { + *p_mcc = d1*100+d2*10+d3; +#if 0 + *(p_mcc+0) = '0' + d1; + *(p_mcc+1) = '0' + d2; + *(p_mcc+2) = '0' + d3; +#endif + } + else + { + //dbg_time( "invalid digits %d %d %d", d1, d2, d3 ); + *p_mcc = 0; + } + } +} + +static void quectel_convert_cdma_mnc_2_ascii_mnc( USHORT *p_mnc, USHORT imsi_11_12) +{ + unsigned int d1, d2, buf = imsi_11_12 + 11; + + if ( imsi_11_12 == 0x7F ) // wildcard + { + *p_mnc = 7; + } + else + { + d2 = buf % 10; + buf = ( d2 == 0 ) ? (buf-10)/10 : buf/10; + + d1 = ( buf == 10 ) ? 0 : buf; + + if ( d1<10 && d2<10 ) + { + *p_mnc = d1*10 + d2; + } + else + { + //dbg_time( "invalid digits %d %d", d1, d2, 0 ); + *p_mnc = 0; + } + } +} + +int requestGetHomeNetwork(USHORT *p_mcc, USHORT *p_mnc, USHORT *p_sid, USHORT *p_nid) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + PHOME_NETWORK pHomeNetwork; + PHOME_NETWORK_SYSTEMID pHomeNetworkSystemID; + + pRequest = ComposeQMUXMsg(QMUX_TYPE_NAS, QMINAS_GET_HOME_NETWORK_REQ, NULL, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + + pHomeNetwork = (PHOME_NETWORK)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x01); + if (pHomeNetwork && p_mcc && p_mnc ) { + *p_mcc = le16_to_cpu(pHomeNetwork->MobileCountryCode); + *p_mnc = le16_to_cpu(pHomeNetwork->MobileNetworkCode); + //dbg_time("%s MobileCountryCode: %d, MobileNetworkCode: %d", __func__, *pMobileCountryCode, *pMobileNetworkCode); + } + + pHomeNetworkSystemID = (PHOME_NETWORK_SYSTEMID)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x10); + if (pHomeNetworkSystemID && p_sid && p_nid) { + *p_sid = le16_to_cpu(pHomeNetworkSystemID->SystemID); //china-hefei: sid 14451 + *p_nid = le16_to_cpu(pHomeNetworkSystemID->NetworkID); + //dbg_time("%s SystemID: %d, NetworkID: %d", __func__, *pSystemID, *pNetworkID); + } + + free(pResponse); + + return 0; +} +#endif + +#if 0 +// Lookup table for carriers known to produce SIMs which incorrectly indicate MNC length. +static const char * MCCMNC_CODES_HAVING_3DIGITS_MNC[] = { + "302370", "302720", "310260", + "405025", "405026", "405027", "405028", "405029", "405030", "405031", "405032", + "405033", "405034", "405035", "405036", "405037", "405038", "405039", "405040", + "405041", "405042", "405043", "405044", "405045", "405046", "405047", "405750", + "405751", "405752", "405753", "405754", "405755", "405756", "405799", "405800", + "405801", "405802", "405803", "405804", "405805", "405806", "405807", "405808", + "405809", "405810", "405811", "405812", "405813", "405814", "405815", "405816", + "405817", "405818", "405819", "405820", "405821", "405822", "405823", "405824", + "405825", "405826", "405827", "405828", "405829", "405830", "405831", "405832", + "405833", "405834", "405835", "405836", "405837", "405838", "405839", "405840", + "405841", "405842", "405843", "405844", "405845", "405846", "405847", "405848", + "405849", "405850", "405851", "405852", "405853", "405875", "405876", "405877", + "405878", "405879", "405880", "405881", "405882", "405883", "405884", "405885", + "405886", "405908", "405909", "405910", "405911", "405912", "405913", "405914", + "405915", "405916", "405917", "405918", "405919", "405920", "405921", "405922", + "405923", "405924", "405925", "405926", "405927", "405928", "405929", "405930", + "405931", "405932", "502142", "502143", "502145", "502146", "502147", "502148" +}; + +static const char * MCC_CODES_HAVING_3DIGITS_MNC[] = { + "302", //Canada + "310", //United States of America + "311", //United States of America + "312", //United States of America + "313", //United States of America + "314", //United States of America + "315", //United States of America + "316", //United States of America + "334", //Mexico + "338", //Jamaica + "342", //Barbados + "344", //Antigua and Barbuda + "346", //Cayman Islands + "348", //British Virgin Islands + "365", //Anguilla + "708", //Honduras (Republic of) + "722", //Argentine Republic + "732" //Colombia (Republic of) +}; + +int requestGetIMSI(const char **pp_imsi, USHORT *pMobileCountryCode, USHORT *pMobileNetworkCode) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + + if (pp_imsi) *pp_imsi = NULL; + if (pMobileCountryCode) *pMobileCountryCode = 0; + if (pMobileNetworkCode) *pMobileNetworkCode = 0; + + pRequest = ComposeQMUXMsg(QMUX_TYPE_DMS, QMIDMS_UIM_GET_IMSI_REQ, NULL, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + + if (pMUXMsg->UIMGetIMSIResp.TLV2Type == 0x01 && le16_to_cpu(pMUXMsg->UIMGetIMSIResp.TLV2Length) >= 5) { + int mnc_len = 2; + unsigned i; + char tmp[4]; + + if (pp_imsi) *pp_imsi = strndup((const char *)(&pMUXMsg->UIMGetIMSIResp.IMSI), le16_to_cpu(pMUXMsg->UIMGetIMSIResp.TLV2Length)); + + for (i = 0; i < sizeof(MCCMNC_CODES_HAVING_3DIGITS_MNC)/sizeof(MCCMNC_CODES_HAVING_3DIGITS_MNC[0]); i++) { + if (!strncmp((const char *)(&pMUXMsg->UIMGetIMSIResp.IMSI), MCCMNC_CODES_HAVING_3DIGITS_MNC[i], 6)) { + mnc_len = 3; + break; + } + } + if (mnc_len == 2) { + for (i = 0; i < sizeof(MCC_CODES_HAVING_3DIGITS_MNC)/sizeof(MCC_CODES_HAVING_3DIGITS_MNC[0]); i++) { + if (!strncmp((const char *)(&pMUXMsg->UIMGetIMSIResp.IMSI), MCC_CODES_HAVING_3DIGITS_MNC[i], 3)) { + mnc_len = 3; + break; + } + } + } + + tmp[0] = (&pMUXMsg->UIMGetIMSIResp.IMSI)[0]; + tmp[1] = (&pMUXMsg->UIMGetIMSIResp.IMSI)[1]; + tmp[2] = (&pMUXMsg->UIMGetIMSIResp.IMSI)[2]; + tmp[3] = 0; + if (pMobileCountryCode) *pMobileCountryCode = atoi(tmp); + tmp[0] = (&pMUXMsg->UIMGetIMSIResp.IMSI)[3]; + tmp[1] = (&pMUXMsg->UIMGetIMSIResp.IMSI)[4]; + tmp[2] = 0; + if (mnc_len == 3) { + tmp[2] = (&pMUXMsg->UIMGetIMSIResp.IMSI)[6]; + } + if (pMobileNetworkCode) *pMobileNetworkCode = atoi(tmp); + } + + free(pResponse); + + return 0; +} +#endif + +struct wwan_data_class_str class2str[] = { + {WWAN_DATA_CLASS_NONE, "UNKNOWN"}, + {WWAN_DATA_CLASS_GPRS, "GPRS"}, + {WWAN_DATA_CLASS_EDGE, "EDGE"}, + {WWAN_DATA_CLASS_UMTS, "UMTS"}, + {WWAN_DATA_CLASS_HSDPA, "HSDPA"}, + {WWAN_DATA_CLASS_HSUPA, "HSUPA"}, + {WWAN_DATA_CLASS_LTE, "LTE"}, + {WWAN_DATA_CLASS_5G_NSA, "5G_NSA"}, + {WWAN_DATA_CLASS_5G_SA, "5G_SA"}, + {WWAN_DATA_CLASS_1XRTT, "1XRTT"}, + {WWAN_DATA_CLASS_1XEVDO, "1XEVDO"}, + {WWAN_DATA_CLASS_1XEVDO_REVA, "1XEVDO_REVA"}, + {WWAN_DATA_CLASS_1XEVDV, "1XEVDV"}, + {WWAN_DATA_CLASS_3XRTT, "3XRTT"}, + {WWAN_DATA_CLASS_1XEVDO_REVB, "1XEVDO_REVB"}, + {WWAN_DATA_CLASS_UMB, "UMB"}, + {WWAN_DATA_CLASS_CUSTOM, "CUSTOM"}, +}; + +CHAR *wwan_data_class2str(ULONG class) +{ + unsigned int i = 0; + for (i = 0; i < sizeof(class2str)/sizeof(class2str[0]); i++) { + if (class2str[i].class == class) { + return class2str[i].str; + } + } + return "UNKNOWN"; +} + +static USHORT char2ushort(UCHAR str[3]) { + int i; + char temp[4]; + USHORT ret= 0; + + memcpy(temp, str, 3); + temp[3] = '\0'; + + for (i = 0; i < 4; i++) { + if ((UCHAR)temp[i] == 0xFF) { + temp[i] = '\0'; + } + } + ret = (USHORT)atoi(temp); + + return ret; +} + +int requestRegistrationState2(UCHAR *pPSAttachedState) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + USHORT MobileCountryCode = 0; + USHORT MobileNetworkCode = 0; + const char *pDataCapStr = "UNKNOW"; + LONG remainingLen; + PSERVICE_STATUS_INFO pServiceStatusInfo; + int is_lte = 0; + PCDMA_SYSTEM_INFO pCdmaSystemInfo; + PHDR_SYSTEM_INFO pHdrSystemInfo; + PGSM_SYSTEM_INFO pGsmSystemInfo; + PWCDMA_SYSTEM_INFO pWcdmaSystemInfo; + PLTE_SYSTEM_INFO pLteSystemInfo; + PTDSCDMA_SYSTEM_INFO pTdscdmaSystemInfo; + PNR5G_SYSTEM_INFO pNr5gSystemInfo; + UCHAR DeviceClass = 0; + ULONG DataCapList = 0; + + /* Additional LTE System Info - Availability of Dual connectivity of E-UTRA with NR5G */ + uint8_t endc_available_valid = 0; /**< Must be set to true if endc_available is being passed */ + uint8_t endc_available = 0x00; + /**< + Upper layer indication in LTE SIB2. Values: \n + - 0x00 -- 5G Not available \n + - 0x01 -- 5G Available + + */ + /* Additional LTE System Info - DCNR restriction Info */ + uint8_t restrict_dcnr_valid = 0; /**< Must be set to true if restrict_dcnr is being passed */ + uint8_t restrict_dcnr = 0x01; + /**< + DCNR restriction in NAS attach/TAU accept. Values: \n + - 0x00 -- Not restricted \n + - 0x01 -- Restricted + */ + + *pPSAttachedState = 0; + pRequest = ComposeQMUXMsg(QMUX_TYPE_NAS, QMINAS_GET_SYS_INFO_REQ, NULL, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + + pServiceStatusInfo = (PSERVICE_STATUS_INFO)(((PCHAR)&pMUXMsg->GetSysInfoResp) + QCQMUX_MSG_HDR_SIZE); + remainingLen = le16_to_cpu(pMUXMsg->GetSysInfoResp.Length); + + s_is_cdma = 0; + s_hdr_personality = 0; + while (remainingLen > 0) { + switch (pServiceStatusInfo->TLVType) { + case 0x10: // CDMA + if (pServiceStatusInfo->SrvStatus == 0x02) { + DataCapList = WWAN_DATA_CLASS_1XRTT| + WWAN_DATA_CLASS_1XEVDO| + WWAN_DATA_CLASS_1XEVDO_REVA| + WWAN_DATA_CLASS_1XEVDV| + WWAN_DATA_CLASS_1XEVDO_REVB; + DeviceClass = DEVICE_CLASS_CDMA; + s_is_cdma = (0 == is_lte); + } + break; + case 0x11: // HDR + if (pServiceStatusInfo->SrvStatus == 0x02) { + DataCapList = WWAN_DATA_CLASS_3XRTT| + WWAN_DATA_CLASS_UMB; + DeviceClass = DEVICE_CLASS_CDMA; + s_is_cdma = (0 == is_lte); + } + break; + case 0x12: // GSM + if (pServiceStatusInfo->SrvStatus == 0x02) { + DataCapList = WWAN_DATA_CLASS_GPRS| + WWAN_DATA_CLASS_EDGE; + DeviceClass = DEVICE_CLASS_GSM; + } + break; + case 0x13: // WCDMA + if (pServiceStatusInfo->SrvStatus == 0x02) { + DataCapList = WWAN_DATA_CLASS_UMTS; + DeviceClass = DEVICE_CLASS_GSM; + } + break; + case 0x14: // LTE + if (pServiceStatusInfo->SrvStatus == 0x02) { + DataCapList = WWAN_DATA_CLASS_LTE; + DeviceClass = DEVICE_CLASS_GSM; + is_lte = 1; + s_is_cdma = 0; + } + break; + case 0x4A: // NR5G Service Status Info + if (pServiceStatusInfo->SrvStatus == NAS_SYS_SRV_STATUS_SRV_V01) { + DataCapList |= WWAN_DATA_CLASS_5G_SA; + DeviceClass = DEVICE_CLASS_GSM; + is_lte = 1; + s_is_cdma = 0; + } + break; + case 0x4B: // NR5G System Info + pNr5gSystemInfo = (PNR5G_SYSTEM_INFO)pServiceStatusInfo; + if (pNr5gSystemInfo->srv_domain_valid == 0x01) { + if (pNr5gSystemInfo->srv_domain & SYS_SRV_DOMAIN_PS_ONLY_V01) { + *pPSAttachedState = 1; + } + } + + if (pNr5gSystemInfo->network_id_valid == 0x01) { + MobileCountryCode = (USHORT)char2ushort(pNr5gSystemInfo->MCC); + MobileNetworkCode = (USHORT)char2ushort(pNr5gSystemInfo->MNC); + } + break; + case 0x4E: //Additional LTE System Info - Availability of Dual Connectivity of E-UTRA with NR5G + endc_available_valid = 1; + endc_available = pServiceStatusInfo->SrvStatus; + break; + + case 0x4F: //Additional LTE System Info - DCNR restriction Info + restrict_dcnr_valid = 1; + restrict_dcnr = pServiceStatusInfo->SrvStatus; + break; + + case 0x24: // TDSCDMA + if (pServiceStatusInfo->SrvStatus == 0x02) { + pDataCapStr = "TD-SCDMA"; + } + break; + case 0x15: // CDMA + // CDMA_SYSTEM_INFO + pCdmaSystemInfo = (PCDMA_SYSTEM_INFO)pServiceStatusInfo; + if (pCdmaSystemInfo->SrvDomainValid == 0x01) { + if (pCdmaSystemInfo->SrvDomain & 0x02) { + *pPSAttachedState = 1; + s_is_cdma = (0 == is_lte); + } + } +#if 0 + if (pCdmaSystemInfo->SrvCapabilityValid == 0x01) { + *pPSAttachedState = 0; + if (pCdmaSystemInfo->SrvCapability & 0x02) { + *pPSAttachedState = 1; + s_is_cdma = (0 == is_lte); + } + } +#endif + if (pCdmaSystemInfo->NetworkIdValid == 0x01) { + MobileCountryCode = (USHORT)char2ushort(pCdmaSystemInfo->MCC); + MobileNetworkCode = (USHORT)char2ushort(pCdmaSystemInfo->MNC); + } + break; + case 0x16: // HDR + // HDR_SYSTEM_INFO + pHdrSystemInfo = (PHDR_SYSTEM_INFO)pServiceStatusInfo; + if (pHdrSystemInfo->SrvDomainValid == 0x01) { + if (pHdrSystemInfo->SrvDomain & 0x02) { + *pPSAttachedState = 1; + s_is_cdma = (0 == is_lte); + } + } +#if 0 + if (pHdrSystemInfo->SrvCapabilityValid == 0x01) { + *pPSAttachedState = 0; + if (pHdrSystemInfo->SrvCapability & 0x02) { + *pPSAttachedState = 1; + s_is_cdma = (0 == is_lte); + } + } +#endif + if (*pPSAttachedState && pHdrSystemInfo->HdrPersonalityValid == 0x01) { + if (pHdrSystemInfo->HdrPersonality == 0x03) + s_hdr_personality = 0x02; + //else if (pHdrSystemInfo->HdrPersonality == 0x02) + // s_hdr_personality = 0x01; + } + USHORT cmda_mcc = 0, cdma_mnc = 0; + if(!requestGetHomeNetwork(&cmda_mcc, &cdma_mnc,NULL, NULL) && cmda_mcc) { + quectel_convert_cdma_mcc_2_ascii_mcc(&MobileCountryCode, cmda_mcc); + quectel_convert_cdma_mnc_2_ascii_mnc(&MobileNetworkCode, cdma_mnc); + } + break; + case 0x17: // GSM + // GSM_SYSTEM_INFO + pGsmSystemInfo = (PGSM_SYSTEM_INFO)pServiceStatusInfo; + if (pGsmSystemInfo->SrvDomainValid == 0x01) { + if (pGsmSystemInfo->SrvDomain & 0x02) { + *pPSAttachedState = 1; + } + } +#if 0 + if (pGsmSystemInfo->SrvCapabilityValid == 0x01) { + *pPSAttachedState = 0; + if (pGsmSystemInfo->SrvCapability & 0x02) { + *pPSAttachedState = 1; + } + } +#endif + if (pGsmSystemInfo->NetworkIdValid == 0x01) { + MobileCountryCode = (USHORT)char2ushort(pGsmSystemInfo->MCC); + MobileNetworkCode = (USHORT)char2ushort(pGsmSystemInfo->MNC); + } + break; + case 0x18: // WCDMA + // WCDMA_SYSTEM_INFO + pWcdmaSystemInfo = (PWCDMA_SYSTEM_INFO)pServiceStatusInfo; + if (pWcdmaSystemInfo->SrvDomainValid == 0x01) { + if (pWcdmaSystemInfo->SrvDomain & 0x02) { + *pPSAttachedState = 1; + } + } +#if 0 + if (pWcdmaSystemInfo->SrvCapabilityValid == 0x01) { + *pPSAttachedState = 0; + if (pWcdmaSystemInfo->SrvCapability & 0x02) { + *pPSAttachedState = 1; + } + } +#endif + if (pWcdmaSystemInfo->NetworkIdValid == 0x01) { + MobileCountryCode = (USHORT)char2ushort(pWcdmaSystemInfo->MCC); + MobileNetworkCode = (USHORT)char2ushort(pWcdmaSystemInfo->MNC); + } + break; + case 0x19: // LTE_SYSTEM_INFO + // LTE_SYSTEM_INFO + pLteSystemInfo = (PLTE_SYSTEM_INFO)pServiceStatusInfo; + if (pLteSystemInfo->SrvDomainValid == 0x01) { + if (pLteSystemInfo->SrvDomain & 0x02) { + *pPSAttachedState = 1; + is_lte = 1; + s_is_cdma = 0; + } + } +#if 0 + if (pLteSystemInfo->SrvCapabilityValid == 0x01) { + *pPSAttachedState = 0; + if (pLteSystemInfo->SrvCapability & 0x02) { + *pPSAttachedState = 1; + is_lte = 1; + s_is_cdma = 0; + } + } +#endif + if (pLteSystemInfo->NetworkIdValid == 0x01) { + MobileCountryCode = (USHORT)char2ushort(pLteSystemInfo->MCC); + MobileNetworkCode = (USHORT)char2ushort(pLteSystemInfo->MNC); + } + break; + case 0x25: // TDSCDMA + // TDSCDMA_SYSTEM_INFO + pTdscdmaSystemInfo = (PTDSCDMA_SYSTEM_INFO)pServiceStatusInfo; + if (pTdscdmaSystemInfo->SrvDomainValid == 0x01) { + if (pTdscdmaSystemInfo->SrvDomain & 0x02) { + *pPSAttachedState = 1; + } + } +#if 0 + if (pTdscdmaSystemInfo->SrvCapabilityValid == 0x01) { + *pPSAttachedState = 0; + if (pTdscdmaSystemInfo->SrvCapability & 0x02) { + *pPSAttachedState = 1; + } + } +#endif + if (pTdscdmaSystemInfo->NetworkIdValid == 0x01) { + MobileCountryCode = (USHORT)char2ushort(pTdscdmaSystemInfo->MCC); + MobileNetworkCode = (USHORT)char2ushort(pTdscdmaSystemInfo->MNC); + } + break; + default: + break; + } /* switch (pServiceStatusInfo->TLYType) */ + + remainingLen -= (le16_to_cpu(pServiceStatusInfo->TLVLength) + 3); + pServiceStatusInfo = (PSERVICE_STATUS_INFO)((PCHAR)&pServiceStatusInfo->TLVLength + le16_to_cpu(pServiceStatusInfo->TLVLength) + sizeof(USHORT)); + } /* while (remainingLen > 0) */ + + if (DataCapList & WWAN_DATA_CLASS_LTE) { + if (endc_available_valid && restrict_dcnr_valid) { + if (endc_available && !restrict_dcnr) { + DataCapList |= WWAN_DATA_CLASS_5G_NSA; + } + } + } + + if (DeviceClass == DEVICE_CLASS_CDMA) { + if (s_hdr_personality == 2) { + pDataCapStr = s_hdr_personality == 2 ? "eHRPD" : "HRPD"; + } else if (DataCapList & WWAN_DATA_CLASS_1XEVDO_REVB) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_1XEVDO_REVB); + } else if (DataCapList & WWAN_DATA_CLASS_1XEVDO_REVA) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_1XEVDO_REVA); + } else if (DataCapList & WWAN_DATA_CLASS_1XEVDO) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_1XEVDO); + } else if (DataCapList & WWAN_DATA_CLASS_1XRTT) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_1XRTT); + } else if (DataCapList & WWAN_DATA_CLASS_3XRTT) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_3XRTT); + } else if (DataCapList & WWAN_DATA_CLASS_UMB) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_UMB); + } + } else { + if (DataCapList & WWAN_DATA_CLASS_5G_SA) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_5G_SA); + } else if (DataCapList & WWAN_DATA_CLASS_5G_NSA) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_5G_NSA); + } else if (DataCapList & WWAN_DATA_CLASS_LTE) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_LTE); + } else if ((DataCapList & WWAN_DATA_CLASS_HSDPA) && (DataCapList & WWAN_DATA_CLASS_HSUPA)) { + pDataCapStr = "HSDPA_HSUPA"; + } else if (DataCapList & WWAN_DATA_CLASS_HSDPA) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_HSDPA); + } else if (DataCapList & WWAN_DATA_CLASS_HSUPA) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_HSUPA); + } else if (DataCapList & WWAN_DATA_CLASS_UMTS) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_UMTS); + } else if (DataCapList & WWAN_DATA_CLASS_EDGE) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_EDGE); + } else if (DataCapList & WWAN_DATA_CLASS_GPRS) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_GPRS); + } + } + + dbg_time("%s MCC: %d, MNC: %d, PS: %s, DataCap: %s", __func__, + MobileCountryCode, MobileNetworkCode, (*pPSAttachedState == 1) ? "Attached" : "Detached" , pDataCapStr); + + free(pResponse); + + return 0; +} + +int requestRegistrationState(UCHAR *pPSAttachedState) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + PQMINAS_CURRENT_PLMN_MSG pCurrentPlmn; + PSERVING_SYSTEM pServingSystem; + PQMINAS_DATA_CAP pDataCap; + USHORT MobileCountryCode = 0; + USHORT MobileNetworkCode = 0; + const char *pDataCapStr = "UNKNOW"; + + if (s_9x07) { + return requestRegistrationState2(pPSAttachedState); + } + + pRequest = ComposeQMUXMsg(QMUX_TYPE_NAS, QMINAS_GET_SERVING_SYSTEM_REQ, NULL, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + + pCurrentPlmn = (PQMINAS_CURRENT_PLMN_MSG)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x12); + if (pCurrentPlmn) { + MobileCountryCode = le16_to_cpu(pCurrentPlmn->MobileCountryCode); + MobileNetworkCode = le16_to_cpu(pCurrentPlmn->MobileNetworkCode); + } + + *pPSAttachedState = 0; + pServingSystem = (PSERVING_SYSTEM)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x01); + if (pServingSystem) { + //Packet-switched domain attach state of the mobile. + //0x00 PS_UNKNOWN ?Unknown or not applicable + //0x01 PS_ATTACHED ?Attached + //0x02 PS_DETACHED ?Detached + *pPSAttachedState = pServingSystem->RegistrationState; + if (pServingSystem->RegistrationState == 0x01) //0x01 ?C REGISTERED ?C Registered with a network + *pPSAttachedState = pServingSystem->PSAttachedState; + else { + //MobileCountryCode = MobileNetworkCode = 0; + *pPSAttachedState = 0x02; + } + } + + pDataCap = (PQMINAS_DATA_CAP)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x11); + if (pDataCap && pDataCap->DataCapListLen) { + UCHAR *DataCap = &pDataCap->DataCap; + if (pDataCap->DataCapListLen == 2) { + if ((DataCap[0] == 0x06) && ((DataCap[1] == 0x08) || (DataCap[1] == 0x0A))) + DataCap[0] = DataCap[1]; + } + switch (DataCap[0]) { + case 0x01: pDataCapStr = "GPRS"; break; + case 0x02: pDataCapStr = "EDGE"; break; + case 0x03: pDataCapStr = "HSDPA"; break; + case 0x04: pDataCapStr = "HSUPA"; break; + case 0x05: pDataCapStr = "UMTS"; break; + case 0x06: pDataCapStr = "1XRTT"; break; + case 0x07: pDataCapStr = "1XEVDO"; break; + case 0x08: pDataCapStr = "1XEVDO_REVA"; break; + case 0x09: pDataCapStr = "GPRS"; break; + case 0x0A: pDataCapStr = "1XEVDO_REVB"; break; + case 0x0B: pDataCapStr = "LTE"; break; + case 0x0C: pDataCapStr = "HSDPA"; break; + case 0x0D: pDataCapStr = "HSDPA"; break; + default: pDataCapStr = "UNKNOW"; break; + } + } + + if (pServingSystem && pServingSystem->RegistrationState == 0x01 && pServingSystem->InUseRadioIF && pServingSystem->RadioIF == 0x09) { + pDataCapStr = "TD-SCDMA"; + } + + s_is_cdma = 0; + if (pServingSystem && pServingSystem->RegistrationState == 0x01 && pServingSystem->InUseRadioIF && (pServingSystem->RadioIF == 0x01 || pServingSystem->RadioIF == 0x02)) { + USHORT cmda_mcc = 0, cdma_mnc = 0; + s_is_cdma = 1; + if(!requestGetHomeNetwork(&cmda_mcc, &cdma_mnc,NULL, NULL) && cmda_mcc) { + quectel_convert_cdma_mcc_2_ascii_mcc(&MobileCountryCode, cmda_mcc); + quectel_convert_cdma_mnc_2_ascii_mnc(&MobileNetworkCode, cdma_mnc); + } + if (1) { + PQCQMUX_TLV pTLV = (PQCQMUX_TLV)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x23); + if (pTLV) + s_hdr_personality = pTLV->Value; + else + s_hdr_personality = 0; + if (s_hdr_personality == 2) + pDataCapStr = "eHRPD"; + } + } + + dbg_time("%s MCC: %d, MNC: %d, PS: %s, DataCap: %s", __func__, + MobileCountryCode, MobileNetworkCode, (*pPSAttachedState == 1) ? "Attached" : "Detached" , pDataCapStr); + + free(pResponse); + + return 0; +} + +int requestQueryDataCall(UCHAR *pConnectionStatus, int curIpFamily) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + PQMIWDS_PKT_SRVC_TLV pPktSrvc; + UCHAR oldConnectionStatus = *pConnectionStatus; + UCHAR QMIType = (curIpFamily == IpFamilyV4) ? QMUX_TYPE_WDS : QMUX_TYPE_WDS_IPV6; + + pRequest = ComposeQMUXMsg(QMIType, QMIWDS_GET_PKT_SRVC_STATUS_REQ, NULL, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + + *pConnectionStatus = QWDS_PKT_DATA_DISCONNECTED; + pPktSrvc = (PQMIWDS_PKT_SRVC_TLV)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x01); + if (pPktSrvc) { + *pConnectionStatus = pPktSrvc->ConnectionStatus; + if ((le16_to_cpu(pPktSrvc->TLVLength) == 2) && (pPktSrvc->ReconfigReqd == 0x01)) + *pConnectionStatus = QWDS_PKT_DATA_DISCONNECTED; + } + + if (*pConnectionStatus == QWDS_PKT_DATA_DISCONNECTED) { + if (curIpFamily == IpFamilyV4) + WdsConnectionIPv4Handle = 0; + else + WdsConnectionIPv6Handle = 0; + } + + if (oldConnectionStatus != *pConnectionStatus || debug_qmi) { + dbg_time("%s %sConnectionStatus: %s", __func__, (curIpFamily == IpFamilyV4) ? "IPv4" : "IPv6", + (*pConnectionStatus == QWDS_PKT_DATA_CONNECTED) ? "CONNECTED" : "DISCONNECTED"); + } + + free(pResponse); + return 0; +} + +int requestSetupDataCall(PROFILE_T *profile, int curIpFamily) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err = 0; + UCHAR QMIType = (curIpFamily == IpFamilyV4) ? QMUX_TYPE_WDS : QMUX_TYPE_WDS_IPV6; + +//DualIPSupported means can get ipv4 & ipv6 address at the same time, one wds for ipv4, the other wds for ipv6 + profile->curIpFamily = curIpFamily; + pRequest = ComposeQMUXMsg(QMIType, QMIWDS_START_NETWORK_INTERFACE_REQ, WdsStartNwInterfaceReq, profile); + err = QmiThreadSendQMITimeout(pRequest, &pResponse, 120 * 1000); + qmi_rsp_check(); + + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + PQMI_TLV_HDR pTLVHdr; + + pTLVHdr = GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x10); + if (pTLVHdr) { + uint16_t *data16 = (uint16_t *)(pTLVHdr+1); + uint16_t call_end_reason = le16_to_cpu(data16[0]); + dbg_time("call_end_reason is %d", call_end_reason); + } + + pTLVHdr = GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x11); + if (pTLVHdr) { + uint16_t *data16 = (uint16_t *)(pTLVHdr+1); + uint16_t call_end_reason_type = le16_to_cpu(data16[0]); + uint16_t verbose_call_end_reason = le16_to_cpu(data16[1]); + + dbg_time("call_end_reason_type is %d", call_end_reason_type); + dbg_time("call_end_reason_verbose is %d", verbose_call_end_reason); + } + + free(pResponse); + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + if (curIpFamily == IpFamilyV4) { + WdsConnectionIPv4Handle = le32_to_cpu(pResponse->MUXMsg.StartNwInterfaceResp.Handle); + dbg_time("%s WdsConnectionIPv4Handle: 0x%08x", __func__, WdsConnectionIPv4Handle); + } else { + WdsConnectionIPv6Handle = le32_to_cpu(pResponse->MUXMsg.StartNwInterfaceResp.Handle); + dbg_time("%s WdsConnectionIPv6Handle: 0x%08x", __func__, WdsConnectionIPv6Handle); + } + + free(pResponse); + + return 0; +} + +int requestDeactivateDefaultPDP(PROFILE_T *profile, int curIpFamily) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + UCHAR QMIType = (curIpFamily == 0x04) ? QMUX_TYPE_WDS : QMUX_TYPE_WDS_IPV6; + + if (curIpFamily == IpFamilyV4 && WdsConnectionIPv4Handle == 0) + return 0; + if (curIpFamily == IpFamilyV6 && WdsConnectionIPv6Handle == 0) + return 0; + + dbg_time("%s WdsConnectionIPv%dHandle", __func__, curIpFamily == IpFamilyV4 ? 4 : 6); + + pRequest = ComposeQMUXMsg(QMIType, QMIWDS_STOP_NETWORK_INTERFACE_REQ , WdsStopNwInterfaceReq, &curIpFamily); + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + + if (curIpFamily == IpFamilyV4) + WdsConnectionIPv4Handle = 0; + else + WdsConnectionIPv6Handle = 0; + free(pResponse); + return 0; +} + +int requestGetIPAddress(PROFILE_T *profile, int curIpFamily) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR pIpv4Addr; + PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV6_ADDR pIpv6Addr = NULL; + PQMIWDS_GET_RUNTIME_SETTINGS_TLV_MTU pMtu; + IPV4_T *pIpv4 = &profile->ipv4; + IPV6_T *pIpv6 = &profile->ipv6; + UCHAR QMIType = (curIpFamily == 0x04) ? QMUX_TYPE_WDS : QMUX_TYPE_WDS_IPV6; + PQMIWDS_GET_RUNNING_SETTINGS_PCSCF_IPV6_ADDR pPCSCFIpv6Addr; + PQMIWDS_GET_RUNNING_SETTINGS_PCSCF_IPV4_ADDR pPCSCFIpv4Addr; + + if (curIpFamily == IpFamilyV4) { + memset(pIpv4, 0x00, sizeof(IPV4_T)); + if (WdsConnectionIPv4Handle == 0) + return 0; + } else if (curIpFamily == IpFamilyV6) { + memset(pIpv6, 0x00, sizeof(IPV6_T)); + if (WdsConnectionIPv6Handle == 0) + return 0; + } + + pRequest = ComposeQMUXMsg(QMIType, QMIWDS_GET_RUNTIME_SETTINGS_REQ, WdsGetRuntimeSettingReq, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + + pPCSCFIpv6Addr = (PQMIWDS_GET_RUNNING_SETTINGS_PCSCF_IPV6_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x2e); // 0x2e - pcscf ipv6 address + if (pPCSCFIpv6Addr) { + if (pPCSCFIpv6Addr->PCSCFNumber == 1) { + UCHAR *PCSCFIpv6Addr1 = (UCHAR *)(pPCSCFIpv6Addr + 1); + memcpy(profile->PCSCFIpv6Addr1, PCSCFIpv6Addr1, 16); + }else if (pPCSCFIpv6Addr->PCSCFNumber == 2) { + UCHAR *PCSCFIpv6Addr1 = (UCHAR *)(pPCSCFIpv6Addr + 1); + UCHAR *PCSCFIpv6Addr2 = PCSCFIpv6Addr1 + 16; + memcpy(profile->PCSCFIpv6Addr1, PCSCFIpv6Addr1, 16); + memcpy(profile->PCSCFIpv6Addr2, PCSCFIpv6Addr2, 16); + } + } + + pPCSCFIpv4Addr = (PQMIWDS_GET_RUNNING_SETTINGS_PCSCF_IPV4_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x23); // 0x23 - pcscf ipv4 address + if (pPCSCFIpv4Addr) { + if (pPCSCFIpv4Addr->PCSCFNumber == 1) { + UCHAR *PCSCFIpv4Addr1 = (UCHAR *)(pPCSCFIpv4Addr + 1); + memcpy(&profile->PCSCFIpv4Addr1, PCSCFIpv4Addr1, 4); + }else if (pPCSCFIpv4Addr->PCSCFNumber == 2) { + UCHAR *PCSCFIpv4Addr1 = (UCHAR *)(pPCSCFIpv4Addr + 1); + UCHAR *PCSCFIpv4Addr2 = PCSCFIpv4Addr1 + 4; + memcpy(&profile->PCSCFIpv4Addr1, PCSCFIpv4Addr1, 4); + memcpy(&profile->PCSCFIpv4Addr2, PCSCFIpv4Addr2, 4); + } + } + + pIpv4Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4PRIMARYDNS); + if (pIpv4Addr) { + pIpv4->DnsPrimary = pIpv4Addr->IPV4Address; + } + + pIpv4Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4SECONDARYDNS); + if (pIpv4Addr) { + pIpv4->DnsSecondary = pIpv4Addr->IPV4Address; + } + + pIpv4Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4GATEWAY); + if (pIpv4Addr) { + pIpv4->Gateway = pIpv4Addr->IPV4Address; + } + + pIpv4Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4SUBNET); + if (pIpv4Addr) { + pIpv4->SubnetMask = pIpv4Addr->IPV4Address; + } + + pIpv4Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4); + if (pIpv4Addr) { + pIpv4->Address = pIpv4Addr->IPV4Address; + } + + pIpv6Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV6_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6PRIMARYDNS); + if (pIpv6Addr) { + memcpy(pIpv6->DnsPrimary, pIpv6Addr->IPV6Address, 16); + } + + pIpv6Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV6_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6SECONDARYDNS); + if (pIpv6Addr) { + memcpy(pIpv6->DnsSecondary, pIpv6Addr->IPV6Address, 16); + } + + pIpv6Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV6_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6GATEWAY); + if (pIpv6Addr) { + memcpy(pIpv6->Gateway, pIpv6Addr->IPV6Address, 16); + pIpv6->PrefixLengthGateway = pIpv6Addr->PrefixLength; + } + + pIpv6Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV6_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6); + if (pIpv6Addr) { + memcpy(pIpv6->Address, pIpv6Addr->IPV6Address, 16); + pIpv6->PrefixLengthIPAddr = pIpv6Addr->PrefixLength; + } + + pMtu = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_MTU)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_MTU); + if (pMtu) { + if (curIpFamily == IpFamilyV4) + pIpv4->Mtu = le32_to_cpu(pMtu->Mtu); + else + pIpv6->Mtu = le32_to_cpu(pMtu->Mtu); + } + + free(pResponse); + return 0; +} + +#ifdef CONFIG_APN +int requestSetProfile(PROFILE_T *profile) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + + if (!profile->pdp) + return 0; + + dbg_time("%s[%d] %s/%s/%s/%d", __func__, profile->pdp, profile->apn, profile->user, profile->password, profile->auth); + pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS, QMIWDS_MODIFY_PROFILE_SETTINGS_REQ, WdsModifyProfileSettingsReq, profile); + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + + free(pResponse); + return 0; +} + +int requestGetProfile(PROFILE_T *profile) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + char *apn = NULL; + char *user = NULL; + char *password = NULL; + int auth = 0; + PQMIWDS_APNNAME pApnName; + PQMIWDS_USERNAME pUserName; + PQMIWDS_PASSWD pPassWd; + PQMIWDS_AUTH_PREFERENCE pAuthPref; + + if (!profile->pdp) + return 0; + + pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS, QMIWDS_GET_PROFILE_SETTINGS_REQ, WdsGetProfileSettingsReqSend, profile); + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + + pApnName = (PQMIWDS_APNNAME)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x14); + pUserName = (PQMIWDS_USERNAME)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x1B); + pPassWd = (PQMIWDS_PASSWD)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x1C); + pAuthPref = (PQMIWDS_AUTH_PREFERENCE)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x1D); + + if (pApnName/* && le16_to_cpu(pApnName->TLVLength)*/) + apn = strndup((const char *)(&pApnName->ApnName), le16_to_cpu(pApnName->TLVLength)); + if (pUserName/* && pUserName->UserName*/) + user = strndup((const char *)(&pUserName->UserName), le16_to_cpu(pUserName->TLVLength)); + if (pPassWd/* && le16_to_cpu(pPassWd->TLVLength)*/) + password = strndup((const char *)(&pPassWd->Passwd), le16_to_cpu(pPassWd->TLVLength)); + if (pAuthPref/* && le16_to_cpu(pAuthPref->TLVLength)*/) { + auth = pAuthPref->AuthPreference; + } + +#if 0 + if (profile) { + profile->apn = apn; + profile->user = user; + profile->password = password; + profile->auth = auth; + } +#endif + + dbg_time("%s[%d] %s/%s/%s/%d", __func__, profile->pdp, apn, user, password, auth); + + free(pResponse); + return 0; +} +#endif + +#ifdef CONFIG_SIGNALINFO +int requestGetSignalInfo(void) +{ + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + + pRequest = ComposeQMUXMsg(QMUX_TYPE_NAS, QMINAS_GET_SIG_INFO_REQ, NULL, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + + + // CDMA + { + PQMINAS_SIG_INFO_CDMA_TLV_MSG ptlv = (PQMINAS_SIG_INFO_CDMA_TLV_MSG)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x10); + if (ptlv && ptlv->TLVLength) + { + dbg_time("%s CDMA: RSSI %d dBm, ECIO %.1lf dBm", __func__, + ptlv->rssi, (-0.5) * (double)ptlv->ecio); + } + } + + // HDR + { + PQMINAS_SIG_INFO_HDR_TLV_MSG ptlv = (PQMINAS_SIG_INFO_HDR_TLV_MSG)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x11); + if (ptlv && ptlv->TLVLength) + { + dbg_time("%s HDR: RSSI %d dBm, ECIO %.1lf dBm, IO %d dBm", __func__, + ptlv->rssi, (-0.5) * (double)ptlv->ecio, ptlv->io); + } + } + + // GSM + { + PQMINAS_SIG_INFO_GSM_TLV_MSG ptlv = (PQMINAS_SIG_INFO_GSM_TLV_MSG)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x12); + if (ptlv && ptlv->TLVLength) + { + dbg_time("%s GSM: RSSI %d dBm", __func__, ptlv->rssi); + } + } + + // WCDMA + { + PQMINAS_SIG_INFO_WCDMA_TLV_MSG ptlv = (PQMINAS_SIG_INFO_WCDMA_TLV_MSG)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x13); + if (ptlv && ptlv->TLVLength) + { + dbg_time("%s WCDMA: RSSI %d dBm, ECIO %.1lf dBm", __func__, + ptlv->rssi, (-0.5) * (double)ptlv->ecio); + } + } + + // LTE + { + PQMINAS_SIG_INFO_LTE_TLV_MSG ptlv = (PQMINAS_SIG_INFO_LTE_TLV_MSG)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x14); + if (ptlv && ptlv->TLVLength) + { + dbg_time("%s LTE: RSSI %d dBm, RSRQ %d dB, RSRP %d dBm, SNR %.1lf dB", __func__, + ptlv->rssi, ptlv->rsrq, ptlv->rsrp, (0.1) * (double)ptlv->snr); + } + } + + // TDSCDMA + { + PQMINAS_SIG_INFO_TDSCDMA_TLV_MSG ptlv = (PQMINAS_SIG_INFO_TDSCDMA_TLV_MSG)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x15); + if (ptlv && ptlv->TLVLength) + { + dbg_time("%s LTE: RSCP %d dBm", __func__, ptlv->rscp); + } + } + free(pResponse); + return 0; +} +#endif + +#ifdef CONFIG_VERSION +int requestBaseBandVersion(const char **pp_reversion) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + PDEVICE_REV_ID revId; + int err; + + if (pp_reversion) *pp_reversion = NULL; + + pRequest = ComposeQMUXMsg(QMUX_TYPE_DMS, QMIDMS_GET_DEVICE_REV_ID_REQ, NULL, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + + revId = (PDEVICE_REV_ID)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x01); + + if (revId && le16_to_cpu(revId->TLVLength)) + { + char *DeviceRevisionID = strndup((const char *)(&revId->RevisionID), le16_to_cpu(revId->TLVLength)); + dbg_time("%s %s", __func__, DeviceRevisionID); + if (s_9x07 == -1) { //fail to get QMUX_TYPE_WDS_ADMIN + if (strncmp(DeviceRevisionID, "EC20", strlen("EC20"))) + s_9x07 = 1; + else + s_9x07 = DeviceRevisionID[5] == 'F' || DeviceRevisionID[6] == 'F'; //EC20CF,EC20EF,EC20CEF + } + if (pp_reversion) *pp_reversion = DeviceRevisionID; + } + + free(pResponse); + return 0; +} +#endif + +#ifdef CONFIG_RESET_RADIO +static USHORT DmsSetOperatingModeReq(PQMUX_MSG pMUXMsg, void *arg) { + pMUXMsg->SetOperatingModeReq.TLVType = 0x01; + pMUXMsg->SetOperatingModeReq.TLVLength = cpu_to_le16(1); + pMUXMsg->SetOperatingModeReq.OperatingMode = *((UCHAR *)arg); + + return sizeof(QMIDMS_SET_OPERATING_MODE_REQ_MSG); +} + +int requestSetOperatingMode(UCHAR OperatingMode) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + + dbg_time("%s(%d)", __func__, OperatingMode); + + pRequest = ComposeQMUXMsg(QMUX_TYPE_DMS, QMIDMS_SET_OPERATING_MODE_REQ, DmsSetOperatingModeReq, &OperatingMode); + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + + free(pResponse); + return 0; +} +#endif + +static USHORT WdaSetLoopBackReq(PQMUX_MSG pMUXMsg, void *arg) { + pMUXMsg->SetLoopBackReq.loopback_state.TLVType = 0x01; + pMUXMsg->SetLoopBackReq.loopback_state.TLVLength = cpu_to_le16(1); + + pMUXMsg->SetLoopBackReq.replication_factor.TLVType = 0x10; + pMUXMsg->SetLoopBackReq.replication_factor.TLVLength = cpu_to_le16(4); + + return sizeof(QMI_WDA_SET_LOOPBACK_CONFIG_REQ_MSG); +} + +int requestSetLoopBackState(UCHAR loopback_state, ULONG replication_factor) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + + dbg_time("%s(loopback_state=%d, replication_factor=%u)", __func__, loopback_state, replication_factor); + + pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS_ADMIN, QMI_WDA_SET_LOOPBACK_CONFIG_REQ, WdaSetLoopBackReq, NULL); + pRequest->MUXMsg.SetLoopBackReq.loopback_state.TLVVaule = loopback_state; + pRequest->MUXMsg.SetLoopBackReq.replication_factor.TLVVaule = cpu_to_le16(replication_factor); + + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + + free(pResponse); + return 0; +} diff --git a/root/package/link4all/quectel-CM/quectel1.6.0.15_src/QMIThread.h b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/QMIThread.h new file mode 100644 index 00000000..7997e8f5 --- /dev/null +++ b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/QMIThread.h @@ -0,0 +1,310 @@ +#ifndef __QMI_THREAD_H__ +#define __QMI_THREAD_H__ + +#define CONFIG_GOBINET +#define CONFIG_QMIWWAN +#define CONFIG_SIM +#define CONFIG_APN +#define CONFIG_VERSION +// #define CONFIG_SIGNALINFO +#define CONFIG_DEFAULT_PDP 1 +//#define CONFIG_IMSI_ICCID +#define QUECTEL_UL_DATA_AGG + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "MPQMI.h" +#include "MPQCTL.h" +#include "MPQMUX.h" +#include "util.h" + +#define DEVICE_CLASS_UNKNOWN 0 +#define DEVICE_CLASS_CDMA 1 +#define DEVICE_CLASS_GSM 2 + +#define WWAN_DATA_CLASS_NONE 0x00000000 +#define WWAN_DATA_CLASS_GPRS 0x00000001 +#define WWAN_DATA_CLASS_EDGE 0x00000002 /* EGPRS */ +#define WWAN_DATA_CLASS_UMTS 0x00000004 +#define WWAN_DATA_CLASS_HSDPA 0x00000008 +#define WWAN_DATA_CLASS_HSUPA 0x00000010 +#define WWAN_DATA_CLASS_LTE 0x00000020 +#define WWAN_DATA_CLASS_5G_NSA 0x00000040 +#define WWAN_DATA_CLASS_5G_SA 0x00000080 +#define WWAN_DATA_CLASS_1XRTT 0x00010000 +#define WWAN_DATA_CLASS_1XEVDO 0x00020000 +#define WWAN_DATA_CLASS_1XEVDO_REVA 0x00040000 +#define WWAN_DATA_CLASS_1XEVDV 0x00080000 +#define WWAN_DATA_CLASS_3XRTT 0x00100000 +#define WWAN_DATA_CLASS_1XEVDO_REVB 0x00200000 /* for future use */ +#define WWAN_DATA_CLASS_UMB 0x00400000 +#define WWAN_DATA_CLASS_CUSTOM 0x80000000 + +struct wwan_data_class_str { + ULONG class; + CHAR *str; +}; + +#pragma pack(push, 1) + +typedef struct _QCQMIMSG { + QCQMI_HDR QMIHdr; + union { + QMICTL_MSG CTLMsg; + QMUX_MSG MUXMsg; + }; +} __attribute__ ((packed)) QCQMIMSG, *PQCQMIMSG; + +typedef struct __IPV4 { + uint32_t Address; + uint32_t Gateway; + uint32_t SubnetMask; + uint32_t DnsPrimary; + uint32_t DnsSecondary; + uint32_t Mtu; +} IPV4_T; + +typedef struct __IPV6 { + UCHAR Address[16]; + UCHAR Gateway[16]; + UCHAR SubnetMask[16]; + UCHAR DnsPrimary[16]; + UCHAR DnsSecondary[16]; + UCHAR PrefixLengthIPAddr; + UCHAR PrefixLengthGateway; + ULONG Mtu; +} IPV6_T; + +typedef struct { + UINT size; + UINT rx_urb_size; + UINT ep_type; + UINT iface_id; + UINT MuxId; + UINT ul_data_aggregation_max_datagrams; //0x17 + UINT ul_data_aggregation_max_size ;//0x18 + UINT dl_minimum_padding; //0x1A +} QMAP_SETTING; + +//Configured downlink data aggregationprotocol +#define WDA_DL_DATA_AGG_DISABLED (0x00) //DL data aggregation is disabled (default) +#define WDA_DL_DATA_AGG_TLP_ENABLED (0x01) // DL TLP is enabled +#define WDA_DL_DATA_AGG_QC_NCM_ENABLED (0x02) // DL QC_NCM isenabled +#define WDA_DL_DATA_AGG_MBIM_ENABLED (0x03) // DL MBIM isenabled +#define WDA_DL_DATA_AGG_RNDIS_ENABLED (0x04) // DL RNDIS is enabled +#define WDA_DL_DATA_AGG_QMAP_ENABLED (0x05) // DL QMAP isenabled +#define WDA_DL_DATA_AGG_QMAP_V2_ENABLED (0x06) // DL QMAP V2 is enabled +#define WDA_DL_DATA_AGG_QMAP_V3_ENABLED (0x07) // DL QMAP V3 is enabled +#define WDA_DL_DATA_AGG_QMAP_V4_ENABLED (0x08) // DL QMAP V4 is enabled +#define WDA_DL_DATA_AGG_QMAP_V5_ENABLED (0x09) // DL QMAP V5 is enabled + +typedef struct { + unsigned int size; + unsigned int rx_urb_size; + unsigned int ep_type; + unsigned int iface_id; + unsigned int qmap_mode; + unsigned int qmap_version; + unsigned int dl_minimum_padding; + char ifname[8][16]; + unsigned char mux_id[8]; +} RMNET_INFO; + +#define IpFamilyV4 (0x04) +#define IpFamilyV6 (0x06) + +struct __PROFILE; +struct qmi_device_ops { + int (*init)(struct __PROFILE *profile); + int (*deinit)(void); + int (*send)(PQCQMIMSG pRequest); + void* (*read)(void *pData); + + + // int (*thread_read)(struct __PROFILE *profile); + // int (*init)(struct __PROFILE *profile); + // int (*open)(struct __PROFILE *profile); + // int (*close)(struct __PROFILE *profile); + // int (*reopen)(struct __PROFILE *profile); + // int (*start_network)(struct __PROFILE *profile); + // int (*stop_network)(struct __PROFILE *profile); + // int (*query_network)(struct __PROFILE *profile); +}; +extern int (*qmidev_send)(PQCQMIMSG pRequest); + +#ifndef bool +#define bool uint8_t +#endif +typedef struct __PROFILE { + char *qmichannel; + char *usbnet_adapter; + char *qmapnet_adapter; + char *driver_name; + int qmap_mode; + int qmap_size; + int qmap_version; + const char *apn; + const char *user; + const char *password; + const char *pincode; + int auth; + int pdp; + int curIpFamily; + int rawIP; + int muxid; + int enable_bridge; + IPV4_T ipv4; + IPV6_T ipv6; + UINT PCSCFIpv4Addr1; + UINT PCSCFIpv4Addr2; + UCHAR PCSCFIpv6Addr1[16]; + UCHAR PCSCFIpv6Addr2[16]; + bool enable_ipv4; + bool enable_ipv6; + int apntype; + bool reattach_flag; + int hardware_interface; + int software_interface; + int busnum; + int devnum; + int usbmon_fd; + int usbmon_logfile_fd; + bool loopback_state; + int replication_factor; + const struct qmi_device_ops *qmi_ops; + RMNET_INFO rmnet_info; +} PROFILE_T; + +typedef enum { + SIM_ABSENT = 0, + SIM_NOT_READY = 1, + SIM_READY = 2, /* SIM_READY means the radio state is RADIO_STATE_SIM_READY */ + SIM_PIN = 3, + SIM_PUK = 4, + SIM_NETWORK_PERSONALIZATION = 5, + SIM_BAD = 6, +} SIM_Status; + +#pragma pack(pop) + +#define WDM_DEFAULT_BUFSIZE 256 +#define RIL_REQUEST_QUIT 0x1000 +#define RIL_INDICATE_DEVICE_CONNECTED 0x1002 +#define RIL_INDICATE_DEVICE_DISCONNECTED 0x1003 +#define RIL_UNSOL_RESPONSE_VOICE_NETWORK_STATE_CHANGED 0x1004 +#define RIL_UNSOL_DATA_CALL_LIST_CHANGED 0x1005 +#define MODEM_REPORT_RESET_EVENT 0x1006 +#define RIL_UNSOL_LOOPBACK_CONFIG_IND 0x1007 + +extern int pthread_cond_timeout_np(pthread_cond_t *cond, pthread_mutex_t * mutex, unsigned msecs); +extern int QmiThreadSendQMI(PQCQMIMSG pRequest, PQCQMIMSG *ppResponse); +extern int QmiThreadSendQMITimeout(PQCQMIMSG pRequest, PQCQMIMSG *ppResponse, unsigned msecs); +extern void QmiThreadRecvQMI(PQCQMIMSG pResponse); +extern void udhcpc_start(PROFILE_T *profile); +extern void udhcpc_stop(PROFILE_T *profile); +extern void ql_set_driver_link_state(PROFILE_T *profile, int link_state); +extern void ql_set_driver_qmap_setting(PROFILE_T *profile, QMAP_SETTING *qmap_settings); +extern void ql_get_driver_rmnet_info(PROFILE_T *profile, RMNET_INFO *rmnet_info); +extern void dump_qmi(void *dataBuffer, int dataLen); +extern void qmidevice_send_event_to_main(int triger_event); +extern void qmidevice_send_event_to_main_ext(int triger_event, void *data, unsigned len); +extern int requestSetEthMode(PROFILE_T *profile); +extern int requestGetSIMStatus(SIM_Status *pSIMStatus); +extern int requestEnterSimPin(const CHAR *pPinCode); +extern int requestGetICCID(void); +extern int requestGetIMSI(void); +extern int requestRegistrationState(UCHAR *pPSAttachedState); +extern int requestQueryDataCall(UCHAR *pConnectionStatus, int curIpFamily); +extern int requestSetupDataCall(PROFILE_T *profile, int curIpFamily); +extern int requestDeactivateDefaultPDP(PROFILE_T *profile, int curIpFamily); +extern int requestSetProfile(PROFILE_T *profile); +extern int requestGetProfile(PROFILE_T *profile); +extern int requestGetSignalInfo(void); +extern int requestBaseBandVersion(const char **pp_reversion); +extern int requestGetIPAddress(PROFILE_T *profile, int curIpFamily); +extern int requestSetOperatingMode(UCHAR OperatingMode); +extern int requestSetLoopBackState(UCHAR loopback_state, ULONG replication_factor); + +extern void cond_setclock_attr(pthread_cond_t *cond, clockid_t clock); +extern int mbim_main(PROFILE_T *profile); +extern int get_driver_type(PROFILE_T *profile); +extern BOOL qmidevice_detect(char *qmichannel, char *usbnet_adapter, unsigned bufsize, int *pbusnum, int *pdevnum); +int mhidevice_detect(char *qmichannel, char *usbnet_adapter, PROFILE_T *profile); +extern int ql_bridge_mode_detect(PROFILE_T *profile); +extern int ql_enable_qmi_wwan_rawip_mode(PROFILE_T *profile); +extern int ql_driver_type_detect(PROFILE_T *profile); +extern int ql_qmap_mode_detect(PROFILE_T *profile); +extern const struct qmi_device_ops gobi_qmidev_ops; +extern const struct qmi_device_ops qmiwwan_qmidev_ops; + +#define qmidev_is_gobinet(_qmichannel) (strncmp(_qmichannel, "/dev/qcqmi", strlen("/dev/qcqmi")) == 0) +#define qmidev_is_qmiwwan(_qmichannel) (strncmp(_qmichannel, "/dev/cdc-wdm", strlen("/dev/cdc-wdm")) == 0) +#define qmidev_is_pciemhi(_qmichannel) (strncmp(_qmichannel, "/dev/mhi_", strlen("/dev/mhi_")) == 0) + +#define driver_is_qmi(_drv_name) (strncasecmp(_drv_name, "qmi_wwan", strlen("qmi_wwan")) == 0) +#define driver_is_mbim(_drv_name) (strncasecmp(_drv_name, "cdc_mbim", strlen("cdc_mbim")) == 0) + +extern FILE *logfilefp; +extern int debug_qmi; +extern int qmidevice_control_fd[2]; +extern int qmiclientId[QMUX_TYPE_WDS_ADMIN + 1]; +extern USHORT le16_to_cpu(USHORT v16); +extern UINT le32_to_cpu (UINT v32); +extern UINT ql_swap32(UINT v32); +extern USHORT cpu_to_le16(USHORT v16); +extern UINT cpu_to_le32(UINT v32); +extern void update_resolv_conf(int iptype, const char *ifname, const char *dns1, const char *dns2); +int reattach_driver(PROFILE_T *profile); + +enum +{ + DRV_INVALID, + SOFTWARE_QMI, + SOFTWARE_MBIM, + HARDWARE_PCIE, + HARDWARE_USB, +}; + +enum +{ + SIG_EVENT_START, + SIG_EVENT_CHECK, + SIG_EVENT_STOP, +}; + +#define CM_MAX_BUFF 256 +#define strset(k, v) {if (k) free(k); k = strdup(v);} +#define mfree(v) {if (v) {free(v); v = NULL;} + +#ifdef CM_DEBUG +#define dbg_time(fmt, args...) do { \ + fprintf(stdout, "[%15s-%04d: %s] " fmt "\n", __FILE__, __LINE__, get_time(), ##args); \ + if (logfilefp) fprintf(logfilefp, "[%s-%04d: %s] " fmt "\n", __FILE__, __LINE__, get_time(), ##args); \ +} while(0) +#else +#define dbg_time(fmt, args...) do { \ + fprintf(stdout, "[%s] " fmt "\n", get_time(), ##args); \ + if (logfilefp) fprintf(logfilefp, "[%s] " fmt "\n", get_time(), ##args); \ +} while(0) +#endif +#endif diff --git a/root/package/link4all/quectel-CM/quectel1.6.0.15_src/QmiWwanCM.c b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/QmiWwanCM.c new file mode 100644 index 00000000..1aed7d16 --- /dev/null +++ b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/QmiWwanCM.c @@ -0,0 +1,389 @@ +/****************************************************************************** + @file QmiWwanCM.c + @brief QMI WWAN connectivity manager. + + DESCRIPTION + Connectivity Management Tool for USB network adapter of Quectel wireless cellular modules. + + INITIALIZATION AND SEQUENCING REQUIREMENTS + None. + + --------------------------------------------------------------------------- + Copyright (c) 2016 - 2020 Quectel Wireless Solution, Co., Ltd. All Rights Reserved. + Quectel Wireless Solution Proprietary and Confidential. + --------------------------------------------------------------------------- +******************************************************************************/ + +#include +#include +#include +#include +#include +typedef unsigned short sa_family_t; +#include +#include "QMIThread.h" + +#ifdef CONFIG_QMIWWAN +static int cdc_wdm_fd = -1; +static UCHAR GetQCTLTransactionId(void) { + static int TransactionId = 0; + if (++TransactionId > 0xFF) + TransactionId = 1; + return TransactionId; +} + +typedef USHORT (*CUSTOMQCTL)(PQMICTL_MSG pCTLMsg, void *arg); + +static PQCQMIMSG ComposeQCTLMsg(USHORT QMICTLType, CUSTOMQCTL customQctlMsgFunction, void *arg) { + UCHAR QMIBuf[WDM_DEFAULT_BUFSIZE]; + PQCQMIMSG pRequest = (PQCQMIMSG)QMIBuf; + int Length; + + pRequest->QMIHdr.IFType = USB_CTL_MSG_TYPE_QMI; + pRequest->QMIHdr.CtlFlags = 0x00; + pRequest->QMIHdr.QMIType = QMUX_TYPE_CTL; + pRequest->QMIHdr.ClientId= 0x00; + + pRequest->CTLMsg.QMICTLMsgHdr.CtlFlags = QMICTL_FLAG_REQUEST; + pRequest->CTLMsg.QMICTLMsgHdr.TransactionId = GetQCTLTransactionId(); + pRequest->CTLMsg.QMICTLMsgHdr.QMICTLType = cpu_to_le16(QMICTLType); + if (customQctlMsgFunction) + pRequest->CTLMsg.QMICTLMsgHdr.Length = cpu_to_le16(customQctlMsgFunction(&pRequest->CTLMsg, arg) - sizeof(QCQMICTL_MSG_HDR)); + else + pRequest->CTLMsg.QMICTLMsgHdr.Length = cpu_to_le16(0x0000); + + pRequest->QMIHdr.Length = cpu_to_le16(le16_to_cpu(pRequest->CTLMsg.QMICTLMsgHdr.Length) + sizeof(QCQMICTL_MSG_HDR) + sizeof(QCQMI_HDR) - 1); + Length = le16_to_cpu(pRequest->QMIHdr.Length) + 1; + + pRequest = (PQCQMIMSG)malloc(Length); + if (pRequest == NULL) { + dbg_time("%s fail to malloc", __func__); + } else { + memcpy(pRequest, QMIBuf, Length); + } + + return pRequest; +} + +static USHORT CtlGetVersionReq(PQMICTL_MSG QCTLMsg, void *arg) { + QCTLMsg->GetVersionReq.TLVType = QCTLV_TYPE_REQUIRED_PARAMETER; + QCTLMsg->GetVersionReq.TLVLength = cpu_to_le16(0x0001); + QCTLMsg->GetVersionReq.QMUXTypes = QMUX_TYPE_ALL; + return sizeof(QMICTL_GET_VERSION_REQ_MSG); +} + +static USHORT CtlGetClientIdReq(PQMICTL_MSG QCTLMsg, void *arg) { + QCTLMsg->GetClientIdReq.TLVType = QCTLV_TYPE_REQUIRED_PARAMETER; + QCTLMsg->GetClientIdReq.TLVLength = cpu_to_le16(0x0001); + QCTLMsg->GetClientIdReq.QMIType = ((UCHAR *)arg)[0]; + return sizeof(QMICTL_GET_CLIENT_ID_REQ_MSG); +} + +static USHORT CtlReleaseClientIdReq(PQMICTL_MSG QCTLMsg, void *arg) { + QCTLMsg->ReleaseClientIdReq.TLVType = QCTLV_TYPE_REQUIRED_PARAMETER; + QCTLMsg->ReleaseClientIdReq.TLVLength = cpu_to_le16(0x0002); + QCTLMsg->ReleaseClientIdReq.QMIType = ((UCHAR *)arg)[0]; + QCTLMsg->ReleaseClientIdReq.ClientId = ((UCHAR *)arg)[1] ; + return sizeof(QMICTL_RELEASE_CLIENT_ID_REQ_MSG); +} + +static int QmiWwanSendQMI(PQCQMIMSG pRequest) { + struct pollfd pollfds[]= {{cdc_wdm_fd, POLLOUT, 0}}; + int ret; + + if (cdc_wdm_fd == -1) { + dbg_time("%s cdc_wdm_fd = -1", __func__); + return -ENODEV; + } + + if (pRequest->QMIHdr.QMIType == QMUX_TYPE_WDS_IPV6) + pRequest->QMIHdr.QMIType = QMUX_TYPE_WDS; + + do { + ret = poll(pollfds, sizeof(pollfds)/sizeof(pollfds[0]), 5000); + } while ((ret < 0) && (errno == EINTR)); + + if (pollfds[0].revents & POLLOUT) { + ssize_t nwrites = le16_to_cpu(pRequest->QMIHdr.Length) + 1; + ret = write(cdc_wdm_fd, pRequest, nwrites); + if (ret == nwrites) { + ret = 0; + } else { + dbg_time("%s write=%d, errno: %d (%s)", __func__, ret, errno, strerror(errno)); + } + } else { + dbg_time("%s poll=%d, revents = 0x%x, errno: %d (%s)", __func__, ret, pollfds[0].revents, errno, strerror(errno)); + } + + return ret; +} + +static int QmiWwanGetClientID(UCHAR QMIType) { + PQCQMIMSG pResponse; + + QmiThreadSendQMI(ComposeQCTLMsg(QMICTL_GET_CLIENT_ID_REQ, CtlGetClientIdReq, &QMIType), &pResponse); + + if (pResponse) { + USHORT QMUXResult = cpu_to_le16(pResponse->CTLMsg.QMICTLMsgHdrRsp.QMUXResult); // QMI_RESULT_SUCCESS + USHORT QMUXError = cpu_to_le16(pResponse->CTLMsg.QMICTLMsgHdrRsp.QMUXError); // QMI_ERR_INVALID_ARG + //UCHAR QMIType = pResponse->CTLMsg.GetClientIdRsp.QMIType; + UCHAR ClientId = pResponse->CTLMsg.GetClientIdRsp.ClientId; + + if (!QMUXResult && !QMUXError && (QMIType == pResponse->CTLMsg.GetClientIdRsp.QMIType)) { + switch (QMIType) { + case QMUX_TYPE_WDS: dbg_time("Get clientWDS = %d", ClientId); break; + case QMUX_TYPE_DMS: dbg_time("Get clientDMS = %d", ClientId); break; + case QMUX_TYPE_NAS: dbg_time("Get clientNAS = %d", ClientId); break; + case QMUX_TYPE_QOS: dbg_time("Get clientQOS = %d", ClientId); break; + case QMUX_TYPE_WMS: dbg_time("Get clientWMS = %d", ClientId); break; + case QMUX_TYPE_PDS: dbg_time("Get clientPDS = %d", ClientId); break; + case QMUX_TYPE_UIM: dbg_time("Get clientUIM = %d", ClientId); break; + case QMUX_TYPE_WDS_ADMIN: dbg_time("Get clientWDA = %d", ClientId); + break; + default: break; + } + return ClientId; + } + } + return 0; +} + +static int QmiWwanReleaseClientID(QMI_SERVICE_TYPE QMIType, UCHAR ClientId) { + UCHAR argv[] = {QMIType, ClientId}; + QmiThreadSendQMI(ComposeQCTLMsg(QMICTL_RELEASE_CLIENT_ID_REQ, CtlReleaseClientIdReq, argv), NULL); + return 0; +} + +static int QmiWwanInit(PROFILE_T *profile) { + unsigned i; + int ret; + PQCQMIMSG pResponse; + + if (profile->qmap_mode == 0 || profile->qmap_mode == 1) + { + for (i = 0; i < 10; i++) { + ret = QmiThreadSendQMITimeout(ComposeQCTLMsg(QMICTL_SYNC_REQ, NULL, NULL), NULL, 1 * 1000); + if (!ret) + break; + sleep(1); + } + if (ret) + return ret; + } + + QmiThreadSendQMI(ComposeQCTLMsg(QMICTL_GET_VERSION_REQ, CtlGetVersionReq, NULL), &pResponse); + if (profile->qmap_mode) { + if (pResponse) { + if (pResponse->CTLMsg.QMICTLMsgHdrRsp.QMUXResult == 0 && pResponse->CTLMsg.QMICTLMsgHdrRsp.QMUXError == 0) { + uint8_t NumElements = 0; + + for (NumElements = 0; NumElements < pResponse->CTLMsg.GetVersionRsp.NumElements; NumElements++) { +#if 0 + dbg_time("QMUXType = %02x Version = %d.%d", + pResponse->CTLMsg.GetVersionRsp.TypeVersion[NumElements].QMUXType, + pResponse->CTLMsg.GetVersionRsp.TypeVersion[NumElements].MajorVersion, + pResponse->CTLMsg.GetVersionRsp.TypeVersion[NumElements].MinorVersion); +#endif + if (pResponse->CTLMsg.GetVersionRsp.TypeVersion[NumElements].QMUXType == QMUX_TYPE_WDS_ADMIN) + profile->qmap_version = (pResponse->CTLMsg.GetVersionRsp.TypeVersion[NumElements].MinorVersion > 16); + } + } + } + } + if (pResponse) free(pResponse); + qmiclientId[QMUX_TYPE_WDS] = QmiWwanGetClientID(QMUX_TYPE_WDS); + if (profile->enable_ipv6) + qmiclientId[QMUX_TYPE_WDS_IPV6] = QmiWwanGetClientID(QMUX_TYPE_WDS); + qmiclientId[QMUX_TYPE_DMS] = QmiWwanGetClientID(QMUX_TYPE_DMS); + qmiclientId[QMUX_TYPE_NAS] = QmiWwanGetClientID(QMUX_TYPE_NAS); + qmiclientId[QMUX_TYPE_UIM] = QmiWwanGetClientID(QMUX_TYPE_UIM); + if (profile->qmap_mode == 0 || profile->qmap_mode == 1) + qmiclientId[QMUX_TYPE_WDS_ADMIN] = QmiWwanGetClientID(QMUX_TYPE_WDS_ADMIN); + + return 0; +} + +static int QmiWwanDeInit(void) { + unsigned int i; + for (i = 0; i < sizeof(qmiclientId)/sizeof(qmiclientId[0]); i++) + { + if (qmiclientId[i] != 0) + { + QmiWwanReleaseClientID(i, qmiclientId[i]); + qmiclientId[i] = 0; + } + } + + return 0; +} + +#define QUECTEL_QMI_PROXY "quectel-qmi-proxy" +static int qmi_proxy_open(const char *name) { + int sockfd = -1; + int reuse_addr = 1; + struct sockaddr_un sockaddr; + socklen_t alen; + + /*Create server socket*/ + (sockfd = socket(AF_LOCAL, SOCK_STREAM, 0)); + if (sockfd < 0) + return sockfd; + + memset(&sockaddr, 0, sizeof(sockaddr)); + sockaddr.sun_family = AF_LOCAL; + sockaddr.sun_path[0] = 0; + memcpy(sockaddr.sun_path + 1, name, strlen(name) ); + + alen = strlen(name) + offsetof(struct sockaddr_un, sun_path) + 1; + if(connect(sockfd, (struct sockaddr *)&sockaddr, alen) < 0) { + close(sockfd); + dbg_time("%s connect %s errno: %d (%s)\n", __func__, name, errno, strerror(errno)); + return -1; + } + (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse_addr,sizeof(reuse_addr))); + + dbg_time("connect to %s sockfd = %d\n", name, sockfd); + + return sockfd; +} + +static ssize_t qmi_proxy_read (int fd, void *buf, size_t size) { + ssize_t nreads; + PQCQMI_HDR pHdr = (PQCQMI_HDR)buf; + + nreads = read(fd, pHdr, sizeof(QCQMI_HDR)); + if (nreads == sizeof(QCQMI_HDR)) { + nreads += read(fd, pHdr+1, le16_to_cpu(pHdr->Length) + 1 - sizeof(QCQMI_HDR)); + } + + return nreads; +} + +static void * QmiWwanThread(void *pData) { + PROFILE_T *profile = (PROFILE_T *)pData; + const char *cdc_wdm = (const char *)profile->qmichannel; + int wait_for_request_quit = 0; + char servername[32]; + int num = cdc_wdm[strlen(cdc_wdm)-1]-'0'; + + sprintf(servername, QUECTEL_QMI_PROXY"%d", num); + + if (profile->qmap_mode == 0 || profile->qmap_mode == 1) + cdc_wdm_fd = open(cdc_wdm, O_RDWR | O_NONBLOCK | O_NOCTTY); + else + cdc_wdm_fd = qmi_proxy_open(servername); + + if (cdc_wdm_fd == -1) { + dbg_time("%s Failed to open %s, errno: %d (%s)", __func__, cdc_wdm, errno, strerror(errno)); + qmidevice_send_event_to_main(RIL_INDICATE_DEVICE_DISCONNECTED); + pthread_exit(NULL); + return NULL; + } + + fcntl(cdc_wdm_fd, F_SETFL, fcntl(cdc_wdm_fd,F_GETFL) | O_NONBLOCK); + fcntl(cdc_wdm_fd, F_SETFD, FD_CLOEXEC); + + dbg_time("cdc_wdm_fd = %d", cdc_wdm_fd); + + qmidevice_send_event_to_main(RIL_INDICATE_DEVICE_CONNECTED); + while (1) { + struct pollfd pollfds[] = {{qmidevice_control_fd[1], POLLIN, 0}, {cdc_wdm_fd, POLLIN, 0}}; + int ne, ret, nevents = sizeof(pollfds)/sizeof(pollfds[0]); + + do { + ret = poll(pollfds, nevents, wait_for_request_quit ? 1000 : -1); + } while ((ret < 0) && (errno == EINTR)); + + if (ret == 0 && wait_for_request_quit) { + QmiThreadRecvQMI(NULL); + continue; + } + + if (ret <= 0) { + dbg_time("%s poll=%d, errno: %d (%s)", __func__, ret, errno, strerror(errno)); + break; + } + + for (ne = 0; ne < nevents; ne++) { + int fd = pollfds[ne].fd; + short revents = pollfds[ne].revents; + + //dbg_time("{%d, %x, %x}", pollfds[ne].fd, pollfds[ne].events, pollfds[ne].revents); + + if (revents & (POLLERR | POLLHUP | POLLNVAL)) { + dbg_time("%s poll err/hup/inval", __func__); + dbg_time("poll fd = %d, events = 0x%04x", fd, revents); + if (fd == cdc_wdm_fd) { + } else { + } + if (revents & (POLLHUP | POLLNVAL)) //EC20 bug, Can get POLLERR + goto __QmiWwanThread_quit; + } + + if ((revents & POLLIN) == 0) + continue; + + if (fd == qmidevice_control_fd[1]) { + int triger_event; + if (read(fd, &triger_event, sizeof(triger_event)) == sizeof(triger_event)) { + //DBG("triger_event = 0x%x", triger_event); + switch (triger_event) { + case RIL_REQUEST_QUIT: + goto __QmiWwanThread_quit; + break; + case SIG_EVENT_STOP: + wait_for_request_quit = 1; + break; + default: + break; + } + } + } + + if (fd == cdc_wdm_fd) { + ssize_t nreads; + static UCHAR QMIBuf[4096]; + PQCQMIMSG pResponse = (PQCQMIMSG)QMIBuf; + + if (profile->qmap_mode == 0 || profile->qmap_mode == 1) + nreads = read(fd, QMIBuf, sizeof(QMIBuf)); + else + nreads = qmi_proxy_read(fd, QMIBuf, sizeof(QMIBuf)); + //dbg_time("%s read=%d errno: %d (%s)", __func__, (int)nreads, errno, strerror(errno)); + if (nreads <= 0) { + dbg_time("%s read=%d errno: %d (%s)", __func__, (int)nreads, errno, strerror(errno)); + break; + } + + if (nreads != (le16_to_cpu(pResponse->QMIHdr.Length) + 1)) { + dbg_time("%s nreads=%d, pQCQMI->QMIHdr.Length = %d", __func__, (int)nreads, le16_to_cpu(pResponse->QMIHdr.Length)); + continue; + } + + QmiThreadRecvQMI(pResponse); + } + } + } + +__QmiWwanThread_quit: + if (cdc_wdm_fd != -1) { close(cdc_wdm_fd); cdc_wdm_fd = -1; } + qmidevice_send_event_to_main(RIL_INDICATE_DEVICE_DISCONNECTED); + QmiThreadRecvQMI(NULL); //main thread may pending on QmiThreadSendQMI() + dbg_time("%s exit", __func__); + pthread_exit(NULL); + return NULL; +} + +#else +static int QmiWwanSendQMI(PQCQMIMSG pRequest) {return -1;} +static int QmiWwanInit(PROFILE_T *profile) {return -1;} +static int QmiWwanDeInit(void) {return -1;} +static void * QmiWwanThread(void *pData) {dbg_time("please set CONFIG_QMIWWAN"); return NULL;} +#endif + +const struct qmi_device_ops qmiwwan_qmidev_ops = { + .init = QmiWwanInit, + .deinit = QmiWwanDeInit, + .send = QmiWwanSendQMI, + .read = QmiWwanThread, +}; \ No newline at end of file diff --git a/root/package/link4all/quectel-CM/quectel1.6.0.15_src/ReleaseNote.txt b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/ReleaseNote.txt new file mode 100644 index 00000000..10033042 --- /dev/null +++ b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/ReleaseNote.txt @@ -0,0 +1,69 @@ +Release Notes + +[WCDMA<E_QConnectManager_Linux&Android_V1.5.3] +Date: 2019/12/11 +enhancement: +1. support show SignalInfo, controlled by macro CONFIG_SIGNALINFO +2. support show 5G_NSA/5G_NA +3. support Microsoft Extend MBIM message + +[WCDMA<E_QConnectManager_Linux&Android_V1.2.1] +Date: 2019/02/26 +enhancement: +1. Implement help message. + +root@ubuntu:# ./quectel-CM -h +[02-26_10:39:21:353] Usage: ./quectel-CM [options] +[02-26_10:39:21:353] -s [apn [user password auth]] Set apn/user/password/auth get from your network provider +[02-26_10:39:21:353] -p pincode Verify sim card pin if sim card is locked +[02-26_10:39:21:353] -f logfilename Save log message of this program to file +[02-26_10:39:21:353] -i interface Specify network interface(default auto-detect) +[02-26_10:39:21:353] -4 IPv4 protocol +[02-26_10:39:21:353] -6 IPv6 protocol +[02-26_10:39:21:353] -m muxID Specify muxid when set multi-pdn data connection. +[02-26_10:39:21:353] -n channelID Specify channelID when set multi-pdn data connection(default 1). +[02-26_10:39:21:353] [Examples] +[02-26_10:39:21:353] Example 1: ./quectel-CM +[02-26_10:39:21:353] Example 2: ./quectel-CM -s 3gnet +[02-26_10:39:21:353] Example 3: ./quectel-CM -s 3gnet carl 1234 0 -p 1234 -f gobinet_log.txt +root@ubuntu:# +2. Support bridge mode when set multi-pdn data connections. +3. Host device can access network in bridge mode. + +[WCDMA<E_QConnectManager_Linux&Android_V1.1.46] +Date: 2019/02/18 +enhancement: +1. support only IPV6 data call. quectel-CM now support three dialing methods: IPV4 only, IPV6 only, IPV4V6. + ./quectel-CM -4(or no argument) only IPV4 + -6 only IPV6 + -4 -6 IPV4 && IPV6 + +[WCDMA<E_QConnectManager_Linux&Android_V1.1.45] +Date: 2018/09/13 +enhancement: +1. support EG12 PCIE interface + +[WCDMA<E_QConnectManager_Linux&Android_V1.1.44] +Date: 2018/09/10 +enhancement: +1. support setup IPV4&IPV6 data call. + +[WCDMA<E_QConnectManager_Linux&Android_V1.1.43] +[WCDMA<E_QConnectManager_Linux&Android_V1.1.42] +Date: 2018/08/29 +enhancement: +1. support QMI_WWAN's QMAP fucntion and bridge mode, please contact Quectel FAE to get qmi_wwan.c patch. + when enable QMI_WWAN's QMAP IP Mux function, must run 'quectel-qmi-proxy -d /dev/cdc-wdmX' before quectel-CM + +[WCDMA<E_QConnectManager_Linux&Android_V1.1.41] +Date: 2018/05/24 +enhancement: +1. fix a cdma data call error + +[WCDMA<E_QConnectManager_Linux&Android_V1.1.40] +Date: 2018/05/12 +enhancement: +1. support GobiNet's QMAP fucntion and bridge mode. + 'Quectel_WCDMA<E_Linux&Android_GobiNet_Driver_V1.3.5' and later version is required to use QMAP and bridge mode. + for detail, please refer to GobiNet Driver + diff --git a/root/package/link4all/quectel-CM/quectel1.6.0.15_src/default.script b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/default.script new file mode 100644 index 00000000..1ac17b6c --- /dev/null +++ b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/default.script @@ -0,0 +1,63 @@ +#!/bin/sh +# Busybox udhcpc dispatcher script. Copyright (C) 2009 by Axel Beckert. +# +# Based on the busybox example scripts and the old udhcp source +# package default.* scripts. + +RESOLV_CONF="/etc/resolv.conf" + +case $1 in + bound|renew) + [ -n "$broadcast" ] && BROADCAST="broadcast $broadcast" + [ -n "$subnet" ] && NETMASK="netmask $subnet" + + /sbin/ifconfig $interface $ip $BROADCAST $NETMASK + + if [ -n "$router" ]; then + echo "$0: Resetting default routes" + while /sbin/route del default gw 0.0.0.0 dev $interface; do :; done + + metric=0 + for i in $router; do + /sbin/route add default gw $i dev $interface metric $metric + metric=$(($metric + 1)) + done + fi + + # Update resolver configuration file + R="" + [ -n "$domain" ] && R="domain $domain +" + for i in $dns; do + echo "$0: Adding DNS $i" + R="${R}nameserver $i +" + done + + if [ -x /sbin/resolvconf ]; then + echo -n "$R" | resolvconf -a "${interface}.udhcpc" + else + echo -n "$R" > "$RESOLV_CONF" + fi + ;; + + deconfig) + if [ -x /sbin/resolvconf ]; then + resolvconf -d "${interface}.udhcpc" + fi + /sbin/ifconfig $interface 0.0.0.0 + ;; + + leasefail) + echo "$0: Lease failed: $message" + ;; + + nak) + echo "$0: Received a NAK: $message" + ;; + + *) + echo "$0: Unknown udhcpc command: $1"; + exit 1; + ;; +esac diff --git a/root/package/link4all/quectel-CM/quectel1.6.0.15_src/device.c b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/device.c new file mode 100644 index 00000000..07d1268a --- /dev/null +++ b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/device.c @@ -0,0 +1,500 @@ +/****************************************************************************** + @file device.c + @brief QMI device dirver. + + DESCRIPTION + Connectivity Management Tool for USB network adapter of Quectel wireless cellular modules. + + INITIALIZATION AND SEQUENCING REQUIREMENTS + None. + + --------------------------------------------------------------------------- + Copyright (c) 2016 - 2020 Quectel Wireless Solution, Co., Ltd. All Rights Reserved. + Quectel Wireless Solution Proprietary and Confidential. + --------------------------------------------------------------------------- +******************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "QMIThread.h" +#include "ethtool-copy.h" + +#define CM_MAX_PATHLEN 256 + +#define CM_INVALID_VAL (~((int)0)) +/* get first line from file 'fname' + * And convert the content into a hex number, then return this number */ +static int file_get_value(const char *fname, int base) +{ + FILE *fp = NULL; + int num; + char buff[32 + 1] = {'\0'}; + char *endptr = NULL; + + fp = fopen(fname, "r"); + if (!fp) goto error; + if (fgets(buff, sizeof(buff), fp) == NULL) + goto error; + fclose(fp); + + num = (int)strtol(buff, &endptr, base); + if (errno == ERANGE && (num == LONG_MAX || num == LONG_MIN)) + goto error; + /* if there is no digit in buff */ + if (endptr == buff) + goto error; + return num; + +error: + if (fp) fclose(fp); + return CM_INVALID_VAL; +} + +/* + * This function will search the directory 'dirname' and return the first child. + * '.' and '..' is ignored by default + */ +int dir_get_child(const char *dirname, char *buff, unsigned bufsize) +{ + struct dirent *entptr = NULL; + DIR *dirptr = opendir(dirname); + if (!dirptr) + goto error; + while ((entptr = readdir(dirptr))) { + if (entptr->d_name[0] == '.') + continue; + snprintf(buff, bufsize, "%s", entptr->d_name); + break; + } + + closedir(dirptr); + return 0; +error: + buff[0] = '\0'; + if (dirptr) closedir(dirptr); + return -1; +} + +int conf_get_val(const char *fname, const char *key) +{ + char buff[CM_MAX_BUFF] = {'\0'}; + FILE *fp = fopen(fname, "r"); + if (!fp) + goto error; + + while (fgets(buff, CM_MAX_BUFF, fp)) { + char prefix[CM_MAX_BUFF] = {'\0'}; + char tail[CM_MAX_BUFF] = {'\0'}; + /* To eliminate cppcheck warnning: Assume string length is no more than 15 */ + sscanf(buff, "%15[^=]=%15s", prefix, tail); + if (!strncasecmp(prefix, key, strlen(key))) { + fclose(fp); + return atoi(tail); + } + } + +error: + fclose(fp); + return CM_INVALID_VAL; +} + +static int detect_path_cdc_wdm_or_qcqmi(char *path, size_t len) +{ + size_t offset = strlen(path); + + if (!access(path, R_OK)) + { + path[offset] = '\0'; + strcat(path, "/GobiQMI"); + if (!access(path, R_OK)) + return 0; + + path[offset] = '\0'; + strcat(path, "/usbmisc"); + if (!access(path, R_OK)) + return 0; + + path[offset] = '\0'; + strcat(path, "/usb"); + if (!access(path, R_OK)) + return 0; + } + + return -1; +} + +/* To detect the device info of the modem. + * return: + * FALSE -> fail + * TRUE -> ok + */ +BOOL qmidevice_detect(char *qmichannel, char *usbnet_adapter, unsigned bufsize, int *pbusnum, int *pdevnum) { + struct dirent* ent = NULL; + DIR *pDir; + const char *rootdir = "/sys/bus/usb/devices"; + struct { + char path[255*2]; + char uevent[255*3]; + } *pl; + pl = (typeof(pl)) malloc(sizeof(*pl)); + memset(pl, 0x00, sizeof(*pl)); + + pDir = opendir(rootdir); + if (!pDir) { + dbg_time("opendir %s failed: %s", rootdir, strerror(errno)); + goto error; + } + + while ((ent = readdir(pDir)) != NULL) { + int idVendor; + int idProduct; + char netcard[32+1] = {'\0'}; + char device[32+1] = {'\0'}; + char devname[32+1+6] = {'\0'}; + int busnum, devnum; + + snprintf(pl->path, sizeof(pl->path), "%s/%s/idVendor", rootdir, ent->d_name); + idVendor = file_get_value(pl->path, 16); + + snprintf(pl->path, sizeof(pl->path), "%s/%s/idProduct", rootdir, ent->d_name); + idProduct = file_get_value(pl->path, 16); + + if (idVendor != 0x05c6 && idVendor != 0x2c7c) + continue; + + snprintf(pl->path, sizeof(pl->path), "%s/%s/busnum", rootdir, ent->d_name); + busnum = file_get_value(pl->path, 10); + snprintf(pl->path, sizeof(pl->path), "%s/%s/devnum", rootdir, ent->d_name); + devnum = file_get_value(pl->path, 10); + dbg_time("Find %s/%s idVendor=0x%x idProduct=0x%x, bus=0x%03x, dev=0x%03x", + rootdir, ent->d_name, idVendor, idProduct, busnum, devnum); + + /* get network interface */ + snprintf(pl->path, sizeof(pl->path), "%s/%s:1.4/net", rootdir, ent->d_name); + dir_get_child(pl->path, netcard, sizeof(netcard)); + if (netcard[0] == '\0') { + snprintf(pl->path, sizeof(pl->path), "%s/%s:1.8/net", rootdir, ent->d_name); //for EM12's MBIM + dir_get_child(pl->path, netcard, sizeof(netcard)); + } + + if (netcard[0] == '\0') { //for centos 2.6.x + const char *n= "usb0"; + const char *c = "qcqmi0"; + + snprintf(pl->path, sizeof(pl->path), "%s/%s:1.4/net:%s", rootdir, ent->d_name, n); + if (!access(pl->path, F_OK)) { + snprintf(pl->path, sizeof(pl->path), "%s/%s:1.4/GobiQMI:%s", rootdir, ent->d_name, c); + if (!access(pl->path, F_OK)) { + snprintf(qmichannel, bufsize, "/dev/%s", c); + snprintf(usbnet_adapter, bufsize, "%s", n); + break; + } + } + } + + if (netcard[0] == '\0') + continue; + + if (usbnet_adapter[0] && strcmp(usbnet_adapter, netcard)) + continue; + + pl->path[strlen(pl->path)-strlen("/net")] = '\0'; + if (detect_path_cdc_wdm_or_qcqmi(pl->path, sizeof(pl->path))) + continue; + + /* get device */ + dir_get_child(pl->path, device, sizeof(device)); + if (device[0] == '\0') + continue; + + /* There is a chance that, no device(qcqmiX|cdc-wdmX) is generated. We should warn user about that! */ + snprintf(devname, sizeof(devname), "/dev/%s", device); + if (access(devname, R_OK | F_OK) && errno == ENOENT) { + int major; + int minor; + int ret; + + dbg_time("%s access failed, errno: %d (%s)", devname, errno, strerror(errno)); + snprintf(pl->uevent, sizeof(pl->uevent), "%s/%s/uevent", pl->path, device); + major = conf_get_val(pl->uevent, "MAJOR"); + minor = conf_get_val(pl->uevent, "MINOR"); + if(major == CM_INVALID_VAL || minor == CM_INVALID_VAL) + dbg_time("get major and minor failed"); + + ret = mknod(devname, S_IFCHR|0666, (((major & 0xfff) << 8) | (minor & 0xff) | ((minor & 0xfff00) << 12))); + if (ret) + dbg_time("please mknod %s c %d %d", devname, major, minor); + } + + if (netcard[0] && device[0]) { + snprintf(qmichannel, bufsize, "/dev/%s", device); + snprintf(usbnet_adapter, bufsize, "%s", netcard); + dbg_time("Auto find qmichannel = %s", qmichannel); + dbg_time("Auto find usbnet_adapter = %s", usbnet_adapter); + *pbusnum = busnum; + *pdevnum = devnum; + break; + } + } + closedir(pDir); + + if (qmichannel[0] == '\0' || usbnet_adapter[0] == '\0') { + dbg_time("network interface '%s' or qmidev '%s' is not exist", usbnet_adapter, qmichannel); + goto error; + } + free(pl); + return TRUE; +error: + free(pl); + return FALSE; +} + +int mhidevice_detect(char *qmichannel, char *usbnet_adapter, PROFILE_T *profile) { + if (!access("/sys/class/net/pcie_mhi0", F_OK)) + strcpy(usbnet_adapter, "pcie_mhi0"); + else if (!access("/sys/class/net/rmnet_mhi0", F_OK)) + strcpy(usbnet_adapter, "rmnet_mhi0"); + else { + dbg_time("qmidevice_detect failed"); + goto error; + } + + if (!access("/dev/mhi_MBIM", F_OK)) { + strcpy(qmichannel, "/dev/mhi_MBIM"); + profile->software_interface = SOFTWARE_MBIM; + } + else if (!access("/dev/mhi_QMI0", F_OK)) { + strcpy(qmichannel, "/dev/mhi_QMI0"); + profile->software_interface = SOFTWARE_QMI; + } + else { + goto error; + } + + return 1; +error: + return 0; +} + +#define USB_CLASS_COMM 2 +#define USB_CLASS_VENDOR_SPEC 0xff +#define USB_CDC_SUBCLASS_MBIM 0x0e + +int get_driver_type(PROFILE_T *profile) +{ + char path[CM_MAX_PATHLEN+1] = {'\0'}; + int bInterfaceClass; + int type = DRV_INVALID; + + snprintf(path, sizeof(path), "/sys/class/net/%s/device/bInterfaceClass", profile->usbnet_adapter); + bInterfaceClass = file_get_value(path, 16); + + /* QMI_WWAN */ + if (bInterfaceClass == USB_CLASS_VENDOR_SPEC) + type = SOFTWARE_QMI; + + /* CDC_MBIM */ + if (bInterfaceClass == USB_CLASS_COMM) + type = SOFTWARE_MBIM; + + return type; +} + +struct usbfs_getdriver +{ + unsigned int interface; + char driver[255 + 1]; +}; + +struct usbfs_ioctl +{ + int ifno; /* interface 0..N ; negative numbers reserved */ + int ioctl_code; /* MUST encode size + direction of data so the + * macros in give correct values */ + void *data; /* param buffer (in, or out) */ +}; + +#define IOCTL_USBFS_DISCONNECT _IO('U', 22) +#define IOCTL_USBFS_CONNECT _IO('U', 23) + +int usbfs_is_kernel_driver_alive(int fd, int ifnum) +{ + struct usbfs_getdriver getdrv; + getdrv.interface = ifnum; + if (ioctl(fd, USBDEVFS_GETDRIVER, &getdrv) < 0) { + dbg_time("%s ioctl USBDEVFS_GETDRIVER failed, kernel driver may be inactive", __func__); + return 0; + } + dbg_time("%s find interface %d has match the driver %s", __func__, ifnum, getdrv.driver); + return 1; +} + +void usbfs_detach_kernel_driver(int fd, int ifnum) +{ + struct usbfs_ioctl operate; + operate.data = NULL; + operate.ifno = ifnum; + operate.ioctl_code = IOCTL_USBFS_DISCONNECT; + if (ioctl(fd, USBDEVFS_IOCTL, &operate) < 0) { + dbg_time("%s detach kernel driver failed", __func__); + } else { + dbg_time("%s detach kernel driver success", __func__); + } +} + +void usbfs_attach_kernel_driver(int fd, int ifnum) +{ + struct usbfs_ioctl operate; + operate.data = NULL; + operate.ifno = ifnum; + operate.ioctl_code = IOCTL_USBFS_CONNECT; + if (ioctl(fd, USBDEVFS_IOCTL, &operate) < 0) { + dbg_time("%s detach kernel driver failed", __func__); + } else { + dbg_time("%s detach kernel driver success", __func__); + } +} + +int reattach_driver(PROFILE_T *profile) +{ + int ifnum = 4; + int fd; + char devpath[128] = {'\0'}; + snprintf(devpath, sizeof(devpath), "/dev/bus/usb/%03d/%03d", profile->busnum, profile->devnum); + fd = open(devpath, O_RDWR | O_NOCTTY); + if (fd < 0) + { + dbg_time("%s fail to open %s", __func__, devpath); + return -1; + } + usbfs_detach_kernel_driver(fd, ifnum); + usbfs_attach_kernel_driver(fd, ifnum); + close(fd); + return 0; +} + +#define SIOCETHTOOL 0x8946 +int ql_get_netcard_driver_info(const char *devname) +{ + int fd = -1; + struct ethtool_drvinfo drvinfo; + struct ifreq ifr; /* ifreq suitable for ethtool ioctl */ + + memset(&ifr, 0, sizeof(ifr)); + strcpy(ifr.ifr_name, devname); + + fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd < 0) { + dbg_time("Cannot get control socket: errno(%d)(%s)", errno, strerror(errno)); + return -1; + } + + drvinfo.cmd = ETHTOOL_GDRVINFO; + ifr.ifr_data = (void *)&drvinfo; + + if (ioctl(fd, SIOCETHTOOL, &ifr) < 0) { + dbg_time("ioctl() error: errno(%d)(%s)", errno, strerror(errno)); + return -1; + } + + dbg_time("netcard driver = %s, driver version = %s", drvinfo.driver, drvinfo.version); + + close(fd); + + return 0; +} + +static void *catch_log(void *arg) +{ + PROFILE_T *profile = (PROFILE_T *)arg; + int nreads = 0; + char tbuff[256+32]; + time_t t; + struct tm *tm; + char filter[10]; + size_t tsize = strlen("2020/06/22_22:50:07 "); + + sprintf(filter, ":%d:%03d:", profile->busnum, profile->devnum); + + while(1) { + nreads = read(profile->usbmon_fd, tbuff + tsize, sizeof(tbuff) - tsize - 1); + if (nreads <= 0) { + break; + } + + tbuff[tsize+nreads] = '\0'; // printf("%s", buff); + + if (!strstr(tbuff+tsize, filter)) + continue; + + time(&t); + tm = localtime(&t); + + sprintf(tbuff, "%04d/%02d/%02d_%02d:%02d:%02d", + tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); + tbuff[tsize-1] = ' '; + + write(profile->usbmon_logfile_fd, tbuff, strlen(tbuff)); + } + + return NULL; +} + +int ql_capture_usbmon_log(PROFILE_T *profile, const char *log_path) +{ + char usbmon_path[64]; + pthread_t pt; + pthread_attr_t attr; + + if (access("/sys/module/usbmon", F_OK)) { + dbg_time("usbmon is not load, please execute \"modprobe usbmon\" or \"insmod usbmon.ko\""); + return -1; + } + + if (access("/sys/kernel/debug/usb", F_OK)) { + dbg_time("debugfs is not mount, please execute \"mount -t debugfs none_debugs /sys/kernel/debug\""); + return -1; + } + + sprintf(usbmon_path, "/sys/kernel/debug/usb/usbmon/%du", profile->busnum); + profile->usbmon_fd = open(usbmon_path, O_RDONLY); + if (profile->usbmon_fd < 0) { + dbg_time("open %s error(%d) (%s)", usbmon_path, errno, strerror(errno)); + return -1; + } + + profile->usbmon_logfile_fd = open(log_path, O_WRONLY | O_CREAT | O_APPEND, 0644); + if (profile->usbmon_logfile_fd < 0) { + dbg_time("open %s error(%d) (%s)", log_path, errno, strerror(errno)); + close(profile->usbmon_fd); + profile->usbmon_fd = -1; + return -1; + } + + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + + pthread_create(&pt, &attr, catch_log, (void *)profile); + + return 0; +} + +void ql_stop_usbmon_log(PROFILE_T *profile) { + if (profile->usbmon_fd > 0) + close(profile->usbmon_fd); + if (profile->usbmon_logfile_fd > 0) + close(profile->usbmon_logfile_fd); +} diff --git a/root/package/link4all/quectel-CM/quectel1.6.0.15_src/ethtool-copy.h b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/ethtool-copy.h new file mode 100644 index 00000000..b5515c2f --- /dev/null +++ b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/ethtool-copy.h @@ -0,0 +1,1100 @@ +/* + * ethtool.h: Defines for Linux ethtool. + * + * Copyright (C) 1998 David S. Miller (davem@redhat.com) + * Copyright 2001 Jeff Garzik + * Portions Copyright 2001 Sun Microsystems (thockin@sun.com) + * Portions Copyright 2002 Intel (eli.kupermann@intel.com, + * christopher.leech@intel.com, + * scott.feldman@intel.com) + * Portions Copyright (C) Sun Microsystems 2008 + */ + +#ifndef _LINUX_ETHTOOL_H +#define _LINUX_ETHTOOL_H + +#include +#include + +/* This should work for both 32 and 64 bit userland. */ +struct ethtool_cmd { + __u32 cmd; + __u32 supported; /* Features this interface supports */ + __u32 advertising; /* Features this interface advertises */ + __u16 speed; /* The forced speed (lower bits) in + * Mbps. Please use + * ethtool_cmd_speed()/_set() to + * access it */ + __u8 duplex; /* Duplex, half or full */ + __u8 port; /* Which connector port */ + __u8 phy_address; /* MDIO PHY address (PRTAD for clause 45). + * May be read-only or read-write + * depending on the driver. + */ + __u8 transceiver; /* Which transceiver to use */ + __u8 autoneg; /* Enable or disable autonegotiation */ + __u8 mdio_support; /* MDIO protocols supported. Read-only. + * Not set by all drivers. + */ + __u32 maxtxpkt; /* Tx pkts before generating tx int */ + __u32 maxrxpkt; /* Rx pkts before generating rx int */ + __u16 speed_hi; /* The forced speed (upper + * bits) in Mbps. Please use + * ethtool_cmd_speed()/_set() to + * access it */ + __u8 eth_tp_mdix; /* twisted pair MDI-X status */ + __u8 eth_tp_mdix_ctrl; /* twisted pair MDI-X control, when set, + * link should be renegotiated if necessary + */ + __u32 lp_advertising; /* Features the link partner advertises */ + __u32 reserved[2]; +}; + +static __inline__ void ethtool_cmd_speed_set(struct ethtool_cmd *ep, + __u32 speed) +{ + + ep->speed = (__u16)speed; + ep->speed_hi = (__u16)(speed >> 16); +} + +static __inline__ __u32 ethtool_cmd_speed(const struct ethtool_cmd *ep) +{ + return (ep->speed_hi << 16) | ep->speed; +} + +/* Device supports clause 22 register access to PHY or peripherals + * using the interface defined in . This should not be + * set if there are known to be no such peripherals present or if + * the driver only emulates clause 22 registers for compatibility. + */ +#define ETH_MDIO_SUPPORTS_C22 1 + +/* Device supports clause 45 register access to PHY or peripherals + * using the interface defined in and . + * This should not be set if there are known to be no such peripherals + * present. + */ +#define ETH_MDIO_SUPPORTS_C45 2 + +#define ETHTOOL_FWVERS_LEN 32 +#define ETHTOOL_BUSINFO_LEN 32 +/* these strings are set to whatever the driver author decides... */ +struct ethtool_drvinfo { + __u32 cmd; + char driver[32]; /* driver short name, "tulip", "eepro100" */ + char version[32]; /* driver version string */ + char fw_version[ETHTOOL_FWVERS_LEN]; /* firmware version string */ + char bus_info[ETHTOOL_BUSINFO_LEN]; /* Bus info for this IF. */ + /* For PCI devices, use pci_name(pci_dev). */ + char reserved1[32]; + char reserved2[12]; + /* + * Some struct members below are filled in + * using ops->get_sset_count(). Obtaining + * this info from ethtool_drvinfo is now + * deprecated; Use ETHTOOL_GSSET_INFO + * instead. + */ + __u32 n_priv_flags; /* number of flags valid in ETHTOOL_GPFLAGS */ + __u32 n_stats; /* number of u64's from ETHTOOL_GSTATS */ + __u32 testinfo_len; + __u32 eedump_len; /* Size of data from ETHTOOL_GEEPROM (bytes) */ + __u32 regdump_len; /* Size of data from ETHTOOL_GREGS (bytes) */ +}; + +#define SOPASS_MAX 6 +/* wake-on-lan settings */ +struct ethtool_wolinfo { + __u32 cmd; + __u32 supported; + __u32 wolopts; + __u8 sopass[SOPASS_MAX]; /* SecureOn(tm) password */ +}; + +/* for passing single values */ +struct ethtool_value { + __u32 cmd; + __u32 data; +}; + +/* for passing big chunks of data */ +struct ethtool_regs { + __u32 cmd; + __u32 version; /* driver-specific, indicates different chips/revs */ + __u32 len; /* bytes */ + __u8 data[0]; +}; + +/* for passing EEPROM chunks */ +struct ethtool_eeprom { + __u32 cmd; + __u32 magic; + __u32 offset; /* in bytes */ + __u32 len; /* in bytes */ + __u8 data[0]; +}; + +/** + * struct ethtool_eee - Energy Efficient Ethernet information + * @cmd: ETHTOOL_{G,S}EEE + * @supported: Mask of %SUPPORTED_* flags for the speed/duplex combinations + * for which there is EEE support. + * @advertised: Mask of %ADVERTISED_* flags for the speed/duplex combinations + * advertised as eee capable. + * @lp_advertised: Mask of %ADVERTISED_* flags for the speed/duplex + * combinations advertised by the link partner as eee capable. + * @eee_active: Result of the eee auto negotiation. + * @eee_enabled: EEE configured mode (enabled/disabled). + * @tx_lpi_enabled: Whether the interface should assert its tx lpi, given + * that eee was negotiated. + * @tx_lpi_timer: Time in microseconds the interface delays prior to asserting + * its tx lpi (after reaching 'idle' state). Effective only when eee + * was negotiated and tx_lpi_enabled was set. + */ +struct ethtool_eee { + __u32 cmd; + __u32 supported; + __u32 advertised; + __u32 lp_advertised; + __u32 eee_active; + __u32 eee_enabled; + __u32 tx_lpi_enabled; + __u32 tx_lpi_timer; + __u32 reserved[2]; +}; + +/** + * struct ethtool_modinfo - plugin module eeprom information + * @cmd: %ETHTOOL_GMODULEINFO + * @type: Standard the module information conforms to %ETH_MODULE_SFF_xxxx + * @eeprom_len: Length of the eeprom + * + * This structure is used to return the information to + * properly size memory for a subsequent call to %ETHTOOL_GMODULEEEPROM. + * The type code indicates the eeprom data format + */ +struct ethtool_modinfo { + __u32 cmd; + __u32 type; + __u32 eeprom_len; + __u32 reserved[8]; +}; + +/** + * struct ethtool_coalesce - coalescing parameters for IRQs and stats updates + * @cmd: ETHTOOL_{G,S}COALESCE + * @rx_coalesce_usecs: How many usecs to delay an RX interrupt after + * a packet arrives. + * @rx_max_coalesced_frames: Maximum number of packets to receive + * before an RX interrupt. + * @rx_coalesce_usecs_irq: Same as @rx_coalesce_usecs, except that + * this value applies while an IRQ is being serviced by the host. + * @rx_max_coalesced_frames_irq: Same as @rx_max_coalesced_frames, + * except that this value applies while an IRQ is being serviced + * by the host. + * @tx_coalesce_usecs: How many usecs to delay a TX interrupt after + * a packet is sent. + * @tx_max_coalesced_frames: Maximum number of packets to be sent + * before a TX interrupt. + * @tx_coalesce_usecs_irq: Same as @tx_coalesce_usecs, except that + * this value applies while an IRQ is being serviced by the host. + * @tx_max_coalesced_frames_irq: Same as @tx_max_coalesced_frames, + * except that this value applies while an IRQ is being serviced + * by the host. + * @stats_block_coalesce_usecs: How many usecs to delay in-memory + * statistics block updates. Some drivers do not have an + * in-memory statistic block, and in such cases this value is + * ignored. This value must not be zero. + * @use_adaptive_rx_coalesce: Enable adaptive RX coalescing. + * @use_adaptive_tx_coalesce: Enable adaptive TX coalescing. + * @pkt_rate_low: Threshold for low packet rate (packets per second). + * @rx_coalesce_usecs_low: How many usecs to delay an RX interrupt after + * a packet arrives, when the packet rate is below @pkt_rate_low. + * @rx_max_coalesced_frames_low: Maximum number of packets to be received + * before an RX interrupt, when the packet rate is below @pkt_rate_low. + * @tx_coalesce_usecs_low: How many usecs to delay a TX interrupt after + * a packet is sent, when the packet rate is below @pkt_rate_low. + * @tx_max_coalesced_frames_low: Maximum nuumber of packets to be sent before + * a TX interrupt, when the packet rate is below @pkt_rate_low. + * @pkt_rate_high: Threshold for high packet rate (packets per second). + * @rx_coalesce_usecs_high: How many usecs to delay an RX interrupt after + * a packet arrives, when the packet rate is above @pkt_rate_high. + * @rx_max_coalesced_frames_high: Maximum number of packets to be received + * before an RX interrupt, when the packet rate is above @pkt_rate_high. + * @tx_coalesce_usecs_high: How many usecs to delay a TX interrupt after + * a packet is sent, when the packet rate is above @pkt_rate_high. + * @tx_max_coalesced_frames_high: Maximum number of packets to be sent before + * a TX interrupt, when the packet rate is above @pkt_rate_high. + * @rate_sample_interval: How often to do adaptive coalescing packet rate + * sampling, measured in seconds. Must not be zero. + * + * Each pair of (usecs, max_frames) fields specifies this exit + * condition for interrupt coalescing: + * (usecs > 0 && time_since_first_completion >= usecs) || + * (max_frames > 0 && completed_frames >= max_frames) + * It is illegal to set both usecs and max_frames to zero as this + * would cause interrupts to never be generated. To disable + * coalescing, set usecs = 0 and max_frames = 1. + * + * Some implementations ignore the value of max_frames and use the + * condition: + * time_since_first_completion >= usecs + * This is deprecated. Drivers for hardware that does not support + * counting completions should validate that max_frames == !rx_usecs. + * + * Adaptive RX/TX coalescing is an algorithm implemented by some + * drivers to improve latency under low packet rates and improve + * throughput under high packet rates. Some drivers only implement + * one of RX or TX adaptive coalescing. Anything not implemented by + * the driver causes these values to be silently ignored. + * + * When the packet rate is below @pkt_rate_high but above + * @pkt_rate_low (both measured in packets per second) the + * normal {rx,tx}_* coalescing parameters are used. + */ +struct ethtool_coalesce { + __u32 cmd; + __u32 rx_coalesce_usecs; + __u32 rx_max_coalesced_frames; + __u32 rx_coalesce_usecs_irq; + __u32 rx_max_coalesced_frames_irq; + __u32 tx_coalesce_usecs; + __u32 tx_max_coalesced_frames; + __u32 tx_coalesce_usecs_irq; + __u32 tx_max_coalesced_frames_irq; + __u32 stats_block_coalesce_usecs; + __u32 use_adaptive_rx_coalesce; + __u32 use_adaptive_tx_coalesce; + __u32 pkt_rate_low; + __u32 rx_coalesce_usecs_low; + __u32 rx_max_coalesced_frames_low; + __u32 tx_coalesce_usecs_low; + __u32 tx_max_coalesced_frames_low; + __u32 pkt_rate_high; + __u32 rx_coalesce_usecs_high; + __u32 rx_max_coalesced_frames_high; + __u32 tx_coalesce_usecs_high; + __u32 tx_max_coalesced_frames_high; + __u32 rate_sample_interval; +}; + +/* for configuring RX/TX ring parameters */ +struct ethtool_ringparam { + __u32 cmd; /* ETHTOOL_{G,S}RINGPARAM */ + + /* Read only attributes. These indicate the maximum number + * of pending RX/TX ring entries the driver will allow the + * user to set. + */ + __u32 rx_max_pending; + __u32 rx_mini_max_pending; + __u32 rx_jumbo_max_pending; + __u32 tx_max_pending; + + /* Values changeable by the user. The valid values are + * in the range 1 to the "*_max_pending" counterpart above. + */ + __u32 rx_pending; + __u32 rx_mini_pending; + __u32 rx_jumbo_pending; + __u32 tx_pending; +}; + +/** + * struct ethtool_channels - configuring number of network channel + * @cmd: ETHTOOL_{G,S}CHANNELS + * @max_rx: Read only. Maximum number of receive channel the driver support. + * @max_tx: Read only. Maximum number of transmit channel the driver support. + * @max_other: Read only. Maximum number of other channel the driver support. + * @max_combined: Read only. Maximum number of combined channel the driver + * support. Set of queues RX, TX or other. + * @rx_count: Valid values are in the range 1 to the max_rx. + * @tx_count: Valid values are in the range 1 to the max_tx. + * @other_count: Valid values are in the range 1 to the max_other. + * @combined_count: Valid values are in the range 1 to the max_combined. + * + * This can be used to configure RX, TX and other channels. + */ + +struct ethtool_channels { + __u32 cmd; + __u32 max_rx; + __u32 max_tx; + __u32 max_other; + __u32 max_combined; + __u32 rx_count; + __u32 tx_count; + __u32 other_count; + __u32 combined_count; +}; + +/* for configuring link flow control parameters */ +struct ethtool_pauseparam { + __u32 cmd; /* ETHTOOL_{G,S}PAUSEPARAM */ + + /* If the link is being auto-negotiated (via ethtool_cmd.autoneg + * being true) the user may set 'autoneg' here non-zero to have the + * pause parameters be auto-negotiated too. In such a case, the + * {rx,tx}_pause values below determine what capabilities are + * advertised. + * + * If 'autoneg' is zero or the link is not being auto-negotiated, + * then {rx,tx}_pause force the driver to use/not-use pause + * flow control. + */ + __u32 autoneg; + __u32 rx_pause; + __u32 tx_pause; +}; + +#define ETH_GSTRING_LEN 32 +enum ethtool_stringset { + ETH_SS_TEST = 0, + ETH_SS_STATS, + ETH_SS_PRIV_FLAGS, + ETH_SS_NTUPLE_FILTERS, /* Do not use, GRXNTUPLE is now deprecated */ + ETH_SS_FEATURES, +}; + +/* for passing string sets for data tagging */ +struct ethtool_gstrings { + __u32 cmd; /* ETHTOOL_GSTRINGS */ + __u32 string_set; /* string set id e.c. ETH_SS_TEST, etc*/ + __u32 len; /* number of strings in the string set */ + __u8 data[0]; +}; + +struct ethtool_sset_info { + __u32 cmd; /* ETHTOOL_GSSET_INFO */ + __u32 reserved; + __u64 sset_mask; /* input: each bit selects an sset to query */ + /* output: each bit a returned sset */ + __u32 data[0]; /* ETH_SS_xxx count, in order, based on bits + in sset_mask. One bit implies one + __u32, two bits implies two + __u32's, etc. */ +}; + +/** + * enum ethtool_test_flags - flags definition of ethtool_test + * @ETH_TEST_FL_OFFLINE: if set perform online and offline tests, otherwise + * only online tests. + * @ETH_TEST_FL_FAILED: Driver set this flag if test fails. + * @ETH_TEST_FL_EXTERNAL_LB: Application request to perform external loopback + * test. + * @ETH_TEST_FL_EXTERNAL_LB_DONE: Driver performed the external loopback test + */ + +enum ethtool_test_flags { + ETH_TEST_FL_OFFLINE = (1 << 0), + ETH_TEST_FL_FAILED = (1 << 1), + ETH_TEST_FL_EXTERNAL_LB = (1 << 2), + ETH_TEST_FL_EXTERNAL_LB_DONE = (1 << 3), +}; + +/* for requesting NIC test and getting results*/ +struct ethtool_test { + __u32 cmd; /* ETHTOOL_TEST */ + __u32 flags; /* ETH_TEST_FL_xxx */ + __u32 reserved; + __u32 len; /* result length, in number of u64 elements */ + __u64 data[0]; +}; + +/* for dumping NIC-specific statistics */ +struct ethtool_stats { + __u32 cmd; /* ETHTOOL_GSTATS */ + __u32 n_stats; /* number of u64's being returned */ + __u64 data[0]; +}; + +struct ethtool_perm_addr { + __u32 cmd; /* ETHTOOL_GPERMADDR */ + __u32 size; + __u8 data[0]; +}; + +/* boolean flags controlling per-interface behavior characteristics. + * When reading, the flag indicates whether or not a certain behavior + * is enabled/present. When writing, the flag indicates whether + * or not the driver should turn on (set) or off (clear) a behavior. + * + * Some behaviors may read-only (unconditionally absent or present). + * If such is the case, return EINVAL in the set-flags operation if the + * flag differs from the read-only value. + */ +enum ethtool_flags { + ETH_FLAG_TXVLAN = (1 << 7), /* TX VLAN offload enabled */ + ETH_FLAG_RXVLAN = (1 << 8), /* RX VLAN offload enabled */ + ETH_FLAG_LRO = (1 << 15), /* LRO is enabled */ + ETH_FLAG_NTUPLE = (1 << 27), /* N-tuple filters enabled */ + ETH_FLAG_RXHASH = (1 << 28), +}; + +/* The following structures are for supporting RX network flow + * classification and RX n-tuple configuration. Note, all multibyte + * fields, e.g., ip4src, ip4dst, psrc, pdst, spi, etc. are expected to + * be in network byte order. + */ + +/** + * struct ethtool_tcpip4_spec - flow specification for TCP/IPv4 etc. + * @ip4src: Source host + * @ip4dst: Destination host + * @psrc: Source port + * @pdst: Destination port + * @tos: Type-of-service + * + * This can be used to specify a TCP/IPv4, UDP/IPv4 or SCTP/IPv4 flow. + */ +struct ethtool_tcpip4_spec { + __be32 ip4src; + __be32 ip4dst; + __be16 psrc; + __be16 pdst; + __u8 tos; +}; + +/** + * struct ethtool_ah_espip4_spec - flow specification for IPsec/IPv4 + * @ip4src: Source host + * @ip4dst: Destination host + * @spi: Security parameters index + * @tos: Type-of-service + * + * This can be used to specify an IPsec transport or tunnel over IPv4. + */ +struct ethtool_ah_espip4_spec { + __be32 ip4src; + __be32 ip4dst; + __be32 spi; + __u8 tos; +}; + +#define ETH_RX_NFC_IP4 1 + +/** + * struct ethtool_usrip4_spec - general flow specification for IPv4 + * @ip4src: Source host + * @ip4dst: Destination host + * @l4_4_bytes: First 4 bytes of transport (layer 4) header + * @tos: Type-of-service + * @ip_ver: Value must be %ETH_RX_NFC_IP4; mask must be 0 + * @proto: Transport protocol number; mask must be 0 + */ +struct ethtool_usrip4_spec { + __be32 ip4src; + __be32 ip4dst; + __be32 l4_4_bytes; + __u8 tos; + __u8 ip_ver; + __u8 proto; +}; + +union ethtool_flow_union { + struct ethtool_tcpip4_spec tcp_ip4_spec; + struct ethtool_tcpip4_spec udp_ip4_spec; + struct ethtool_tcpip4_spec sctp_ip4_spec; + struct ethtool_ah_espip4_spec ah_ip4_spec; + struct ethtool_ah_espip4_spec esp_ip4_spec; + struct ethtool_usrip4_spec usr_ip4_spec; + struct ethhdr ether_spec; + __u8 hdata[52]; +}; + +/** + * struct ethtool_flow_ext - additional RX flow fields + * @h_dest: destination MAC address + * @vlan_etype: VLAN EtherType + * @vlan_tci: VLAN tag control information + * @data: user defined data + * + * Note, @vlan_etype, @vlan_tci, and @data are only valid if %FLOW_EXT + * is set in &struct ethtool_rx_flow_spec @flow_type. + * @h_dest is valid if %FLOW_MAC_EXT is set. + */ +struct ethtool_flow_ext { + __u8 padding[2]; + unsigned char h_dest[ETH_ALEN]; + __be16 vlan_etype; + __be16 vlan_tci; + __be32 data[2]; +}; + +/** + * struct ethtool_rx_flow_spec - classification rule for RX flows + * @flow_type: Type of match to perform, e.g. %TCP_V4_FLOW + * @h_u: Flow fields to match (dependent on @flow_type) + * @h_ext: Additional fields to match + * @m_u: Masks for flow field bits to be matched + * @m_ext: Masks for additional field bits to be matched + * Note, all additional fields must be ignored unless @flow_type + * includes the %FLOW_EXT or %FLOW_MAC_EXT flag + * (see &struct ethtool_flow_ext description). + * @ring_cookie: RX ring/queue index to deliver to, or %RX_CLS_FLOW_DISC + * if packets should be discarded + * @location: Location of rule in the table. Locations must be + * numbered such that a flow matching multiple rules will be + * classified according to the first (lowest numbered) rule. + */ +struct ethtool_rx_flow_spec { + __u32 flow_type; + union ethtool_flow_union h_u; + struct ethtool_flow_ext h_ext; + union ethtool_flow_union m_u; + struct ethtool_flow_ext m_ext; + __u64 ring_cookie; + __u32 location; +}; + +/** + * struct ethtool_rxnfc - command to get or set RX flow classification rules + * @cmd: Specific command number - %ETHTOOL_GRXFH, %ETHTOOL_SRXFH, + * %ETHTOOL_GRXRINGS, %ETHTOOL_GRXCLSRLCNT, %ETHTOOL_GRXCLSRULE, + * %ETHTOOL_GRXCLSRLALL, %ETHTOOL_SRXCLSRLDEL or %ETHTOOL_SRXCLSRLINS + * @flow_type: Type of flow to be affected, e.g. %TCP_V4_FLOW + * @data: Command-dependent value + * @fs: Flow classification rule + * @rule_cnt: Number of rules to be affected + * @rule_locs: Array of used rule locations + * + * For %ETHTOOL_GRXFH and %ETHTOOL_SRXFH, @data is a bitmask indicating + * the fields included in the flow hash, e.g. %RXH_IP_SRC. The following + * structure fields must not be used. + * + * For %ETHTOOL_GRXRINGS, @data is set to the number of RX rings/queues + * on return. + * + * For %ETHTOOL_GRXCLSRLCNT, @rule_cnt is set to the number of defined + * rules on return. If @data is non-zero on return then it is the + * size of the rule table, plus the flag %RX_CLS_LOC_SPECIAL if the + * driver supports any special location values. If that flag is not + * set in @data then special location values should not be used. + * + * For %ETHTOOL_GRXCLSRULE, @fs.@location specifies the location of an + * existing rule on entry and @fs contains the rule on return. + * + * For %ETHTOOL_GRXCLSRLALL, @rule_cnt specifies the array size of the + * user buffer for @rule_locs on entry. On return, @data is the size + * of the rule table, @rule_cnt is the number of defined rules, and + * @rule_locs contains the locations of the defined rules. Drivers + * must use the second parameter to get_rxnfc() instead of @rule_locs. + * + * For %ETHTOOL_SRXCLSRLINS, @fs specifies the rule to add or update. + * @fs.@location either specifies the location to use or is a special + * location value with %RX_CLS_LOC_SPECIAL flag set. On return, + * @fs.@location is the actual rule location. + * + * For %ETHTOOL_SRXCLSRLDEL, @fs.@location specifies the location of an + * existing rule on entry. + * + * A driver supporting the special location values for + * %ETHTOOL_SRXCLSRLINS may add the rule at any suitable unused + * location, and may remove a rule at a later location (lower + * priority) that matches exactly the same set of flows. The special + * values are: %RX_CLS_LOC_ANY, selecting any location; + * %RX_CLS_LOC_FIRST, selecting the first suitable location (maximum + * priority); and %RX_CLS_LOC_LAST, selecting the last suitable + * location (minimum priority). Additional special values may be + * defined in future and drivers must return -%EINVAL for any + * unrecognised value. + */ +struct ethtool_rxnfc { + __u32 cmd; + __u32 flow_type; + __u64 data; + struct ethtool_rx_flow_spec fs; + __u32 rule_cnt; + __u32 rule_locs[0]; +}; + + +/** + * struct ethtool_rxfh_indir - command to get or set RX flow hash indirection + * @cmd: Specific command number - %ETHTOOL_GRXFHINDIR or %ETHTOOL_SRXFHINDIR + * @size: On entry, the array size of the user buffer, which may be zero. + * On return from %ETHTOOL_GRXFHINDIR, the array size of the hardware + * indirection table. + * @ring_index: RX ring/queue index for each hash value + * + * For %ETHTOOL_GRXFHINDIR, a @size of zero means that only the size + * should be returned. For %ETHTOOL_SRXFHINDIR, a @size of zero means + * the table should be reset to default values. This last feature + * is not supported by the original implementations. + */ +struct ethtool_rxfh_indir { + __u32 cmd; + __u32 size; + __u32 ring_index[0]; +}; + +/** + * struct ethtool_rx_ntuple_flow_spec - specification for RX flow filter + * @flow_type: Type of match to perform, e.g. %TCP_V4_FLOW + * @h_u: Flow field values to match (dependent on @flow_type) + * @m_u: Masks for flow field value bits to be ignored + * @vlan_tag: VLAN tag to match + * @vlan_tag_mask: Mask for VLAN tag bits to be ignored + * @data: Driver-dependent data to match + * @data_mask: Mask for driver-dependent data bits to be ignored + * @action: RX ring/queue index to deliver to (non-negative) or other action + * (negative, e.g. %ETHTOOL_RXNTUPLE_ACTION_DROP) + * + * For flow types %TCP_V4_FLOW, %UDP_V4_FLOW and %SCTP_V4_FLOW, where + * a field value and mask are both zero this is treated as if all mask + * bits are set i.e. the field is ignored. + */ +struct ethtool_rx_ntuple_flow_spec { + __u32 flow_type; + union { + struct ethtool_tcpip4_spec tcp_ip4_spec; + struct ethtool_tcpip4_spec udp_ip4_spec; + struct ethtool_tcpip4_spec sctp_ip4_spec; + struct ethtool_ah_espip4_spec ah_ip4_spec; + struct ethtool_ah_espip4_spec esp_ip4_spec; + struct ethtool_usrip4_spec usr_ip4_spec; + struct ethhdr ether_spec; + __u8 hdata[72]; + } h_u, m_u; + + __u16 vlan_tag; + __u16 vlan_tag_mask; + __u64 data; + __u64 data_mask; + + __s32 action; +#define ETHTOOL_RXNTUPLE_ACTION_DROP (-1) /* drop packet */ +#define ETHTOOL_RXNTUPLE_ACTION_CLEAR (-2) /* clear filter */ +}; + +/** + * struct ethtool_rx_ntuple - command to set or clear RX flow filter + * @cmd: Command number - %ETHTOOL_SRXNTUPLE + * @fs: Flow filter specification + */ +struct ethtool_rx_ntuple { + __u32 cmd; + struct ethtool_rx_ntuple_flow_spec fs; +}; + +#define ETHTOOL_FLASH_MAX_FILENAME 128 +enum ethtool_flash_op_type { + ETHTOOL_FLASH_ALL_REGIONS = 0, +}; + +/* for passing firmware flashing related parameters */ +struct ethtool_flash { + __u32 cmd; + __u32 region; + char data[ETHTOOL_FLASH_MAX_FILENAME]; +}; + +/** + * struct ethtool_dump - used for retrieving, setting device dump + * @cmd: Command number - %ETHTOOL_GET_DUMP_FLAG, %ETHTOOL_GET_DUMP_DATA, or + * %ETHTOOL_SET_DUMP + * @version: FW version of the dump, filled in by driver + * @flag: driver dependent flag for dump setting, filled in by driver during + * get and filled in by ethtool for set operation. + * flag must be initialized by macro ETH_FW_DUMP_DISABLE value when + * firmware dump is disabled. + * @len: length of dump data, used as the length of the user buffer on entry to + * %ETHTOOL_GET_DUMP_DATA and this is returned as dump length by driver + * for %ETHTOOL_GET_DUMP_FLAG command + * @data: data collected for get dump data operation + */ + +#define ETH_FW_DUMP_DISABLE 0 + +struct ethtool_dump { + __u32 cmd; + __u32 version; + __u32 flag; + __u32 len; + __u8 data[0]; +}; + +/* for returning and changing feature sets */ + +/** + * struct ethtool_get_features_block - block with state of 32 features + * @available: mask of changeable features + * @requested: mask of features requested to be enabled if possible + * @active: mask of currently enabled features + * @never_changed: mask of features not changeable for any device + */ +struct ethtool_get_features_block { + __u32 available; + __u32 requested; + __u32 active; + __u32 never_changed; +}; + +/** + * struct ethtool_gfeatures - command to get state of device's features + * @cmd: command number = %ETHTOOL_GFEATURES + * @size: in: number of elements in the features[] array; + * out: number of elements in features[] needed to hold all features + * @features: state of features + */ +struct ethtool_gfeatures { + __u32 cmd; + __u32 size; + struct ethtool_get_features_block features[0]; +}; + +/** + * struct ethtool_set_features_block - block with request for 32 features + * @valid: mask of features to be changed + * @requested: values of features to be changed + */ +struct ethtool_set_features_block { + __u32 valid; + __u32 requested; +}; + +/** + * struct ethtool_sfeatures - command to request change in device's features + * @cmd: command number = %ETHTOOL_SFEATURES + * @size: array size of the features[] array + * @features: feature change masks + */ +struct ethtool_sfeatures { + __u32 cmd; + __u32 size; + struct ethtool_set_features_block features[0]; +}; + +/** + * struct ethtool_ts_info - holds a device's timestamping and PHC association + * @cmd: command number = %ETHTOOL_GET_TS_INFO + * @so_timestamping: bit mask of the sum of the supported SO_TIMESTAMPING flags + * @phc_index: device index of the associated PHC, or -1 if there is none + * @tx_types: bit mask of the supported hwtstamp_tx_types enumeration values + * @rx_filters: bit mask of the supported hwtstamp_rx_filters enumeration values + * + * The bits in the 'tx_types' and 'rx_filters' fields correspond to + * the 'hwtstamp_tx_types' and 'hwtstamp_rx_filters' enumeration values, + * respectively. For example, if the device supports HWTSTAMP_TX_ON, + * then (1 << HWTSTAMP_TX_ON) in 'tx_types' will be set. + */ +struct ethtool_ts_info { + __u32 cmd; + __u32 so_timestamping; + __s32 phc_index; + __u32 tx_types; + __u32 tx_reserved[3]; + __u32 rx_filters; + __u32 rx_reserved[3]; +}; + +/* + * %ETHTOOL_SFEATURES changes features present in features[].valid to the + * values of corresponding bits in features[].requested. Bits in .requested + * not set in .valid or not changeable are ignored. + * + * Returns %EINVAL when .valid contains undefined or never-changeable bits + * or size is not equal to required number of features words (32-bit blocks). + * Returns >= 0 if request was completed; bits set in the value mean: + * %ETHTOOL_F_UNSUPPORTED - there were bits set in .valid that are not + * changeable (not present in %ETHTOOL_GFEATURES' features[].available) + * those bits were ignored. + * %ETHTOOL_F_WISH - some or all changes requested were recorded but the + * resulting state of bits masked by .valid is not equal to .requested. + * Probably there are other device-specific constraints on some features + * in the set. When %ETHTOOL_F_UNSUPPORTED is set, .valid is considered + * here as though ignored bits were cleared. + * %ETHTOOL_F_COMPAT - some or all changes requested were made by calling + * compatibility functions. Requested offload state cannot be properly + * managed by kernel. + * + * Meaning of bits in the masks are obtained by %ETHTOOL_GSSET_INFO (number of + * bits in the arrays - always multiple of 32) and %ETHTOOL_GSTRINGS commands + * for ETH_SS_FEATURES string set. First entry in the table corresponds to least + * significant bit in features[0] fields. Empty strings mark undefined features. + */ +enum ethtool_sfeatures_retval_bits { + ETHTOOL_F_UNSUPPORTED__BIT, + ETHTOOL_F_WISH__BIT, + ETHTOOL_F_COMPAT__BIT, +}; + +#define ETHTOOL_F_UNSUPPORTED (1 << ETHTOOL_F_UNSUPPORTED__BIT) +#define ETHTOOL_F_WISH (1 << ETHTOOL_F_WISH__BIT) +#define ETHTOOL_F_COMPAT (1 << ETHTOOL_F_COMPAT__BIT) + + +/* CMDs currently supported */ +#define ETHTOOL_GSET 0x00000001 /* Get settings. */ +#define ETHTOOL_SSET 0x00000002 /* Set settings. */ +#define ETHTOOL_GDRVINFO 0x00000003 /* Get driver info. */ +#define ETHTOOL_GREGS 0x00000004 /* Get NIC registers. */ +#define ETHTOOL_GWOL 0x00000005 /* Get wake-on-lan options. */ +#define ETHTOOL_SWOL 0x00000006 /* Set wake-on-lan options. */ +#define ETHTOOL_GMSGLVL 0x00000007 /* Get driver message level */ +#define ETHTOOL_SMSGLVL 0x00000008 /* Set driver msg level. */ +#define ETHTOOL_NWAY_RST 0x00000009 /* Restart autonegotiation. */ +/* Get link status for host, i.e. whether the interface *and* the + * physical port (if there is one) are up (ethtool_value). */ +#define ETHTOOL_GLINK 0x0000000a +#define ETHTOOL_GEEPROM 0x0000000b /* Get EEPROM data */ +#define ETHTOOL_SEEPROM 0x0000000c /* Set EEPROM data. */ +#define ETHTOOL_GCOALESCE 0x0000000e /* Get coalesce config */ +#define ETHTOOL_SCOALESCE 0x0000000f /* Set coalesce config. */ +#define ETHTOOL_GRINGPARAM 0x00000010 /* Get ring parameters */ +#define ETHTOOL_SRINGPARAM 0x00000011 /* Set ring parameters. */ +#define ETHTOOL_GPAUSEPARAM 0x00000012 /* Get pause parameters */ +#define ETHTOOL_SPAUSEPARAM 0x00000013 /* Set pause parameters. */ +#define ETHTOOL_GRXCSUM 0x00000014 /* Get RX hw csum enable (ethtool_value) */ +#define ETHTOOL_SRXCSUM 0x00000015 /* Set RX hw csum enable (ethtool_value) */ +#define ETHTOOL_GTXCSUM 0x00000016 /* Get TX hw csum enable (ethtool_value) */ +#define ETHTOOL_STXCSUM 0x00000017 /* Set TX hw csum enable (ethtool_value) */ +#define ETHTOOL_GSG 0x00000018 /* Get scatter-gather enable + * (ethtool_value) */ +#define ETHTOOL_SSG 0x00000019 /* Set scatter-gather enable + * (ethtool_value). */ +#define ETHTOOL_TEST 0x0000001a /* execute NIC self-test. */ +#define ETHTOOL_GSTRINGS 0x0000001b /* get specified string set */ +#define ETHTOOL_PHYS_ID 0x0000001c /* identify the NIC */ +#define ETHTOOL_GSTATS 0x0000001d /* get NIC-specific statistics */ +#define ETHTOOL_GTSO 0x0000001e /* Get TSO enable (ethtool_value) */ +#define ETHTOOL_STSO 0x0000001f /* Set TSO enable (ethtool_value) */ +#define ETHTOOL_GPERMADDR 0x00000020 /* Get permanent hardware address */ +#define ETHTOOL_GUFO 0x00000021 /* Get UFO enable (ethtool_value) */ +#define ETHTOOL_SUFO 0x00000022 /* Set UFO enable (ethtool_value) */ +#define ETHTOOL_GGSO 0x00000023 /* Get GSO enable (ethtool_value) */ +#define ETHTOOL_SGSO 0x00000024 /* Set GSO enable (ethtool_value) */ +#define ETHTOOL_GFLAGS 0x00000025 /* Get flags bitmap(ethtool_value) */ +#define ETHTOOL_SFLAGS 0x00000026 /* Set flags bitmap(ethtool_value) */ +#define ETHTOOL_GPFLAGS 0x00000027 /* Get driver-private flags bitmap */ +#define ETHTOOL_SPFLAGS 0x00000028 /* Set driver-private flags bitmap */ + +#define ETHTOOL_GRXFH 0x00000029 /* Get RX flow hash configuration */ +#define ETHTOOL_SRXFH 0x0000002a /* Set RX flow hash configuration */ +#define ETHTOOL_GGRO 0x0000002b /* Get GRO enable (ethtool_value) */ +#define ETHTOOL_SGRO 0x0000002c /* Set GRO enable (ethtool_value) */ +#define ETHTOOL_GRXRINGS 0x0000002d /* Get RX rings available for LB */ +#define ETHTOOL_GRXCLSRLCNT 0x0000002e /* Get RX class rule count */ +#define ETHTOOL_GRXCLSRULE 0x0000002f /* Get RX classification rule */ +#define ETHTOOL_GRXCLSRLALL 0x00000030 /* Get all RX classification rule */ +#define ETHTOOL_SRXCLSRLDEL 0x00000031 /* Delete RX classification rule */ +#define ETHTOOL_SRXCLSRLINS 0x00000032 /* Insert RX classification rule */ +#define ETHTOOL_FLASHDEV 0x00000033 /* Flash firmware to device */ +#define ETHTOOL_RESET 0x00000034 /* Reset hardware */ +#define ETHTOOL_SRXNTUPLE 0x00000035 /* Add an n-tuple filter to device */ +#define ETHTOOL_GRXNTUPLE 0x00000036 /* deprecated */ +#define ETHTOOL_GSSET_INFO 0x00000037 /* Get string set info */ +#define ETHTOOL_GRXFHINDIR 0x00000038 /* Get RX flow hash indir'n table */ +#define ETHTOOL_SRXFHINDIR 0x00000039 /* Set RX flow hash indir'n table */ + +#define ETHTOOL_GFEATURES 0x0000003a /* Get device offload settings */ +#define ETHTOOL_SFEATURES 0x0000003b /* Change device offload settings */ +#define ETHTOOL_GCHANNELS 0x0000003c /* Get no of channels */ +#define ETHTOOL_SCHANNELS 0x0000003d /* Set no of channels */ +#define ETHTOOL_SET_DUMP 0x0000003e /* Set dump settings */ +#define ETHTOOL_GET_DUMP_FLAG 0x0000003f /* Get dump settings */ +#define ETHTOOL_GET_DUMP_DATA 0x00000040 /* Get dump data */ +#define ETHTOOL_GET_TS_INFO 0x00000041 /* Get time stamping and PHC info */ +#define ETHTOOL_GMODULEINFO 0x00000042 /* Get plug-in module information */ +#define ETHTOOL_GMODULEEEPROM 0x00000043 /* Get plug-in module eeprom */ +#define ETHTOOL_GEEE 0x00000044 /* Get EEE settings */ +#define ETHTOOL_SEEE 0x00000045 /* Set EEE settings */ + +/* compatibility with older code */ +#define SPARC_ETH_GSET ETHTOOL_GSET +#define SPARC_ETH_SSET ETHTOOL_SSET + +/* Indicates what features are supported by the interface. */ +#define SUPPORTED_10baseT_Half (1 << 0) +#define SUPPORTED_10baseT_Full (1 << 1) +#define SUPPORTED_100baseT_Half (1 << 2) +#define SUPPORTED_100baseT_Full (1 << 3) +#define SUPPORTED_1000baseT_Half (1 << 4) +#define SUPPORTED_1000baseT_Full (1 << 5) +#define SUPPORTED_Autoneg (1 << 6) +#define SUPPORTED_TP (1 << 7) +#define SUPPORTED_AUI (1 << 8) +#define SUPPORTED_MII (1 << 9) +#define SUPPORTED_FIBRE (1 << 10) +#define SUPPORTED_BNC (1 << 11) +#define SUPPORTED_10000baseT_Full (1 << 12) +#define SUPPORTED_Pause (1 << 13) +#define SUPPORTED_Asym_Pause (1 << 14) +#define SUPPORTED_2500baseX_Full (1 << 15) +#define SUPPORTED_Backplane (1 << 16) +#define SUPPORTED_1000baseKX_Full (1 << 17) +#define SUPPORTED_10000baseKX4_Full (1 << 18) +#define SUPPORTED_10000baseKR_Full (1 << 19) +#define SUPPORTED_10000baseR_FEC (1 << 20) +#define SUPPORTED_20000baseMLD2_Full (1 << 21) +#define SUPPORTED_20000baseKR2_Full (1 << 22) +#define SUPPORTED_40000baseKR4_Full (1 << 23) +#define SUPPORTED_40000baseCR4_Full (1 << 24) +#define SUPPORTED_40000baseSR4_Full (1 << 25) +#define SUPPORTED_40000baseLR4_Full (1 << 26) + +/* Indicates what features are advertised by the interface. */ +#define ADVERTISED_10baseT_Half (1 << 0) +#define ADVERTISED_10baseT_Full (1 << 1) +#define ADVERTISED_100baseT_Half (1 << 2) +#define ADVERTISED_100baseT_Full (1 << 3) +#define ADVERTISED_1000baseT_Half (1 << 4) +#define ADVERTISED_1000baseT_Full (1 << 5) +#define ADVERTISED_Autoneg (1 << 6) +#define ADVERTISED_TP (1 << 7) +#define ADVERTISED_AUI (1 << 8) +#define ADVERTISED_MII (1 << 9) +#define ADVERTISED_FIBRE (1 << 10) +#define ADVERTISED_BNC (1 << 11) +#define ADVERTISED_10000baseT_Full (1 << 12) +#define ADVERTISED_Pause (1 << 13) +#define ADVERTISED_Asym_Pause (1 << 14) +#define ADVERTISED_2500baseX_Full (1 << 15) +#define ADVERTISED_Backplane (1 << 16) +#define ADVERTISED_1000baseKX_Full (1 << 17) +#define ADVERTISED_10000baseKX4_Full (1 << 18) +#define ADVERTISED_10000baseKR_Full (1 << 19) +#define ADVERTISED_10000baseR_FEC (1 << 20) +#define ADVERTISED_20000baseMLD2_Full (1 << 21) +#define ADVERTISED_20000baseKR2_Full (1 << 22) +#define ADVERTISED_40000baseKR4_Full (1 << 23) +#define ADVERTISED_40000baseCR4_Full (1 << 24) +#define ADVERTISED_40000baseSR4_Full (1 << 25) +#define ADVERTISED_40000baseLR4_Full (1 << 26) + +/* The following are all involved in forcing a particular link + * mode for the device for setting things. When getting the + * devices settings, these indicate the current mode and whether + * it was forced up into this mode or autonegotiated. + */ + +/* The forced speed, 10Mb, 100Mb, gigabit, 2.5Gb, 10GbE. */ +#define SPEED_10 10 +#define SPEED_100 100 +#define SPEED_1000 1000 +#define SPEED_2500 2500 +#define SPEED_10000 10000 +#define SPEED_UNKNOWN -1 + +/* Duplex, half or full. */ +#define DUPLEX_HALF 0x00 +#define DUPLEX_FULL 0x01 +#define DUPLEX_UNKNOWN 0xff + +/* Which connector port. */ +#define PORT_TP 0x00 +#define PORT_AUI 0x01 +#define PORT_MII 0x02 +#define PORT_FIBRE 0x03 +#define PORT_BNC 0x04 +#define PORT_DA 0x05 +#define PORT_NONE 0xef +#define PORT_OTHER 0xff + +/* Which transceiver to use. */ +#define XCVR_INTERNAL 0x00 +#define XCVR_EXTERNAL 0x01 +#define XCVR_DUMMY1 0x02 +#define XCVR_DUMMY2 0x03 +#define XCVR_DUMMY3 0x04 + +/* Enable or disable autonegotiation. If this is set to enable, + * the forced link modes above are completely ignored. + */ +#define AUTONEG_DISABLE 0x00 +#define AUTONEG_ENABLE 0x01 + +/* MDI or MDI-X status/control - if MDI/MDI_X/AUTO is set then + * the driver is required to renegotiate link + */ +#define ETH_TP_MDI_INVALID 0x00 /* status: unknown; control: unsupported */ +#define ETH_TP_MDI 0x01 /* status: MDI; control: force MDI */ +#define ETH_TP_MDI_X 0x02 /* status: MDI-X; control: force MDI-X */ +#define ETH_TP_MDI_AUTO 0x03 /* control: auto-select */ + +/* Wake-On-Lan options. */ +#define WAKE_PHY (1 << 0) +#define WAKE_UCAST (1 << 1) +#define WAKE_MCAST (1 << 2) +#define WAKE_BCAST (1 << 3) +#define WAKE_ARP (1 << 4) +#define WAKE_MAGIC (1 << 5) +#define WAKE_MAGICSECURE (1 << 6) /* only meaningful if WAKE_MAGIC */ + +/* L2-L4 network traffic flow types */ +#define TCP_V4_FLOW 0x01 /* hash or spec (tcp_ip4_spec) */ +#define UDP_V4_FLOW 0x02 /* hash or spec (udp_ip4_spec) */ +#define SCTP_V4_FLOW 0x03 /* hash or spec (sctp_ip4_spec) */ +#define AH_ESP_V4_FLOW 0x04 /* hash only */ +#define TCP_V6_FLOW 0x05 /* hash only */ +#define UDP_V6_FLOW 0x06 /* hash only */ +#define SCTP_V6_FLOW 0x07 /* hash only */ +#define AH_ESP_V6_FLOW 0x08 /* hash only */ +#define AH_V4_FLOW 0x09 /* hash or spec (ah_ip4_spec) */ +#define ESP_V4_FLOW 0x0a /* hash or spec (esp_ip4_spec) */ +#define AH_V6_FLOW 0x0b /* hash only */ +#define ESP_V6_FLOW 0x0c /* hash only */ +#define IP_USER_FLOW 0x0d /* spec only (usr_ip4_spec) */ +#define IPV4_FLOW 0x10 /* hash only */ +#define IPV6_FLOW 0x11 /* hash only */ +#define ETHER_FLOW 0x12 /* spec only (ether_spec) */ +/* Flag to enable additional fields in struct ethtool_rx_flow_spec */ +#define FLOW_EXT 0x80000000 +#define FLOW_MAC_EXT 0x40000000 + +/* L3-L4 network traffic flow hash options */ +#define RXH_L2DA (1 << 1) +#define RXH_VLAN (1 << 2) +#define RXH_L3_PROTO (1 << 3) +#define RXH_IP_SRC (1 << 4) +#define RXH_IP_DST (1 << 5) +#define RXH_L4_B_0_1 (1 << 6) /* src port in case of TCP/UDP/SCTP */ +#define RXH_L4_B_2_3 (1 << 7) /* dst port in case of TCP/UDP/SCTP */ +#define RXH_DISCARD (1 << 31) + +#define RX_CLS_FLOW_DISC 0xffffffffffffffffULL + +/* Special RX classification rule insert location values */ +#define RX_CLS_LOC_SPECIAL 0x80000000 /* flag */ +#define RX_CLS_LOC_ANY 0xffffffff +#define RX_CLS_LOC_FIRST 0xfffffffe +#define RX_CLS_LOC_LAST 0xfffffffd + +/* EEPROM Standards for plug in modules */ +#define ETH_MODULE_SFF_8079 0x1 +#define ETH_MODULE_SFF_8079_LEN 256 +#define ETH_MODULE_SFF_8472 0x2 +#define ETH_MODULE_SFF_8472_LEN 512 + +/* Reset flags */ +/* The reset() operation must clear the flags for the components which + * were actually reset. On successful return, the flags indicate the + * components which were not reset, either because they do not exist + * in the hardware or because they cannot be reset independently. The + * driver must never reset any components that were not requested. + */ +enum ethtool_reset_flags { + /* These flags represent components dedicated to the interface + * the command is addressed to. Shift any flag left by + * ETH_RESET_SHARED_SHIFT to reset a shared component of the + * same type. + */ + ETH_RESET_MGMT = 1 << 0, /* Management processor */ + ETH_RESET_IRQ = 1 << 1, /* Interrupt requester */ + ETH_RESET_DMA = 1 << 2, /* DMA engine */ + ETH_RESET_FILTER = 1 << 3, /* Filtering/flow direction */ + ETH_RESET_OFFLOAD = 1 << 4, /* Protocol offload */ + ETH_RESET_MAC = 1 << 5, /* Media access controller */ + ETH_RESET_PHY = 1 << 6, /* Transceiver/PHY */ + ETH_RESET_RAM = 1 << 7, /* RAM shared between + * multiple components */ + + ETH_RESET_DEDICATED = 0x0000ffff, /* All components dedicated to + * this interface */ + ETH_RESET_ALL = 0xffffffff, /* All components used by this + * interface, even if shared */ +}; +#define ETH_RESET_SHARED_SHIFT 16 + +#endif /* _LINUX_ETHTOOL_H */ diff --git a/root/package/link4all/quectel-CM/quectel1.6.0.15_src/libmnl/README b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/libmnl/README new file mode 100644 index 00000000..fbac9d2a --- /dev/null +++ b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/libmnl/README @@ -0,0 +1,28 @@ += What is libmnl? = + +libmnl is a minimalistic user-space library oriented to Netlink developers. +There are a lot of common tasks in parsing, validating, constructing of +both the Netlink header and TLVs that are repetitive and easy to get wrong. +This library aims to provide simple helpers that allows you to re-use code +and to avoid re-inventing the wheel. The main features of this library are: + +* Small: the shared library requires around 30KB for an x86-based computer. +* Simple: this library avoids complexity and elaborated abstractions that +tend to hide Netlink details. +* Easy to use: the library simplifies the work for Netlink-wise developers. +It provides functions to make socket handling, message building, validating, +parsing and sequence tracking, easier. +* Easy to re-use: you can use the library to build your own abstraction layer +on top of this library. +* Decoupling: the interdependency of the main bricks that compose the library +is reduced, i.e. the library provides many helpers, but the programmer is not +forced to use them. + += Example files = + +You can find several example files under examples/ that you can compile by +invoking `make check'. + +-- +08/sep/2010 +Pablo Neira Ayuso diff --git a/root/package/link4all/quectel-CM/quectel1.6.0.15_src/libmnl/attr.c b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/libmnl/attr.c new file mode 100644 index 00000000..30eb537b --- /dev/null +++ b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/libmnl/attr.c @@ -0,0 +1,722 @@ +/* + * (C) 2008-2012 by Pablo Neira Ayuso + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + */ +#include /* for INT_MAX */ +#include +#include + +#include "libmnl.h" + +/** + * \defgroup attr Netlink attribute helpers + * + * Netlink Type-Length-Value (TLV) attribute: + * \verbatim + |<-- 2 bytes -->|<-- 2 bytes -->|<-- variable -->| + ------------------------------------------------- + | length | type | value | + ------------------------------------------------- + |<--------- header ------------>|<-- payload --->| +\endverbatim + * The payload of the Netlink message contains sequences of attributes that are + * expressed in TLV format. + * + * @{ + */ + +/** + * mnl_attr_get_type - get type of netlink attribute + * \param attr pointer to netlink attribute + * + * This function returns the attribute type. + */ +uint16_t mnl_attr_get_type(const struct nlattr *attr) +{ + return attr->nla_type & NLA_TYPE_MASK; +} + +/** + * mnl_attr_get_len - get length of netlink attribute + * \param attr pointer to netlink attribute + * + * This function returns the attribute length that is the attribute header + * plus the attribute payload. + */ +uint16_t mnl_attr_get_len(const struct nlattr *attr) +{ + return attr->nla_len; +} + +/** + * mnl_attr_get_payload_len - get the attribute payload-value length + * \param attr pointer to netlink attribute + * + * This function returns the attribute payload-value length. + */ +uint16_t mnl_attr_get_payload_len(const struct nlattr *attr) +{ + return attr->nla_len - MNL_ATTR_HDRLEN; +} + +/** + * mnl_attr_get_payload - get pointer to the attribute payload + * \param attr pointer to netlink attribute + * + * This function return a pointer to the attribute payload. + */ +void *mnl_attr_get_payload(const struct nlattr *attr) +{ + return (void *)attr + MNL_ATTR_HDRLEN; +} + +/** + * mnl_attr_ok - check if there is room for an attribute in a buffer + * \param attr attribute that we want to check if there is room for + * \param len remaining bytes in a buffer that contains the attribute + * + * This function is used to check that a buffer, which is supposed to contain + * an attribute, has enough room for the attribute that it stores, i.e. this + * function can be used to verify that an attribute is neither malformed nor + * truncated. + * + * This function does not set errno in case of error since it is intended + * for iterations. Thus, it returns true on success and false on error. + * + * The len parameter may be negative in the case of malformed messages during + * attribute iteration, that is why we use a signed integer. + */ +bool mnl_attr_ok(const struct nlattr *attr, int len) +{ + return len >= (int)sizeof(struct nlattr) && + attr->nla_len >= sizeof(struct nlattr) && + (int)attr->nla_len <= len; +} + +/** + * mnl_attr_next - get the next attribute in the payload of a netlink message + * \param attr pointer to the current attribute + * + * This function returns a pointer to the next attribute after the one passed + * as parameter. You have to use mnl_attr_ok() to ensure that the next + * attribute is valid. + */ +struct nlattr *mnl_attr_next(const struct nlattr *attr) +{ + return (struct nlattr *)((void *)attr + MNL_ALIGN(attr->nla_len)); +} + +/** + * mnl_attr_type_valid - check if the attribute type is valid + * \param attr pointer to attribute to be checked + * \param max maximum attribute type + * + * This function allows to check if the attribute type is higher than the + * maximum supported type. If the attribute type is invalid, this function + * returns -1 and errno is explicitly set. On success, this function returns 1. + * + * Strict attribute checking in user-space is not a good idea since you may + * run an old application with a newer kernel that supports new attributes. + * This leads to backward compatibility breakages in user-space. Better check + * if you support an attribute, if not, skip it. + */ +int mnl_attr_type_valid(const struct nlattr *attr, uint16_t max) +{ + if (mnl_attr_get_type(attr) > max) { + errno = EOPNOTSUPP; + return -1; + } + return 1; +} + +static int __mnl_attr_validate(const struct nlattr *attr, + enum mnl_attr_data_type type, size_t exp_len) +{ + uint16_t attr_len = mnl_attr_get_payload_len(attr); + const char *attr_data = mnl_attr_get_payload(attr); + + if (attr_len < exp_len) { + errno = ERANGE; + return -1; + } + switch(type) { + case MNL_TYPE_FLAG: + if (attr_len > 0) { + errno = ERANGE; + return -1; + } + break; + case MNL_TYPE_NUL_STRING: + if (attr_len == 0) { + errno = ERANGE; + return -1; + } + if (attr_data[attr_len-1] != '\0') { + errno = EINVAL; + return -1; + } + break; + case MNL_TYPE_STRING: + if (attr_len == 0) { + errno = ERANGE; + return -1; + } + break; + case MNL_TYPE_NESTED: + /* empty nested attributes are OK. */ + if (attr_len == 0) + break; + /* if not empty, they must contain one header, eg. flag */ + if (attr_len < MNL_ATTR_HDRLEN) { + errno = ERANGE; + return -1; + } + break; + default: + /* make gcc happy. */ + break; + } + if (exp_len && attr_len > exp_len) { + errno = ERANGE; + return -1; + } + return 0; +} + +static const size_t mnl_attr_data_type_len[MNL_TYPE_MAX] = { + [MNL_TYPE_U8] = sizeof(uint8_t), + [MNL_TYPE_U16] = sizeof(uint16_t), + [MNL_TYPE_U32] = sizeof(uint32_t), + [MNL_TYPE_U64] = sizeof(uint64_t), + [MNL_TYPE_MSECS] = sizeof(uint64_t), +}; + +/** + * mnl_attr_validate - validate netlink attribute (simplified version) + * \param attr pointer to netlink attribute that we want to validate + * \param type data type (see enum mnl_attr_data_type) + * + * The validation is based on the data type. Specifically, it checks that + * integers (u8, u16, u32 and u64) have enough room for them. This function + * returns -1 in case of error, and errno is explicitly set. + */ +int mnl_attr_validate(const struct nlattr *attr, enum mnl_attr_data_type type) +{ + int exp_len; + + if (type >= MNL_TYPE_MAX) { + errno = EINVAL; + return -1; + } + exp_len = mnl_attr_data_type_len[type]; + return __mnl_attr_validate(attr, type, exp_len); +} + +/** + * mnl_attr_validate2 - validate netlink attribute (extended version) + * \param attr pointer to netlink attribute that we want to validate + * \param type attribute type (see enum mnl_attr_data_type) + * \param exp_len expected attribute data size + * + * This function allows to perform a more accurate validation for attributes + * whose size is variable. If the size of the attribute is not what we expect, + * this functions returns -1 and errno is explicitly set. + */ +int mnl_attr_validate2(const struct nlattr *attr, + enum mnl_attr_data_type type, + size_t exp_len) +{ + if (type >= MNL_TYPE_MAX) { + errno = EINVAL; + return -1; + } + return __mnl_attr_validate(attr, type, exp_len); +} + +/** + * mnl_attr_parse - parse attributes + * \param nlh pointer to netlink message + * \param offset offset to start parsing from (if payload is after any header) + * \param cb callback function that is called for each attribute + * \param data pointer to data that is passed to the callback function + * + * This function allows to iterate over the sequence of attributes that compose + * the Netlink message. You can then put the attribute in an array as it + * usually happens at this stage or you can use any other data structure (such + * as lists or trees). + * + * This function propagates the return value of the callback, which can be + * MNL_CB_ERROR, MNL_CB_OK or MNL_CB_STOP. + */ +int mnl_attr_parse(const struct nlmsghdr *nlh, + unsigned int offset, mnl_attr_cb_t cb, + void *data) +{ + int ret = MNL_CB_OK; + const struct nlattr *attr; + + mnl_attr_for_each(attr, nlh, offset) + if ((ret = cb(attr, data)) <= MNL_CB_STOP) + return ret; + return ret; +} + +/** + * mnl_attr_parse_nested - parse attributes inside a nest + * \param nested pointer to netlink attribute that contains a nest + * \param cb callback function that is called for each attribute in the nest + * \param data pointer to data passed to the callback function + * + * This function allows to iterate over the sequence of attributes that compose + * the Netlink message. You can then put the attribute in an array as it + * usually happens at this stage or you can use any other data structure (such + * as lists or trees). + * + * This function propagates the return value of the callback, which can be + * MNL_CB_ERROR, MNL_CB_OK or MNL_CB_STOP. + */ +int mnl_attr_parse_nested(const struct nlattr *nested, + mnl_attr_cb_t cb, void *data) +{ + int ret = MNL_CB_OK; + const struct nlattr *attr; + + mnl_attr_for_each_nested(attr, nested) + if ((ret = cb(attr, data)) <= MNL_CB_STOP) + return ret; + return ret; +} + +/** + * mnl_attr_parse_payload - parse attributes in payload of Netlink message + * \param payload pointer to payload of the Netlink message + * \param payload_len payload length that contains the attributes + * \param cb callback function that is called for each attribute + * \param data pointer to data that is passed to the callback function + * + * This function takes a pointer to the area that contains the attributes, + * commonly known as the payload of the Netlink message. Thus, you have to + * pass a pointer to the Netlink message payload, instead of the entire + * message. + * + * This function allows you to iterate over the sequence of attributes that are + * located at some payload offset. You can then put the attributes in one array + * as usual, or you can use any other data structure (such as lists or trees). + * + * This function propagates the return value of the callback, which can be + * MNL_CB_ERROR, MNL_CB_OK or MNL_CB_STOP. + */ +int mnl_attr_parse_payload(const void *payload, + size_t payload_len, + mnl_attr_cb_t cb, void *data) +{ + int ret = MNL_CB_OK; + const struct nlattr *attr; + + mnl_attr_for_each_payload(payload, payload_len) + if ((ret = cb(attr, data)) <= MNL_CB_STOP) + return ret; + return ret; +} + +/** + * mnl_attr_get_u8 - returns 8-bit unsigned integer attribute payload + * \param attr pointer to netlink attribute + * + * This function returns the 8-bit value of the attribute payload. + */ +uint8_t mnl_attr_get_u8(const struct nlattr *attr) +{ + return *((uint8_t *)mnl_attr_get_payload(attr)); +} + +/** + * mnl_attr_get_u16 - returns 16-bit unsigned integer attribute payload + * \param attr pointer to netlink attribute + * + * This function returns the 16-bit value of the attribute payload. + */ +uint16_t mnl_attr_get_u16(const struct nlattr *attr) +{ + return *((uint16_t *)mnl_attr_get_payload(attr)); +} + +/** + * mnl_attr_get_u32 - returns 32-bit unsigned integer attribute payload + * \param attr pointer to netlink attribute + * + * This function returns the 32-bit value of the attribute payload. + */ +uint32_t mnl_attr_get_u32(const struct nlattr *attr) +{ + return *((uint32_t *)mnl_attr_get_payload(attr)); +} + +/** + * mnl_attr_get_u64 - returns 64-bit unsigned integer attribute. + * \param attr pointer to netlink attribute + * + * This function returns the 64-bit value of the attribute payload. This + * function is align-safe, since accessing 64-bit Netlink attributes is a + * common source of alignment issues. + */ +uint64_t mnl_attr_get_u64(const struct nlattr *attr) +{ + uint64_t tmp; + memcpy(&tmp, mnl_attr_get_payload(attr), sizeof(tmp)); + return tmp; +} + +/** + * mnl_attr_get_str - returns pointer to string attribute. + * \param attr pointer to netlink attribute + * + * This function returns the payload of string attribute value. + */ +const char *mnl_attr_get_str(const struct nlattr *attr) +{ + return mnl_attr_get_payload(attr); +} + +/** + * mnl_attr_put - add an attribute to netlink message + * \param nlh pointer to the netlink message + * \param type netlink attribute type that you want to add + * \param len netlink attribute payload length + * \param data pointer to the data that will be stored by the new attribute + * + * This function updates the length field of the Netlink message (nlmsg_len) + * by adding the size (header + payload) of the new attribute. + */ +void mnl_attr_put(struct nlmsghdr *nlh, uint16_t type, + size_t len, const void *data) +{ + struct nlattr *attr = mnl_nlmsg_get_payload_tail(nlh); + uint16_t payload_len = MNL_ALIGN(sizeof(struct nlattr)) + len; + int pad; + + attr->nla_type = type; + attr->nla_len = payload_len; + memcpy(mnl_attr_get_payload(attr), data, len); + pad = MNL_ALIGN(len) - len; + if (pad > 0) + memset(mnl_attr_get_payload(attr) + len, 0, pad); + + nlh->nlmsg_len += MNL_ALIGN(payload_len); +} + +/** + * mnl_attr_put_u8 - add 8-bit unsigned integer attribute to netlink message + * \param nlh pointer to the netlink message + * \param type netlink attribute type + * \param data 8-bit unsigned integer data that is stored by the new attribute + * + * This function updates the length field of the Netlink message (nlmsg_len) + * by adding the size (header + payload) of the new attribute. + */ +void mnl_attr_put_u8(struct nlmsghdr *nlh, uint16_t type, + uint8_t data) +{ + mnl_attr_put(nlh, type, sizeof(uint8_t), &data); +} + +/** + * mnl_attr_put_u16 - add 16-bit unsigned integer attribute to netlink message + * \param nlh pointer to the netlink message + * \param type netlink attribute type + * \param data 16-bit unsigned integer data that is stored by the new attribute + * + * This function updates the length field of the Netlink message (nlmsg_len) + * by adding the size (header + payload) of the new attribute. + */ +void mnl_attr_put_u16(struct nlmsghdr *nlh, uint16_t type, + uint16_t data) +{ + mnl_attr_put(nlh, type, sizeof(uint16_t), &data); +} + +/** + * mnl_attr_put_u32 - add 32-bit unsigned integer attribute to netlink message + * \param nlh pointer to the netlink message + * \param type netlink attribute type + * \param data 32-bit unsigned integer data that is stored by the new attribute + * + * This function updates the length field of the Netlink message (nlmsg_len) + * by adding the size (header + payload) of the new attribute. + */ +void mnl_attr_put_u32(struct nlmsghdr *nlh, uint16_t type, + uint32_t data) +{ + mnl_attr_put(nlh, type, sizeof(uint32_t), &data); +} + +/** + * mnl_attr_put_u64 - add 64-bit unsigned integer attribute to netlink message + * \param nlh pointer to the netlink message + * \param type netlink attribute type + * \param data 64-bit unsigned integer data that is stored by the new attribute + * + * This function updates the length field of the Netlink message (nlmsg_len) + * by adding the size (header + payload) of the new attribute. + */ +void mnl_attr_put_u64(struct nlmsghdr *nlh, uint16_t type, + uint64_t data) +{ + mnl_attr_put(nlh, type, sizeof(uint64_t), &data); +} + +/** + * mnl_attr_put_str - add string attribute to netlink message + * \param nlh pointer to the netlink message + * \param type netlink attribute type + * \param data pointer to string data that is stored by the new attribute + * + * This function updates the length field of the Netlink message (nlmsg_len) + * by adding the size (header + payload) of the new attribute. + */ +void mnl_attr_put_str(struct nlmsghdr *nlh, uint16_t type, + const char *data) +{ + mnl_attr_put(nlh, type, strlen(data), data); +} + +/** + * mnl_attr_put_strz - add string attribute to netlink message + * \param nlh pointer to the netlink message + * \param type netlink attribute type + * \param data pointer to string data that is stored by the new attribute + * + * This function is similar to mnl_attr_put_str, but it includes the + * NUL/zero ('\0') terminator at the end of the string. + * + * This function updates the length field of the Netlink message (nlmsg_len) + * by adding the size (header + payload) of the new attribute. + */ +void mnl_attr_put_strz(struct nlmsghdr *nlh, uint16_t type, + const char *data) +{ + mnl_attr_put(nlh, type, strlen(data)+1, data); +} + +/** + * mnl_attr_nest_start - start an attribute nest + * \param nlh pointer to the netlink message + * \param type netlink attribute type + * + * This function adds the attribute header that identifies the beginning of + * an attribute nest. This function always returns a valid pointer to the + * beginning of the nest. + */ +struct nlattr *mnl_attr_nest_start(struct nlmsghdr *nlh, + uint16_t type) +{ + struct nlattr *start = mnl_nlmsg_get_payload_tail(nlh); + + /* set start->nla_len in mnl_attr_nest_end() */ + start->nla_type = NLA_F_NESTED | type; + nlh->nlmsg_len += MNL_ALIGN(sizeof(struct nlattr)); + + return start; +} + +/** + * mnl_attr_put_check - add an attribute to netlink message + * \param nlh pointer to the netlink message + * \param buflen size of buffer which stores the message + * \param type netlink attribute type that you want to add + * \param len netlink attribute payload length + * \param data pointer to the data that will be stored by the new attribute + * + * This function first checks that the data can be added to the message + * (fits into the buffer) and then updates the length field of the Netlink + * message (nlmsg_len) by adding the size (header + payload) of the new + * attribute. The function returns true if the attribute could be added + * to the message, otherwise false is returned. + */ +bool mnl_attr_put_check(struct nlmsghdr *nlh, size_t buflen, + uint16_t type, size_t len, + const void *data) +{ + if (nlh->nlmsg_len + MNL_ATTR_HDRLEN + MNL_ALIGN(len) > buflen) + return false; + mnl_attr_put(nlh, type, len, data); + return true; +} + +/** + * mnl_attr_put_u8_check - add 8-bit unsigned int attribute to netlink message + * \param nlh pointer to the netlink message + * \param buflen size of buffer which stores the message + * \param type netlink attribute type + * \param data 8-bit unsigned integer data that is stored by the new attribute + * + * This function first checks that the data can be added to the message + * (fits into the buffer) and then updates the length field of the Netlink + * message (nlmsg_len) by adding the size (header + payload) of the new + * attribute. The function returns true if the attribute could be added + * to the message, otherwise false is returned. + */ +bool mnl_attr_put_u8_check(struct nlmsghdr *nlh, size_t buflen, + uint16_t type, uint8_t data) +{ + return mnl_attr_put_check(nlh, buflen, type, sizeof(uint8_t), &data); +} + +/** + * mnl_attr_put_u16_check - add 16-bit unsigned int attribute to netlink message + * \param nlh pointer to the netlink message + * \param buflen size of buffer which stores the message + * \param type netlink attribute type + * \param data 16-bit unsigned integer data that is stored by the new attribute + * + * This function first checks that the data can be added to the message + * (fits into the buffer) and then updates the length field of the Netlink + * message (nlmsg_len) by adding the size (header + payload) of the new + * attribute. The function returns true if the attribute could be added + * to the message, otherwise false is returned. + * This function updates the length field of the Netlink message (nlmsg_len) + * by adding the size (header + payload) of the new attribute. + */ +bool mnl_attr_put_u16_check(struct nlmsghdr *nlh, size_t buflen, + uint16_t type, uint16_t data) +{ + return mnl_attr_put_check(nlh, buflen, type, sizeof(uint16_t), &data); +} + +/** + * mnl_attr_put_u32_check - add 32-bit unsigned int attribute to netlink message + * \param nlh pointer to the netlink message + * \param buflen size of buffer which stores the message + * \param type netlink attribute type + * \param data 32-bit unsigned integer data that is stored by the new attribute + * + * This function first checks that the data can be added to the message + * (fits into the buffer) and then updates the length field of the Netlink + * message (nlmsg_len) by adding the size (header + payload) of the new + * attribute. The function returns true if the attribute could be added + * to the message, otherwise false is returned. + * This function updates the length field of the Netlink message (nlmsg_len) + * by adding the size (header + payload) of the new attribute. + */ +bool mnl_attr_put_u32_check(struct nlmsghdr *nlh, size_t buflen, + uint16_t type, uint32_t data) +{ + return mnl_attr_put_check(nlh, buflen, type, sizeof(uint32_t), &data); +} + +/** + * mnl_attr_put_u64_check - add 64-bit unsigned int attribute to netlink message + * \param nlh pointer to the netlink message + * \param buflen size of buffer which stores the message + * \param type netlink attribute type + * \param data 64-bit unsigned integer data that is stored by the new attribute + * + * This function first checks that the data can be added to the message + * (fits into the buffer) and then updates the length field of the Netlink + * message (nlmsg_len) by adding the size (header + payload) of the new + * attribute. The function returns true if the attribute could be added + * to the message, otherwise false is returned. + * This function updates the length field of the Netlink message (nlmsg_len) + * by adding the size (header + payload) of the new attribute. + */ +bool mnl_attr_put_u64_check(struct nlmsghdr *nlh, size_t buflen, + uint16_t type, uint64_t data) +{ + return mnl_attr_put_check(nlh, buflen, type, sizeof(uint64_t), &data); +} + +/** + * mnl_attr_put_str_check - add string attribute to netlink message + * \param nlh pointer to the netlink message + * \param buflen size of buffer which stores the message + * \param type netlink attribute type + * \param data pointer to string data that is stored by the new attribute + * + * This function first checks that the data can be added to the message + * (fits into the buffer) and then updates the length field of the Netlink + * message (nlmsg_len) by adding the size (header + payload) of the new + * attribute. The function returns true if the attribute could be added + * to the message, otherwise false is returned. + * This function updates the length field of the Netlink message (nlmsg_len) + * by adding the size (header + payload) of the new attribute. + */ +bool mnl_attr_put_str_check(struct nlmsghdr *nlh, size_t buflen, + uint16_t type, const char *data) +{ + return mnl_attr_put_check(nlh, buflen, type, strlen(data), data); +} + +/** + * mnl_attr_put_strz_check - add string attribute to netlink message + * \param nlh pointer to the netlink message + * \param buflen size of buffer which stores the message + * \param type netlink attribute type + * \param data pointer to string data that is stored by the new attribute + * + * This function is similar to mnl_attr_put_str, but it includes the + * NUL/zero ('\0') terminator at the end of the string. + * + * This function first checks that the data can be added to the message + * (fits into the buffer) and then updates the length field of the Netlink + * message (nlmsg_len) by adding the size (header + payload) of the new + * attribute. The function returns true if the attribute could be added + * to the message, otherwise false is returned. + */ +bool mnl_attr_put_strz_check(struct nlmsghdr *nlh, size_t buflen, + uint16_t type, const char *data) +{ + return mnl_attr_put_check(nlh, buflen, type, strlen(data)+1, data); +} + +/** + * mnl_attr_nest_start_check - start an attribute nest + * \param buflen size of buffer which stores the message + * \param nlh pointer to the netlink message + * \param type netlink attribute type + * + * This function adds the attribute header that identifies the beginning of + * an attribute nest. If the nested attribute cannot be added then NULL, + * otherwise valid pointer to the beginning of the nest is returned. + */ +struct nlattr *mnl_attr_nest_start_check(struct nlmsghdr *nlh, + size_t buflen, + uint16_t type) +{ + if (nlh->nlmsg_len + MNL_ATTR_HDRLEN > buflen) + return NULL; + return mnl_attr_nest_start(nlh, type); +} + +/** + * mnl_attr_nest_end - end an attribute nest + * \param nlh pointer to the netlink message + * \param start pointer to the attribute nest returned by mnl_attr_nest_start() + * + * This function updates the attribute header that identifies the nest. + */ +void mnl_attr_nest_end(struct nlmsghdr *nlh, + struct nlattr *start) +{ + start->nla_len = mnl_nlmsg_get_payload_tail(nlh) - (void *)start; +} + +/** + * mnl_attr_nest_cancel - cancel an attribute nest + * \param nlh pointer to the netlink message + * \param start pointer to the attribute nest returned by mnl_attr_nest_start() + * + * This function updates the attribute header that identifies the nest. + */ +void mnl_attr_nest_cancel(struct nlmsghdr *nlh, + struct nlattr *start) +{ + nlh->nlmsg_len -= mnl_nlmsg_get_payload_tail(nlh) - (void *)start; +} + +/** + * @} + */ diff --git a/root/package/link4all/quectel-CM/quectel1.6.0.15_src/libmnl/callback.c b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/libmnl/callback.c new file mode 100644 index 00000000..8283a93b --- /dev/null +++ b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/libmnl/callback.c @@ -0,0 +1,167 @@ +/* + * (C) 2008-2010 by Pablo Neira Ayuso + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + */ + +#include + +#include "libmnl.h" + +static int mnl_cb_noop(const struct nlmsghdr *nlh, void *data) +{ + return MNL_CB_OK; +} + +static int mnl_cb_error(const struct nlmsghdr *nlh, void *data) +{ + const struct nlmsgerr *err = mnl_nlmsg_get_payload(nlh); + + if (nlh->nlmsg_len < mnl_nlmsg_size(sizeof(struct nlmsgerr))) { + errno = EBADMSG; + return MNL_CB_ERROR; + } + /* Netlink subsystems returns the errno value with different signess */ + if (err->error < 0) + errno = -err->error; + else + errno = err->error; + + return err->error == 0 ? MNL_CB_STOP : MNL_CB_ERROR; +} + +static int mnl_cb_stop(const struct nlmsghdr *nlh, void *data) +{ + return MNL_CB_STOP; +} + +static const mnl_cb_t default_cb_array[NLMSG_MIN_TYPE] = { + [NLMSG_NOOP] = mnl_cb_noop, + [NLMSG_ERROR] = mnl_cb_error, + [NLMSG_DONE] = mnl_cb_stop, + [NLMSG_OVERRUN] = mnl_cb_noop, +}; + +static inline int __mnl_cb_run(const void *buf, size_t numbytes, + unsigned int seq, unsigned int portid, + mnl_cb_t cb_data, void *data, + const mnl_cb_t *cb_ctl_array, + unsigned int cb_ctl_array_len) +{ + int ret = MNL_CB_OK, len = numbytes; + const struct nlmsghdr *nlh = buf; + + while (mnl_nlmsg_ok(nlh, len)) { + /* check message source */ + if (!mnl_nlmsg_portid_ok(nlh, portid)) { + errno = ESRCH; + return -1; + } + /* perform sequence tracking */ + if (!mnl_nlmsg_seq_ok(nlh, seq)) { + errno = EPROTO; + return -1; + } + + /* dump was interrupted */ + if (nlh->nlmsg_flags & NLM_F_DUMP_INTR) { + errno = EINTR; + return -1; + } + + /* netlink data message handling */ + if (nlh->nlmsg_type >= NLMSG_MIN_TYPE) { + if (cb_data){ + ret = cb_data(nlh, data); + if (ret <= MNL_CB_STOP) + goto out; + } + } else if (nlh->nlmsg_type < cb_ctl_array_len) { + if (cb_ctl_array && cb_ctl_array[nlh->nlmsg_type]) { + ret = cb_ctl_array[nlh->nlmsg_type](nlh, data); + if (ret <= MNL_CB_STOP) + goto out; + } + } else if (default_cb_array[nlh->nlmsg_type]) { + ret = default_cb_array[nlh->nlmsg_type](nlh, data); + if (ret <= MNL_CB_STOP) + goto out; + } + nlh = mnl_nlmsg_next(nlh, &len); + } +out: + return ret; +} + +/** + * \defgroup callback Callback helpers + * @{ + */ + +/** + * mnl_cb_run2 - callback runqueue for netlink messages + * \param buf buffer that contains the netlink messages + * \param numbytes number of bytes stored in the buffer + * \param seq sequence number that we expect to receive + * \param portid Netlink PortID that we expect to receive + * \param cb_data callback handler for data messages + * \param data pointer to data that will be passed to the data callback handler + * \param cb_ctl_array array of custom callback handlers from control messages + * \param cb_ctl_array_len array length of custom control callback handlers + * + * You can set the cb_ctl_array to NULL if you want to use the default control + * callback handlers, in that case, the parameter cb_ctl_array_len is not + * checked. + * + * Your callback may return three possible values: + * - MNL_CB_ERROR (<=-1): an error has occurred. Stop callback runqueue. + * - MNL_CB_STOP (=0): stop callback runqueue. + * - MNL_CB_OK (>=1): no problem has occurred. + * + * This function propagates the callback return value. On error, it returns + * -1 and errno is explicitly set. If the portID is not the expected, errno + * is set to ESRCH. If the sequence number is not the expected, errno is set + * to EPROTO. If the dump was interrupted, errno is set to EINTR and you should + * request a new fresh dump again. + */ +int mnl_cb_run2(const void *buf, size_t numbytes, + unsigned int seq, unsigned int portid, + mnl_cb_t cb_data, void *data, + const mnl_cb_t *cb_ctl_array, + unsigned int cb_ctl_array_len) +{ + return __mnl_cb_run(buf, numbytes, seq, portid, cb_data, data, + cb_ctl_array, cb_ctl_array_len); +} + +/** + * mnl_cb_run - callback runqueue for netlink messages (simplified version) + * \param buf buffer that contains the netlink messages + * \param numbytes number of bytes stored in the buffer + * \param seq sequence number that we expect to receive + * \param portid Netlink PortID that we expect to receive + * \param cb_data callback handler for data messages + * \param data pointer to data that will be passed to the data callback handler + * + * This function is like mnl_cb_run2() but it does not allow you to set + * the control callback handlers. + * + * Your callback may return three possible values: + * - MNL_CB_ERROR (<=-1): an error has occurred. Stop callback runqueue. + * - MNL_CB_STOP (=0): stop callback runqueue. + * - MNL_CB_OK (>=1): no problems has occurred. + * + * This function propagates the callback return value. + */ +int mnl_cb_run(const void *buf, size_t numbytes, unsigned int seq, + unsigned int portid, mnl_cb_t cb_data, void *data) +{ + return __mnl_cb_run(buf, numbytes, seq, portid, cb_data, data, NULL, 0); +} + +/** + * @} + */ diff --git a/root/package/link4all/quectel-CM/quectel1.6.0.15_src/libmnl/dhcp/dhcp.h b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/libmnl/dhcp/dhcp.h new file mode 100644 index 00000000..f4802855 --- /dev/null +++ b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/libmnl/dhcp/dhcp.h @@ -0,0 +1,5 @@ +#ifndef __DHCP_H__ +#define __DHCP_H__ + +int do_dhcp(char *iname); +#endif //__DHCP_H__ \ No newline at end of file diff --git a/root/package/link4all/quectel-CM/quectel1.6.0.15_src/libmnl/dhcp/dhcpclient.c b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/libmnl/dhcp/dhcpclient.c new file mode 100644 index 00000000..ccb71b5d --- /dev/null +++ b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/libmnl/dhcp/dhcpclient.c @@ -0,0 +1,515 @@ +/* + * Copyright 2008, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../ifutils.h" +#include "dhcpmsg.h" +#include "packet.h" + +#define VERBOSE 2 + +static int verbose = 1; +static char errmsg[2048]; + +typedef unsigned long long msecs_t; +#if VERBOSE +void dump_dhcp_msg(); +#endif + +msecs_t get_msecs(void) +{ + struct timespec ts; + + if (clock_gettime(CLOCK_MONOTONIC, &ts)) { + return 0; + } else { + return (((msecs_t) ts.tv_sec) * ((msecs_t) 1000)) + + (((msecs_t) ts.tv_nsec) / ((msecs_t) 1000000)); + } +} + +void printerr(char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vsnprintf(errmsg, sizeof(errmsg), fmt, ap); + va_end(ap); + + printf("%s\n", errmsg); +} + +const char *dhcp_lasterror() +{ + return errmsg; +} + +int fatal(const char *reason) +{ + printerr("%s: %s\n", reason, strerror(errno)); + return -1; +// exit(1); +} + +typedef struct dhcp_info dhcp_info; + +struct dhcp_info { + uint32_t type; + + uint32_t ipaddr; + uint32_t gateway; + uint32_t prefixLength; + + uint32_t dns1; + uint32_t dns2; + + uint32_t serveraddr; + uint32_t lease; +}; + +dhcp_info last_good_info; + +void get_dhcp_info(uint32_t *ipaddr, uint32_t *gateway, uint32_t *prefixLength, + uint32_t *dns1, uint32_t *dns2, uint32_t *server, + uint32_t *lease) +{ + *ipaddr = last_good_info.ipaddr; + *gateway = last_good_info.gateway; + *prefixLength = last_good_info.prefixLength; + *dns1 = last_good_info.dns1; + *dns2 = last_good_info.dns2; + *server = last_good_info.serveraddr; + *lease = last_good_info.lease; +} + +static int dhcp_configure(const char *ifname, dhcp_info *info) +{ + last_good_info = *info; + return if_set_network_v4(ifname, info->ipaddr, info->prefixLength, info->gateway, + info->dns1, info->dns2); +} + +static const char *dhcp_type_to_name(uint32_t type) +{ + switch(type) { + case DHCPDISCOVER: return "discover"; + case DHCPOFFER: return "offer"; + case DHCPREQUEST: return "request"; + case DHCPDECLINE: return "decline"; + case DHCPACK: return "ack"; + case DHCPNAK: return "nak"; + case DHCPRELEASE: return "release"; + case DHCPINFORM: return "inform"; + default: return "???"; + } +} + +void dump_dhcp_info(dhcp_info *info) +{ + char addr[20], gway[20]; + printf("--- dhcp %s (%d) ---\n", + dhcp_type_to_name(info->type), info->type); + strcpy(addr, ipaddr_to_string_v4(info->ipaddr)); + strcpy(gway, ipaddr_to_string_v4(info->gateway)); + printf("ip %s gw %s prefixLength %d\n", addr, gway, info->prefixLength); + if (info->dns1) printf("dns1: %s\n", ipaddr_to_string_v4(info->dns1)); + if (info->dns2) printf("dns2: %s\n", ipaddr_to_string_v4(info->dns2)); + printf("server %s, lease %d seconds\n", + ipaddr_to_string_v4(info->serveraddr), info->lease); +} + + +int decode_dhcp_msg(dhcp_msg *msg, int len, dhcp_info *info) +{ + uint8_t *x; + unsigned int opt; + int optlen; + + memset(info, 0, sizeof(dhcp_info)); + if (len < (DHCP_MSG_FIXED_SIZE + 4)) return -1; + + len -= (DHCP_MSG_FIXED_SIZE + 4); + + if (msg->options[0] != OPT_COOKIE1) return -1; + if (msg->options[1] != OPT_COOKIE2) return -1; + if (msg->options[2] != OPT_COOKIE3) return -1; + if (msg->options[3] != OPT_COOKIE4) return -1; + + x = msg->options + 4; + + while (len > 2) { + opt = *x++; + if (opt == OPT_PAD) { + len--; + continue; + } + if (opt == OPT_END) { + break; + } + optlen = *x++; + len -= 2; + if (optlen > len) { + break; + } + switch(opt) { + case OPT_SUBNET_MASK: + if (optlen >= 4) { + in_addr_t mask; + memcpy(&mask, x, 4); + info->prefixLength = mask_to_prefix_v4(mask); + } + break; + case OPT_GATEWAY: + if (optlen >= 4) memcpy(&info->gateway, x, 4); + break; + case OPT_DNS: + if (optlen >= 4) memcpy(&info->dns1, x + 0, 4); + if (optlen >= 8) memcpy(&info->dns2, x + 4, 4); + break; + case OPT_LEASE_TIME: + if (optlen >= 4) { + memcpy(&info->lease, x, 4); + info->lease = ntohl(info->lease); + } + break; + case OPT_SERVER_ID: + if (optlen >= 4) memcpy(&info->serveraddr, x, 4); + break; + case OPT_MESSAGE_TYPE: + info->type = *x; + break; + default: + break; + } + x += optlen; + len -= optlen; + } + + info->ipaddr = msg->yiaddr; + + return 0; +} + +#if VERBOSE + +static void hex2str(char *buf, size_t buf_size, const unsigned char *array, int len) +{ + int i; + char *cp = buf; + char *buf_end = buf + buf_size; + for (i = 0; i < len; i++) { + cp += snprintf(cp, buf_end - cp, " %02x ", array[i]); + } +} + +void dump_dhcp_msg(dhcp_msg *msg, int len) +{ + unsigned char *x; + unsigned int n,c; + int optsz; + const char *name; + char buf[2048]; + + if (len < DHCP_MSG_FIXED_SIZE) { + printf("Invalid length %d, should be %d", len, DHCP_MSG_FIXED_SIZE); + return; + } + + len -= DHCP_MSG_FIXED_SIZE; + + if (msg->op == OP_BOOTREQUEST) + name = "BOOTREQUEST"; + else if (msg->op == OP_BOOTREPLY) + name = "BOOTREPLY"; + else + name = "????"; + + c = msg->hlen > 16 ? 16 : msg->hlen; + hex2str(buf, sizeof(buf), msg->chaddr, c); + + for (n = 0; n < 64; n++) { + unsigned char x = msg->sname[n]; + if ((x < ' ') || (x > 127)) { + if (x == 0) break; + msg->sname[n] = '.'; + } + } + msg->sname[63] = 0; + + for (n = 0; n < 128; n++) { + unsigned char x = msg->file[n]; + if ((x < ' ') || (x > 127)) { + if (x == 0) break; + msg->file[n] = '.'; + } + } + msg->file[127] = 0; + + if (len < 4) return; + len -= 4; + x = msg->options + 4; + + while (len > 2) { + if (*x == 0) { + x++; + len--; + continue; + } + if (*x == OPT_END) { + break; + } + len -= 2; + optsz = x[1]; + if (optsz > len) break; + if (x[0] == OPT_DOMAIN_NAME || x[0] == OPT_MESSAGE) { + if ((unsigned int)optsz < sizeof(buf) - 1) { + n = optsz; + } else { + n = sizeof(buf) - 1; + } + memcpy(buf, &x[2], n); + buf[n] = '\0'; + } else { + hex2str(buf, sizeof(buf), &x[2], optsz); + } + if (x[0] == OPT_MESSAGE_TYPE) + name = dhcp_type_to_name(x[2]); + else + name = NULL; + len -= optsz; + x = x + optsz + 2; + } +} + +#endif + +static int send_message(int sock, int if_index, dhcp_msg *msg, int size) +{ +#if VERBOSE > 1 + dump_dhcp_msg(msg, size); +#endif + return send_packet(sock, if_index, msg, size, INADDR_ANY, INADDR_BROADCAST, + PORT_BOOTP_CLIENT, PORT_BOOTP_SERVER); +} + +static int is_valid_reply(dhcp_msg *msg, dhcp_msg *reply, int sz) +{ + if (sz < DHCP_MSG_FIXED_SIZE) { + if (verbose) printf("Wrong size %d != %d\n", sz, DHCP_MSG_FIXED_SIZE); + return 0; + } + if (reply->op != OP_BOOTREPLY) { + if (verbose) printf("Wrong Op %d != %d\n", reply->op, OP_BOOTREPLY); + return 0; + } + if (reply->xid != msg->xid) { + if (verbose) printf("Wrong Xid 0x%x != 0x%x\n", ntohl(reply->xid), + ntohl(msg->xid)); + return 0; + } + if (reply->htype != msg->htype) { + if (verbose) printf("Wrong Htype %d != %d\n", reply->htype, msg->htype); + return 0; + } + if (reply->hlen != msg->hlen) { + if (verbose) printf("Wrong Hlen %d != %d\n", reply->hlen, msg->hlen); + return 0; + } + if (memcmp(msg->chaddr, reply->chaddr, msg->hlen)) { + if (verbose) printf("Wrong chaddr %x != %x\n", *(reply->chaddr),*(msg->chaddr)); + return 0; + } + return 1; +} + +#define STATE_SELECTING 1 +#define STATE_REQUESTING 2 + +#define TIMEOUT_INITIAL 4000 +#define TIMEOUT_MAX 32000 + +int dhcp_init_ifc(const char *ifname) +{ + dhcp_msg discover_msg; + dhcp_msg request_msg; + dhcp_msg reply; + dhcp_msg *msg; + dhcp_info info; + int s, r, size; + int valid_reply; + uint32_t xid; + unsigned char hwaddr[6]; + struct pollfd pfd; + unsigned int state; + unsigned int timeout; + int if_index; + + xid = (uint32_t) get_msecs(); + + if (if_get_hwaddr(ifname, hwaddr)) { + return fatal("cannot obtain interface address"); + } + if ((if_index = if_nametoindex(ifname)) == 0) { + return fatal("cannot obtain interface index"); + } + + s = open_raw_socket(ifname, hwaddr, if_index); + + timeout = TIMEOUT_INITIAL; + state = STATE_SELECTING; + info.type = 0; + goto transmit; + + for (;;) { + pfd.fd = s; + pfd.events = POLLIN; + pfd.revents = 0; + r = poll(&pfd, 1, timeout); + + if (r == 0) { +#if VERBOSE + printerr("TIMEOUT\n"); +#endif + if (timeout >= TIMEOUT_MAX) { + printerr("timed out\n"); + if ( info.type == DHCPOFFER ) { + printerr("no acknowledgement from DHCP server\nconfiguring %s with offered parameters\n", ifname); + return dhcp_configure(ifname, &info); + } + errno = ETIME; + close(s); + return -1; + } + timeout = timeout * 2; + + transmit: + size = 0; + msg = NULL; + switch(state) { + case STATE_SELECTING: + msg = &discover_msg; + size = init_dhcp_discover_msg(msg, hwaddr, xid); + break; + case STATE_REQUESTING: + msg = &request_msg; + size = init_dhcp_request_msg(msg, hwaddr, xid, info.ipaddr, info.serveraddr); + break; + default: + r = 0; + } + if (size != 0) { + r = send_message(s, if_index, msg, size); + if (r < 0) { + printerr("error sending dhcp msg: %s\n", strerror(errno)); + } + } + continue; + } + + if (r < 0) { + if ((errno == EAGAIN) || (errno == EINTR)) { + continue; + } + return fatal("poll failed"); + } + + errno = 0; + r = receive_packet(s, &reply); + if (r < 0) { + if (errno != 0) { + printf("receive_packet failed (%d): %s", r, strerror(errno)); + if (errno == ENETDOWN || errno == ENXIO) { + return -1; + } + } + continue; + } + +#if VERBOSE > 1 + dump_dhcp_msg(&reply, r); +#endif + decode_dhcp_msg(&reply, r, &info); + + if (state == STATE_SELECTING) { + valid_reply = is_valid_reply(&discover_msg, &reply, r); + } else { + valid_reply = is_valid_reply(&request_msg, &reply, r); + } + if (!valid_reply) { + printerr("invalid reply\n"); + continue; + } + + if (verbose) dump_dhcp_info(&info); + + switch(state) { + case STATE_SELECTING: + if (info.type == DHCPOFFER) { + state = STATE_REQUESTING; + timeout = TIMEOUT_INITIAL; + xid++; + goto transmit; + } + break; + case STATE_REQUESTING: + if (info.type == DHCPACK) { + printerr("configuring %s\n", ifname); + close(s); + return dhcp_configure(ifname, &info); + } else if (info.type == DHCPNAK) { + printerr("configuration request denied\n"); + close(s); + return -1; + } else { + printerr("ignoring %s message in state %d\n", + dhcp_type_to_name(info.type), state); + } + break; + } + } + close(s); + return 0; +} + +int do_dhcp(char *iname) +{ + if (if_set_addr_v4(iname, 0, 32)) { + printerr("failed to set ip addr for %s to 0.0.0.0: %s\n", iname, strerror(errno)); + return -1; + } + + if (if_link_up(iname)) { + printerr("failed to bring up interface %s: %s\n", iname, strerror(errno)); + return -1; + } + + return dhcp_init_ifc(iname); +} diff --git a/root/package/link4all/quectel-CM/quectel1.6.0.15_src/libmnl/dhcp/dhcpmsg.c b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/libmnl/dhcp/dhcpmsg.c new file mode 100644 index 00000000..1e0a233f --- /dev/null +++ b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/libmnl/dhcp/dhcpmsg.c @@ -0,0 +1,100 @@ +/* + * Copyright 2008, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include + +#include "dhcpmsg.h" + +static void *init_dhcp_msg(dhcp_msg *msg, int type, void *hwaddr, uint32_t xid) +{ + uint8_t *x; + + memset(msg, 0, sizeof(dhcp_msg)); + + msg->op = OP_BOOTREQUEST; + msg->htype = HTYPE_ETHER; + msg->hlen = 6; + msg->hops = 0; + + msg->flags = htons(FLAGS_BROADCAST); + + msg->xid = xid; + + memcpy(msg->chaddr, hwaddr, 6); + + x = msg->options; + + *x++ = OPT_COOKIE1; + *x++ = OPT_COOKIE2; + *x++ = OPT_COOKIE3; + *x++ = OPT_COOKIE4; + + *x++ = OPT_MESSAGE_TYPE; + *x++ = 1; + *x++ = type; + + return x; +} + +int init_dhcp_discover_msg(dhcp_msg *msg, void *hwaddr, uint32_t xid) +{ + uint8_t *x; + + x = init_dhcp_msg(msg, DHCPDISCOVER, hwaddr, xid); + + *x++ = OPT_PARAMETER_LIST; + *x++ = 4; + *x++ = OPT_SUBNET_MASK; + *x++ = OPT_GATEWAY; + *x++ = OPT_DNS; + *x++ = OPT_BROADCAST_ADDR; + + *x++ = OPT_END; + + return DHCP_MSG_FIXED_SIZE + (x - msg->options); +} + +int init_dhcp_request_msg(dhcp_msg *msg, void *hwaddr, uint32_t xid, + uint32_t ipaddr, uint32_t serveraddr) +{ + uint8_t *x; + + x = init_dhcp_msg(msg, DHCPREQUEST, hwaddr, xid); + + *x++ = OPT_PARAMETER_LIST; + *x++ = 4; + *x++ = OPT_SUBNET_MASK; + *x++ = OPT_GATEWAY; + *x++ = OPT_DNS; + *x++ = OPT_BROADCAST_ADDR; + + *x++ = OPT_REQUESTED_IP; + *x++ = 4; + memcpy(x, &ipaddr, 4); + x += 4; + + *x++ = OPT_SERVER_ID; + *x++ = 4; + memcpy(x, &serveraddr, 4); + x += 4; + + *x++ = OPT_END; + + return DHCP_MSG_FIXED_SIZE + (x - msg->options); +} diff --git a/root/package/link4all/quectel-CM/quectel1.6.0.15_src/libmnl/dhcp/dhcpmsg.h b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/libmnl/dhcp/dhcpmsg.h new file mode 100644 index 00000000..fb99490a --- /dev/null +++ b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/libmnl/dhcp/dhcpmsg.h @@ -0,0 +1,106 @@ +/* + * Copyright 2008, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _WIFI_DHCP_H_ +#define _WIFI_DHCP_H_ + +#include + +#define PORT_BOOTP_SERVER 67 +#define PORT_BOOTP_CLIENT 68 + +/* RFC 2131 p 9 */ +typedef struct dhcp_msg dhcp_msg; + +#define OP_BOOTREQUEST 1 +#define OP_BOOTREPLY 2 + +#define FLAGS_BROADCAST 0x8000 + +#define HTYPE_ETHER 1 + +struct dhcp_msg +{ + uint8_t op; /* BOOTREQUEST / BOOTREPLY */ + uint8_t htype; /* hw addr type */ + uint8_t hlen; /* hw addr len */ + uint8_t hops; /* client set to 0 */ + + uint32_t xid; /* transaction id */ + + uint16_t secs; /* seconds since start of acq */ + uint16_t flags; + + uint32_t ciaddr; /* client IP addr */ + uint32_t yiaddr; /* your (client) IP addr */ + uint32_t siaddr; /* ip addr of next server */ + /* (DHCPOFFER and DHCPACK) */ + uint32_t giaddr; /* relay agent IP addr */ + + uint8_t chaddr[16]; /* client hw addr */ + char sname[64]; /* asciiz server hostname */ + char file[128]; /* asciiz boot file name */ + + uint8_t options[1024]; /* optional parameters */ +}; + +#define DHCP_MSG_FIXED_SIZE 236 + +/* first four bytes of options are a cookie to indicate that +** the payload are DHCP options as opposed to some other BOOTP +** extension. +*/ +#define OPT_COOKIE1 0x63 +#define OPT_COOKIE2 0x82 +#define OPT_COOKIE3 0x53 +#define OPT_COOKIE4 0x63 + +/* BOOTP/DHCP options - see RFC 2132 */ +#define OPT_PAD 0 + +#define OPT_SUBNET_MASK 1 /* 4 */ +#define OPT_TIME_OFFSET 2 /* 4 */ +#define OPT_GATEWAY 3 /* 4*n * n */ +#define OPT_DNS 6 /* 4*n * n */ +#define OPT_DOMAIN_NAME 15 /* n */ +#define OPT_BROADCAST_ADDR 28 /* 4 */ + +#define OPT_REQUESTED_IP 50 /* 4 */ +#define OPT_LEASE_TIME 51 /* 4 */ +#define OPT_MESSAGE_TYPE 53 /* 1 */ +#define OPT_SERVER_ID 54 /* 4 */ +#define OPT_PARAMETER_LIST 55 /* n * n */ +#define OPT_MESSAGE 56 /* n */ +#define OPT_CLASS_ID 60 /* n */ +#define OPT_CLIENT_ID 61 /* n */ +#define OPT_END 255 + +/* DHCP message types */ +#define DHCPDISCOVER 1 +#define DHCPOFFER 2 +#define DHCPREQUEST 3 +#define DHCPDECLINE 4 +#define DHCPACK 5 +#define DHCPNAK 6 +#define DHCPRELEASE 7 +#define DHCPINFORM 8 + +int init_dhcp_discover_msg(dhcp_msg *msg, void *hwaddr, uint32_t xid); + +int init_dhcp_request_msg(dhcp_msg *msg, void *hwaddr, uint32_t xid, + uint32_t ipaddr, uint32_t serveraddr); + +#endif diff --git a/root/package/link4all/quectel-CM/quectel1.6.0.15_src/libmnl/dhcp/packet.c b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/libmnl/dhcp/packet.c new file mode 100644 index 00000000..9515dd1e --- /dev/null +++ b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/libmnl/dhcp/packet.c @@ -0,0 +1,247 @@ +/* + * Copyright 2008, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dhcpmsg.h" + +int fatal(); + +int open_raw_socket(const char *ifname __attribute__((unused)), uint8_t *hwaddr, int if_index) +{ + int s; + struct sockaddr_ll bindaddr; + + if((s = socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_IP))) < 0) { + return fatal("socket(PF_PACKET)"); + } + + memset(&bindaddr, 0, sizeof(bindaddr)); + bindaddr.sll_family = AF_PACKET; + bindaddr.sll_protocol = htons(ETH_P_IP); + bindaddr.sll_halen = ETH_ALEN; + memcpy(bindaddr.sll_addr, hwaddr, ETH_ALEN); + bindaddr.sll_ifindex = if_index; + + if (bind(s, (struct sockaddr *)&bindaddr, sizeof(bindaddr)) < 0) { + return fatal("Cannot bind raw socket to interface"); + } + + return s; +} + +static uint32_t checksum(void *buffer, unsigned int count, uint32_t startsum) +{ + uint16_t *up = (uint16_t *)buffer; + uint32_t sum = startsum; + uint32_t upper16; + + while (count > 1) { + sum += *up++; + count -= 2; + } + if (count > 0) { + sum += (uint16_t) *(uint8_t *)up; + } + while ((upper16 = (sum >> 16)) != 0) { + sum = (sum & 0xffff) + upper16; + } + return sum; +} + +static uint32_t finish_sum(uint32_t sum) +{ + return ~sum & 0xffff; +} + +int send_packet(int s, int if_index, struct dhcp_msg *msg, int size, + uint32_t saddr, uint32_t daddr, uint32_t sport, uint32_t dport) +{ + struct iphdr ip; + struct udphdr udp; + struct iovec iov[3]; + uint32_t udpsum; + uint16_t temp; + struct msghdr msghdr; + struct sockaddr_ll destaddr; + + ip.version = IPVERSION; + ip.ihl = sizeof(ip) >> 2; + ip.tos = 0; + ip.tot_len = htons(sizeof(ip) + sizeof(udp) + size); + ip.id = 0; + ip.frag_off = 0; + ip.ttl = IPDEFTTL; + ip.protocol = IPPROTO_UDP; + ip.check = 0; + ip.saddr = saddr; + ip.daddr = daddr; + ip.check = finish_sum(checksum(&ip, sizeof(ip), 0)); + + udp.source = htons(sport); + udp.dest = htons(dport); + udp.len = htons(sizeof(udp) + size); + udp.check = 0; + + /* Calculate checksum for pseudo header */ + udpsum = checksum(&ip.saddr, sizeof(ip.saddr), 0); + udpsum = checksum(&ip.daddr, sizeof(ip.daddr), udpsum); + temp = htons(IPPROTO_UDP); + udpsum = checksum(&temp, sizeof(temp), udpsum); + temp = udp.len; + udpsum = checksum(&temp, sizeof(temp), udpsum); + + /* Add in the checksum for the udp header */ + udpsum = checksum(&udp, sizeof(udp), udpsum); + + /* Add in the checksum for the data */ + udpsum = checksum(msg, size, udpsum); + udp.check = finish_sum(udpsum); + + iov[0].iov_base = (char *)&ip; + iov[0].iov_len = sizeof(ip); + iov[1].iov_base = (char *)&udp; + iov[1].iov_len = sizeof(udp); + iov[2].iov_base = (char *)msg; + iov[2].iov_len = size; + memset(&destaddr, 0, sizeof(destaddr)); + destaddr.sll_family = AF_PACKET; + destaddr.sll_protocol = htons(ETH_P_IP); + destaddr.sll_ifindex = if_index; + destaddr.sll_halen = ETH_ALEN; + memcpy(destaddr.sll_addr, "\xff\xff\xff\xff\xff\xff", ETH_ALEN); + + msghdr.msg_name = &destaddr; + msghdr.msg_namelen = sizeof(destaddr); + msghdr.msg_iov = iov; + msghdr.msg_iovlen = sizeof(iov) / sizeof(struct iovec); + msghdr.msg_flags = 0; + msghdr.msg_control = 0; + msghdr.msg_controllen = 0; + return sendmsg(s, &msghdr, 0); +} + +int receive_packet(int s, struct dhcp_msg *msg) +{ + int nread; + int is_valid; + struct dhcp_packet { + struct iphdr ip; + struct udphdr udp; + struct dhcp_msg dhcp; + } packet; + int dhcp_size; + uint32_t sum; + uint16_t temp; + uint32_t saddr, daddr; + + nread = read(s, &packet, sizeof(packet)); + if (nread < 0) { + return -1; + } + /* + * The raw packet interface gives us all packets received by the + * network interface. We need to filter out all packets that are + * not meant for us. + */ + is_valid = 0; + if (nread < (int)(sizeof(struct iphdr) + sizeof(struct udphdr))) { +#if VERBOSE + ALOGD("Packet is too small (%d) to be a UDP datagram", nread); +#endif + } else if (packet.ip.version != IPVERSION || packet.ip.ihl != (sizeof(packet.ip) >> 2)) { +#if VERBOSE + ALOGD("Not a valid IP packet"); +#endif + } else if (nread < ntohs(packet.ip.tot_len)) { +#if VERBOSE + ALOGD("Packet was truncated (read %d, needed %d)", nread, ntohs(packet.ip.tot_len)); +#endif + } else if (packet.ip.protocol != IPPROTO_UDP) { +#if VERBOSE + ALOGD("IP protocol (%d) is not UDP", packet.ip.protocol); +#endif + } else if (packet.udp.dest != htons(PORT_BOOTP_CLIENT)) { +#if VERBOSE + ALOGD("UDP dest port (%d) is not DHCP client", ntohs(packet.udp.dest)); +#endif + } else { + is_valid = 1; + } + + if (!is_valid) { + return -1; + } + + /* Seems like it's probably a valid DHCP packet */ + /* validate IP header checksum */ + sum = finish_sum(checksum(&packet.ip, sizeof(packet.ip), 0)); + if (sum != 0) { + printf("IP header checksum failure (0x%x)\n", packet.ip.check); + return -1; + } + /* + * Validate the UDP checksum. + * Since we don't need the IP header anymore, we "borrow" it + * to construct the pseudo header used in the checksum calculation. + */ + dhcp_size = ntohs(packet.udp.len) - sizeof(packet.udp); + /* + * check validity of dhcp_size. + * 1) cannot be negative or zero. + * 2) src buffer contains enough bytes to copy + * 3) cannot exceed destination buffer + */ + if ((dhcp_size <= 0) || + ((int)(nread - sizeof(struct iphdr) - sizeof(struct udphdr)) < dhcp_size) || + ((int)sizeof(struct dhcp_msg) < dhcp_size)) { +#if VERBOSE + printf("Malformed Packet\n"); +#endif + return -1; + } + saddr = packet.ip.saddr; + daddr = packet.ip.daddr; + nread = ntohs(packet.ip.tot_len); + memset(&packet.ip, 0, sizeof(packet.ip)); + packet.ip.saddr = saddr; + packet.ip.daddr = daddr; + packet.ip.protocol = IPPROTO_UDP; + packet.ip.tot_len = packet.udp.len; + temp = packet.udp.check; + packet.udp.check = 0; + sum = finish_sum(checksum(&packet, nread, 0)); + packet.udp.check = temp; + if (!sum) + sum = finish_sum(sum); + if (temp != sum) { + printf("UDP header checksum failure (0x%x should be 0x%x)\n", sum, temp); + return -1; + } + memcpy(msg, &packet.dhcp, dhcp_size); + return dhcp_size; +} diff --git a/root/package/link4all/quectel-CM/quectel1.6.0.15_src/libmnl/dhcp/packet.h b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/libmnl/dhcp/packet.h new file mode 100644 index 00000000..aade392d --- /dev/null +++ b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/libmnl/dhcp/packet.h @@ -0,0 +1,25 @@ +/* + * Copyright 2008, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _WIFI_PACKET_H_ +#define _WIFI_PACKET_H_ + +int open_raw_socket(const char *ifname, uint8_t *hwaddr, int if_index); +int send_packet(int s, int if_index, struct dhcp_msg *msg, int size, + uint32_t saddr, uint32_t daddr, uint32_t sport, uint32_t dport); +int receive_packet(int s, struct dhcp_msg *msg); + +#endif diff --git a/root/package/link4all/quectel-CM/quectel1.6.0.15_src/libmnl/ifutils.c b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/libmnl/ifutils.c new file mode 100644 index 00000000..309fa672 --- /dev/null +++ b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/libmnl/ifutils.c @@ -0,0 +1,742 @@ +/* This example is placed in the public domain. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "libmnl.h" +#include "ifutils.h" + +#define ERRMSG(v...) printf("%s-%d: error=%s %s\n", __func__, __LINE__, strerror(errno), ##v) + +int mask_to_prefix_v4(uint32_t mask) +{ + int ret = 0; + while (mask) + { + mask = mask & (mask - 1); + ret++; + } + return ret; +} + +const char *ipaddr_to_string_v4(in_addr_t ipaddr) +{ + static char buf[INET6_ADDRSTRLEN] = {'\0'}; + buf[0] = '\0'; + uint32_t addr = ipaddr; + return inet_ntop(AF_INET, &addr, buf, sizeof(buf)); +} + +const char *ipaddr_to_string_v6(uint8_t *ipaddr) +{ + static char buf[INET6_ADDRSTRLEN] = {'\0'}; + buf[0] = '\0'; + return inet_ntop(AF_INET6, ipaddr, buf, sizeof(buf)); +} + +static void ifc_init_ifr(const char *name, struct ifreq *ifr) +{ + memset(ifr, 0, sizeof(struct ifreq)); + strncpy(ifr->ifr_name, name, IFNAMSIZ); + ifr->ifr_name[IFNAMSIZ - 1] = 0; +} + +int if_get_hwaddr(const char *name, void *ptr) +{ + int r; + struct ifreq ifr; + ifc_init_ifr(name, &ifr); + + int ifc_ctl_sock = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0); + if (ifc_ctl_sock < 0) + { + return -1; + } + r = ioctl(ifc_ctl_sock, SIOCGIFHWADDR, &ifr); + if (r < 0) + return -1; + + memcpy(ptr, &ifr.ifr_hwaddr.sa_data, ETH_ALEN); + return 0; +} + +static int if_act_on_link(const char *ifname, int state) +{ + struct mnl_socket *nl; + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + struct ifinfomsg *ifm; + int ret; + unsigned int seq, portid, change = 0, flags = 0; + static int oldstate = -1; + + if (state == oldstate) + return 0; + oldstate = state; + + if (state) + { + change |= IFF_UP; + flags |= IFF_UP; + } + else + { + change |= IFF_UP; + flags &= ~IFF_UP; + } + + nlh = mnl_nlmsg_put_header(buf); + nlh->nlmsg_type = RTM_NEWLINK; + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + nlh->nlmsg_seq = seq = time(NULL); + ifm = mnl_nlmsg_put_extra_header(nlh, sizeof(*ifm)); + ifm->ifi_family = AF_UNSPEC; + ifm->ifi_change = change; + ifm->ifi_flags = flags; + + mnl_attr_put_str(nlh, IFLA_IFNAME, ifname); + + nl = mnl_socket_open(NETLINK_ROUTE); + if (nl == NULL) + { + ERRMSG("mnl_socket_open"); + return -1; + } + + if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) + { + ERRMSG(" mnl_socket_bind"); + return -1; + } + portid = mnl_socket_get_portid(nl); + + if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) + { + ERRMSG(" mnl_socket_sendto"); + return -1; + } + + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + if (ret == -1) + { + ERRMSG(" mnl_socket_recvfrom"); + return -1; + } + + ret = mnl_cb_run(buf, ret, seq, portid, NULL, NULL); + if (ret == -1) + { + ERRMSG(" mnl_cb_run"); + return -1; + } + + mnl_socket_close(nl); + + return 0; +} + +int if_link_up(const char *ifname) +{ + return if_act_on_link(ifname, 1); +} + +int if_link_down(const char *ifname) +{ + return if_act_on_link(ifname, 0); +} + +int if_set_mtu(const char *ifname, uint32_t mtu) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + unsigned int seq, portid; + struct mnl_socket *nl; + struct nlmsghdr *nlh; + struct ifinfomsg *ifm; + int ret; + int iface; + static uint32_t oldmtu = 1500; + + if (mtu == oldmtu) + return 0; + oldmtu = mtu; + + iface = if_nametoindex(ifname); + if (iface == 0) + { + ERRMSG(" if_nametoindex"); + return -1; + } + + nlh = mnl_nlmsg_put_header(buf); + nlh->nlmsg_type = RTM_NEWLINK; + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + nlh->nlmsg_seq = seq = time(NULL); + ifm = mnl_nlmsg_put_extra_header(nlh, sizeof(struct ifinfomsg)); + ifm->ifi_family = AF_UNSPEC; + ifm->ifi_index = iface; + ifm->ifi_change = 0xFFFFFFFF; + ifm->ifi_type = ifm->ifi_flags = 0; + + mnl_attr_put_u32(nlh, IFLA_MTU, mtu); + + nl = mnl_socket_open(NETLINK_ROUTE); + if (nl == NULL) + { + ERRMSG(" mnl_socket_open"); + return -1; + } + + if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) + { + ERRMSG(" mnl_socket_bind"); + return -1; + } + portid = mnl_socket_get_portid(nl); + + if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) + { + ERRMSG(" mnl_socket_sendto"); + return -1; + } + + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + if (ret == -1) + { + ERRMSG(" mnl_socket_recvfrom"); + return -1; + } + + ret = mnl_cb_run(buf, ret, seq, portid, NULL, NULL); + if (ret == -1) + { + ERRMSG(" mnl_cb_run"); + return -1; + } + + mnl_socket_close(nl); + + return 0; +} + +/** + * @brief Set the ip addr object + * + * @param operate + * 0 -> add address on interface + * 1 -> delete address on interface + * @param ifname + * @param ipaddr + * @param prefix + * @return int + */ +static int if_act_on_addr(bool operate, int proto, const char *ifname, addr_t *ipaddr, uint32_t prefix) +{ + struct mnl_socket *nl; + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + struct ifaddrmsg *ifm; + uint32_t seq, portid; + int ret, family = proto; + + int iface; + + iface = if_nametoindex(ifname); + if (iface == 0) + { + ERRMSG(" if_nametoindex"); + return -1; + } + + nlh = mnl_nlmsg_put_header(buf); + if (operate) + nlh->nlmsg_type = RTM_NEWADDR; + else + nlh->nlmsg_type = RTM_DELADDR; + + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_REPLACE | NLM_F_ACK; + nlh->nlmsg_seq = seq = time(NULL); + + ifm = mnl_nlmsg_put_extra_header(nlh, sizeof(struct ifaddrmsg)); + + ifm->ifa_family = family; + ifm->ifa_prefixlen = prefix; + ifm->ifa_flags = IFA_F_PERMANENT; + + ifm->ifa_scope = RT_SCOPE_UNIVERSE; + ifm->ifa_index = iface; + + /* + * The exact meaning of IFA_LOCAL and IFA_ADDRESS depend + * on the address family being used and the device type. + * For broadcast devices (like the interfaces we use), + * for IPv4 we specify both and they are used interchangeably. + * For IPv6, only IFA_ADDRESS needs to be set. + */ + if (family == AF_INET) + { + mnl_attr_put_u32(nlh, IFA_LOCAL, ipaddr->ip); + mnl_attr_put_u32(nlh, IFA_ADDRESS, ipaddr->ip); + } + else + { + mnl_attr_put(nlh, IFA_ADDRESS, sizeof(struct in6_addr), ipaddr); + } + + nl = mnl_socket_open(NETLINK_ROUTE); + if (nl == NULL) + { + ERRMSG(" mnl_socket_open"); + return -1; + } + + if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) + { + ERRMSG(" mnl_socket_bind"); + return -1; + } + portid = mnl_socket_get_portid(nl); + + if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) + { + ERRMSG(" mnl_socket_sendto"); + return -1; + } + + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + if (ret < 0) + { + ERRMSG(" mnl_socket_recvfrom"); + return -1; + } + + ret = mnl_cb_run(buf, ret, seq, portid, NULL, NULL); + if (ret < 0) + { + ERRMSG(" mnl_cb_run"); + return -1; + } + + mnl_socket_close(nl); + + return 0; +} + +int if_set_addr_v4(const char *ifname, in_addr_t ipaddr, uint32_t prefix) +{ + addr_t addr; + addr.ip = ipaddr; + return if_act_on_addr(1, AF_INET, ifname, &addr, prefix); +} + +int if_del_addr_v4(const char *ifname, in_addr_t ipaddr, uint32_t prefix) +{ + addr_t addr; + addr.ip = ipaddr; + return if_act_on_addr(0, AF_INET, ifname, &addr, prefix); +} + +int if_set_addr_v6(const char *ifname, uint8_t *ipaddr, uint32_t prefix) +{ + addr_t addr; + memcpy(&addr.ip6, ipaddr, 16); + return if_act_on_addr(1, AF_INET6, ifname, &addr, prefix); +} + +int if_del_addr_v6(const char *ifname, uint8_t *ipaddr, uint32_t prefix) +{ + addr_t addr; + memcpy(&addr.ip6, ipaddr, 16); + return if_act_on_addr(0, AF_INET6, ifname, &addr, prefix); +} + +static int data_attr_cb(const struct nlattr *attr, void *data) +{ + const struct nlattr **tb = data; + int type = mnl_attr_get_type(attr); + + /* skip unsupported attribute in user-space */ + if (mnl_attr_type_valid(attr, IFA_MAX) < 0) + return MNL_CB_OK; + + switch (type) + { + case IFA_ADDRESS: + if (mnl_attr_validate(attr, MNL_TYPE_BINARY) < 0) + { + ERRMSG(" mnl_attr_validate"); + return MNL_CB_ERROR; + } + break; + } + tb[type] = attr; + return MNL_CB_OK; +} + +static int data_cb(const struct nlmsghdr *nlh, void *data) +{ + struct nlattr *tb[IFA_MAX + 1] = {}; + struct ifaddrmsg *ifa = mnl_nlmsg_get_payload(nlh); + struct addrinfo_t *addrinfo = (struct addrinfo_t *)data; + void *addr = NULL; + + mnl_attr_parse(nlh, sizeof(*ifa), data_attr_cb, tb); + if (tb[IFA_ADDRESS]) + { + char out[INET6_ADDRSTRLEN]; + + addr = mnl_attr_get_payload(tb[IFLA_ADDRESS]); + addr = mnl_attr_get_payload(tb[IFA_ADDRESS]); + if (!inet_ntop(ifa->ifa_family, addr, out, sizeof(out))) + ERRMSG("inet_ntop"); + // printf("%d %d-> %d %s\n", addrinfo->iface, ifa->ifa_index, ifa->ifa_scope, out); + + addrinfo->addrs[addrinfo->num].prefix = ifa->ifa_prefixlen; + if (ifa->ifa_index == addrinfo->iface) + { + if (ifa->ifa_family == AF_INET6) + memcpy(addrinfo->addrs[addrinfo->num].address.ip6.s6_addr, addr, 16); + if (ifa->ifa_family == AF_INET) + memcpy(&(addrinfo->addrs[addrinfo->num].address.ip), addr, 4); + addrinfo->num++; + } + } + + // ifa->ifa_scope + // 0: global + // 200: site + // 253: link + // 254: host + // 255: nowhere + + return MNL_CB_OK; +} + +/** + * @brief + * + * @param ifname + * @param proto + * AF_INET -> for IPv4 + * AF_INET6 -> for IPv6 + * @return int + */ +static int if_get_addr(const char *ifname, int proto, struct addrinfo_t *addrinfo) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + unsigned int seq, portid; + struct mnl_socket *nl; + struct nlmsghdr *nlh; + struct rtgenmsg *rt; + int ret; + + addrinfo->iface = if_nametoindex(ifname); + if (addrinfo->iface == 0) + { + ERRMSG(" if_nametoindex"); + return -1; + } + + nlh = mnl_nlmsg_put_header(buf); + nlh->nlmsg_type = RTM_GETADDR; + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; + nlh->nlmsg_seq = seq = time(NULL); + rt = mnl_nlmsg_put_extra_header(nlh, sizeof(struct rtgenmsg)); + if (proto == AF_INET) + rt->rtgen_family = AF_INET; + else if (proto == AF_INET6) + rt->rtgen_family = AF_INET6; + + nl = mnl_socket_open(NETLINK_ROUTE); + if (nl == NULL) + { + ERRMSG(" mnl_socket_open"); + return -1; + } + + if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) + { + ERRMSG(" mnl_socket_bind"); + return -1; + } + portid = mnl_socket_get_portid(nl); + + if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) + { + ERRMSG(" mnl_socket_sendto"); + return -1; + } + + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + while (ret > 0) + { + ret = mnl_cb_run(buf, ret, seq, portid, data_cb, addrinfo); + if (ret <= MNL_CB_STOP) + break; + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + } + if (ret == -1) + { + ERRMSG(" error"); + return -1; + } + mnl_socket_close(nl); + + return 0; +} + +int if_flush_v4_addr(const char *ifname) +{ + struct addrinfo_t addrinfo; + int i = 0; + + memset(&addrinfo, 0, sizeof(struct addrinfo_t)); + if_get_addr(ifname, AF_INET, &addrinfo); + for (; i < addrinfo.num; i++) + { + // printf("remove address: %s\n", ipaddr_to_string_v4(addrinfo.addrs[i].address.ip)); + if_del_addr_v4(ifname, addrinfo.addrs[i].address.ip, addrinfo.addrs[i].prefix); + } + return 0; +} + +int if_flush_v6_addr(const char *ifname) +{ + struct addrinfo_t addrinfo; + int i = 0; + + memset(&addrinfo, 0, sizeof(struct addrinfo_t)); + if_get_addr(ifname, AF_INET6, &addrinfo); + for (; i < addrinfo.num; i++) + { + // printf("remove address: %s\n", ipaddr_to_string_v6(addrinfo.addrs[i].address.ip6.s6_addr)); + if_del_addr_v6(ifname, addrinfo.addrs[i].address.ip6.s6_addr, addrinfo.addrs[i].prefix); + } + return 0; +} + +/** + * @brief Set the route addr object + * Usage: + * iface destination cidr [gateway] + * Example: + * eth0 10.0.1.12 32 10.0.1.11 + * eth0 ffff::10.0.1.12 128 fdff::1 + * @param operate + * add or del + * @param ifname + * @param dstaddr + * @param prefix + * @param gwaddr + * @return int + */ +int if_act_on_route(bool operate, int proto, const char *ifname, addr_t *dstaddr, uint32_t prefix, addr_t *gwaddr) +{ + struct mnl_socket *nl; + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + struct rtmsg *rtm; + uint32_t seq, portid; + int iface, ret, family = proto; + + iface = if_nametoindex(ifname); + if (iface == 0) + { + ERRMSG(" if_nametoindex"); + return -1; + } + + nlh = mnl_nlmsg_put_header(buf); + if (operate) + nlh->nlmsg_type = RTM_NEWROUTE; + else + nlh->nlmsg_type = RTM_DELROUTE; + + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_ACK; + nlh->nlmsg_seq = seq = time(NULL); + + rtm = mnl_nlmsg_put_extra_header(nlh, sizeof(struct rtmsg)); + rtm->rtm_family = family; + rtm->rtm_dst_len = prefix; + rtm->rtm_src_len = 0; + rtm->rtm_tos = 0; + rtm->rtm_protocol = RTPROT_STATIC; + rtm->rtm_table = RT_TABLE_MAIN; + rtm->rtm_type = RTN_UNICAST; + /* is there any gateway? */ + rtm->rtm_scope = gwaddr ? RT_SCOPE_UNIVERSE : RT_SCOPE_LINK; + rtm->rtm_flags = 0; + + if (family == AF_INET) + mnl_attr_put_u32(nlh, RTA_DST, dstaddr->ip); + else + mnl_attr_put(nlh, RTA_DST, sizeof(struct in6_addr), dstaddr); + + mnl_attr_put_u32(nlh, RTA_OIF, iface); + if (gwaddr) + { + if (family == AF_INET) + mnl_attr_put_u32(nlh, RTA_GATEWAY, gwaddr->ip); + else + { + mnl_attr_put(nlh, RTA_GATEWAY, sizeof(struct in6_addr), gwaddr); + } + } + + nl = mnl_socket_open(NETLINK_ROUTE); + if (nl == NULL) + { + ERRMSG(" mnl_socket_open"); + return -1; + } + + if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) + { + ERRMSG(" mnl_socket_bind"); + return -1; + } + portid = mnl_socket_get_portid(nl); + + if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) + { + ERRMSG(" mnl_socket_sendto"); + return -1; + } + + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + if (ret < 0) + { + ERRMSG(" mnl_socket_recvfrom"); + return -1; + } + + ret = mnl_cb_run(buf, ret, seq, portid, NULL, NULL); + if (ret < 0) + { + ERRMSG(" mnl_cb_run"); + return -1; + } + + mnl_socket_close(nl); + + return 0; +} + +int if_set_default_route_v4(const char *ifname) +{ + return if_act_on_route(1, AF_INET, ifname, (addr_t *)&in6addr_any, 0, NULL); +} + +int if_del_default_route_v4(const char *ifname) +{ + return if_act_on_route(0, AF_INET, ifname, (addr_t *)&in6addr_any, 0, NULL); +} + +int if_set_default_route_v6(const char *ifname) +{ + return if_act_on_route(1, AF_INET6, ifname, (addr_t *)&in6addr_any, 0, NULL); +} + +int if_del_default_route_v6(const char *ifname) +{ + return if_act_on_route(0, AF_INET6, ifname, (addr_t *)&in6addr_any, 0, NULL); +} + +/** + * @brief Set the default gwaddr object + * set default gw + * @param operate + * @param ifname + * @param gwaddr + * gateway ip + * @return int + */ +int if_set_route_gw_v4(const char *ifname, in_addr_t gwaddr) +{ + addr_t addr; + memset(&addr, 0, sizeof(addr_t)); + addr.ip = gwaddr; + return if_act_on_route(1, AF_INET, ifname, (addr_t *)&in6addr_any, 0, &addr); +} + +int if_del_route_gw_v4(const char *ifname, in_addr_t gwaddr) +{ + addr_t addr; + memset(&addr, 0, sizeof(addr_t)); + addr.ip = gwaddr; + return if_act_on_route(0, AF_INET, ifname, (addr_t *)&in6addr_any, 0, &addr); +} + +int if_set_route_gw_v6(const char *ifname, uint8_t *gwaddr) +{ + addr_t addr; + memset(&addr, 0, sizeof(addr_t)); + memcpy(&addr.ip6, gwaddr, 16); + return if_act_on_route(1, AF_INET6, ifname, (addr_t *)&in6addr_any, 0, &addr); +} + +int if_del_route_gw_v6(const char *ifname, uint8_t *gwaddr) +{ + addr_t addr; + memset(&addr, 0, sizeof(addr_t)); + memcpy(&addr.ip6, gwaddr, 16); + return if_act_on_route(0, AF_INET6, ifname, (addr_t *)&in6addr_any, 0, &addr); +} + +int if_set_dns(const char *dns1, const char *dns2) +{ + int ret = 0; + char buf[128] = {'\0'}; + int fd = open("/etc/resolv.conf", O_CREAT | O_WRONLY | O_TRUNC); + if (fd < 0) + { + ERRMSG(" fail to open /etc/resolv.conf"); + return -1; + } + + if (dns1) + snprintf(buf, sizeof(buf), "nameserver %s\n", dns1); + if (dns2) + snprintf(buf, sizeof(buf), "nameserver %s\n", dns2); + ret = write(fd, buf, strlen(buf)); + if (ret < 0) + { + ERRMSG(" write dns"); + } + close(fd); + return ret > 0 ? 0 : -1; +} + +int if_set_network_v4(const char *ifname, in_addr_t ipaddr, uint32_t prefix, + in_addr_t gwaddr, in_addr_t dns1, in_addr_t dns2) +{ + if_link_up(ifname); + if_set_addr_v4(ifname, ipaddr, prefix); + if_set_default_route_v4(ifname); + if_set_dns(ipaddr_to_string_v4(dns1), ipaddr_to_string_v4(dns2)); + return 0; +} + +int if_set_network_v6(const char *ifname, uint8_t *ipaddr, uint32_t prefix, + uint8_t *gwaddr, uint8_t *dns1, uint8_t *dns2) +{ + if_link_up(ifname); + if_set_addr_v6(ifname, ipaddr, prefix); + if_set_default_route_v6(ifname); + if_set_dns(ipaddr_to_string_v6(dns1), ipaddr_to_string_v6(dns2)); + return 0; +} \ No newline at end of file diff --git a/root/package/link4all/quectel-CM/quectel1.6.0.15_src/libmnl/ifutils.h b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/libmnl/ifutils.h new file mode 100644 index 00000000..4c2b5650 --- /dev/null +++ b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/libmnl/ifutils.h @@ -0,0 +1,53 @@ +#ifndef __IFUTILS_H__ +#define __IFUTILS_H__ + +typedef union { + in_addr_t ip; + struct in6_addr ip6; +} addr_t; + +#define MAX_IP_NUM 32 +struct addrinfo_t +{ + int iface; + int num; + struct + { + int prefix; + addr_t address; + } addrs[MAX_IP_NUM]; +}; + +const char *ipaddr_to_string_v4(in_addr_t ipaddr); +const char *ipaddr_to_string_v6(uint8_t *ipaddr); +int mask_to_prefix_v4(in_addr_t mask); + +int if_get_hwaddr(const char *name, void *ptr); + +int if_link_down(const char *ifname); +int if_link_up(const char *ifname); +int if_set_mtu(const char *ifname, uint32_t mtu); + +int if_set_addr_v4(const char *name, in_addr_t address, uint32_t prefixlen); +int if_del_addr_v4(const char *name, in_addr_t address, uint32_t prefixlen); +int if_set_addr_v6(const char *name, uint8_t *address, uint32_t prefixlen); +int if_del_addr_v6(const char *name, uint8_t *address, uint32_t prefixlen); +int if_flush_v4_addr(const char *ifname); +int if_flush_v6_addr(const char *ifname); + +int if_set_route_gw_v4(const char *ifname, in_addr_t gwaddr); +int if_del_route_gw_v4(const char *ifname, in_addr_t gwaddr); +int if_set_default_route_v4(const char *ifname); +int if_del_default_route_v4(const char *ifname); + +int if_set_route_gw_v6(const char *ifname, uint8_t *gwaddr); +int if_del_route_gw_v6(const char *ifname, uint8_t *gwaddr); +int if_set_default_route_v6(const char *ifname); +int if_del_default_route_v6(const char *ifname); + +int if_set_network_v4(const char *ifname, in_addr_t ipaddr, uint32_t prefix, + in_addr_t gwaddr, in_addr_t dns1, in_addr_t dns2); +int if_set_network_v6(const char *ifname, uint8_t *ipaddr, uint32_t prefix, + uint8_t *gwaddr, uint8_t *dns1, uint8_t *dns2); + +#endif //__IFUTILS_H__ \ No newline at end of file diff --git a/root/package/link4all/quectel-CM/quectel1.6.0.15_src/libmnl/libmnl.h b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/libmnl/libmnl.h new file mode 100644 index 00000000..4bd0b92e --- /dev/null +++ b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/libmnl/libmnl.h @@ -0,0 +1,202 @@ +#ifndef _LIBMNL_H_ +#define _LIBMNL_H_ + +#include +#include +#include +#include +#include /* for sa_family_t */ +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Netlink socket API + */ + +#define MNL_SOCKET_AUTOPID 0 +#define MNL_SOCKET_BUFFER_SIZE (sysconf(_SC_PAGESIZE) < 8192L ? sysconf(_SC_PAGESIZE) : 8192L) +#define MNL_SOCKET_DUMP_SIZE 32768 + +struct mnl_socket; + +extern struct mnl_socket *mnl_socket_open(int bus); +extern struct mnl_socket *mnl_socket_open2(int bus, int flags); +extern struct mnl_socket *mnl_socket_fdopen(int fd); +extern int mnl_socket_bind(struct mnl_socket *nl, unsigned int groups, pid_t pid); +extern int mnl_socket_close(struct mnl_socket *nl); +extern int mnl_socket_get_fd(const struct mnl_socket *nl); +extern unsigned int mnl_socket_get_portid(const struct mnl_socket *nl); +extern ssize_t mnl_socket_sendto(const struct mnl_socket *nl, const void *req, size_t siz); +extern ssize_t mnl_socket_recvfrom(const struct mnl_socket *nl, void *buf, size_t siz); +extern int mnl_socket_setsockopt(const struct mnl_socket *nl, int type, void *buf, socklen_t len); +extern int mnl_socket_getsockopt(const struct mnl_socket *nl, int type, void *buf, socklen_t *len); + +/* + * Netlink message API + */ + +#define MNL_ALIGNTO 4 +#define MNL_ALIGN(len) (((len)+MNL_ALIGNTO-1) & ~(MNL_ALIGNTO-1)) +#define MNL_NLMSG_HDRLEN MNL_ALIGN(sizeof(struct nlmsghdr)) + +extern size_t mnl_nlmsg_size(size_t len); +extern size_t mnl_nlmsg_get_payload_len(const struct nlmsghdr *nlh); + +/* Netlink message header builder */ +extern struct nlmsghdr *mnl_nlmsg_put_header(void *buf); +extern void *mnl_nlmsg_put_extra_header(struct nlmsghdr *nlh, size_t size); + +/* Netlink message iterators */ +extern bool mnl_nlmsg_ok(const struct nlmsghdr *nlh, int len); +extern struct nlmsghdr *mnl_nlmsg_next(const struct nlmsghdr *nlh, int *len); + +/* Netlink sequence tracking */ +extern bool mnl_nlmsg_seq_ok(const struct nlmsghdr *nlh, unsigned int seq); + +/* Netlink portID checking */ +extern bool mnl_nlmsg_portid_ok(const struct nlmsghdr *nlh, unsigned int portid); + +/* Netlink message getters */ +extern void *mnl_nlmsg_get_payload(const struct nlmsghdr *nlh); +extern void *mnl_nlmsg_get_payload_offset(const struct nlmsghdr *nlh, size_t offset); +extern void *mnl_nlmsg_get_payload_tail(const struct nlmsghdr *nlh); + +/* Netlink message printer */ +extern void mnl_nlmsg_fprintf(FILE *fd, const void *data, size_t datalen, size_t extra_header_size); + +/* Message batch helpers */ +struct mnl_nlmsg_batch; +extern struct mnl_nlmsg_batch *mnl_nlmsg_batch_start(void *buf, size_t bufsiz); +extern bool mnl_nlmsg_batch_next(struct mnl_nlmsg_batch *b); +extern void mnl_nlmsg_batch_stop(struct mnl_nlmsg_batch *b); +extern size_t mnl_nlmsg_batch_size(struct mnl_nlmsg_batch *b); +extern void mnl_nlmsg_batch_reset(struct mnl_nlmsg_batch *b); +extern void *mnl_nlmsg_batch_head(struct mnl_nlmsg_batch *b); +extern void *mnl_nlmsg_batch_current(struct mnl_nlmsg_batch *b); +extern bool mnl_nlmsg_batch_is_empty(struct mnl_nlmsg_batch *b); + +/* + * Netlink attributes API + */ +#define MNL_ATTR_HDRLEN MNL_ALIGN(sizeof(struct nlattr)) + +/* TLV attribute getters */ +extern uint16_t mnl_attr_get_type(const struct nlattr *attr); +extern uint16_t mnl_attr_get_len(const struct nlattr *attr); +extern uint16_t mnl_attr_get_payload_len(const struct nlattr *attr); +extern void *mnl_attr_get_payload(const struct nlattr *attr); +extern uint8_t mnl_attr_get_u8(const struct nlattr *attr); +extern uint16_t mnl_attr_get_u16(const struct nlattr *attr); +extern uint32_t mnl_attr_get_u32(const struct nlattr *attr); +extern uint64_t mnl_attr_get_u64(const struct nlattr *attr); +extern const char *mnl_attr_get_str(const struct nlattr *attr); + +/* TLV attribute putters */ +extern void mnl_attr_put(struct nlmsghdr *nlh, uint16_t type, size_t len, const void *data); +extern void mnl_attr_put_u8(struct nlmsghdr *nlh, uint16_t type, uint8_t data); +extern void mnl_attr_put_u16(struct nlmsghdr *nlh, uint16_t type, uint16_t data); +extern void mnl_attr_put_u32(struct nlmsghdr *nlh, uint16_t type, uint32_t data); +extern void mnl_attr_put_u64(struct nlmsghdr *nlh, uint16_t type, uint64_t data); +extern void mnl_attr_put_str(struct nlmsghdr *nlh, uint16_t type, const char *data); +extern void mnl_attr_put_strz(struct nlmsghdr *nlh, uint16_t type, const char *data); + +/* TLV attribute putters with buffer boundary checkings */ +extern bool mnl_attr_put_check(struct nlmsghdr *nlh, size_t buflen, uint16_t type, size_t len, const void *data); +extern bool mnl_attr_put_u8_check(struct nlmsghdr *nlh, size_t buflen, uint16_t type, uint8_t data); +extern bool mnl_attr_put_u16_check(struct nlmsghdr *nlh, size_t buflen, uint16_t type, uint16_t data); +extern bool mnl_attr_put_u32_check(struct nlmsghdr *nlh, size_t buflen, uint16_t type, uint32_t data); +extern bool mnl_attr_put_u64_check(struct nlmsghdr *nlh, size_t buflen, uint16_t type, uint64_t data); +extern bool mnl_attr_put_str_check(struct nlmsghdr *nlh, size_t buflen, uint16_t type, const char *data); +extern bool mnl_attr_put_strz_check(struct nlmsghdr *nlh, size_t buflen, uint16_t type, const char *data); + +/* TLV attribute nesting */ +extern struct nlattr *mnl_attr_nest_start(struct nlmsghdr *nlh, uint16_t type); +extern struct nlattr *mnl_attr_nest_start_check(struct nlmsghdr *nlh, size_t buflen, uint16_t type); +extern void mnl_attr_nest_end(struct nlmsghdr *nlh, struct nlattr *start); +extern void mnl_attr_nest_cancel(struct nlmsghdr *nlh, struct nlattr *start); + +/* TLV validation */ +extern int mnl_attr_type_valid(const struct nlattr *attr, uint16_t maxtype); + +enum mnl_attr_data_type { + MNL_TYPE_UNSPEC, + MNL_TYPE_U8, + MNL_TYPE_U16, + MNL_TYPE_U32, + MNL_TYPE_U64, + MNL_TYPE_STRING, + MNL_TYPE_FLAG, + MNL_TYPE_MSECS, + MNL_TYPE_NESTED, + MNL_TYPE_NESTED_COMPAT, + MNL_TYPE_NUL_STRING, + MNL_TYPE_BINARY, + MNL_TYPE_MAX, +}; + +extern int mnl_attr_validate(const struct nlattr *attr, enum mnl_attr_data_type type); +extern int mnl_attr_validate2(const struct nlattr *attr, enum mnl_attr_data_type type, size_t len); + +/* TLV iterators */ +extern bool mnl_attr_ok(const struct nlattr *attr, int len); +extern struct nlattr *mnl_attr_next(const struct nlattr *attr); + +#define mnl_attr_for_each(attr, nlh, offset) \ + for ((attr) = mnl_nlmsg_get_payload_offset((nlh), (offset)); \ + mnl_attr_ok((attr), (char *)mnl_nlmsg_get_payload_tail(nlh) - (char *)(attr)); \ + (attr) = mnl_attr_next(attr)) + +#define mnl_attr_for_each_nested(attr, nest) \ + for ((attr) = mnl_attr_get_payload(nest); \ + mnl_attr_ok((attr), (char *)mnl_attr_get_payload(nest) + mnl_attr_get_payload_len(nest) - (char *)(attr)); \ + (attr) = mnl_attr_next(attr)) + +#define mnl_attr_for_each_payload(payload, payload_size) \ + for ((attr) = (payload); \ + mnl_attr_ok((attr), (char *)(payload) + payload_size - (char *)(attr)); \ + (attr) = mnl_attr_next(attr)) + +/* TLV callback-based attribute parsers */ +typedef int (*mnl_attr_cb_t)(const struct nlattr *attr, void *data); + +extern int mnl_attr_parse(const struct nlmsghdr *nlh, unsigned int offset, mnl_attr_cb_t cb, void *data); +extern int mnl_attr_parse_nested(const struct nlattr *attr, mnl_attr_cb_t cb, void *data); +extern int mnl_attr_parse_payload(const void *payload, size_t payload_len, mnl_attr_cb_t cb, void *data); + +/* + * callback API + */ +#define MNL_CB_ERROR -1 +#define MNL_CB_STOP 0 +#define MNL_CB_OK 1 + +typedef int (*mnl_cb_t)(const struct nlmsghdr *nlh, void *data); + +extern int mnl_cb_run(const void *buf, size_t numbytes, unsigned int seq, + unsigned int portid, mnl_cb_t cb_data, void *data); + +extern int mnl_cb_run2(const void *buf, size_t numbytes, unsigned int seq, + unsigned int portid, mnl_cb_t cb_data, void *data, + const mnl_cb_t *cb_ctl_array, + unsigned int cb_ctl_array_len); + +/* + * other declarations + */ + +#ifndef SOL_NETLINK +#define SOL_NETLINK 270 +#endif + +#ifndef MNL_ARRAY_SIZE +#define MNL_ARRAY_SIZE(a) (sizeof(a)/sizeof((a)[0])) +#endif + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/root/package/link4all/quectel-CM/quectel1.6.0.15_src/libmnl/nlmsg.c b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/libmnl/nlmsg.c new file mode 100644 index 00000000..d960cf33 --- /dev/null +++ b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/libmnl/nlmsg.c @@ -0,0 +1,556 @@ +/* + * (C) 2008-2010 by Pablo Neira Ayuso + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + */ +#include +#include +#include +#include +#include +#include + +#include "libmnl.h" + +/** + * \defgroup nlmsg Netlink message helpers + * + * Netlink message: + * \verbatim + |<----------------- 4 bytes ------------------->| + |<----- 2 bytes ------>|<------- 2 bytes ------>| + |-----------------------------------------------| + | Message length (including header) | + |-----------------------------------------------| + | Message type | Message flags | + |-----------------------------------------------| + | Message sequence number | + |-----------------------------------------------| + | Netlink PortID | + |-----------------------------------------------| + | | + . Payload . + |_______________________________________________| +\endverbatim + * + * There is usually an extra header after the the Netlink header (at the + * beginning of the payload). This extra header is specific of the Netlink + * subsystem. After this extra header, it comes the sequence of attributes + * that are expressed in Type-Length-Value (TLV) format. + * + * @{ + */ + +/** + * mnl_nlmsg_size - calculate the size of Netlink message (without alignment) + * \param len length of the Netlink payload + * + * This function returns the size of a netlink message (header plus payload) + * without alignment. + */ +size_t mnl_nlmsg_size(size_t len) +{ + return len + MNL_NLMSG_HDRLEN; +} + +/** + * mnl_nlmsg_get_payload_len - get the length of the Netlink payload + * \param nlh pointer to the header of the Netlink message + * + * This function returns the Length of the netlink payload, ie. the length + * of the full message minus the size of the Netlink header. + */ +size_t mnl_nlmsg_get_payload_len(const struct nlmsghdr *nlh) +{ + return nlh->nlmsg_len - MNL_NLMSG_HDRLEN; +} + +/** + * mnl_nlmsg_put_header - reserve and prepare room for Netlink header + * \param buf memory already allocated to store the Netlink header + * + * This function sets to zero the room that is required to put the Netlink + * header in the memory buffer passed as parameter. This function also + * initializes the nlmsg_len field to the size of the Netlink header. This + * function returns a pointer to the Netlink header structure. + */ +struct nlmsghdr *mnl_nlmsg_put_header(void *buf) +{ + int len = MNL_ALIGN(sizeof(struct nlmsghdr)); + struct nlmsghdr *nlh = buf; + + memset(buf, 0, len); + nlh->nlmsg_len = len; + return nlh; +} + +/** + * mnl_nlmsg_put_extra_header - reserve and prepare room for an extra header + * \param nlh pointer to Netlink header + * \param size size of the extra header that we want to put + * + * This function sets to zero the room that is required to put the extra + * header after the initial Netlink header. This function also increases + * the nlmsg_len field. You have to invoke mnl_nlmsg_put_header() before + * you call this function. This function returns a pointer to the extra + * header. + */ +void *mnl_nlmsg_put_extra_header(struct nlmsghdr *nlh, + size_t size) +{ + char *ptr = (char *)nlh + nlh->nlmsg_len; + size_t len = MNL_ALIGN(size); + nlh->nlmsg_len += len; + memset(ptr, 0, len); + return ptr; +} + +/** + * mnl_nlmsg_get_payload - get a pointer to the payload of the netlink message + * \param nlh pointer to a netlink header + * + * This function returns a pointer to the payload of the netlink message. + */ +void *mnl_nlmsg_get_payload(const struct nlmsghdr *nlh) +{ + return (void *)nlh + MNL_NLMSG_HDRLEN; +} + +/** + * mnl_nlmsg_get_payload_offset - get a pointer to the payload of the message + * \param nlh pointer to a netlink header + * \param offset offset to the payload of the attributes TLV set + * + * This function returns a pointer to the payload of the netlink message plus + * a given offset. + */ +void *mnl_nlmsg_get_payload_offset(const struct nlmsghdr *nlh, + size_t offset) +{ + return (void *)nlh + MNL_NLMSG_HDRLEN + MNL_ALIGN(offset); +} + +/** + * mnl_nlmsg_ok - check a there is room for netlink message + * \param nlh netlink message that we want to check + * \param len remaining bytes in a buffer that contains the netlink message + * + * This function is used to check that a buffer that contains a netlink + * message has enough room for the netlink message that it stores, ie. this + * function can be used to verify that a netlink message is not malformed nor + * truncated. + * + * This function does not set errno in case of error since it is intended + * for iterations. Thus, it returns true on success and false on error. + * + * The len parameter may become negative in malformed messages during message + * iteration, that is why we use a signed integer. + */ +bool mnl_nlmsg_ok(const struct nlmsghdr *nlh, int len) +{ + return len >= (int)sizeof(struct nlmsghdr) && + nlh->nlmsg_len >= sizeof(struct nlmsghdr) && + (int)nlh->nlmsg_len <= len; +} + +/** + * mnl_nlmsg_next - get the next netlink message in a multipart message + * \param nlh current netlink message that we are handling + * \param len length of the remaining bytes in the buffer (passed by reference). + * + * This function returns a pointer to the next netlink message that is part + * of a multi-part netlink message. Netlink can batch several messages into + * one buffer so that the receiver has to iterate over the whole set of + * Netlink messages. + * + * You have to use mnl_nlmsg_ok() to check if the next Netlink message is + * valid. + */ +struct nlmsghdr *mnl_nlmsg_next(const struct nlmsghdr *nlh, + int *len) +{ + *len -= MNL_ALIGN(nlh->nlmsg_len); + return (struct nlmsghdr *)((void *)nlh + MNL_ALIGN(nlh->nlmsg_len)); +} + +/** + * mnl_nlmsg_get_payload_tail - get the ending of the netlink message + * \param nlh pointer to netlink message + * + * This function returns a pointer to the netlink message tail. This is useful + * to build a message since we continue adding attributes at the end of the + * message. + */ +void *mnl_nlmsg_get_payload_tail(const struct nlmsghdr *nlh) +{ + return (void *)nlh + MNL_ALIGN(nlh->nlmsg_len); +} + +/** + * mnl_nlmsg_seq_ok - perform sequence tracking + * \param nlh current netlink message that we are handling + * \param seq last sequence number used to send a message + * + * This functions returns true if the sequence tracking is fulfilled, otherwise + * false is returned. We skip the tracking for netlink messages whose sequence + * number is zero since it is usually reserved for event-based kernel + * notifications. On the other hand, if seq is set but the message sequence + * number is not set (i.e. this is an event message coming from kernel-space), + * then we also skip the tracking. This approach is good if we use the same + * socket to send commands to kernel-space (that we want to track) and to + * listen to events (that we do not track). + */ +bool mnl_nlmsg_seq_ok(const struct nlmsghdr *nlh, + unsigned int seq) +{ + return nlh->nlmsg_seq && seq ? nlh->nlmsg_seq == seq : true; +} + +/** + * mnl_nlmsg_portid_ok - perform portID origin check + * \param nlh current netlink message that we are handling + * \param portid netlink portid that we want to check + * + * This functions returns true if the origin is fulfilled, otherwise + * false is returned. We skip the tracking for netlink message whose portID + * is zero since it is reserved for event-based kernel notifications. On the + * other hand, if portid is set but the message PortID is not (i.e. this + * is an event message coming from kernel-space), then we also skip the + * tracking. This approach is good if we use the same socket to send commands + * to kernel-space (that we want to track) and to listen to events (that we + * do not track). + */ +bool mnl_nlmsg_portid_ok(const struct nlmsghdr *nlh, + unsigned int portid) +{ + return nlh->nlmsg_pid && portid ? nlh->nlmsg_pid == portid : true; +} + +static void mnl_nlmsg_fprintf_header(FILE *fd, const struct nlmsghdr *nlh) +{ + fprintf(fd, "----------------\t------------------\n"); + fprintf(fd, "| %.010u |\t| message length |\n", nlh->nlmsg_len); + fprintf(fd, "| %.05u | %c%c%c%c |\t| type | flags |\n", + nlh->nlmsg_type, + nlh->nlmsg_flags & NLM_F_REQUEST ? 'R' : '-', + nlh->nlmsg_flags & NLM_F_MULTI ? 'M' : '-', + nlh->nlmsg_flags & NLM_F_ACK ? 'A' : '-', + nlh->nlmsg_flags & NLM_F_ECHO ? 'E' : '-'); + fprintf(fd, "| %.010u |\t| sequence number|\n", nlh->nlmsg_seq); + fprintf(fd, "| %.010u |\t| port ID |\n", nlh->nlmsg_pid); + fprintf(fd, "----------------\t------------------\n"); +} + +static void mnl_nlmsg_fprintf_payload(FILE *fd, const struct nlmsghdr *nlh, + size_t extra_header_size) +{ + int rem = 0; + unsigned int i; + + for (i=sizeof(struct nlmsghdr); inlmsg_len; i+=4) { + char *b = (char *) nlh; + struct nlattr *attr = (struct nlattr *) (b+i); + + /* netlink control message. */ + if (nlh->nlmsg_type < NLMSG_MIN_TYPE) { + fprintf(fd, "| %.2x %.2x %.2x %.2x |\t", + 0xff & b[i], 0xff & b[i+1], + 0xff & b[i+2], 0xff & b[i+3]); + fprintf(fd, "| |\n"); + /* special handling for the extra header. */ + } else if (extra_header_size > 0) { + extra_header_size -= 4; + fprintf(fd, "| %.2x %.2x %.2x %.2x |\t", + 0xff & b[i], 0xff & b[i+1], + 0xff & b[i+2], 0xff & b[i+3]); + fprintf(fd, "| extra header |\n"); + /* this seems like an attribute header. */ + } else if (rem == 0 && (attr->nla_type & NLA_TYPE_MASK) != 0) { + fprintf(fd, "|%c[%d;%dm" + "%.5u" + "%c[%dm" + "|" + "%c[%d;%dm" + "%c%c" + "%c[%dm" + "|" + "%c[%d;%dm" + "%.5u" + "%c[%dm|\t", + 27, 1, 31, + attr->nla_len, + 27, 0, + 27, 1, 32, + attr->nla_type & NLA_F_NESTED ? 'N' : '-', + attr->nla_type & + NLA_F_NET_BYTEORDER ? 'B' : '-', + 27, 0, + 27, 1, 34, + attr->nla_type & NLA_TYPE_MASK, + 27, 0); + fprintf(fd, "|len |flags| type|\n"); + + if (!(attr->nla_type & NLA_F_NESTED)) { + rem = NLA_ALIGN(attr->nla_len) - + sizeof(struct nlattr); + } + /* this is the attribute payload. */ + } else if (rem > 0) { + rem -= 4; + fprintf(fd, "| %.2x %.2x %.2x %.2x |\t", + 0xff & b[i], 0xff & b[i+1], + 0xff & b[i+2], 0xff & b[i+3]); + fprintf(fd, "| data |"); + fprintf(fd, "\t %c %c %c %c\n", + isprint(b[i]) ? b[i] : ' ', + isprint(b[i+1]) ? b[i+1] : ' ', + isprint(b[i+2]) ? b[i+2] : ' ', + isprint(b[i+3]) ? b[i+3] : ' '); + } + } + fprintf(fd, "----------------\t------------------\n"); +} + +/** + * mnl_nlmsg_fprintf - print netlink message to file + * \param fd pointer to file type + * \param data pointer to the buffer that contains messages to be printed + * \param datalen length of data stored in the buffer + * \param extra_header_size size of the extra header (if any) + * + * This function prints the netlink header to a file handle. + * It may be useful for debugging purposes. One example of the output + * is the following: + * + *\verbatim +---------------- ------------------ +| 0000000040 | | message length | +| 00016 | R-A- | | type | flags | +| 1289148991 | | sequence number| +| 0000000000 | | port ID | +---------------- ------------------ +| 00 00 00 00 | | extra header | +| 00 00 00 00 | | extra header | +| 01 00 00 00 | | extra header | +| 01 00 00 00 | | extra header | +|00008|--|00003| |len |flags| type| +| 65 74 68 30 | | data | e t h 0 +---------------- ------------------ +\endverbatim + * + * This example above shows the netlink message that is send to kernel-space + * to set up the link interface eth0. The netlink and attribute header data + * are displayed in base 10 whereas the extra header and the attribute payload + * are expressed in base 16. The possible flags in the netlink header are: + * + * - R, that indicates that NLM_F_REQUEST is set. + * - M, that indicates that NLM_F_MULTI is set. + * - A, that indicates that NLM_F_ACK is set. + * - E, that indicates that NLM_F_ECHO is set. + * + * The lack of one flag is displayed with '-'. On the other hand, the possible + * attribute flags available are: + * + * - N, that indicates that NLA_F_NESTED is set. + * - B, that indicates that NLA_F_NET_BYTEORDER is set. + */ +void mnl_nlmsg_fprintf(FILE *fd, const void *data, size_t datalen, + size_t extra_header_size) +{ + const struct nlmsghdr *nlh = data; + int len = datalen; + + while (mnl_nlmsg_ok(nlh, len)) { + mnl_nlmsg_fprintf_header(fd, nlh); + mnl_nlmsg_fprintf_payload(fd, nlh, extra_header_size); + nlh = mnl_nlmsg_next(nlh, &len); + } +} + +/** + * @} + */ + +/** + * \defgroup batch Netlink message batch helpers + * + * This library provides helpers to batch several messages into one single + * datagram. These helpers do not perform strict memory boundary checkings. + * + * The following figure represents a Netlink message batch: + * + * |<-------------- MNL_SOCKET_BUFFER_SIZE ------------->| + * |<-------------------- batch ------------------>| | + * |-----------|-----------|-----------|-----------|-----------| + * |<- nlmsg ->|<- nlmsg ->|<- nlmsg ->|<- nlmsg ->|<- nlmsg ->| + * |-----------|-----------|-----------|-----------|-----------| + * ^ ^ + * | | + * message N message N+1 + * + * To start the batch, you have to call mnl_nlmsg_batch_start() and you can + * use mnl_nlmsg_batch_stop() to release it. + * + * You have to invoke mnl_nlmsg_batch_next() to get room for a new message + * in the batch. If this function returns NULL, it means that the last + * message that was added (message N+1 in the figure above) does not fit the + * batch. Thus, you have to send the batch (which includes until message N) + * and, then, you have to call mnl_nlmsg_batch_reset() to re-initialize + * the batch (this moves message N+1 to the head of the buffer). For that + * reason, the buffer that you have to use to store the batch must be double + * of MNL_SOCKET_BUFFER_SIZE to ensure that the last message (message N+1) + * that did not fit into the batch is written inside valid memory boundaries. + * + * @{ + */ + +struct mnl_nlmsg_batch { + /* the buffer that is used to store the batch. */ + void *buf; + size_t limit; + size_t buflen; + /* the current netlink message in the batch. */ + void *cur; + bool overflow; +}; + +/** + * mnl_nlmsg_batch_start - initialize a batch + * \param buf pointer to the buffer that will store this batch + * \param limit maximum size of the batch (should be MNL_SOCKET_BUFFER_SIZE). + * + * The buffer that you pass must be double of MNL_SOCKET_BUFFER_SIZE. The + * limit must be half of the buffer size, otherwise expect funny memory + * corruptions 8-). + * + * You can allocate the buffer that you use to store the batch in the stack or + * the heap, no restrictions in this regard. This function returns NULL on + * error. + */ +struct mnl_nlmsg_batch *mnl_nlmsg_batch_start(void *buf, + size_t limit) +{ + struct mnl_nlmsg_batch *b; + + b = malloc(sizeof(struct mnl_nlmsg_batch)); + if (b == NULL) + return NULL; + + b->buf = buf; + b->limit = limit; + b->buflen = 0; + b->cur = buf; + b->overflow = false; + + return b; +} + +/** + * mnl_nlmsg_batch_stop - release a batch + * \param b pointer to batch + * + * This function releases the batch allocated by mnl_nlmsg_batch_start(). + */ +void mnl_nlmsg_batch_stop(struct mnl_nlmsg_batch *b) +{ + free(b); +} + +/** + * mnl_nlmsg_batch_next - get room for the next message in the batch + * \param b pointer to batch + * + * This function returns false if the last message did not fit into the + * batch. Otherwise, it prepares the batch to provide room for the new + * Netlink message in the batch and returns true. + * + * You have to put at least one message in the batch before calling this + * function, otherwise your application is likely to crash. + */ +bool mnl_nlmsg_batch_next(struct mnl_nlmsg_batch *b) +{ + struct nlmsghdr *nlh = b->cur; + + if (b->buflen + nlh->nlmsg_len > b->limit) { + b->overflow = true; + return false; + } + b->cur = b->buf + b->buflen + nlh->nlmsg_len; + b->buflen += nlh->nlmsg_len; + return true; +} + +/** + * mnl_nlmsg_batch_reset - reset the batch + * \param b pointer to batch + * + * This function allows to reset a batch, so you can reuse it to create a + * new one. This function moves the last message which does not fit the + * batch to the head of the buffer, if any. + */ +void mnl_nlmsg_batch_reset(struct mnl_nlmsg_batch *b) +{ + if (b->overflow) { + struct nlmsghdr *nlh = b->cur; + memcpy(b->buf, b->cur, nlh->nlmsg_len); + b->buflen = nlh->nlmsg_len; + b->cur = b->buf + b->buflen; + b->overflow = false; + } else { + b->buflen = 0; + b->cur = b->buf; + } +} + +/** + * mnl_nlmsg_batch_size - get current size of the batch + * \param b pointer to batch + * + * This function returns the current size of the batch. + */ +size_t mnl_nlmsg_batch_size(struct mnl_nlmsg_batch *b) +{ + return b->buflen; +} + +/** + * mnl_nlmsg_batch_head - get head of this batch + * \param b pointer to batch + * + * This function returns a pointer to the head of the batch, which is the + * beginning of the buffer that is used. + */ +void *mnl_nlmsg_batch_head(struct mnl_nlmsg_batch *b) +{ + return b->buf; +} + +/** + * mnl_nlmsg_batch_current - returns current position in the batch + * \param b pointer to batch + * + * This function returns a pointer to the current position in the buffer + * that is used to store the batch. + */ +void *mnl_nlmsg_batch_current(struct mnl_nlmsg_batch *b) +{ + return b->cur; +} + +/** + * mnl_nlmsg_batch_is_empty - check if there is any message in the batch + * \param b pointer to batch + * + * This function returns true if the batch is empty. + */ +bool mnl_nlmsg_batch_is_empty(struct mnl_nlmsg_batch *b) +{ + return b->buflen == 0; +} + +/** + * @} + */ diff --git a/root/package/link4all/quectel-CM/quectel1.6.0.15_src/libmnl/socket.c b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/libmnl/socket.c new file mode 100644 index 00000000..dd5ab664 --- /dev/null +++ b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/libmnl/socket.c @@ -0,0 +1,351 @@ +/* + * (C) 2008-2010 by Pablo Neira Ayuso + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include + +#include "libmnl.h" + +/** + * \mainpage + * + * libmnl is a minimalistic user-space library oriented to Netlink developers. + * There are a lot of common tasks in parsing, validating, constructing of + * both the Netlink header and TLVs that are repetitive and easy to get wrong. + * This library aims to provide simple helpers that allows you to avoid + * re-inventing the wheel in common Netlink tasks. + * + * \verbatim +"Simplify, simplify" -- Henry David Thoureau. Walden (1854) +\endverbatim + * + * The acronym libmnl stands for LIBrary Minimalistic NetLink. + * + * libmnl homepage is: + * http://www.netfilter.org/projects/libmnl/ + * + * \section features Main Features + * - Small: the shared library requires around 30KB for an x86-based computer. + * - Simple: this library avoids complex abstractions that tend to hide Netlink + * details. It avoids elaborated object-oriented infrastructure and complex + * callback-based workflow. + * - Easy to use: the library simplifies the work for Netlink-wise developers. + * It provides functions to make socket handling, message building, + * validating, parsing and sequence tracking, easier. + * - Easy to re-use: you can use this library to build your own abstraction + * layer upon this library, if you want to provide another library that + * hides Netlink details to your users. + * - Decoupling: the interdependency of the main bricks that compose this + * library is reduced, i.e. the library provides many helpers, but the + * programmer is not forced to use them. + * + * \section licensing Licensing terms + * This library is released under the LGPLv2.1 or any later (at your option). + * + * \section Dependencies + * You have to install the Linux kernel headers that you want to use to develop + * your application. Moreover, this library requires that you have some basics + * on Netlink. + * + * \section scm Git Tree + * The current development version of libmnl can be accessed at: + * http://git.netfilter.org/cgi-bin/gitweb.cgi?p=libmnl.git;a=summary + * + * \section using Using libmnl + * You can access several example files under examples/ in the libmnl source + * code tree. + */ + +struct mnl_socket { + int fd; + struct sockaddr_nl addr; +}; + +/** + * \defgroup socket Netlink socket helpers + * @{ + */ + +/** + * mnl_socket_get_fd - obtain file descriptor from netlink socket + * \param nl netlink socket obtained via mnl_socket_open() + * + * This function returns the file descriptor of a given netlink socket. + */ +int mnl_socket_get_fd(const struct mnl_socket *nl) +{ + return nl->fd; +} + +/** + * mnl_socket_get_portid - obtain Netlink PortID from netlink socket + * \param nl netlink socket obtained via mnl_socket_open() + * + * This function returns the Netlink PortID of a given netlink socket. + * It's a common mistake to assume that this PortID equals the process ID + * which is not always true. This is the case if you open more than one + * socket that is binded to the same Netlink subsystem from the same process. + */ +unsigned int mnl_socket_get_portid(const struct mnl_socket *nl) +{ + return nl->addr.nl_pid; +} + +static struct mnl_socket *__mnl_socket_open(int bus, int flags) +{ + struct mnl_socket *nl; + + nl = calloc(1, sizeof(struct mnl_socket)); + if (nl == NULL) + return NULL; + + nl->fd = socket(AF_NETLINK, SOCK_RAW | flags, bus); + if (nl->fd == -1) { + free(nl); + return NULL; + } + + return nl; +} + +/** + * mnl_socket_open - open a netlink socket + * \param bus the netlink socket bus ID (see NETLINK_* constants) + * + * On error, it returns NULL and errno is appropriately set. Otherwise, it + * returns a valid pointer to the mnl_socket structure. + */ +struct mnl_socket *mnl_socket_open(int bus) +{ + return __mnl_socket_open(bus, 0); +} + +/** + * mnl_socket_open2 - open a netlink socket with appropriate flags + * \param bus the netlink socket bus ID (see NETLINK_* constants) + * \param flags the netlink socket flags (see SOCK_* constants in socket(2)) + * + * This is similar to mnl_socket_open(), but allows to set flags like + * SOCK_CLOEXEC at socket creation time (useful for multi-threaded programs + * performing exec calls). + * + * On error, it returns NULL and errno is appropriately set. Otherwise, it + * returns a valid pointer to the mnl_socket structure. + */ +struct mnl_socket *mnl_socket_open2(int bus, int flags) +{ + return __mnl_socket_open(bus, flags); +} + +/** + * mnl_socket_fdopen - associates a mnl_socket object with pre-existing socket. + * \param fd pre-existing socket descriptor. + * + * On error, it returns NULL and errno is appropriately set. Otherwise, it + * returns a valid pointer to the mnl_socket structure. It also sets the portID + * if the socket fd is already bound and it is AF_NETLINK. + * + * Note that mnl_socket_get_portid() returns 0 if this function is used with + * non-netlink socket. + */ +struct mnl_socket *mnl_socket_fdopen(int fd) +{ + int ret; + struct mnl_socket *nl; + struct sockaddr_nl addr; + socklen_t addr_len = sizeof(struct sockaddr_nl); + + ret = getsockname(fd, (struct sockaddr *) &addr, &addr_len); + if (ret == -1) + return NULL; + + nl = calloc(1, sizeof(struct mnl_socket)); + if (nl == NULL) + return NULL; + + nl->fd = fd; + if (addr.nl_family == AF_NETLINK) + nl->addr = addr; + + return nl; +} + +/** + * mnl_socket_bind - bind netlink socket + * \param nl netlink socket obtained via mnl_socket_open() + * \param groups the group of message you're interested in + * \param pid the port ID you want to use (use zero for automatic selection) + * + * On error, this function returns -1 and errno is appropriately set. On + * success, 0 is returned. You can use MNL_SOCKET_AUTOPID which is 0 for + * automatic port ID selection. + */ +int mnl_socket_bind(struct mnl_socket *nl, unsigned int groups, + pid_t pid) +{ + int ret; + socklen_t addr_len; + + nl->addr.nl_family = AF_NETLINK; + nl->addr.nl_groups = groups; + nl->addr.nl_pid = pid; + + ret = bind(nl->fd, (struct sockaddr *) &nl->addr, sizeof (nl->addr)); + if (ret < 0) + return ret; + + addr_len = sizeof(nl->addr); + ret = getsockname(nl->fd, (struct sockaddr *) &nl->addr, &addr_len); + if (ret < 0) + return ret; + + if (addr_len != sizeof(nl->addr)) { + errno = EINVAL; + return -1; + } + if (nl->addr.nl_family != AF_NETLINK) { + errno = EINVAL; + return -1; + } + return 0; +} + +/** + * mnl_socket_sendto - send a netlink message of a certain size + * \param nl netlink socket obtained via mnl_socket_open() + * \param buf buffer containing the netlink message to be sent + * \param len number of bytes in the buffer that you want to send + * + * On error, it returns -1 and errno is appropriately set. Otherwise, it + * returns the number of bytes sent. + */ +ssize_t mnl_socket_sendto(const struct mnl_socket *nl, + const void *buf, size_t len) +{ + static const struct sockaddr_nl snl = { + .nl_family = AF_NETLINK + }; + return sendto(nl->fd, buf, len, 0, + (struct sockaddr *) &snl, sizeof(snl)); +} + +/** + * mnl_socket_recvfrom - receive a netlink message + * \param nl netlink socket obtained via mnl_socket_open() + * \param buf buffer that you want to use to store the netlink message + * \param bufsiz size of the buffer passed to store the netlink message + * + * On error, it returns -1 and errno is appropriately set. If errno is set + * to ENOSPC, it means that the buffer that you have passed to store the + * netlink message is too small, so you have received a truncated message. + * To avoid this, you have to allocate a buffer of MNL_SOCKET_BUFFER_SIZE + * (which is 8KB, see linux/netlink.h for more information). Using this + * buffer size ensures that your buffer is big enough to store the netlink + * message without truncating it. + */ +ssize_t mnl_socket_recvfrom(const struct mnl_socket *nl, + void *buf, size_t bufsiz) +{ + ssize_t ret; + struct sockaddr_nl addr; + struct iovec iov = { + .iov_base = buf, + .iov_len = bufsiz, + }; + struct msghdr msg = { + .msg_name = &addr, + .msg_namelen = sizeof(struct sockaddr_nl), + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = NULL, + .msg_controllen = 0, + .msg_flags = 0, + }; + ret = recvmsg(nl->fd, &msg, 0); + if (ret == -1) + return ret; + + if (msg.msg_flags & MSG_TRUNC) { + errno = ENOSPC; + return -1; + } + if (msg.msg_namelen != sizeof(struct sockaddr_nl)) { + errno = EINVAL; + return -1; + } + return ret; +} + +/** + * mnl_socket_close - close a given netlink socket + * \param nl netlink socket obtained via mnl_socket_open() + * + * On error, this function returns -1 and errno is appropriately set. + * On success, it returns 0. + */ +int mnl_socket_close(struct mnl_socket *nl) +{ + int ret = close(nl->fd); + free(nl); + return ret; +} + +/** + * mnl_socket_setsockopt - set Netlink socket option + * \param nl netlink socket obtained via mnl_socket_open() + * \param type type of Netlink socket options + * \param buf the buffer that contains the data about this option + * \param len the size of the buffer passed + * + * This function allows you to set some Netlink socket option. As of writing + * this (see linux/netlink.h), the existing options are: + * + * - \#define NETLINK_ADD_MEMBERSHIP 1 + * - \#define NETLINK_DROP_MEMBERSHIP 2 + * - \#define NETLINK_PKTINFO 3 + * - \#define NETLINK_BROADCAST_ERROR 4 + * - \#define NETLINK_NO_ENOBUFS 5 + * + * In the early days, Netlink only supported 32 groups expressed in a + * 32-bits mask. However, since 2.6.14, Netlink may have up to 2^32 multicast + * groups but you have to use setsockopt() with NETLINK_ADD_MEMBERSHIP to + * join a given multicast group. This function internally calls setsockopt() + * to join a given netlink multicast group. You can still use mnl_bind() + * and the 32-bit mask to join a set of Netlink multicast groups. + * + * On error, this function returns -1 and errno is appropriately set. + */ +int mnl_socket_setsockopt(const struct mnl_socket *nl, int type, + void *buf, socklen_t len) +{ + return setsockopt(nl->fd, SOL_NETLINK, type, buf, len); +} + +/** + * mnl_socket_getsockopt - get a Netlink socket option + * \param nl netlink socket obtained via mnl_socket_open() + * \param type type of Netlink socket options + * \param buf pointer to the buffer to store the value of this option + * \param len size of the information written in the buffer + * + * On error, this function returns -1 and errno is appropriately set. + */ +int mnl_socket_getsockopt(const struct mnl_socket *nl, int type, + void *buf, socklen_t *len) +{ + return getsockopt(nl->fd, SOL_NETLINK, type, buf, len); +} + +/** + * @} + */ diff --git a/root/package/link4all/quectel-CM/quectel1.6.0.15_src/main.c b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/main.c new file mode 100644 index 00000000..c082b301 --- /dev/null +++ b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/main.c @@ -0,0 +1,947 @@ +/****************************************************************************** + @file main.c + @brief The entry program. + + DESCRIPTION + Connectivity Management Tool for USB network adapter of Quectel wireless cellular modules. + + INITIALIZATION AND SEQUENCING REQUIREMENTS + None. + + --------------------------------------------------------------------------- + Copyright (c) 2016 -2020 Quectel Wireless Solution, Co., Ltd. All Rights Reserved. + Quectel Wireless Solution Proprietary and Confidential. + --------------------------------------------------------------------------- +******************************************************************************/ + +#include "QMIThread.h" +#include +#include +#include +#include + +#include "util.h" +//#define CONFIG_EXIT_WHEN_DIAL_FAILED +//#define CONFIG_BACKGROUND_WHEN_GET_IP +//#define CONFIG_PID_FILE_FORMAT "/var/run/quectel-CM-%s.pid" //for example /var/run/quectel-CM-wwan0.pid + +int debug_qmi = 0; +int main_loop = 0; +int qmidevice_control_fd[2]; +static int signal_control_fd[2]; + +extern const struct qmi_device_ops gobi_qmidev_ops; +extern const struct qmi_device_ops qmiwwan_qmidev_ops; +extern int ql_ifconfig(int argc, char *argv[]); +extern int ql_get_netcard_driver_info(const char*); +extern int ql_capture_usbmon_log(PROFILE_T *profile, const char *log_path); +extern void ql_stop_usbmon_log(PROFILE_T *profile); + +#ifdef CONFIG_BACKGROUND_WHEN_GET_IP +static int daemon_pipe_fd[2]; + +static void ql_prepare_daemon(void) { + pid_t daemon_child_pid; + + if (pipe(daemon_pipe_fd) < 0) { + dbg_time("%s Faild to create daemon_pipe_fd: %d (%s)", __func__, errno, strerror(errno)); + return; + } + + daemon_child_pid = fork(); + if (daemon_child_pid > 0) { + struct pollfd pollfds[] = {{daemon_pipe_fd[0], POLLIN, 0}, {0, POLLIN, 0}}; + int ne, ret, nevents = sizeof(pollfds)/sizeof(pollfds[0]); + int signo; + + //dbg_time("father"); + + close(daemon_pipe_fd[1]); + + if (socketpair( AF_LOCAL, SOCK_STREAM, 0, signal_control_fd) < 0 ) { + dbg_time("%s Faild to create main_control_fd: %d (%s)", __func__, errno, strerror(errno)); + return; + } + + pollfds[1].fd = signal_control_fd[1]; + + while (1) { + do { + ret = poll(pollfds, nevents, -1); + } while ((ret < 0) && (errno == EINTR)); + + if (ret < 0) { + dbg_time("%s poll=%d, errno: %d (%s)", __func__, ret, errno, strerror(errno)); + goto __daemon_quit; + } + + for (ne = 0; ne < nevents; ne++) { + int fd = pollfds[ne].fd; + short revents = pollfds[ne].revents; + + if (revents & (POLLERR | POLLHUP | POLLNVAL)) { + //dbg_time("%s poll err/hup", __func__); + //dbg_time("poll fd = %d, events = 0x%04x", fd, revents); + if (revents & POLLHUP) + goto __daemon_quit; + } + + if ((revents & POLLIN) && read(fd, &signo, sizeof(signo)) == sizeof(signo)) { + if (signal_control_fd[1] == fd) { + if (signo == SIGCHLD) { + int status; + int pid = waitpid(daemon_child_pid, &status, 0); + dbg_time("waitpid pid=%d, status=%x", pid, status); + goto __daemon_quit; + } else { + kill(daemon_child_pid, signo); + } + } else if (daemon_pipe_fd[0] == fd) { + //dbg_time("daemon_pipe_signo = %d", signo); + goto __daemon_quit; + } + } + } + } +__daemon_quit: + //dbg_time("father exit"); + _exit(0); + } else if (daemon_child_pid == 0) { + close(daemon_pipe_fd[0]); + //dbg_time("child", getpid()); + } else { + close(daemon_pipe_fd[0]); + close(daemon_pipe_fd[1]); + dbg_time("%s Faild to create daemon_child_pid: %d (%s)", __func__, errno, strerror(errno)); + } +} + +static void ql_enter_daemon(int signo) { + if (daemon_pipe_fd[1] > 0) + if (signo) { + write(daemon_pipe_fd[1], &signo, sizeof(signo)); + sleep(1); + } + close(daemon_pipe_fd[1]); + daemon_pipe_fd[1] = -1; + setsid(); + } +#endif + +//UINT ifc_get_addr(const char *ifname); +static int s_link = -1; +static void usbnet_link_state(int state) +{ + s_link = state ? 1 : 0; +} +static void usbnet_link_change(int link, PROFILE_T *profile) { + if (s_link == link) + return; + + s_link = link; + + if (link) { + udhcpc_start(profile); + } else { + udhcpc_stop(profile); + } + +#ifdef CONFIG_BACKGROUND_WHEN_GET_IP + if (link && daemon_pipe_fd[1] > 0) { + int timeout = 6; + while (timeout-- /*&& ifc_get_addr(profile->usbnet_adapter) == 0*/) { + sleep(1); + } + ql_enter_daemon(SIG_EVENT_START); + } +#endif +} + +static int check_ipv4_address(PROFILE_T *now_profile) { + PROFILE_T new_profile_v; + PROFILE_T *new_profile = &new_profile_v; + + memcpy(new_profile, now_profile, sizeof(PROFILE_T)); + if (requestGetIPAddress(new_profile, IpFamilyV4) == 0) { + if (new_profile->ipv4.Address != now_profile->ipv4.Address || debug_qmi) { + unsigned char *l = (unsigned char *)&now_profile->ipv4.Address; + unsigned char *r = (unsigned char *)&new_profile->ipv4.Address; + dbg_time("localIP: %d.%d.%d.%d VS remoteIP: %d.%d.%d.%d", + l[3], l[2], l[1], l[0], r[3], r[2], r[1], r[0]); + } + return (new_profile->ipv4.Address == now_profile->ipv4.Address); + } + return 0; +} + +static void main_send_event_to_qmidevice(int triger_event) { + write(qmidevice_control_fd[0], &triger_event, sizeof(triger_event)); +} + +static void send_signo_to_main(int signo) { + write(signal_control_fd[0], &signo, sizeof(signo)); +} + +void qmidevice_send_event_to_main(int triger_event) { + write(qmidevice_control_fd[1], &triger_event, sizeof(triger_event)); +} + +void qmidevice_send_event_to_main_ext(int triger_event, void *data, unsigned len) { + write(qmidevice_control_fd[1], &triger_event, sizeof(triger_event)); + write(qmidevice_control_fd[1], data, len); +} + +#define MAX_PATH 256 + +static int ls_dir(const char *dir, int (*match)(const char *dir, const char *file, void *argv[]), void *argv[]) +{ + DIR *pDir; + struct dirent* ent = NULL; + int match_times = 0; + + pDir = opendir(dir); + if (pDir == NULL) { + dbg_time("Cannot open directory: %s, errno: %d (%s)", dir, errno, strerror(errno)); + return 0; + } + + while ((ent = readdir(pDir)) != NULL) { + match_times += match(dir, ent->d_name, argv); + } + closedir(pDir); + + return match_times; +} + +static int is_same_linkfile(const char *dir, const char *file, void *argv[]) +{ + const char *qmichannel = (const char *)argv[1]; + char linkname[MAX_PATH]; + char filename[MAX_PATH]; + int linksize; + + snprintf(linkname, MAX_PATH, "%s/%s", dir, file); + linksize = readlink(linkname, filename, MAX_PATH); + if (linksize <= 0) + return 0; + + filename[linksize] = 0; + if (strcmp(filename, qmichannel)) + return 0; + + dbg_time("%s -> %s", linkname, filename); + return 1; +} + +static int is_brother_process(const char *dir, const char *file, void *argv[]) +{ + //const char *myself = (const char *)argv[0]; + char linkname[MAX_PATH]; + char filename[MAX_PATH]; + int linksize; + int i = 0, kill_timeout = 15; + pid_t pid; + + //dbg_time("%s", file); + while (file[i]) { + if (!isdigit(file[i])) + break; + i++; + } + + if (file[i]) { + //dbg_time("%s not digit", file); + return 0; + } + + snprintf(linkname, MAX_PATH, "%s/%s/exe", dir, file); + linksize = readlink(linkname, filename, MAX_PATH); + if (linksize <= 0) + return 0; + + filename[linksize] = 0; + + pid = atoi(file); + if (pid >= getpid()) + return 0; + + snprintf(linkname, MAX_PATH, "%s/%s/fd", dir, file); + if (!ls_dir(linkname, is_same_linkfile, argv)) + return 0; + + dbg_time("%s/%s/exe -> %s", dir, file, filename); + while (kill_timeout-- && !kill(pid, 0)) + { + kill(pid, SIGTERM); + sleep(1); + } + if (!kill(pid, 0)) + { + dbg_time("force kill %s/%s/exe -> %s", dir, file, filename); + kill(pid, SIGKILL); + sleep(1); + } + + return 1; +} + +static int kill_brothers(const char *qmichannel) +{ + char myself[MAX_PATH]; + int filenamesize; + void *argv[2] = {myself, (void *)qmichannel}; + + filenamesize = readlink("/proc/self/exe", myself, MAX_PATH); + if (filenamesize <= 0) + return 0; + myself[filenamesize] = 0; + + if (ls_dir("/proc", is_brother_process, argv)) + sleep(1); + + return 0; +} + +static int kill_data_call_pdp(int pdp, char *self) { + int pid; + char *p = NULL; + + p = self; + while (*self) { + if (*self == '/') + p = self+1; + self++; + } + + pid = getpid_by_pdp(pdp, p); + if (pid > 0) { + dbg_time("send SIGINT to process %d", pid); + return kill(pid, SIGINT); + } + + return -1; +} + +static void ql_sigaction(int signo) { + if (SIGALRM == signo) + send_signo_to_main(SIG_EVENT_START); + else + { + main_loop = 0; + send_signo_to_main(SIG_EVENT_STOP); + main_send_event_to_qmidevice(SIG_EVENT_STOP); //main may be wating qmi response + } +} + +pthread_t gQmiThreadID; + +static int usage(const char *progname) { + dbg_time("Usage: %s [options]", progname); + dbg_time("-s [apn [user password auth]] Set apn/user/password/auth get from your network provider"); + dbg_time("-p pincode Verify sim card pin if sim card is locked"); + dbg_time("-f logfilename Save log message of this program to file"); + dbg_time("-u usbmonlog filename Save usbmon log of this program to file"); + dbg_time("-i interface Specify network interface(default auto-detect)"); + dbg_time("-4 IPv4 protocol"); + dbg_time("-6 IPv6 protocol"); + dbg_time("-m muxID Specify muxid when set multi-pdn data connection."); + dbg_time("-n channelID Specify channelID when set multi-pdn data connection(default 1)."); + dbg_time("-k channelID Send SIGINT to quectel-CM which multi-pdn is channelID."); + dbg_time("-r Detach and attach kernel driver before open device."); + dbg_time("-b enable network interface bridge function(default 0)."); + dbg_time("[Examples]"); + dbg_time("Example 1: %s ", progname); + dbg_time("Example 2: %s -s 3gnet ", progname); + dbg_time("Example 3: %s -s 3gnet carl 1234 0 -p 1234 -f gobinet_log.txt", progname); + return 0; +} + +int qmi_main(PROFILE_T *profile) +{ + int triger_event = 0; + int signo; +#ifdef CONFIG_SIM + SIM_Status SIMStatus; +#endif + UCHAR PSAttachedState; + UCHAR IPv4ConnectionStatus = 0xff; //unknow state + UCHAR IPv6ConnectionStatus = 0xff; //unknow state + unsigned SetupCallFail = 0; + unsigned long SetupCallAllowTime = clock_msec(); + int qmierr = 0; + char * save_usbnet_adapter = NULL; + + /* signal trigger quit event */ + signal(SIGINT, ql_sigaction); + signal(SIGTERM, ql_sigaction); + /* timer routine */ + signal(SIGALRM, ql_sigaction); + +#ifdef CONFIG_BACKGROUND_WHEN_GET_IP + ql_prepare_daemon(); +#endif + +//sudo apt-get install udhcpc +//sudo apt-get remove ModemManager +__main_loop: + if (profile->reattach_flag) { + if (!reattach_driver(profile)) + sleep(2); + } + + /* try to recreate FDs*/ + if (socketpair( AF_LOCAL, SOCK_STREAM, 0, signal_control_fd) < 0 ) { + dbg_time("%s Faild to create main_control_fd: %d (%s)", __func__, errno, strerror(errno)); + return -1; + } + + if ( socketpair( AF_LOCAL, SOCK_STREAM, 0, qmidevice_control_fd ) < 0 ) { + dbg_time("%s Failed to create thread control socket pair: %d (%s)", __func__, errno, strerror(errno)); + return 0; + } + + while (!profile->qmichannel) { + char qmichannel[32+1] = {'\0'}; + char usbnet_adapter[32+1] = {'\0'}; + + if (!qmidevice_detect(qmichannel, usbnet_adapter, sizeof(qmichannel), &profile->busnum, &profile->devnum)) { + dbg_time("qmidevice_detect failed"); + continue; + } else { + if (!(profile->qmichannel)) + strset(profile->qmichannel, qmichannel); + if (!(profile->usbnet_adapter)) + strset(profile->usbnet_adapter, usbnet_adapter); + break; + } + if (main_loop) { + int wait_for_device = 3000; + dbg_time("Wait for Quectel modules connect"); + while (wait_for_device && main_loop) { + wait_for_device -= 100; + usleep(100*1000); + } + continue; + } + dbg_time("Cannot find qmichannel(%s) usbnet_adapter(%s) for Quectel modules", profile->qmichannel, profile->usbnet_adapter); + return -ENODEV; + } + + if (qmidev_is_gobinet(profile->qmichannel)) { + profile->qmi_ops = &gobi_qmidev_ops; + } + else { + profile->qmi_ops = &qmiwwan_qmidev_ops; + } + qmidev_send = profile->qmi_ops->send; + + if (profile->qmap_mode == 0 || profile->qmap_mode == 1) + kill_brothers(profile->qmichannel); + + if (pthread_create( &gQmiThreadID, 0, profile->qmi_ops->read, (void *)profile) != 0) { + dbg_time("%s Failed to create QMIThread: %d (%s)", __func__, errno, strerror(errno)); + return 0; + } + + if ((read(qmidevice_control_fd[0], &triger_event, sizeof(triger_event)) != sizeof(triger_event)) + || (triger_event != RIL_INDICATE_DEVICE_CONNECTED)) { + dbg_time("%s Failed to init QMIThread: %d (%s)", __func__, errno, strerror(errno)); + return 0; + } + + if (profile->qmi_ops->init && profile->qmi_ops->init(profile)) { + dbg_time("%s Failed to qmi init: %d (%s)", __func__, errno, strerror(errno)); + return 0; + } + +#ifdef CONFIG_VERSION + requestBaseBandVersion(NULL); +#endif + requestSetEthMode(profile); + if (profile->loopback_state) { + requestSetLoopBackState(profile->loopback_state, profile->replication_factor); + profile->loopback_state = 0; + } +#ifdef CONFIG_SIM + qmierr = requestGetSIMStatus(&SIMStatus); + while (qmierr == QMI_ERR_OP_DEVICE_UNSUPPORTED) { + sleep(1); + qmierr = requestGetSIMStatus(&SIMStatus); + } + if ((SIMStatus == SIM_PIN) && profile->pincode) { + requestEnterSimPin(profile->pincode); + } +#ifdef CONFIG_IMSI_ICCID + if (SIMStatus == SIM_READY) { + requestGetICCID(); + requestGetIMSI(); + } +#endif +#endif +#ifdef CONFIG_APN + if (profile->apn || profile->user || profile->password) { + requestSetProfile(profile); + } + requestGetProfile(profile); +#endif + requestRegistrationState(&PSAttachedState); + + send_signo_to_main(SIG_EVENT_CHECK); + +#ifdef CONFIG_PID_FILE_FORMAT + { + char cmd[255]; + sprintf(cmd, "echo %d > " CONFIG_PID_FILE_FORMAT, getpid(), profile->usbnet_adapter); + system(cmd); + } +#endif + + while (1) + { + struct pollfd pollfds[] = {{signal_control_fd[1], POLLIN, 0}, {qmidevice_control_fd[0], POLLIN, 0}}; + int ne, ret, nevents = sizeof(pollfds)/sizeof(pollfds[0]); + + do { + ret = poll(pollfds, nevents, 15*1000); + } while ((ret < 0) && (errno == EINTR)); + + if (ret == 0) + { + send_signo_to_main(SIG_EVENT_CHECK); + continue; + } + + if (ret <= 0) { + dbg_time("%s poll=%d, errno: %d (%s)", __func__, ret, errno, strerror(errno)); + goto __main_quit; + } + + for (ne = 0; ne < nevents; ne++) { + int fd = pollfds[ne].fd; + short revents = pollfds[ne].revents; + + if (revents & (POLLERR | POLLHUP | POLLNVAL)) { + dbg_time("%s poll err/hup", __func__); + dbg_time("epoll fd = %d, events = 0x%04x", fd, revents); + main_send_event_to_qmidevice(RIL_REQUEST_QUIT); + if (revents & POLLHUP) + goto __main_quit; + } + + if ((revents & POLLIN) == 0) + continue; + + if (fd == signal_control_fd[1]) + { + if (read(fd, &signo, sizeof(signo)) == sizeof(signo)) + { + alarm(0); + switch (signo) + { + case SIG_EVENT_START: + if (PSAttachedState != 1 && profile->loopback_state == 0) + break; + + if (SetupCallAllowTime > clock_msec()) { + alarm((SetupCallAllowTime - clock_msec()+999)/1000); + break; + } + + if (profile->enable_ipv4 && IPv4ConnectionStatus != QWDS_PKT_DATA_CONNECTED) { + qmierr = requestSetupDataCall(profile, IpFamilyV4); + + if ((qmierr > 0) && profile->user && profile->user[0] && profile->password && profile->password[0]) { + int old_auto = profile->auth; + + //may be fail because wrong auth mode, try pap->chap, or chap->pap + profile->auth = (profile->auth == 1) ? 2 : 1; + qmierr = requestSetupDataCall(profile, IpFamilyV4); + + if (qmierr) + profile->auth = old_auto; //still fail, restore old auth moe + } + + if (!qmierr) { + qmierr = requestGetIPAddress(profile, IpFamilyV4); + if (!qmierr) + IPv4ConnectionStatus = QWDS_PKT_DATA_CONNECTED; + } + + } + + if (profile->enable_ipv6 && IPv6ConnectionStatus != QWDS_PKT_DATA_CONNECTED) { + qmierr = requestSetupDataCall(profile, IpFamilyV6); + + if (!qmierr) { + qmierr = requestGetIPAddress(profile, IpFamilyV6); + if (!qmierr) + IPv6ConnectionStatus = QWDS_PKT_DATA_CONNECTED; + } + } + + if ((profile->enable_ipv4 && IPv4ConnectionStatus == QWDS_PKT_DATA_DISCONNECTED) + || (profile->enable_ipv6 && IPv6ConnectionStatus == QWDS_PKT_DATA_DISCONNECTED)) { + const unsigned allow_time[] = {5, 10, 20, 40, 60}; + + if (SetupCallFail < (sizeof(allow_time)/sizeof(unsigned))) + SetupCallAllowTime = allow_time[SetupCallFail]; + else + SetupCallAllowTime = 60; + SetupCallFail++; + dbg_time("try to requestSetupDataCall %ld second later", SetupCallAllowTime); + alarm(SetupCallAllowTime); + SetupCallAllowTime = SetupCallAllowTime*1000 + clock_msec(); +#ifdef CONFIG_EXIT_WHEN_DIAL_FAILED + send_signo_to_main(SIG_EVENT_STOP); +#endif + } + else if (IPv4ConnectionStatus == QWDS_PKT_DATA_CONNECTED || IPv6ConnectionStatus == QWDS_PKT_DATA_CONNECTED) { + SetupCallFail = 0; + SetupCallAllowTime = clock_msec(); + } + break; + + case SIG_EVENT_CHECK: + #ifdef CONFIG_SIGNALINFO + requestGetSignalInfo(); + #endif + if (profile->enable_ipv4) { + requestQueryDataCall(&IPv4ConnectionStatus, IpFamilyV4); + + //local ip is different with remote ip + if (QWDS_PKT_DATA_CONNECTED == IPv4ConnectionStatus && check_ipv4_address(profile) == 0) { + requestDeactivateDefaultPDP(profile, IpFamilyV4); + IPv4ConnectionStatus = QWDS_PKT_DATA_DISCONNECTED; + } + } + else { + IPv4ConnectionStatus = QWDS_PKT_DATA_DISCONNECTED; + } + + if (profile->enable_ipv6) { + requestQueryDataCall(&IPv6ConnectionStatus, IpFamilyV6); + } + else { + IPv6ConnectionStatus = QWDS_PKT_DATA_DISCONNECTED; + } + + if (IPv4ConnectionStatus == QWDS_PKT_DATA_DISCONNECTED && IPv6ConnectionStatus == QWDS_PKT_DATA_DISCONNECTED) { + usbnet_link_change(0, profile); + } + else if (IPv4ConnectionStatus == QWDS_PKT_DATA_CONNECTED || IPv6ConnectionStatus == QWDS_PKT_DATA_CONNECTED) { + usbnet_link_change(1, profile); + } + + if ((profile->enable_ipv4 && IPv4ConnectionStatus == QWDS_PKT_DATA_DISCONNECTED) + || (profile->enable_ipv6 && IPv6ConnectionStatus == QWDS_PKT_DATA_DISCONNECTED)) { + send_signo_to_main(SIG_EVENT_START); + } + break; + + case SIG_EVENT_STOP: + if (profile->enable_ipv4 && IPv4ConnectionStatus == QWDS_PKT_DATA_CONNECTED) { + requestDeactivateDefaultPDP(profile, IpFamilyV4); + } + if (profile->enable_ipv6 && IPv6ConnectionStatus == QWDS_PKT_DATA_CONNECTED) { + requestDeactivateDefaultPDP(profile, IpFamilyV6); + } + usbnet_link_change(0, profile); + if (profile->qmi_ops->deinit) + profile->qmi_ops->deinit(); + main_send_event_to_qmidevice(RIL_REQUEST_QUIT); + goto __main_quit; + break; + + default: + break; + } + } + } + + if (fd == qmidevice_control_fd[0]) { + if (read(fd, &triger_event, sizeof(triger_event)) == sizeof(triger_event)) { + switch (triger_event) { + case RIL_INDICATE_DEVICE_DISCONNECTED: + usbnet_link_change(0, profile); + if (main_loop) + { + if (pthread_join(gQmiThreadID, NULL)) { + dbg_time("%s Error joining to listener thread (%s)", __func__, strerror(errno)); + } + profile->qmichannel = NULL; + profile->usbnet_adapter = save_usbnet_adapter; + goto __main_loop; + } + goto __main_quit; + break; + + case RIL_UNSOL_RESPONSE_VOICE_NETWORK_STATE_CHANGED: + requestRegistrationState(&PSAttachedState); + if (PSAttachedState == 1) { + if ((profile->enable_ipv4 && IPv4ConnectionStatus == QWDS_PKT_DATA_DISCONNECTED) + || (profile->enable_ipv6 && IPv6ConnectionStatus == QWDS_PKT_DATA_DISCONNECTED)) { + send_signo_to_main(SIG_EVENT_START); + } + } else { + SetupCallAllowTime = clock_msec(); + } + break; + + case RIL_UNSOL_DATA_CALL_LIST_CHANGED: + if (IPv4ConnectionStatus == QWDS_PKT_DATA_CONNECTED || IPv6ConnectionStatus == QWDS_PKT_DATA_CONNECTED) + SetupCallAllowTime = clock_msec() + 1000; //from connect -> disconnect, do not re-dail immediately, wait network stable + send_signo_to_main(SIG_EVENT_CHECK); + break; + + case MODEM_REPORT_RESET_EVENT: + { + unsigned int time_to_wait = 20; + unsigned int time_expired = 0; + dbg_time("main recv MODEM RESET SIGNAL"); + dbg_time("quit QMI thread and wait %ds and try to restart", time_to_wait); + main_send_event_to_qmidevice(RIL_REQUEST_QUIT); + /** NOTICE + * DO NOT CALL usbnet_link_change(0, profile) DIRECTLLY + * for, the modem may go into wrong state(only ttyUSB0 left) and wont go back + */ + usbnet_link_state(0); + /* close FDs, for we want restart. */ + close(signal_control_fd[0]); + close(signal_control_fd[1]); + close(qmidevice_control_fd[0]); + close(qmidevice_control_fd[1]); + while (time_expired++ < time_to_wait) { + sleep(1); + char qmidev[64] = {'\0'}; + snprintf(qmidev, sizeof(qmidev), "/dev/bus/usb/%03d/%03d", profile->busnum, profile->devnum); + if (access(qmidev, F_OK)) { + dbg_time("whoo, fatal error info, qmi device node disappeared!!! cannot continue!\n"); + goto __main_quit; + } + } + dbg_time("main try do restart"); + goto __main_loop; + } + case RIL_UNSOL_LOOPBACK_CONFIG_IND: + { + QMI_WDA_SET_LOOPBACK_CONFIG_IND_MSG SetLoopBackInd; + if (read(fd, &SetLoopBackInd, sizeof(SetLoopBackInd)) == sizeof(SetLoopBackInd)) { + profile->loopback_state = SetLoopBackInd.loopback_state.TLVVaule; + profile->replication_factor = le32_to_cpu(SetLoopBackInd.replication_factor.TLVVaule); + dbg_time("SetLoopBackInd: loopback_state=%d, replication_factor=%u", + profile->loopback_state, profile->replication_factor); + if (profile->loopback_state) + send_signo_to_main(SIG_EVENT_START); + } + } + break; + default: + break; + } + } + } + } + } + +__main_quit: + usbnet_link_change(0, profile); + if (pthread_join(gQmiThreadID, NULL)) { + dbg_time("%s Error joining to listener thread (%s)", __func__, strerror(errno)); + } + close(signal_control_fd[0]); + close(signal_control_fd[1]); + close(qmidevice_control_fd[0]); + close(qmidevice_control_fd[1]); + dbg_time("%s exit", __func__); + +#ifdef CONFIG_PID_FILE_FORMAT + { + char cmd[255]; + sprintf(cmd, "rm " CONFIG_PID_FILE_FORMAT, profile.usbnet_adapter); + system(cmd); + } +#endif + + return 0; +} + +#define has_more_argv() ((opt < argc) && (argv[opt][0] != '-')) +int main(int argc, char *argv[]) +{ + int opt = 1; + char * save_usbnet_adapter = NULL; + const char *usbmon_logfile = NULL; + PROFILE_T profile; + int ret = -1; + + dbg_time("Quectel_QConnectManager_Linux_V1.6.0.15"); + memset(&profile, 0x00, sizeof(profile)); + profile.pdp = CONFIG_DEFAULT_PDP; + + if (!strcmp(argv[argc-1], "&")) + argc--; + + opt = 1; + while (opt < argc) { + if (argv[opt][0] != '-') + return usage(argv[0]); + + switch (argv[opt++][1]) + { + case 's': + profile.apn = profile.user = profile.password = ""; + if (has_more_argv()) + profile.apn = argv[opt++]; + if (has_more_argv()) + profile.user = argv[opt++]; + if (has_more_argv()) + { + profile.password = argv[opt++]; + if (profile.password && profile.password[0]) + profile.auth = 2; //default chap, customers may miss auth + } + if (has_more_argv()) + profile.auth = argv[opt++][0] - '0'; + break; + + case 'm': + if (has_more_argv()) + profile.muxid = argv[opt++][0] - '0'; + break; + + case 'p': + if (has_more_argv()) + profile.pincode = argv[opt++]; + break; + + case 'n': + if (has_more_argv()) + profile.pdp = argv[opt++][0] - '0'; + break; + + case 'f': + if (has_more_argv()) + { + const char * filename = argv[opt++]; + logfilefp = fopen(filename, "a+"); + if (!logfilefp) { + dbg_time("Fail to open %s, errno: %d(%s)", filename, errno, strerror(errno)); + } + } + break; + + case 'i': + if (has_more_argv()) + profile.usbnet_adapter = save_usbnet_adapter = argv[opt++]; + break; + + case 'v': + debug_qmi = 1; + break; + + case 'l': + if (has_more_argv()) { + profile.replication_factor = atoi(argv[opt++]); + if (profile.replication_factor > 0) + profile.loopback_state = 1; + } + else + main_loop = 1; + break; + + case '4': + profile.enable_ipv4 = 1; + break; + + case '6': + profile.enable_ipv6 = 1; + break; + + case 'd': + if (has_more_argv()) { + profile.qmichannel = argv[opt++]; + if (qmidev_is_pciemhi(profile.qmichannel)) + profile.usbnet_adapter = "pcie_mhi0"; + } + break; + + case 'r': + profile.reattach_flag = 1; + break; + + case 'u': + if (has_more_argv()) { + usbmon_logfile = argv[opt++]; + } + break; + + case 'b': + profile.enable_bridge = 1; + break; + + case 'k': + if (has_more_argv()) { + return kill_data_call_pdp(argv[opt++][0] - '0', argv[0]); + } + + default: + return usage(argv[0]); + break; + } + } + + if (profile.enable_ipv4 != 1 && profile.enable_ipv6 != 1) { // default enable IPv4 + profile.enable_ipv4 = 1; + } + + if (!(profile.qmichannel) || !(profile.usbnet_adapter)) { + char qmichannel[32+1] = {'\0'}; + char usbnet_adapter[32+1] = {'\0'}; + + if (profile.usbnet_adapter) + strcpy(usbnet_adapter, profile.usbnet_adapter); + + if (qmidevice_detect(qmichannel, usbnet_adapter, sizeof(qmichannel), &profile.busnum, &profile.devnum)) { + profile.hardware_interface = HARDWARE_USB; + } + else if (mhidevice_detect(qmichannel, usbnet_adapter, &profile)) { + profile.hardware_interface = HARDWARE_PCIE; + } + else { + dbg_time("qmidevice_detect failed"); + goto error; + } + + if (!(profile.qmichannel)) + strset(profile.qmichannel, qmichannel); + if (!(profile.usbnet_adapter)) + strset(profile.usbnet_adapter, usbnet_adapter); + + ql_get_netcard_driver_info(profile.usbnet_adapter); + + if ((profile.hardware_interface == HARDWARE_USB) && usbmon_logfile) + ql_capture_usbmon_log(&profile, usbmon_logfile); + + if (profile.hardware_interface == HARDWARE_USB) { + profile.software_interface = get_driver_type(&profile); + } + } + + ql_qmap_mode_detect(&profile); + + if (profile.software_interface == SOFTWARE_MBIM) { + dbg_time("Modem works in MBIM mode"); + ret = mbim_main(&profile); + } else if (profile.software_interface == SOFTWARE_QMI) { + dbg_time("Modem works in QMI mode"); + ret = qmi_main(&profile); + } + + ql_stop_usbmon_log(&profile); + if (logfilefp) + fclose(logfilefp); + +error: + + return ret; +} diff --git a/root/package/link4all/quectel-CM/quectel1.6.0.15_src/mbim-cm.c b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/mbim-cm.c new file mode 100644 index 00000000..10c94c40 --- /dev/null +++ b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/mbim-cm.c @@ -0,0 +1,2268 @@ +/****************************************************************************** + @file mbim-cm.c + @brief MIBIM drivers. + + DESCRIPTION + Connectivity Management Tool for USB network adapter of Quectel wireless cellular modules. + + INITIALIZATION AND SEQUENCING REQUIREMENTS + None. + + --------------------------------------------------------------------------- + Copyright (c) 2016 - 2020 Quectel Wireless Solution, Co., Ltd. All Rights Reserved. + Quectel Wireless Solution Proprietary and Confidential. + --------------------------------------------------------------------------- +******************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "QMIThread.h" + +#ifndef htole32 +#if __BYTE_ORDER == __LITTLE_ENDIAN +#define htole16(x) (uint16_t)(x) +#define le16toh(x) (uint16_t)(x) +#define letoh16(x) (uint16_t)(x) +#define htole32(x) (uint32_t)(x) +#define le32toh(x) (uint32_t)(x) +#define letoh32(x) (uint32_t)(x) +#define htole64(x) (uint64_t)(x) +#define le64toh(x) (uint64_t)(x) +#define letoh64(x) (uint64_t)(x) +#else +static __inline uint16_t __bswap16(uint16_t __x) { + return (__x<<8) | (__x>>8); +} + +static __inline uint32_t __bswap32(uint32_t __x) { + return (__x>>24) | (__x>>8&0xff00) | (__x<<8&0xff0000) | (__x<<24); +} + +static __inline uint64_t __bswap64(uint64_t __x) { + return (__bswap32(__x)+0ULL<<32) | (__bswap32(__x>>32)); +} + +#define htole16(x) __bswap16(x) +#define le16toh(x) __bswap16(x) +#define letoh16(x) __bswap16(x) +#define htole32(x) __bswap32(x) +#define le32toh(x) __bswap32(x) +#define letoh32(x) __bswap32(x) +#define htole64(x) __bswap64(x) +#define le64toh(x) __bswap64(x) +#define letoh64(x) __bswap64(x) +#endif +#endif + +#define mbim_debug dbg_time + +#define UUID_BASIC_CONNECT "a289cc33-bcbb-8b4f-b6b0-133ec2aae6df" +//https://docs.microsoft.com/en-us/windows-hardware/drivers/network/mb-5g-data-class-support +#define UUID_BASIC_CONNECT_EXT "3d01dcc5-fef5-4d05-0d3a-bef7058e9aaf" +#define UUID_SMS "533fbeeb-14fe-4467-9f90-33a223e56c3f" +#define UUID_USSD "e550a0c8-5e82-479e-82f7-10abf4c3351f" +#define UUID_PHONEBOOK "4bf38476-1e6a-41db-b1d8-bed289c25bdb" +#define UUID_STK "d8f20131-fcb5-4e17-8602-d6ed3816164c" +#define UUID_AUTH "1d2b5ff7-0aa1-48b2-aa52-50f15767174e" +#define UUID_DSS "c08a26dd-7718-4382-8482-6e0d583c4d0e" +#define uuid_ext_qmux "d1a30bc2-f97a-6e43-bf65-c7e24fb0f0d3" +#define uuid_mshsd "883b7c26-985f-43fa-9804-27d7fb80959c" +#define uuid_qmbe "2d0c12c9-0e6a-495a-915c-8d174fe5d63c" +#define UUID_MSFWID "e9f7dea2-feaf-4009-93ce-90a3694103b6" +#define uuid_atds "5967bdcc-7fd2-49a2-9f5c-b2e70e527db3" +#define uuid_qdu "6427015f-579d-48f5-8c54-f43ed1e76f83" +#define UUID_MS_UICC_LOW_LEVEL "c2f6588e-f037-4bc9-8665-f4d44bd09367" +#define UUID_MS_SARControl "68223D04-9F6C-4E0F-822D-28441FB72340" +#define UUID_VOICEEXTENSIONS "8d8b9eba-37be-449b-8f1e-61cb034a702e" + +#define UUID_MBIMContextTypeInternet "7E5E2A7E-4E6F-7272-736B-656E7E5E2A7E" + +typedef unsigned char UINT8; +typedef unsigned short UINT16; +typedef unsigned int UINT32; +typedef unsigned long long UINT64; + +#define STRINGFY(v) #v +/* The function name will be _ENUM_NAMEStr */ +#define enumstrfunc(_ENUM_NAME, _ENUM_MEMS) \ +static const char *_ENUM_NAME##Str(int _val) { \ + struct { int val;char *name;} _enumstr[] = { _ENUM_MEMS }; \ + int idx; for (idx = 0; idx < sizeof(_enumstr)/sizeof(_enumstr[0]); idx++) { \ + if (_val == _enumstr[idx].val) return _enumstr[idx].name;} \ + return STRINGFY(_ENUM_NAME##Unknow); \ +} + +#pragma pack(4) +typedef enum { + MBIM_CID_CMD_TYPE_QUERY = 0, + MBIM_CID_CMD_TYPE_SET = 1, +} MBIM_CID_CMD_TYPE_E; + +//Set Query Notification +#define UUID_BASIC_CONNECT_CIDs \ + MBIM_ENUM_HELPER(MBIM_CID_DEVICE_CAPS, 1) \ + MBIM_ENUM_HELPER(MBIM_CID_SUBSCRIBER_READY_STATUS, 2) \ + MBIM_ENUM_HELPER(MBIM_CID_RADIO_STATE, 3) \ + MBIM_ENUM_HELPER(MBIM_CID_PIN, 4) \ + MBIM_ENUM_HELPER(MBIM_CID_PIN_LIS, 5) \ + MBIM_ENUM_HELPER(MBIM_CID_HOME_PROVIDER, 6) \ + MBIM_ENUM_HELPER(MBIM_CID_PREFERRED_PROVIDERS, 7) \ + MBIM_ENUM_HELPER(MBIM_CID_VISIBLE_PROVIDERS, 8) \ + MBIM_ENUM_HELPER(MBIM_CID_REGISTER_STATE, 9) \ + MBIM_ENUM_HELPER(MBIM_CID_PACKET_SERVICE, 10) \ + MBIM_ENUM_HELPER(MBIM_CID_SIGNAL_STATE, 11) \ + MBIM_ENUM_HELPER(MBIM_CID_CONNECT, 12) \ + MBIM_ENUM_HELPER(MBIM_CID_PROVISIONED_CONTEXTS, 13) \ + MBIM_ENUM_HELPER(MBIM_CID_SERVICE_ACTIVATION, 14) \ + MBIM_ENUM_HELPER(MBIM_CID_IP_CONFIGURATION, 15) \ + MBIM_ENUM_HELPER(MBIM_CID_DEVICE_SERVICES, 16) \ + MBIM_ENUM_HELPER(MBIM_CID_DEVICE_SERVICE_SUBSCRIBE_LIST, 19) \ + MBIM_ENUM_HELPER(MBIM_CID_PACKET_STATISTICS, 20) \ + MBIM_ENUM_HELPER(MBIM_CID_NETWORK_IDLE_HINT, 21) \ + MBIM_ENUM_HELPER(MBIM_CID_EMERGENCY_MODE, 22) \ + MBIM_ENUM_HELPER(MBIM_CID_IP_PACKET_FILTERS, 23) \ + MBIM_ENUM_HELPER(MBIM_CID_MULTICARRIER_PROVIDERS, 24) + +#define MBIM_ENUM_HELPER(k, v) k = v, +typedef enum{ + UUID_BASIC_CONNECT_CIDs +} UUID_BASIC_CONNECT_CID_E; +#undef MBIM_ENUM_HELPER +#define MBIM_ENUM_HELPER(k, v) {k, #k}, +enumstrfunc(CID2, UUID_BASIC_CONNECT_CIDs); +#undef MBIM_ENUM_HELPER + +static int mbim_ms_version = 1; + +#define UUID_BASIC_CONNECT_EXT_CIDs \ + MBIM_ENUM_HELPER(MBIM_CID_MS_PROVISIONED_CONTEXT_V2, 1) \ + MBIM_ENUM_HELPER(MBIM_CID_MS_NETWORK_BLACKLIST, 2) \ + MBIM_ENUM_HELPER(MBIM_CID_MS_LTE_ATTACH_CONFIG, 3) \ + MBIM_ENUM_HELPER(MBIM_CID_MS_LTE_ATTACH_STATUS , 4) \ + MBIM_ENUM_HELPER(MBIM_CID_MS_SYS_CAPS , 5) \ + MBIM_ENUM_HELPER(MBIM_CID_MS_DEVICE_CAPS_V2, 6) \ + MBIM_ENUM_HELPER(MBIM_CID_MS_DEVICE_SLOT_MAPPING, 7) \ + MBIM_ENUM_HELPER(MBIM_CID_MS_SLOT_INFO_STATUS, 8) \ + MBIM_ENUM_HELPER(MBIM_CID_MS_PCO, 9) \ + MBIM_ENUM_HELPER(MBIM_CID_MS_DEVICE_RESET, 10) \ + MBIM_ENUM_HELPER(MBIM_CID_MS_BASE_STATIONS_INFO, 11) \ + MBIM_ENUM_HELPER(MBIM_CID_MS_LOCATION_INFO_STATUS, 12) \ + MBIM_ENUM_HELPER(MBIM_CID_NOT_DEFINED, 13) \ + MBIM_ENUM_HELPER(MBIM_CID_MS_PIN_EX, 14) \ + MBIM_ENUM_HELPER(MBIM_CID_MS_VERSION , 15) + +#define MBIM_ENUM_HELPER(k, v) k = v, +typedef enum{ + UUID_BASIC_CONNECT_EXT_CIDs +} UUID_BASIC_CONNECT_EXT_CID_E; +#undef MBIM_ENUM_HELPER +#define MBIM_ENUM_HELPER(k, v) {k, #k}, +enumstrfunc(MS_CID2, UUID_BASIC_CONNECT_EXT_CIDs); +#undef MBIM_ENUM_HELPER + +typedef enum { + MBIM_CID_SMS_CONFIGURATION = 1, // Y Y Y + MBIM_CID_SMS_READ = 2, // N Y Y + MBIM_CID_SMS_SEND = 3, // Y N N + MBIM_CID_SMS_DELETE = 4, // Y N N + MBIM_CID_SMS_MESSAGE_STORE_STATUS = 5, // N Y Y +} UUID_SMS_CID_E; + +typedef enum { + MBIM_CID_DSS_CONNECT = 1, // Y N N +} UUID_DSS_CID_E; + +#define MBIM_MSGS \ + MBIM_ENUM_HELPER(MBIM_OPEN_MSG, 1) \ + MBIM_ENUM_HELPER(MBIM_CLOSE_MSG, 2) \ + MBIM_ENUM_HELPER(MBIM_COMMAND_MSG, 3) \ + MBIM_ENUM_HELPER(MBIM_HOST_ERROR_MSG, 4) \ + \ + MBIM_ENUM_HELPER(MBIM_OPEN_DONE, 0x80000001) \ + MBIM_ENUM_HELPER(MBIM_CLOSE_DONE, 0x80000002) \ + MBIM_ENUM_HELPER(MBIM_COMMAND_DONE, 0x80000003) \ + MBIM_ENUM_HELPER(MBIM_FUNCTION_ERROR_MSG, 0x80000004) \ + MBIM_ENUM_HELPER(MBIM_INDICATE_STATUS_MSG, 0x80000007) + +#define MBIM_ENUM_HELPER(k, v) k = v, +typedef enum{ + MBIM_MSGS +} MBIM_MSG_Type_E; +#undef MBIM_ENUM_HELPER +#define MBIM_ENUM_HELPER(k, v) {k, #k}, +enumstrfunc(MBIMMSGType, MBIM_MSGS); +#undef MBIM_ENUM_HELPER + +typedef enum { + MBIM_ERROR_TIMEOUT_FRAGMENT = 1, + MBIM_ERROR_FRAGMENT_OUT_OF_SEQUENCE = 2, + MBIM_ERROR_LENGTH_MISMATCH = 3, + MBIM_ERROR_DUPLICATED_TID = 4, + MBIM_ERROR_NOT_OPENED = 5, + MBIM_ERROR_UNKNOWN = 6, + MBIM_ERROR_CANCEL = 7, + MBIM_ERROR_MAX_TRANSFER = 8, +} MBIM_ERROR_E; + +typedef enum { + MBIM_STATUS_SUCCESS = 0, + MBIM_STATUS_BUSY = 1, + MBIM_STATUS_FAILURE = 2, + MBIM_STATUS_SIM_NOT_INSERTED = 3, + MBIM_STATUS_BAD_SIM = 4, + MBIM_STATUS_PIN_REQUIRED = 5, + MBIM_STATUS_PIN_DISABLED = 6, + MBIM_STATUS_NOT_REGISTERED = 7, + MBIM_STATUS_PROVIDERS_NOT_FOUND = 8, + MBIM_STATUS_NO_DEVICE_SUPPORT = 9, + MBIM_STATUS_PROVIDER_NOT_VISIBLE = 10, + MBIM_STATUS_DATA_CLASS_NOT_AVAILABL = 11, + MBIM_STATUS_PACKET_SERVICE_DETACHED = 12, +} MBIM_STATUS_CODES_E; + +typedef enum { + MBIMPacketServiceActionAttach = 0, + MBIMPacketServiceActionDetach = 1, +} MBIM_PACKET_SERVICE_ACTION_E; + +typedef enum { + MBIMPacketServiceStateUnknown = 0, + MBIMPacketServiceStateAttaching = 1, + MBIMPacketServiceStateAttached = 2, + MBIMPacketServiceStateDetaching = 3, + MBIMPacketServiceStateDetached = 4, +} MBIM_PACKET_SERVICE_STATE_E; + +static const char *MBIMPacketServiceStateStr(int _val) { + struct { int val;char *name;} _enumstr[] = { + {MBIMPacketServiceStateUnknown, "Unknown"}, + {MBIMPacketServiceStateAttaching, "Attaching"}, + {MBIMPacketServiceStateAttached, "Attached"}, + {MBIMPacketServiceStateDetaching, "Detaching"}, + {MBIMPacketServiceStateDetached, "Detached"}, + }; + int idx; + + for (idx = 0; idx < sizeof(_enumstr)/sizeof(_enumstr[0]); idx++) { + if (_val == _enumstr[idx].val) + return _enumstr[idx].name; + } + + return "Undefined"; +}; + +typedef enum { + MBIMDataClassNone = 0x0, + MBIMDataClassGPRS = 0x1, + MBIMDataClassEDGE = 0x2, + MBIMDataClassUMTS = 0x4, + MBIMDataClassHSDPA = 0x8, + MBIMDataClassHSUPA = 0x10, + MBIMDataClassLTE = 0x20, + MBIMDataClass5G_NSA = 0x40, + MBIMDataClass5G_SA = 0x80, + MBIMDataClass1XRTT = 0x10000, + MBIMDataClass1XEVDO = 0x20000, + MBIMDataClass1XEVDORevA = 0x40000, + MBIMDataClass1XEVDV = 0x80000, + MBIMDataClass3XRTT = 0x100000, + MBIMDataClass1XEVDORevB = 0x200000, + MBIMDataClassUMB = 0x400000, + MBIMDataClassCustom = 0x80000000, +} MBIM_DATA_CLASS_E; + +static const char *MBIMDataClassStr(int _val) { + struct { int val;char *name;} _enumstr[] = { + {MBIMDataClassNone, "None"}, + {MBIMDataClassGPRS, "GPRS"}, + {MBIMDataClassEDGE, "EDGE"}, + {MBIMDataClassUMTS, "UMTS"}, + {MBIMDataClassHSDPA, "HSDPA"}, + {MBIMDataClassHSUPA, "HSUPA"}, + {MBIMDataClassLTE, "LTE"}, + {MBIMDataClass5G_NSA, "5G_NSA"}, + {MBIMDataClass5G_SA, "5G_SA"}, + {MBIMDataClass1XRTT, "1XRTT"}, + {MBIMDataClass1XEVDO, "1XEVDO"}, + {MBIMDataClass1XEVDORevA, "1XEVDORevA"}, + {MBIMDataClass1XEVDV, "1XEVDV"}, + {MBIMDataClass3XRTT, "3XRTT"}, + {MBIMDataClass1XEVDORevB, "1XEVDORevB"}, + {MBIMDataClassUMB, "UMB"}, + {MBIMDataClassCustom, "Custom"}, + }; + int idx; + + for (idx = 0; idx < sizeof(_enumstr)/sizeof(_enumstr[0]); idx++) { + if (_val == _enumstr[idx].val) + return _enumstr[idx].name; + } + + return "Unknow"; +}; + +typedef struct { + UINT32 NwError; + UINT32 PacketServiceState; //MBIM_PACKET_SERVICE_STATE_E + UINT32 HighestAvailableDataClass; //MBIM_DATA_CLASS_E + UINT64 UplinkSpeed; + UINT64 DownlinkSpeed; +} MBIM_PACKET_SERVICE_INFO_T; + +typedef struct { + UINT32 NwError; + UINT32 PacketServiceState; //MBIM_PACKET_SERVICE_STATE_E + UINT32 CurrentDataClass; //MBIM_DATA_CLASS_E + UINT64 UplinkSpeed; + UINT64 DownlinkSpeed; + UINT32 FrequencyRange; +} MBIM_PACKET_SERVICE_INFO_V2_T; + +typedef enum { + MBIMSubscriberReadyStateNotInitialized = 0, + MBIMSubscriberReadyStateInitialized = 1, + MBIMSubscriberReadyStateSimNotInserted = 2, + MBIMSubscriberReadyStateBadSim = 3, + MBIMSubscriberReadyStateFailure = 4, + MBIMSubscriberReadyStateNotActivated = 5, + MBIMSubscriberReadyStateDeviceLocked = 6, +}MBIM_SUBSCRIBER_READY_STATE_E; + +static const char *MBIMSubscriberReadyStateStr(int _val) { + struct { int val;char *name;} _enumstr[] = { + {MBIMSubscriberReadyStateNotInitialized, "NotInitialized"}, + {MBIMSubscriberReadyStateInitialized, "Initialized"}, + {MBIMSubscriberReadyStateSimNotInserted, "NotInserted"}, + {MBIMSubscriberReadyStateBadSim, "BadSim"}, + {MBIMSubscriberReadyStateFailure, "Failure"}, + {MBIMSubscriberReadyStateNotActivated, "NotActivated"}, + {MBIMSubscriberReadyStateDeviceLocked, "DeviceLocked"}, + }; + int idx; + + for (idx = 0; idx < sizeof(_enumstr)/sizeof(_enumstr[0]); idx++) { + if (_val == _enumstr[idx].val) + return _enumstr[idx].name; + } + + return "Undefined"; +}; + +typedef struct { + UINT32 DeviceType; //MBIM_DEVICE_TYPE + UINT32 CellularClass; //MBIM_CELLULAR_CLASS + UINT32 VoiceClass; //MBIM_VOICE_CLASS + UINT32 SimClass; //MBIM_SIM_CLASS + UINT32 DataClass; //MBIM_DATA_CLASS + UINT32 SmsCaps; //MBIM_SMS_CAPS + UINT32 ControlCaps; //MBIM_CTRL_CAPS + UINT32 MaxSessions; + UINT32 CustomDataClassOffset; + UINT32 CustomDataClassSize; + UINT32 DeviceIdOffset; + UINT32 DeviceIdSize; + UINT32 FirmwareInfoOffset; + UINT32 FirmwareInfoSize; + UINT32 HardwareInfoOffset; + UINT32 HardwareInfoSize; + UINT8 DataBuffer[0]; //DeviceId FirmwareInfo HardwareInfo +} MBIM_DEVICE_CAPS_INFO_T; + +typedef enum { + MBIMRadioOff = 0, + MBIMRadioOn = 1, +} MBIM_RADIO_SWITCH_STATE_E; + +typedef struct { + MBIM_RADIO_SWITCH_STATE_E RadioState; +} MBIM_SET_RADIO_STATE_T; + +typedef struct { + MBIM_RADIO_SWITCH_STATE_E HwRadioState; + MBIM_RADIO_SWITCH_STATE_E SwRadioState; +} MBIM_RADIO_STATE_INFO_T; + +typedef enum { + MBIMReadyInfoFlagsNone, + MBIMReadyInfoFlagsProtectUniqueID, +}MBIM_UNIQUE_ID_FLAGS; + +typedef struct { + UINT32 ReadyState; + UINT32 SubscriberIdOffset; + UINT32 SubscriberIdSize; + UINT32 SimIccIdOffset; + UINT32 SimIccIdSize; + UINT32 ReadyInfo; + UINT32 ElementCount; + UINT8 *TelephoneNumbersRefList; + UINT8 *DataBuffer; +} MBIM_SUBSCRIBER_READY_STATUS_T; + +typedef enum { + MBIMRegisterActionAutomatic, + MBIMRegisterActionManual, +}MBIM_REGISTER_ACTION_E; + +typedef enum { + MBIMRegisterStateUnknown = 0, + MBIMRegisterStateDeregistered = 1, + MBIMRegisterStateSearching = 2, + MBIMRegisterStateHome = 3, + MBIMRegisterStateRoaming = 4, + MBIMRegisterStatePartner = 5, + MBIMRegisterStateDenied = 6, +}MBIM_REGISTER_STATE_E; + +typedef enum { + MBIMRegisterModeUnknown = 0, + MBIMRegisterModeAutomatic = 1, + MBIMRegisterModeManual = 2, +}MBIM_REGISTER_MODE_E; + +static const char *MBIMRegisterStateStr(int _val) { + struct { int val;char *name;} _enumstr[] ={ + {MBIMRegisterStateUnknown, "Unknown"}, + {MBIMRegisterStateDeregistered, "Deregistered"}, + {MBIMRegisterStateSearching, "Searching"}, + {MBIMRegisterStateHome, "Home"}, + {MBIMRegisterStateRoaming, "Roaming"}, + {MBIMRegisterStatePartner, "Partner"}, + {MBIMRegisterStateDenied, "Denied"}, + }; + int idx; + + for (idx = 0; idx < sizeof(_enumstr)/sizeof(_enumstr[0]); idx++) { + if (_val == _enumstr[idx].val) + return _enumstr[idx].name; + } + + return "Undefined"; +}; + +static const char *MBIMRegisterModeStr(int _val) { + struct { int val;char *name;} _enumstr[] = { + {MBIMRegisterModeUnknown, "Unknown"}, + {MBIMRegisterModeAutomatic, "Automatic"}, + {MBIMRegisterModeManual, "Manual"}, + }; + int idx; + + for (idx = 0; idx < sizeof(_enumstr)/sizeof(_enumstr[0]); idx++) { + if (_val == _enumstr[idx].val) + return _enumstr[idx].name; + } + + return "Undefined"; +}; + +typedef enum { + MBIM_REGISTRATION_NONE, + MBIM_REGISTRATION_MANUAL_SELECTION_NOT_AVAILABLE, + MBIM_REGISTRATION_PACKET_SERVICE_AUTOMATIC_ATTACH, +}MBIM_REGISTRATION_FLAGS_E; + +typedef struct { + UINT32 NwError; + UINT32 RegisterState; //MBIM_REGISTER_STATE_E + UINT32 RegisterMode; + UINT32 AvailableDataClasses; + UINT32 CurrentCellularClass; + UINT32 ProviderIdOffset; + UINT32 ProviderIdSize; + UINT32 ProviderNameOffset; + UINT32 ProviderNameSize; + UINT32 RoamingTextOffset; + UINT32 RoamingTextSize; + UINT32 RegistrationFlag; + UINT8 *DataBuffer; +} MBIM_REGISTRATION_STATE_INFO_T; + +typedef struct { + UINT32 NwError; + UINT32 RegisterState; //MBIM_REGISTER_STATE_E + UINT32 RegisterMode; + UINT32 AvailableDataClasses; + UINT32 CurrentCellularClass; + UINT32 ProviderIdOffset; + UINT32 ProviderIdSize; + UINT32 ProviderNameOffset; + UINT32 ProviderNameSize; + UINT32 RoamingTextOffset; + UINT32 RoamingTextSize; + UINT32 RegistrationFlag; + UINT32 PreferredDataClass; + UINT8 *DataBuffer; +} MBIM_REGISTRATION_STATE_INFO_V2_T; + +typedef struct { + UINT32 MessageType; //Specifies the MBIM message type. + UINT32 MessageLength; //Specifies the total length of this MBIM message in bytes. + /* Specifies the MBIM message id value. This value is used to match host sent messages with function responses. + This value must be unique among all outstanding transactions. + For notifications, the TransactionId must be set to 0 by the function */ + UINT32 TransactionId; +} MBIM_MESSAGE_HEADER; + +typedef struct { + UINT32 TotalFragments; //this field indicates how many fragments there are intotal. + UINT32 CurrentFragment; //This field indicates which fragment this message is. Values are 0 to TotalFragments?\1 +} MBIM_FRAGMENT_HEADER; + +typedef struct { + MBIM_MESSAGE_HEADER MessageHeader; + UINT32 MaxControlTransfer; +} MBIM_OPEN_MSG_T; + +typedef struct { + MBIM_MESSAGE_HEADER MessageHeader; + UINT32 Status; +} MBIM_OPEN_DONE_T; + +typedef struct { + MBIM_MESSAGE_HEADER MessageHeader; +} MBIM_CLOSE_MSG_T; + +typedef struct { + MBIM_MESSAGE_HEADER MessageHeader; + UINT32 Status; +} MBIM_CLOSE_DONE_T; + +typedef struct { + UINT8 uuid[16]; +} UUID_T; + +typedef struct { + MBIM_MESSAGE_HEADER MessageHeader; + MBIM_FRAGMENT_HEADER FragmentHeader; + UUID_T DeviceServiceId; //A 16 byte UUID that identifies the device service the following CID value applies. + UINT32 CID; //Specifies the CID that identifies the parameter being queried for + UINT32 CommandType; //0 for a query operation, 1 for a Set operation + UINT32 InformationBufferLength; //Size of the Total InformationBuffer, may be larger than current message if fragmented. + UINT8 InformationBuffer[0]; //Data supplied to device specific to the CID +} MBIM_COMMAND_MSG_T; + +typedef struct { + MBIM_MESSAGE_HEADER MessageHeader; + MBIM_FRAGMENT_HEADER FragmentHeader; + UUID_T DeviceServiceId; //A 16 byte UUID that identifies the device service the following CID value applies. + UINT32 CID; //Specifies the CID that identifies the parameter being queried for + UINT32 Status; + UINT32 InformationBufferLength; //Size of the Total InformationBuffer, may be larger than current message if fragmented. + UINT8 InformationBuffer[0]; //Data supplied to device specific to the CID +} MBIM_COMMAND_DONE_T; + +typedef struct { + MBIM_MESSAGE_HEADER MessageHeader; + UINT32 ErrorStatusCode; +} MBIM_HOST_ERROR_MSG_T; + +typedef struct { + MBIM_MESSAGE_HEADER MessageHeader; + UINT32 ErrorStatusCode; +} MBIM_FUNCTION_ERROR_MSG_T; + +typedef struct { + MBIM_MESSAGE_HEADER MessageHeader; + MBIM_FRAGMENT_HEADER FragmentHeader; + UUID_T DeviceServiceId; //A 16 byte UUID that identifies the device service the following CID value applies. + UINT32 CID; //Specifies the CID that identifies the parameter being queried for + UINT32 InformationBufferLength; //Size of the Total InformationBuffer, may be larger than current message if fragmented. + UINT8 InformationBuffer[0]; //Data supplied to device specific to the CID +} MBIM_INDICATE_STATUS_MSG_T; + +typedef struct { + UINT32 offset; + UINT32 size; +} OL_PAIR_LIST; + +typedef struct { + UUID_T DeviceServiceId; + UINT32 DssPayload; + UINT32 MaxDssInstances; + UINT32 CidCount; + UINT32 CidList[]; +} MBIM_DEVICE_SERVICE_ELEMENT_T; + +typedef struct { + UINT32 DeviceServicesCount; + UINT32 MaxDssSessions; + OL_PAIR_LIST DeviceServicesRefList[]; +} MBIM_DEVICE_SERVICES_INFO_T; + +typedef enum { + MBIMActivationCommandDeactivate = 0, + MBIMActivationCommandActivate = 1, +} MBIM_ACTIVATION_COMMAND_E; + +typedef enum { + MBIMCompressionNone = 0, + MBIMCompressionEnable = 1, +} MBIM_COMPRESSION_E; + +typedef enum { + MBIMAuthProtocolNone = 0, + MBIMAuthProtocolPap = 1, + MBIMAuthProtocolChap = 2, + MBIMAuthProtocolMsChapV2 = 3, +} MBIM_AUTH_PROTOCOL_E; + +#define MBIMContextIPTypes \ + MBIM_ENUM_HELPER(MBIMContextIPTypeDefault, 0) \ + MBIM_ENUM_HELPER(MBIMContextIPTypeIPv4, 1) \ + MBIM_ENUM_HELPER(MBIMContextIPTypeIPv6, 2) \ + MBIM_ENUM_HELPER(MBIMContextIPTypeIPv4v6, 3) \ + MBIM_ENUM_HELPER(MBIMContextIPTypeIPv4AndIPv6, 4) + +#define MBIM_ENUM_HELPER(k, v) k = v, +typedef enum { + MBIMContextIPTypes +} MBIM_CONTEXT_IP_TYPE_E; +#undef MBIM_ENUM_HELPER +#define MBIM_ENUM_HELPER(k, v) {k, #k}, +enumstrfunc(MBIMContextIPType, MBIMContextIPTypes); +#undef MBIM_ENUM_HELPER + +typedef enum { + MBIMActivationStateUnknown = 0, + MBIMActivationStateActivated = 1, + MBIMActivationStateActivating = 2, + MBIMActivationStateDeactivated = 3, + MBIMActivationStateDeactivating = 4, +} MBIM_ACTIVATION_STATE_E; + +typedef enum { + MBIMVoiceCallStateNone = 0, + MBIMVoiceCallStateInProgress = 1, + MBIMVoiceCallStateHangUp = 2, +} MBIM_VOICECALL_STATE_E; + +static const char *MBIMActivationStateStr(int _val) { + struct { int val;char *name;} _enumstr[] = { + {MBIMActivationStateUnknown, "Unknown"}, + {MBIMActivationStateActivated, "Activated"}, + {MBIMActivationStateActivating, "Activating"}, + {MBIMActivationStateDeactivated, "Deactivated"}, + {MBIMActivationStateDeactivating, "Deactivating"}, + }; + int idx; + + for (idx = 0; idx < sizeof(_enumstr)/sizeof(_enumstr[0]); idx++) { + if (_val == _enumstr[idx].val) + return _enumstr[idx].name; + } + + return "Undefined"; +}; + +static const char *MBIMVoiceCallStateStr(int _val) { + struct { int val;char *name;} _enumstr[] = { + {MBIMVoiceCallStateNone, "None"}, + {MBIMVoiceCallStateInProgress, "InProgress"}, + {MBIMVoiceCallStateHangUp, "HangUp"}, + }; + int idx; + + for (idx = 0; idx < sizeof(_enumstr)/sizeof(_enumstr[0]); idx++) { + if (_val == _enumstr[idx].val) + return _enumstr[idx].name; + } + + return "Undefined"; +}; + +typedef struct { + UINT32 SessionId; + UINT32 ActivationCommand; //MBIM_ACTIVATION_COMMAND_E + UINT32 AccessStringOffset; + UINT32 AccessStringSize; + UINT32 UserNameOffset; + UINT32 UserNameSize; + UINT32 PasswordOffset; + UINT32 PasswordSize; + UINT32 Compression; //MBIM_COMPRESSION_E + UINT32 AuthProtocol; //MBIM_AUTH_PROTOCOL_E + UINT32 IPType; //MBIM_CONTEXT_IP_TYPE_E + UUID_T ContextType; + UINT8 DataBuffer[0]; /* apn, username, password */ +} MBIM_SET_CONNECT_T; + +typedef struct { + UINT32 SessionId; + UINT32 ActivationState; //MBIM_ACTIVATION_STATE_E + UINT32 VoiceCallState; + UINT32 IPType; //MBIM_CONTEXT_IP_TYPE_E + UUID_T ContextType; + UINT32 NwError; +} MBIM_CONNECT_T; + +typedef struct { + UINT32 OnLinkPrefixLength; + UINT8 IPv4Address[4]; +} MBIM_IPV4_ELEMENT_T; + +typedef struct { + UINT32 OnLinkPrefixLength; + UINT8 IPv6Address[16]; +} MBIM_IPV6_ELEMENT_T; + +typedef struct { + UINT32 SessionId; + UINT32 IPv4ConfigurationAvailable; //bit0~Address, bit1~gateway, bit2~DNS, bit3~MTU + UINT32 IPv6ConfigurationAvailable; //bit0~Address, bit1~gateway, bit2~DNS, bit3~MTU + UINT32 IPv4AddressCount; + UINT32 IPv4AddressOffset; + UINT32 IPv6AddressCount; + UINT32 IPv6AddressOffset; + UINT32 IPv4GatewayOffset; + UINT32 IPv6GatewayOffset; + UINT32 IPv4DnsServerCount; + UINT32 IPv4DnsServerOffset; + UINT32 IPv6DnsServerCount; + UINT32 IPv6DnsServerOffset; + UINT32 IPv4Mtu; + UINT32 IPv6Mtu; + UINT8 DataBuffer[]; +} MBIM_IP_CONFIGURATION_INFO_T; + +typedef struct { + UINT32 RSRP; + UINT32 SNR; + UINT32 RSRPThreshold; + UINT32 SNRThreshold; + UINT32 SystemType; +} MBIM_RSRP_SNR_INFO_T; + +typedef struct { + UINT32 Elementcount; + MBIM_RSRP_SNR_INFO_T RsrpSnr[0]; +} MBIM_RSRP_SNR_T; + +typedef struct { + UINT32 Rssi; + UINT32 ErrorRate; + UINT32 SignalStrengthInterval; + UINT32 RssiThreshold; + UINT32 ErrorRateThreshold; +} MBIM_SIGNAL_STATE_INFO_T; + +typedef struct { + UINT32 Rssi; + UINT32 ErrorRate; + UINT32 SignalStrengthInterval; + UINT32 RssiThreshold; + UINT32 ErrorRateThreshold; + UINT32 RsrpSnrOffset; + UINT32 RsrpSnrSize; + UINT8 DataBuffer[]; +} MBIM_SIGNAL_STATE_INFO_V2_T; + +typedef struct { + UINT32 SignalStrengthInterval; + UINT32 RssiThreshold; + UINT32 ErrorRateThreshold; +} MBIM_SET_SIGNAL_STATE_T; + +#pragma pack() + +static pthread_t read_tid = 0; +static int mbim_verbose = 0; +static UINT32 TransactionId = 1; +static unsigned mbim_default_timeout = 10000; +static const char *mbim_netcard = "wwan0"; +static const char *real_netcard = NULL; +static const char *mbim_dev = "/dev/cdc-wdm0"; +static const char *mbim_apn = NULL; +static const char *mbim_user = NULL; +static const char *mbim_passwd = NULL; +static int mbim_iptype = MBIMContextIPTypeDefault; +static int mbim_auth = MBIMAuthProtocolNone; +static int mbim_fd = -1; +static pthread_mutex_t mbim_command_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t mbim_command_cond = PTHREAD_COND_INITIALIZER; +static pthread_mutex_t mbim_state_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t mbim_state_cond = PTHREAD_COND_INITIALIZER; +static MBIM_MESSAGE_HEADER *mbim_pRequest; +static MBIM_MESSAGE_HEADER *mbim_pResponse; +static int bridge_mode; +extern int ql_ifconfig(int argc, char *argv[]); + +static int mysystem(const char *cmd) +{ + int status = system(cmd); + mbim_debug("system(%s)=%d", cmd, status); + + return status; +} + +static void prefix_to_addr(int prefix, char *buf) +{ + UINT32 _addr = 0xffffffff - (1 << (32 - prefix)) + 1; + sprintf(buf, "%d.%d.%d.%d", + ((unsigned char*)&_addr)[3], ((unsigned char*)&_addr)[2], + ((unsigned char*)&_addr)[1], ((unsigned char*)&_addr)[0]); +} + +static int file_get_value(const char *fname) +{ + FILE *fp = NULL; + int hexnum; + char buff[32 + 1] = {'\0'}; + char *endptr = NULL; + + fp = fopen(fname, "r"); + if (!fp) goto error; + if (fgets(buff, sizeof(buff), fp) == NULL) + goto error; + fclose(fp); + + hexnum = strtol(buff, &endptr, 16); + if (errno == ERANGE && (hexnum == LONG_MAX || hexnum == LONG_MIN)) + goto error; + /* if there is no digit in buff */ + if (endptr == buff) + goto error; + return (int)hexnum; + +error: + if (fp) fclose(fp); + return 0; +} + +static int bridge_mode_detect() +{ + char path[128] = {'\0'}; + int val; + + snprintf(path, sizeof(path), "/sys/class/net/%s/mbim/bridge_mode", mbim_netcard); + val = file_get_value(path); + if (val) { + mbim_debug("mbim interface %s works in bridge mode", mbim_netcard); + } + + return !!val; +} + +static void bridge_set_kernel_attr(const unsigned char *ipaddr, const unsigned char *gw, const unsigned char *dns, UINT32 prefix) +{ + char cmd[256] = {'\0'}; + char ipstr[32] = {'\0'}; + char maskstr[32] = {'\0'}; + char gwstr[32] = {'\0'}; + char dnsstr[32] = {'\0'}; + + if (ipaddr) + snprintf(ipstr, sizeof(ipstr), "%d.%d.%d.%d", ipaddr[0], ipaddr[1], ipaddr[2], ipaddr[3]); + if (gw) + snprintf(gwstr, sizeof(gwstr), "%d.%d.%d.%d", gw[0], gw[1], gw[2], gw[3]); + if (dns) + snprintf(dnsstr, sizeof(dnsstr), "%d.%d.%d.%d", dns[0], dns[1], dns[2], dns[3]); + prefix_to_addr(prefix, maskstr); + + /* srv ip mask router dns */ + snprintf(cmd, sizeof(cmd), "echo \"%s %s %s %s\" > /sys/class/net/%s/mbim/bridge_dhcp_info", + ipstr, maskstr, gwstr, dnsstr, mbim_netcard); + mysystem(cmd); +} + +static void mbim_ifconfig(int iptype, const char *ifname, const unsigned char *ipaddr, const unsigned char *gw, + const unsigned char *dns1, const unsigned char *dns2, UINT32 prefix, UINT32 mtu) { + char shell_cmd[256] = {'\0'}; + char dns1str[128], dns2str[128]; + + bridge_mode = bridge_mode_detect(); + if (ipaddr) { + snprintf(shell_cmd, sizeof(shell_cmd), "echo 1 > /sys/class/net/%s/mbim/link_state", ifname); + mysystem(shell_cmd); + if (bridge_mode) + bridge_set_kernel_attr(ipaddr, gw, dns1, prefix); + + if(real_netcard) { + snprintf(shell_cmd, sizeof(shell_cmd), "ip link set dev %s up", real_netcard); + mysystem(shell_cmd); + } + snprintf(shell_cmd, sizeof(shell_cmd), "ip link set dev %s up", ifname); + mysystem(shell_cmd); + if (bridge_mode) + return; + } else { + snprintf(shell_cmd, sizeof(shell_cmd), "echo 0 > /sys/class/net/%s/mbim/link_state", ifname); + mysystem(shell_cmd); + /* remove all address */ + snprintf(shell_cmd, sizeof(shell_cmd), "ip address flush dev %s", ifname); + mysystem(shell_cmd); + snprintf(shell_cmd, sizeof(shell_cmd), "ip link set dev %s down", ifname); + mysystem(shell_cmd); + + if(real_netcard) { + snprintf(shell_cmd, sizeof(shell_cmd), "ip link set dev %s down", real_netcard); + mysystem(shell_cmd); + } + update_resolv_conf(4, ifname, NULL, NULL); + update_resolv_conf(6, ifname, NULL, NULL); + return; + } + + if (ipaddr) { + const unsigned char *d = ipaddr; + snprintf(shell_cmd, sizeof(shell_cmd), "ip -%d address flush dev %s", iptype, ifname); + mysystem(shell_cmd); + if (iptype == 4) + snprintf(shell_cmd, sizeof(shell_cmd), "ip -%d address add %u.%u.%u.%u/%u dev %s", + iptype, d[0], d[1], d[2], d[3], prefix, ifname); + if (iptype == 6) + snprintf(shell_cmd, sizeof(shell_cmd), + "ip -%d address add %02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x/%u dev %s", + iptype, d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7], d[8], d[9], d[10], d[11], d[12], d[13], d[14], d[15], + prefix, ifname); + mysystem(shell_cmd); + } + + if (gw) { + const unsigned char *d = gw; + if (iptype == 4) + snprintf(shell_cmd, sizeof(shell_cmd), "ip -%d route add default via %d.%d.%d.%d dev %s", + iptype, d[0], d[1], d[2], d[3], ifname); + if (iptype == 6) + snprintf(shell_cmd, sizeof(shell_cmd), + "ip -%d route add default via %02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x dev %s", + iptype, d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7], d[8], d[9], d[10], d[11], d[12], d[13], d[14], d[15], + ifname); + mysystem(shell_cmd); + } else { + snprintf(shell_cmd, sizeof(shell_cmd), "ip -%d route add default dev %s", iptype, ifname); + mysystem(shell_cmd); + } + + if (mtu) { + snprintf(shell_cmd, sizeof(shell_cmd), "ip -%d link set dev %s mtu %u", iptype, ifname, mtu); + mysystem(shell_cmd); + } + + if (dns1) { + const unsigned char *d = dns1; + if (iptype == 4) + snprintf(dns1str, sizeof(dns1str), "%d.%d.%d.%d", d[0], d[1], d[2], d[3]); + if (iptype == 6) + snprintf(dns1str, sizeof(dns1str), "%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x", + d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7], d[8], d[9], d[10], d[11], d[12], d[13], d[14], d[15]); + } + + if (dns2) { + const unsigned char *d = dns2; + if (iptype == 4) + snprintf(dns2str, sizeof(dns2str), "%d.%d.%d.%d", d[0], d[1], d[2], d[3]); + if (iptype == 6) + snprintf(dns2str, sizeof(dns2str), "%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x", + d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7], d[8], d[9], d[10], d[11], d[12], d[13], d[14], d[15]); + } + + update_resolv_conf(iptype, ifname, dns1 ? dns1str : NULL, dns2 ? dns2str : NULL); +} + +static const UUID_T * str2uuid(const char *str) { + static UUID_T uuid; + UINT32 d[16]; + char tmp[16*2+4+1]; + unsigned i = 0; + + while (str[i]) { + tmp[i] = tolower(str[i]); + i++; + } + tmp[i] = '\0'; + + sscanf(tmp, "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", + &d[0], &d[1], &d[2], &d[3], &d[4], &d[5], &d[6], &d[7], + &d[8], &d[9], &d[10], &d[11], &d[12], &d[13], &d[14], &d[15]); + + for (i = 0; i < 16; i++) { + uuid.uuid[i] = d[i]&0xFF; + } + + return &uuid; +} + +#define mbim_alloc( _size) malloc(_size) +#define mbim_free(_mem) do { if (_mem) { free(_mem); _mem = NULL;}} while(0) + +static int mbim_quit = 0; +static int mbim_open_state = 0; +static MBIM_SUBSCRIBER_READY_STATE_E ReadyState = MBIMSubscriberReadyStateNotInitialized; +static MBIM_REGISTER_STATE_E RegisterState = MBIMRegisterStateUnknown; +static MBIM_PACKET_SERVICE_STATE_E PacketServiceState = MBIMPacketServiceStateUnknown; +static MBIM_ACTIVATION_STATE_E ActivationState = MBIMActivationStateUnknown; +static MBIM_SUBSCRIBER_READY_STATE_E oldReadyState = MBIMSubscriberReadyStateNotInitialized; +static MBIM_REGISTER_STATE_E oldRegisterState = MBIMRegisterStateUnknown; +static MBIM_PACKET_SERVICE_STATE_E oldPacketServiceState = MBIMPacketServiceStateUnknown; +static MBIM_ACTIVATION_STATE_E oldActivationState = MBIMActivationStateUnknown; + +static void _notify_state_chage(void) { + pthread_mutex_lock(&mbim_state_mutex); + pthread_cond_signal(&mbim_state_cond); + pthread_mutex_unlock(&mbim_state_mutex); +} + +#define notify_state_chage(_var, _new) do {if (_var != _new) {_var = _new; _notify_state_chage();}} while (0) + +static int wait_state_change(uint32_t seconds) { + int retval = 0; + + pthread_mutex_lock(&mbim_state_mutex); + retval = pthread_cond_timeout_np(&mbim_state_cond, &mbim_state_mutex, seconds*1000); + pthread_mutex_unlock(&mbim_state_mutex); + + if (retval !=0 && retval != ETIMEDOUT) mbim_debug("seconds=%u, retval=%d", seconds, retval); + return retval; +} + +static MBIM_MESSAGE_HEADER *compose_open_command(UINT32 MaxControlTransfer) +{ + MBIM_OPEN_MSG_T *pRequest = (MBIM_OPEN_MSG_T *)mbim_alloc(sizeof(MBIM_OPEN_MSG_T)); + + if(!pRequest) + return NULL; + + pRequest->MessageHeader.MessageType = htole32(MBIM_OPEN_MSG); + pRequest->MessageHeader.MessageLength = htole32(sizeof(MBIM_COMMAND_MSG_T)); + pRequest->MessageHeader.TransactionId = htole32(TransactionId++); + pRequest->MaxControlTransfer = htole32(MaxControlTransfer); + + return &pRequest->MessageHeader; +} + +static MBIM_MESSAGE_HEADER *compose_close_command(void) +{ + MBIM_CLOSE_MSG_T *pRequest = (MBIM_CLOSE_MSG_T *)mbim_alloc(sizeof(MBIM_CLOSE_MSG_T)); + + if(!pRequest) + return NULL; + + pRequest->MessageHeader.MessageType = htole32(MBIM_CLOSE_MSG); + pRequest->MessageHeader.MessageLength = htole32(sizeof(MBIM_CLOSE_MSG_T)); + pRequest->MessageHeader.TransactionId = htole32(TransactionId++); + + return &pRequest->MessageHeader; +} + +static MBIM_MESSAGE_HEADER *compose_basic_connect_command(UINT32 CID, UINT32 CommandType, void *pInformationBuffer, UINT32 InformationBufferLength) +{ + MBIM_COMMAND_MSG_T *pRequest = (MBIM_COMMAND_MSG_T *)mbim_alloc(sizeof(MBIM_COMMAND_MSG_T) + InformationBufferLength); + + if (!pRequest) + return NULL; + + pRequest->MessageHeader.MessageType = htole32(MBIM_COMMAND_MSG); + pRequest->MessageHeader.MessageLength = htole32((sizeof(MBIM_COMMAND_MSG_T) + InformationBufferLength)); + pRequest->MessageHeader.TransactionId = htole32(TransactionId++); + + pRequest->FragmentHeader.TotalFragments = htole32(1); + pRequest->FragmentHeader.CurrentFragment= htole32(0); + + memcpy(pRequest->DeviceServiceId.uuid, str2uuid(UUID_BASIC_CONNECT), 16); + + pRequest->CID = htole32(CID); + pRequest->CommandType = htole32(CommandType); + if (InformationBufferLength && pInformationBuffer) { + pRequest->InformationBufferLength = htole32(InformationBufferLength); + memcpy(pRequest->InformationBuffer, pInformationBuffer, InformationBufferLength); + } else { + pRequest->InformationBufferLength = htole32(0); + } + + return &pRequest->MessageHeader; +} + +static MBIM_MESSAGE_HEADER *compose_basic_connect_ext_command(UINT32 CID, UINT32 CommandType, void *pInformationBuffer, UINT32 InformationBufferLength) +{ + MBIM_COMMAND_MSG_T *pRequest = (MBIM_COMMAND_MSG_T *)compose_basic_connect_command(CID, CommandType, pInformationBuffer, InformationBufferLength); + + if (!pRequest) + return NULL; + + memcpy(pRequest->DeviceServiceId.uuid, str2uuid(UUID_BASIC_CONNECT_EXT), 16); + + return &pRequest->MessageHeader; +} + +static const char * uuid2str(const UUID_T *pUUID) { + static char str[16*2+4+1]; + const UINT8 *d = pUUID->uuid; + + snprintf(str, sizeof(str), "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", + d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7], + d[8], d[9], d[10], d[11], d[12], d[13], d[14], d[15]); + + return str; +} + +static const char *DeviceServiceId2str(const UUID_T *pUUID) { + const char *str = uuid2str(pUUID); + + struct { char *val;char *name;} _enumstr[] = { + {UUID_BASIC_CONNECT, "UUID_BASIC_CONNECT"}, + {UUID_BASIC_CONNECT_EXT, "UUID_BASIC_CONNECT_EXT"}, + {UUID_SMS, "UUID_SMS"}, + {UUID_USSD, "UUID_USSD"}, + {UUID_PHONEBOOK, "UUID_PHONEBOOK"}, + {UUID_STK, "UUID_STK"}, + {UUID_AUTH, "UUID_AUTH"}, + {UUID_DSS, "UUID_DSS"}, + {uuid_ext_qmux, "uuid_ext_qmux"}, + {uuid_mshsd, "uuid_mshsd"}, + {uuid_qmbe, "uuid_qmbe"}, + {UUID_MSFWID, "UUID_MSFWID"}, + {uuid_atds, "uuid_atds"}, + {uuid_qdu, "uuid_qdu"}, + {UUID_MS_UICC_LOW_LEVEL, "UUID_MS_UICC_LOW_LEVEL"}, + {UUID_MS_SARControl, "UUID_MS_SARControl"}, + {UUID_VOICEEXTENSIONS, "UUID_VOICEEXTENSIONS"}, + }; + int idx; + + for (idx = 0; idx < sizeof(_enumstr)/sizeof(_enumstr[0]); idx++) { + if (!strcasecmp(str, _enumstr[idx].val)) + return _enumstr[idx].name; + } + + return str; +} + +static const char *mbim_get_segment(void *_pMsg, UINT32 offset, UINT32 len) +{ + int idx; + static char buff[256] = {'\0'}; + UINT8 *pMsg = (UINT8*)_pMsg; + + for (idx = 0; idx < len/2; idx++) + buff[idx] = pMsg[offset+idx*2]; + buff[idx] = '\0'; + return buff; +} + +static void mbim_dump_header(MBIM_MESSAGE_HEADER *pMsg, const char *direction) { + mbim_debug("%s Header:", direction); + mbim_debug("%s MessageLength = %u", direction, le32toh(pMsg->MessageLength)); + mbim_debug("%s MessageType = %s (0x%08x)", direction, MBIMMSGTypeStr(le32toh(pMsg->MessageType)), le32toh(pMsg->MessageType)); + mbim_debug("%s TransactionId = %u", direction, le32toh(pMsg->TransactionId)); + mbim_debug("%s Contents:", direction); +} + +static void mbim_dump_command_msg(MBIM_COMMAND_MSG_T *pCmdMsg, const char *direction) { + mbim_debug("%s DeviceServiceId = %s (%s)", direction, DeviceServiceId2str(&pCmdMsg->DeviceServiceId), uuid2str(&pCmdMsg->DeviceServiceId)); + mbim_debug("%s CID = %s (%u)", direction, CID2Str(le32toh(pCmdMsg->CID)), le32toh(pCmdMsg->CID)); + mbim_debug("%s CommandType = %s (%u)", direction, le32toh(pCmdMsg->CommandType) ? "set" : "query", le32toh(pCmdMsg->CommandType)); + mbim_debug("%s InformationBufferLength = %u", direction, le32toh(pCmdMsg->InformationBufferLength)); +} + +static void mbim_dump_command_done(MBIM_COMMAND_DONE_T *pCmdDone, const char *direction) { + mbim_debug("%s DeviceServiceId = %s (%s)", direction, DeviceServiceId2str(&pCmdDone->DeviceServiceId), uuid2str(&pCmdDone->DeviceServiceId)); + mbim_debug("%s CID = %s (%u)", direction, CID2Str(le32toh(pCmdDone->CID)), le32toh(pCmdDone->CID)); + mbim_debug("%s Status = %u", direction, le32toh(pCmdDone->Status)); + mbim_debug("%s InformationBufferLength = %u", direction, le32toh(pCmdDone->InformationBufferLength)); +} + +static void mbim_dump_indicate_msg(MBIM_INDICATE_STATUS_MSG_T *pIndMsg, const char *direction) { + mbim_debug("%s DeviceServiceId = %s (%s)", direction, DeviceServiceId2str(&pIndMsg->DeviceServiceId), uuid2str(&pIndMsg->DeviceServiceId)); + if (!memcmp(pIndMsg->DeviceServiceId.uuid, str2uuid(UUID_BASIC_CONNECT_EXT), 16)) + mbim_debug("%s CID = %s (%u)", direction, MS_CID2Str(le32toh(pIndMsg->CID)), le32toh(pIndMsg->CID)); + else + mbim_debug("%s CID = %s (%u)", direction, CID2Str(le32toh(pIndMsg->CID)), le32toh(pIndMsg->CID)); + mbim_debug("%s InformationBufferLength = %u", direction, le32toh(pIndMsg->InformationBufferLength)); +} + +static void mbim_dump_connect(MBIM_CONNECT_T *pInfo, const char *direction) { + mbim_debug("%s SessionId = %u", direction, le32toh(pInfo->SessionId)); + mbim_debug("%s ActivationState = %s (%u)", direction, MBIMActivationStateStr(le32toh(pInfo->ActivationState)), le32toh(pInfo->ActivationState)); + mbim_debug("%s IPType = %s", direction, MBIMContextIPTypeStr(le32toh(pInfo->IPType))); + mbim_debug("%s VoiceCallState = %s", direction, MBIMVoiceCallStateStr(le32toh(pInfo->VoiceCallState))); + mbim_debug("%s ContextType = %s", direction, uuid2str(&pInfo->ContextType)); + mbim_debug("%s NwError = %u", direction, le32toh(pInfo->NwError)); +} + +static void mbim_dump_signal_state(MBIM_SIGNAL_STATE_INFO_T *pInfo, const char *direction) +{ + mbim_debug("%s Rssi = %u", direction, le32toh(pInfo->Rssi)); + mbim_debug("%s ErrorRate = %u", direction, le32toh(pInfo->ErrorRate)); + mbim_debug("%s SignalStrengthInterval = %u", direction, le32toh(pInfo->SignalStrengthInterval)); + mbim_debug("%s RssiThreshold = %u", direction, le32toh(pInfo->RssiThreshold)); + mbim_debug("%s ErrorRateThreshold = %u", direction, le32toh(pInfo->ErrorRateThreshold)); +} + +static void mbim_dump_packet_service(MBIM_PACKET_SERVICE_INFO_T *pInfo, const char *direction) +{ + mbim_debug("%s NwError = %u", direction, le32toh(pInfo->NwError)); + mbim_debug("%s PacketServiceState = %s", direction, MBIMPacketServiceStateStr(le32toh(pInfo->PacketServiceState))); + mbim_debug("%s HighestAvailableDataClass = %s", direction, MBIMDataClassStr(le32toh(pInfo->HighestAvailableDataClass))); + mbim_debug("%s UplinkSpeed = %ld", direction, (long)le64toh(pInfo->UplinkSpeed)); + mbim_debug("%s DownlinkSpeed = %ld", direction, (long)le64toh(pInfo->DownlinkSpeed)); +} + +static void mbim_dump_subscriber_status(MBIM_SUBSCRIBER_READY_STATUS_T *pInfo, const char *direction) +{ + mbim_debug("%s ReadyState = %s", direction, MBIMSubscriberReadyStateStr(le32toh(pInfo->ReadyState))); + mbim_debug("%s SIMICCID = %s", direction, mbim_get_segment(pInfo, le32toh(pInfo->SimIccIdOffset), le32toh(pInfo->SimIccIdSize))); + mbim_debug("%s SubscriberID = %s", direction, mbim_get_segment(pInfo, le32toh(pInfo->SubscriberIdOffset), le32toh(pInfo->SubscriberIdSize))); + /* maybe more than one number */ + int idx; + for (idx = 0; idx < le32toh(pInfo->ElementCount); idx++) { + UINT32 offset = ((UINT32*)((UINT8*)pInfo+offsetof(MBIM_SUBSCRIBER_READY_STATUS_T, TelephoneNumbersRefList)))[0]; + UINT32 length = ((UINT32*)((UINT8*)pInfo+offsetof(MBIM_SUBSCRIBER_READY_STATUS_T, TelephoneNumbersRefList)))[1]; + mbim_debug("%s Number = %s", direction, mbim_get_segment(pInfo, le32toh(offset), le32toh(length))); + } +} + +static void mbim_dump_regiester_status(MBIM_REGISTRATION_STATE_INFO_T *pInfo, const char *direction) +{ + mbim_debug("%s NwError = %u", direction, le32toh(pInfo->NwError)); + mbim_debug("%s RegisterState = %s", direction, MBIMRegisterStateStr(le32toh(pInfo->RegisterState))); + mbim_debug("%s RegisterMode = %s", direction, MBIMRegisterModeStr(le32toh(pInfo->RegisterMode))); +} + +static void mbim_dump_ipconfig(MBIM_IP_CONFIGURATION_INFO_T *pInfo, const char *direction) +{ + UINT8 prefix = 0, *ipv4=NULL, *ipv6=NULL, *gw=NULL, *dns1=NULL, *dns2=NULL; + + mbim_debug("%s SessionId = %u", direction, le32toh(pInfo->SessionId)); + mbim_debug("%s IPv4ConfigurationAvailable = 0x%x", direction, le32toh(pInfo->IPv4ConfigurationAvailable)); + mbim_debug("%s IPv6ConfigurationAvailable = 0x%x", direction, le32toh(pInfo->IPv6ConfigurationAvailable)); + mbim_debug("%s IPv4AddressCount = 0x%x", direction, le32toh(pInfo->IPv4AddressCount)); + mbim_debug("%s IPv4AddressOffset = 0x%x", direction, le32toh(pInfo->IPv4AddressOffset)); + mbim_debug("%s IPv6AddressCount = 0x%x", direction, le32toh(pInfo->IPv6AddressCount)); + mbim_debug("%s IPv6AddressOffset = 0x%x", direction, le32toh(pInfo->IPv6AddressOffset)); + + /* IPv4 */ + if (le32toh(pInfo->IPv4ConfigurationAvailable)&0x1) { + MBIM_IPV4_ELEMENT_T *pAddress = (MBIM_IPV4_ELEMENT_T *)(&pInfo->DataBuffer[le32toh(pInfo->IPv4AddressOffset)-sizeof(MBIM_IP_CONFIGURATION_INFO_T)]); + prefix = le32toh(pAddress->OnLinkPrefixLength); + ipv4 = pAddress->IPv4Address; + mbim_debug("%s IPv4 = %u.%u.%u.%u/%u", direction, ipv4[0], ipv4[1], ipv4[2], ipv4[3], prefix); + } + if (le32toh(pInfo->IPv4ConfigurationAvailable)&0x2) { + gw = (UINT8 *)(&pInfo->DataBuffer[le32toh(pInfo->IPv4GatewayOffset)-sizeof(MBIM_IP_CONFIGURATION_INFO_T)]); + mbim_debug("%s gw = %u.%u.%u.%u", direction, gw[0], gw[1], gw[2], gw[3]); + } + if (le32toh(pInfo->IPv4ConfigurationAvailable)&0x3) { + dns1 = (UINT8 *)(&pInfo->DataBuffer[le32toh(pInfo->IPv4DnsServerOffset) -sizeof(MBIM_IP_CONFIGURATION_INFO_T)]); + mbim_debug("%s dns1 = %u.%u.%u.%u", direction, dns1[0], dns1[1], dns1[2], dns1[3]); + if (le32toh(pInfo->IPv4DnsServerCount) == 2) { + dns2 = dns1 + 4; + mbim_debug("%s dns2 = %u.%u.%u.%u", direction, dns2[0], dns2[1], dns2[2], dns2[3]); + } + } + if (le32toh(pInfo->IPv4Mtu)) mbim_debug("%s ipv4 mtu = %u", direction, le32toh(pInfo->IPv4Mtu)); + + /* IPv6 */ + if (le32toh(pInfo->IPv6ConfigurationAvailable)&0x1) { + MBIM_IPV6_ELEMENT_T *pAddress = (MBIM_IPV6_ELEMENT_T *)(&pInfo->DataBuffer[le32toh(pInfo->IPv6AddressOffset)-sizeof(MBIM_IP_CONFIGURATION_INFO_T)]); + prefix = le32toh(pAddress->OnLinkPrefixLength); + ipv6 = pAddress->IPv6Address; + mbim_debug("%s IPv6 = %02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x/%d", \ + direction, ipv6[0], ipv6[1], ipv6[2], ipv6[3], ipv6[4], ipv6[5], ipv6[6], ipv6[7], \ + ipv6[8], ipv6[9], ipv6[10], ipv6[11], ipv6[12], ipv6[13], ipv6[14], ipv6[15], prefix); + } + if (le32toh(pInfo->IPv6ConfigurationAvailable)&0x2) { + gw = (UINT8 *)(&pInfo->DataBuffer[le32toh(pInfo->IPv6GatewayOffset)-sizeof(MBIM_IP_CONFIGURATION_INFO_T)]); + mbim_debug("%s gw = %02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x", \ + direction, gw[0], gw[1], gw[2], gw[3], gw[4], gw[5], gw[6], gw[7], \ + gw[8], gw[9], gw[10], gw[11], gw[12], gw[13], gw[14], gw[15]); + } + if (le32toh(pInfo->IPv6ConfigurationAvailable)&0x3) { + dns1 = (UINT8 *)(&pInfo->DataBuffer[le32toh(pInfo->IPv6DnsServerOffset)-sizeof(MBIM_IP_CONFIGURATION_INFO_T)]); + mbim_debug("%s dns1 = %02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x", \ + direction, dns1[0], dns1[1], dns1[2], dns1[3], dns1[4], dns1[5], dns1[6], dns1[7], \ + dns1[8], dns1[9], dns1[10], dns1[11], dns1[12], dns1[13], dns1[14], dns1[15]); + if (le32toh(pInfo->IPv6DnsServerCount) == 2) { + dns2 = dns1 + 16; + mbim_debug("%s dns2 = %02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x", \ + direction, dns2[0], dns2[1], dns2[2], dns2[3], dns1[4], dns1[5], dns1[6], dns1[7], + dns2[8], dns2[9], dns2[10], dns2[11], dns2[12], dns2[13], dns2[14], dns2[15]); + } + } + if (le32toh(pInfo->IPv6Mtu)) mbim_debug("%s ipv6 mtu = %u", direction, le32toh(pInfo->IPv6Mtu)); +} + +static void mbim_dump(MBIM_MESSAGE_HEADER *pMsg, int mbim_verbose) { + unsigned char *data = (unsigned char *)pMsg; + const char *direction = (pMsg->MessageType & 0x80000000) ? "<" : ">"; + + if (!mbim_verbose) + return; + + if (mbim_verbose) { + unsigned i; + static char _tmp[4096] = {'\0'}; + _tmp[0] = (le32toh(pMsg->MessageType) & 0x80000000) ? '<' : '>'; + _tmp[1] = '\0'; + for (i = 0; i < le32toh(pMsg->MessageLength) && i < 4096; i++) + snprintf(_tmp + strlen(_tmp), 4096 - strlen(_tmp), "%02X:", data[i]); + mbim_debug("%s", _tmp); + } + + mbim_dump_header(pMsg, direction); + + switch (le32toh(pMsg->MessageType)) { + case MBIM_OPEN_MSG: { + MBIM_OPEN_MSG_T *pOpenMsg = (MBIM_OPEN_MSG_T *)pMsg; + mbim_debug("%s MaxControlTransfer = %u", direction, le32toh(pOpenMsg->MaxControlTransfer)); + } + break; + case MBIM_OPEN_DONE: { + MBIM_OPEN_DONE_T *pOpenDone = (MBIM_OPEN_DONE_T *)pMsg; + mbim_debug("%s Status = %u", direction, le32toh(pOpenDone->Status)); + } + break; + case MBIM_CLOSE_MSG: { + + } + break; + case MBIM_CLOSE_DONE: { + MBIM_CLOSE_DONE_T *pCloseDone = (MBIM_CLOSE_DONE_T *)pMsg; + mbim_debug("%s Status = %u", direction, le32toh(pCloseDone->Status)); + } + break; + case MBIM_COMMAND_MSG: { + MBIM_COMMAND_MSG_T *pCmdMsg = (MBIM_COMMAND_MSG_T *)pMsg; + + mbim_dump_command_msg(pCmdMsg, direction); + if (!memcmp(pCmdMsg->DeviceServiceId.uuid, str2uuid(UUID_BASIC_CONNECT), 16)) { + switch (le32toh(pCmdMsg->CID)) { + case MBIM_CID_CONNECT: { + MBIM_SET_CONNECT_T *pInfo = (MBIM_SET_CONNECT_T *)pCmdMsg->InformationBuffer; + mbim_debug("%s SessionId = %u", direction, le32toh(pInfo->SessionId)); + } + break; + case MBIM_CID_IP_CONFIGURATION: { + MBIM_IP_CONFIGURATION_INFO_T *pInfo = (MBIM_IP_CONFIGURATION_INFO_T *)pCmdMsg->InformationBuffer; + mbim_debug("%s SessionId = %u", direction, le32toh(pInfo->SessionId)); + } + break; + default: + break; + } + } + } + break; + case MBIM_COMMAND_DONE: { + MBIM_COMMAND_DONE_T *pCmdDone = (MBIM_COMMAND_DONE_T *)pMsg; + + mbim_dump_command_done(pCmdDone, direction); + if (le32toh(pCmdDone->InformationBufferLength) == 0) + return; + + if (!memcmp(pCmdDone->DeviceServiceId.uuid, str2uuid(UUID_BASIC_CONNECT), 16)) { + switch (le32toh(pCmdDone->CID)) { + case MBIM_CID_CONNECT: { + MBIM_CONNECT_T *pInfo = (MBIM_CONNECT_T *)pCmdDone->InformationBuffer; + mbim_dump_connect(pInfo, direction); + } + break; + case MBIM_CID_IP_CONFIGURATION: { + MBIM_IP_CONFIGURATION_INFO_T *pInfo = (MBIM_IP_CONFIGURATION_INFO_T *)pCmdDone->InformationBuffer; + mbim_dump_ipconfig(pInfo, direction); + } + break; + case MBIM_CID_PACKET_SERVICE: { + MBIM_PACKET_SERVICE_INFO_T *pInfo = (MBIM_PACKET_SERVICE_INFO_T *)pCmdDone->InformationBuffer; + mbim_dump_packet_service(pInfo, direction); + } + break; + case MBIM_CID_SUBSCRIBER_READY_STATUS: { + MBIM_SUBSCRIBER_READY_STATUS_T *pInfo = (MBIM_SUBSCRIBER_READY_STATUS_T *)pCmdDone->InformationBuffer; + mbim_dump_subscriber_status(pInfo, direction); + } + break; + case MBIM_CID_REGISTER_STATE: { + MBIM_REGISTRATION_STATE_INFO_T *pInfo = (MBIM_REGISTRATION_STATE_INFO_T *)pCmdDone->InformationBuffer; + mbim_dump_regiester_status(pInfo, direction); + } + break; + default: + break; + } + } + } + break; + case MBIM_INDICATE_STATUS_MSG: { + MBIM_INDICATE_STATUS_MSG_T *pIndMsg = (MBIM_INDICATE_STATUS_MSG_T *)pMsg; + + mbim_dump_indicate_msg(pIndMsg, direction); + if (le32toh(pIndMsg->InformationBufferLength) == 0) + return; + + if (!memcmp(pIndMsg->DeviceServiceId.uuid, str2uuid(UUID_BASIC_CONNECT), 16)) { + switch (le32toh(pIndMsg->CID)) { + case MBIM_CID_CONNECT: { + MBIM_CONNECT_T *pInfo = (MBIM_CONNECT_T *)pIndMsg->InformationBuffer; + mbim_dump_connect(pInfo, direction); + } + break; + case MBIM_CID_SIGNAL_STATE: { + MBIM_SIGNAL_STATE_INFO_T *pInfo = (MBIM_SIGNAL_STATE_INFO_T *)pIndMsg->InformationBuffer; + mbim_dump_signal_state(pInfo, direction); + } + break; + case MBIM_CID_SUBSCRIBER_READY_STATUS: { + MBIM_SUBSCRIBER_READY_STATUS_T *pInfo = (MBIM_SUBSCRIBER_READY_STATUS_T *)pIndMsg->InformationBuffer; + mbim_dump_subscriber_status(pInfo, direction); + } + break; + case MBIM_CID_REGISTER_STATE: { + MBIM_REGISTRATION_STATE_INFO_T *pInfo = (MBIM_REGISTRATION_STATE_INFO_T *)pIndMsg->InformationBuffer; + mbim_dump_regiester_status(pInfo, direction); + } + break; + case MBIM_CID_PACKET_SERVICE: { + MBIM_PACKET_SERVICE_INFO_T *pInfo = (MBIM_PACKET_SERVICE_INFO_T *)pIndMsg->InformationBuffer; + mbim_dump_packet_service(pInfo, direction); + } + break; + default: + break; + } + } + else if (!memcmp(pIndMsg->DeviceServiceId.uuid, str2uuid(UUID_BASIC_CONNECT_EXT), 16)) { + } + } + break; + case MBIM_FUNCTION_ERROR_MSG: { + MBIM_FUNCTION_ERROR_MSG_T *pErrMsg = (MBIM_FUNCTION_ERROR_MSG_T*)pMsg; + mbim_debug("%s ErrorStatusCode = %u", direction, le32toh(pErrMsg->ErrorStatusCode)); + } + break; + default: + break; + } +} + +static void mbim_recv_command(MBIM_MESSAGE_HEADER *pResponse, unsigned size) +{ + pthread_mutex_lock(&mbim_command_mutex); + + if (pResponse) + mbim_dump(pResponse, mbim_verbose); + + if (pResponse == NULL) { + pthread_cond_signal(&mbim_command_cond); + } + else if (mbim_pRequest && le32toh(mbim_pRequest->TransactionId) == le32toh(pResponse->TransactionId)) { + mbim_pResponse = mbim_alloc(le32toh(pResponse->MessageLength)); + if (mbim_pResponse) + memcpy(mbim_pResponse, pResponse, le32toh(pResponse->MessageLength)); + pthread_cond_signal(&mbim_command_cond); + } + else if (le32toh(pResponse->MessageType) == MBIM_INDICATE_STATUS_MSG) { + MBIM_INDICATE_STATUS_MSG_T *pIndMsg = (MBIM_INDICATE_STATUS_MSG_T *)pResponse; + + if (!memcmp(pIndMsg->DeviceServiceId.uuid, str2uuid(UUID_BASIC_CONNECT), 16)) + { + switch (le32toh(pIndMsg->CID)) { + case MBIM_CID_SUBSCRIBER_READY_STATUS: { + MBIM_SUBSCRIBER_READY_STATUS_T *pInfo = (MBIM_SUBSCRIBER_READY_STATUS_T *)pIndMsg->InformationBuffer; + notify_state_chage(ReadyState, le32toh(pInfo->ReadyState)); + } + break; + case MBIM_CID_REGISTER_STATE: { + MBIM_REGISTRATION_STATE_INFO_T *pInfo = (MBIM_REGISTRATION_STATE_INFO_T *)pIndMsg->InformationBuffer; + notify_state_chage(RegisterState, le32toh(pInfo->RegisterState)); + } + case MBIM_CID_PACKET_SERVICE: { + MBIM_PACKET_SERVICE_INFO_T *pInfo = (MBIM_PACKET_SERVICE_INFO_T *)pIndMsg->InformationBuffer; + notify_state_chage(PacketServiceState, le32toh(pInfo->PacketServiceState)); + } + break; + case MBIM_CID_CONNECT: { + MBIM_CONNECT_T *pInfo = (MBIM_CONNECT_T *)pIndMsg->InformationBuffer; + if (le32toh(pInfo->ActivationState) == MBIMActivationStateDeactivated || le32toh(pInfo->ActivationState) == MBIMActivationStateDeactivating) + mbim_ifconfig(4, mbim_netcard, NULL, NULL, NULL, NULL, 0, 0); + notify_state_chage(ActivationState, le32toh(pInfo->ActivationState)); + } + break; + default: + break; + } + } + } + + pthread_mutex_unlock(&mbim_command_mutex); +} + +static int mbim_send_command(MBIM_MESSAGE_HEADER *pRequest, MBIM_COMMAND_DONE_T **ppCmdDone, unsigned msecs) { + int ret; + + if (ppCmdDone) + *ppCmdDone = NULL; + + if (mbim_fd <= 0) + return -ENODEV; + + if (!pRequest) + return -ENOMEM; + + pthread_mutex_lock(&mbim_command_mutex); + + if (pRequest) + mbim_dump(pRequest, mbim_verbose); + + mbim_pRequest = pRequest; + mbim_pResponse = NULL; + + ret = write(mbim_fd, pRequest, le32toh(pRequest->MessageLength)); + + if (ret == le32toh(pRequest->MessageLength)) { + ret = pthread_cond_timeout_np(&mbim_command_cond, &mbim_command_mutex, msecs); + if (!ret) { + if (mbim_pResponse && ppCmdDone) { + *ppCmdDone = (MBIM_COMMAND_DONE_T *)mbim_pResponse; + } + } + } else { + mbim_debug("%s pthread_cond_timeout_np=%d", __func__, ret); + } + + mbim_pRequest = mbim_pResponse = NULL; + + pthread_mutex_unlock(&mbim_command_mutex); + + return ret; +} + +static UINT32 mbim_recv_buf[1024]; +static void * mbim_read_thread(void *param) { + mbim_debug("%s is created", __func__); + + while (mbim_fd > 0) { + struct pollfd pollfds[] = {{mbim_fd, POLLIN, 0}}; + int ne, ret, nevents = 1; + + ret = poll(pollfds, nevents, -1); + + if (ret <= 0) { + if (mbim_quit == 0) mbim_debug("%s poll=%d, errno: %d (%s)", __func__, ret, errno, strerror(errno)); + break; + } + + for (ne = 0; ne < nevents; ne++) { + int fd = pollfds[ne].fd; + short revents = pollfds[ne].revents; + + if (revents & (POLLERR | POLLHUP | POLLNVAL)) { + mbim_debug("%s poll err/hup/inval", __func__); + mbim_debug("epoll fd = %d, events = 0x%04x", fd, revents); + if (revents & (POLLERR | POLLHUP | POLLNVAL)) + goto __quit; + } + + if ((revents & POLLIN) == 0) + continue; + + if (mbim_fd == fd) { + ssize_t nreads; + MBIM_MESSAGE_HEADER *pResponse = (MBIM_MESSAGE_HEADER *) mbim_recv_buf; + + nreads = read(fd, pResponse, sizeof(mbim_recv_buf)); + if (nreads <= 0) { + mbim_debug("%s read=%d errno: %d (%s)", __func__, (int)nreads, errno, strerror(errno)); + break; + } + + mbim_recv_command(pResponse, nreads); + } + } + } + +__quit: + mbim_quit++; + mbim_recv_command(NULL, 0); + mbim_debug("%s exit", __func__); + _notify_state_chage(); + read_tid = 0; + + return NULL; +} + +static int mbim_status_code(MBIM_MESSAGE_HEADER *pMsgHdr) { + int status = 0; + + if (!pMsgHdr) + return 0; + + switch (pMsgHdr->MessageType) { + case MBIM_OPEN_DONE: { + MBIM_OPEN_DONE_T *pOpenDone = (MBIM_OPEN_DONE_T *)pMsgHdr; + status = le32toh(pOpenDone->Status); + } + break; + case MBIM_CLOSE_DONE: { + MBIM_CLOSE_DONE_T *pCloseDone = (MBIM_CLOSE_DONE_T *)pMsgHdr; + status = le32toh(pCloseDone->Status); + } + break; + case MBIM_COMMAND_DONE: { + MBIM_COMMAND_DONE_T *pCmdDone = (MBIM_COMMAND_DONE_T *)pMsgHdr; + status = le32toh(pCmdDone->Status); + } + break; + case MBIM_FUNCTION_ERROR_MSG: { + MBIM_FUNCTION_ERROR_MSG_T *pErrMsg = (MBIM_FUNCTION_ERROR_MSG_T *)pMsgHdr; + status = le32toh(pErrMsg->ErrorStatusCode); + if (status == MBIM_ERROR_NOT_OPENED) + mbim_open_state = 0; //EM06ELAR03A05M4G when suspend/resume, may get this error + } + break; + default: + break; + } + + return status; +} + +#define mbim_check_err(err, pRequest, pCmdDone) do { \ + int _status = mbim_status_code(&pCmdDone->MessageHeader); \ + if ((err || _status) && pCmdDone) { mbim_dump(&pCmdDone->MessageHeader, (mbim_verbose == 0)); } \ + if (err || _status) {mbim_debug("%s:%d err=%d, Status=%d", __func__, __LINE__, err, _status); } \ + if (err || pCmdDone == NULL) { mbim_free(pRequest); mbim_free(pCmdDone); return (err ? err : 8888);} \ +} while(0) + +/* + * MBIM device can be open repeatly without error + * So, we can call the function, no matter it have been opened or not + */ +static int mbim_open_device(uint32_t MaxControlTransfer) { + MBIM_MESSAGE_HEADER *pRequest = NULL; + MBIM_OPEN_DONE_T *pOpenDone = NULL; + int err; + + mbim_debug("%s()", __func__); + pRequest = compose_open_command(MaxControlTransfer); + err = mbim_send_command(pRequest, (MBIM_COMMAND_DONE_T **)&pOpenDone, mbim_default_timeout); + mbim_check_err(err, pRequest, pOpenDone); + + err = le32toh(pOpenDone->Status); + mbim_free(pRequest); mbim_free(pOpenDone); + + return err; +} + +static int mbim_close_device(void) { + MBIM_MESSAGE_HEADER *pRequest = NULL; + MBIM_CLOSE_DONE_T *pCloseDone = NULL; + int err = 0; + + mbim_debug("%s()", __func__); + pRequest = compose_close_command(); + err = mbim_send_command(pRequest, (MBIM_COMMAND_DONE_T **)&pCloseDone, mbim_default_timeout); + mbim_check_err(err, pRequest, pCloseDone); + + err = le32toh(pCloseDone->Status); + mbim_free(pRequest); mbim_free(pCloseDone); + + return err; +} + +static int mbim_query_connect(int sessionID) { + MBIM_MESSAGE_HEADER *pRequest = NULL; + MBIM_COMMAND_DONE_T *pCmdDone = NULL; + MBIM_SET_CONNECT_T set_connect; + int err; + + mbim_debug("%s(sessionID=%d)", __func__, sessionID); + set_connect.SessionId = htole32(sessionID); + pRequest = compose_basic_connect_command(MBIM_CID_CONNECT, MBIM_CID_CMD_TYPE_QUERY, &set_connect, sizeof(set_connect)); + err = mbim_send_command(pRequest, &pCmdDone, mbim_default_timeout); + mbim_check_err(err, pRequest, pCmdDone); + + if (le32toh(pCmdDone->InformationBufferLength)) + { + MBIM_CONNECT_T *pInfo = (MBIM_CONNECT_T *)pCmdDone->InformationBuffer; + ActivationState = le32toh(pInfo->ActivationState); + } + mbim_free(pRequest); mbim_free(pCmdDone); + return err; +} + +static int mbim_ms_version_query(void) { + MBIM_MESSAGE_HEADER *pRequest = NULL; + MBIM_COMMAND_DONE_T *pCmdDone = NULL; + int err; + + struct _bc_ext_version { + UINT8 ver_minor; + UINT8 ver_major; + UINT8 ext_ver_minor; + UINT8 ext_ver_major; + } __attribute__ ((packed)) bc_ext_version; + + bc_ext_version.ver_major = 1; + bc_ext_version.ver_minor = 0; + bc_ext_version.ext_ver_major = 2; + bc_ext_version.ext_ver_minor = 0; + + pRequest = compose_basic_connect_ext_command(MBIM_CID_MS_VERSION, MBIM_CID_CMD_TYPE_QUERY, &bc_ext_version, sizeof(bc_ext_version)); + err = mbim_send_command(pRequest, &pCmdDone, mbim_default_timeout); + mbim_check_err(err, pRequest, pCmdDone); + + if (le32toh(pCmdDone->InformationBufferLength)) { + struct _bc_ext_version *pInfo = (struct _bc_ext_version *)pCmdDone->InformationBuffer; + //mbim_debug("%s ext_rel_ver major=%d, minor=%d", __func__, pInfo->ext_ver_major, pInfo->ext_ver_minor); + mbim_ms_version = pInfo->ext_ver_major; + } + + mbim_free(pRequest); mbim_free(pCmdDone); + return err; +} + +static int mbim_device_services_query(void) { + MBIM_MESSAGE_HEADER *pRequest = NULL; + MBIM_COMMAND_DONE_T *pCmdDone = NULL; + int err; + int mbim_v2_support = 0; + + mbim_debug("%s()", __func__); + pRequest = compose_basic_connect_command(MBIM_CID_DEVICE_SERVICES, MBIM_CID_CMD_TYPE_QUERY, NULL, 0); + err = mbim_send_command(pRequest, &pCmdDone, mbim_default_timeout); + mbim_check_err(err, pRequest, pCmdDone); + + if (pCmdDone->InformationBufferLength) { + MBIM_DEVICE_SERVICES_INFO_T *pInfo = (MBIM_DEVICE_SERVICES_INFO_T *)pCmdDone->InformationBuffer; + UINT32 i; + + for (i = 0; i < le32toh(pInfo->DeviceServicesCount) ; i++) { + //UINT32 size = pInfo->DeviceServicesRefList[i].size; + UINT32 offset = le32toh(pInfo->DeviceServicesRefList[i].offset); + MBIM_DEVICE_SERVICE_ELEMENT_T *pSrvEle = (MBIM_DEVICE_SERVICE_ELEMENT_T *)((void *)pInfo + offset); + + if (!strcasecmp(UUID_BASIC_CONNECT_EXT, uuid2str(&pSrvEle->DeviceServiceId))) { + UINT32 cid = 0; + + for (cid = 0; cid < le32toh(pSrvEle->CidCount); cid++) { + if (MBIM_CID_MS_VERSION == le32toh(pSrvEle->CidList[cid])) { + mbim_v2_support = 1; + } + } + } + } + } + mbim_free(pRequest); mbim_free(pCmdDone); + + if (mbim_v2_support) { + mbim_ms_version_query(); + } + + return err; +} + +static int mbim_device_caps_query(void) { + MBIM_MESSAGE_HEADER *pRequest = NULL; + MBIM_COMMAND_DONE_T *pCmdDone = NULL; + int err; + + mbim_debug("%s()", __func__); + pRequest = compose_basic_connect_command(MBIM_CID_DEVICE_CAPS, MBIM_CID_CMD_TYPE_QUERY, NULL, 0); + err = mbim_send_command(pRequest, &pCmdDone, mbim_default_timeout); + mbim_check_err(err, pRequest, pCmdDone); + + if (le32toh(pCmdDone->InformationBufferLength)) { + MBIM_DEVICE_CAPS_INFO_T *pInfo = (MBIM_DEVICE_CAPS_INFO_T *)pCmdDone->InformationBuffer; + char tmp[32]; + UINT32 i; + + if (le32toh(pInfo->DeviceIdOffset) && le32toh(pInfo->DeviceIdSize)) { + for (i = 0; i < 32 && i < (le32toh(pInfo->DeviceIdSize)/2); i++) + tmp[i] = pInfo->DataBuffer[le32toh(pInfo->DeviceIdOffset) - sizeof(MBIM_DEVICE_CAPS_INFO_T) + i*2]; + tmp[i] = '\0'; + mbim_debug("DeviceId: %s", tmp); + } + if (le32toh(pInfo->FirmwareInfoOffset) && le32toh(pInfo->FirmwareInfoSize)) { + for (i = 0; i < 32 && i < (le32toh(pInfo->FirmwareInfoSize)/2); i++) + tmp[i] = pInfo->DataBuffer[le32toh(pInfo->FirmwareInfoOffset) - sizeof(MBIM_DEVICE_CAPS_INFO_T) + i*2]; + tmp[i] = '\0'; + mbim_debug("FirmwareInfo: %s", tmp); + } + if (le32toh(pInfo->HardwareInfoOffset) && le32toh(pInfo->HardwareInfoSize)) { + for (i = 0; i < 32 && i < (le32toh(pInfo->HardwareInfoSize)/2); i++) + tmp[i] = pInfo->DataBuffer[le32toh(pInfo->HardwareInfoOffset) - sizeof(MBIM_DEVICE_CAPS_INFO_T) + i*2]; + tmp[i] = '\0'; + mbim_debug("HardwareInfo: %s", tmp); + } + } + mbim_free(pRequest); mbim_free(pCmdDone); + return err; +} + +#if 0 +static int mbim_radio_state_query(void) { + MBIM_MESSAGE_HEADER *pRequest = NULL; + MBIM_COMMAND_DONE_T *pCmdDone = NULL; + int err; + + mbim_debug("%s()", __func__); + pRequest = compose_basic_connect_command(MBIM_CID_RADIO_STATE, MBIM_CID_CMD_TYPE_QUERY, NULL, 0); + err = mbim_send_command(pRequest, &pCmdDone, mbim_default_timeout); + mbim_check_err(err, pRequest, pCmdDone); + + if (pCmdDone->InformationBufferLength) { + MBIM_RADIO_STATE_INFO_T *pInfo = (MBIM_RADIO_STATE_INFO_T *)pCmdDone->InformationBuffer; + mbim_debug("HwRadioState: %d, SwRadioState: %d", pInfo->HwRadioState, pInfo->SwRadioState); + } + mbim_free(pRequest); mbim_free(pCmdDone); + return err; +} +#endif + +static int mbim_set_radio_state(MBIM_RADIO_SWITCH_STATE_E RadioState) { + MBIM_MESSAGE_HEADER *pRequest = NULL; + MBIM_COMMAND_DONE_T *pCmdDone = NULL; + UINT32 value = htole32(RadioState); + int err; + + mbim_debug("%s( %d )", __func__, RadioState); + pRequest = compose_basic_connect_command(MBIM_CID_RADIO_STATE, MBIM_CID_CMD_TYPE_SET, &value, sizeof(value)); + err = mbim_send_command(pRequest, &pCmdDone, mbim_default_timeout); + mbim_check_err(err, pRequest, pCmdDone); + + if (le32toh(pCmdDone->InformationBufferLength)) { + MBIM_RADIO_STATE_INFO_T *pInfo = (MBIM_RADIO_STATE_INFO_T *)pCmdDone->InformationBuffer; + mbim_debug("HwRadioState: %d, SwRadioState: %d", le32toh(pInfo->HwRadioState), le32toh(pInfo->SwRadioState)); + } + mbim_free(pRequest); mbim_free(pCmdDone); + return err; +} + +static int mbim_subscriber_status_query(void) { + MBIM_MESSAGE_HEADER *pRequest = NULL; + MBIM_COMMAND_DONE_T *pCmdDone = NULL; + int err; + + mbim_debug("%s()", __func__); + pRequest = compose_basic_connect_command(MBIM_CID_SUBSCRIBER_READY_STATUS, MBIM_CID_CMD_TYPE_QUERY, NULL, 0); + err = mbim_send_command(pRequest, &pCmdDone, mbim_default_timeout); + mbim_check_err(err, pRequest, pCmdDone); + + if (le32toh(pCmdDone->InformationBufferLength)) { + MBIM_SUBSCRIBER_READY_STATUS_T *pInfo = (MBIM_SUBSCRIBER_READY_STATUS_T *)pCmdDone->InformationBuffer; + ReadyState = le32toh(pInfo->ReadyState); + } + mbim_free(pRequest); mbim_free(pCmdDone); + return err; +} + +static int mbim_register_state_query(void) { + MBIM_MESSAGE_HEADER *pRequest = NULL; + MBIM_COMMAND_DONE_T *pCmdDone = NULL; + int err; + + mbim_debug("%s()", __func__); + pRequest = compose_basic_connect_command(MBIM_CID_REGISTER_STATE, MBIM_CID_CMD_TYPE_QUERY, NULL, 0); + err = mbim_send_command(pRequest, &pCmdDone, mbim_default_timeout); + mbim_check_err(err, pRequest, pCmdDone); + + if (le32toh(pCmdDone->InformationBufferLength)) { + MBIM_REGISTRATION_STATE_INFO_T *pInfo = (MBIM_REGISTRATION_STATE_INFO_T *)pCmdDone->InformationBuffer;; + RegisterState = le32toh(pInfo->RegisterState); + } + mbim_free(pRequest); mbim_free(pCmdDone); + return err; +} + +static int mbim_packet_service_query(void) { + MBIM_MESSAGE_HEADER *pRequest = NULL; + MBIM_COMMAND_DONE_T *pCmdDone = NULL; + int err; + + mbim_debug("%s()", __func__); + pRequest = compose_basic_connect_command(MBIM_CID_PACKET_SERVICE, MBIM_CID_CMD_TYPE_QUERY, NULL, 0); + err = mbim_send_command(pRequest, &pCmdDone, mbim_default_timeout); + mbim_check_err(err, pRequest, pCmdDone); + + if (le32toh(pCmdDone->InformationBufferLength)) { + MBIM_PACKET_SERVICE_INFO_T *pInfo = (MBIM_PACKET_SERVICE_INFO_T *)pCmdDone->InformationBuffer; + PacketServiceState = le32toh(pInfo->PacketServiceState); + + if (le32toh(pCmdDone->InformationBufferLength) == sizeof(MBIM_PACKET_SERVICE_INFO_V2_T)) { + MBIM_PACKET_SERVICE_INFO_V2_T *pInfo = (MBIM_PACKET_SERVICE_INFO_V2_T *)pCmdDone->InformationBuffer; + mbim_debug("CurrentDataClass = %s", MBIMDataClassStr(le32toh(pInfo->CurrentDataClass))); + } + } + mbim_free(pRequest); mbim_free(pCmdDone); + return err; +} + +static int mbim_packet_service_set(MBIM_PACKET_SERVICE_ACTION_E action) { + MBIM_MESSAGE_HEADER *pRequest = NULL; + MBIM_COMMAND_DONE_T *pCmdDone = NULL; + UINT32 value = htole32(action); + int err; + + mbim_debug("%s()", __func__); + pRequest = compose_basic_connect_command(MBIM_CID_PACKET_SERVICE, MBIM_CID_CMD_TYPE_SET, &value, sizeof(value)); + err = mbim_send_command(pRequest, &pCmdDone, mbim_default_timeout); + mbim_check_err(err, pRequest, pCmdDone); + + if (le32toh(pCmdDone->InformationBufferLength)) { + MBIM_PACKET_SERVICE_INFO_T *pInfo = (MBIM_PACKET_SERVICE_INFO_T *)pCmdDone->InformationBuffer; + PacketServiceState = le32toh(pInfo->PacketServiceState); + } + mbim_free(pRequest); mbim_free(pCmdDone); + return err; +} + +#define _align_32(len) {len += (len % 4) ? (4 - (len % 4)) : 0;} +static int mbim_populate_connect_data(MBIM_SET_CONNECT_T **connect_req_ptr) { + int offset; + int buflen = 0; + int i; + + if (mbim_apn && strlen(mbim_apn) > 0) buflen += 2*strlen(mbim_apn) ; + _align_32(buflen); + if (mbim_user && strlen(mbim_user) > 0) buflen += 2*strlen(mbim_user); + _align_32(buflen); + if (mbim_passwd && strlen(mbim_passwd) > 0) buflen += 2*strlen(mbim_passwd); + _align_32(buflen); + + *connect_req_ptr = (MBIM_SET_CONNECT_T*)malloc(sizeof(MBIM_SET_CONNECT_T) + buflen); + if (! *connect_req_ptr) { + mbim_debug("not enough memory\n"); + return -1; + } + memset(*connect_req_ptr, 0, sizeof(MBIM_SET_CONNECT_T) + buflen); + + offset = 0; + if (mbim_apn && strlen(mbim_apn) > 0) { + (*connect_req_ptr)->AccessStringSize = htole32(2*strlen(mbim_apn)); + (*connect_req_ptr)->AccessStringOffset = htole32(offset + sizeof(MBIM_SET_CONNECT_T)); + for (i = 0; i < strlen(mbim_apn); i++) { + (*connect_req_ptr)->DataBuffer[offset + i*2] = mbim_apn[i]; + (*connect_req_ptr)->DataBuffer[offset + i*2 + 1] = 0; + } + offset += 2 * strlen(mbim_apn); + _align_32(offset); + } + + if (mbim_user && strlen(mbim_user) > 0) { + (*connect_req_ptr)->UserNameSize = htole32(2*strlen(mbim_user)); + (*connect_req_ptr)->UserNameOffset = htole32(offset + sizeof(MBIM_SET_CONNECT_T)); + for (i = 0; i < strlen(mbim_user); i++) { + (*connect_req_ptr)->DataBuffer[offset + i*2] = mbim_user[i]; + (*connect_req_ptr)->DataBuffer[offset + i*2 + 1] = 0; + } + offset += 2 * strlen(mbim_user); + _align_32(offset); + } + + if (mbim_passwd && strlen(mbim_passwd) > 0) { + (*connect_req_ptr)->PasswordSize = htole32(2*strlen(mbim_passwd)); + (*connect_req_ptr)->PasswordOffset = htole32(offset + sizeof(MBIM_SET_CONNECT_T)); + for (i = 0; i < strlen(mbim_passwd); i++) { + (*connect_req_ptr)->DataBuffer[offset + i*2] = mbim_passwd[i]; + (*connect_req_ptr)->DataBuffer[offset + i*2 + 1] = 0; + } + } + + return buflen; +} + +static int mbim_set_connect(int onoff, int sessionID) { + MBIM_MESSAGE_HEADER *pRequest = NULL; + MBIM_COMMAND_DONE_T *pCmdDone = NULL; + MBIM_SET_CONNECT_T *set_connect = NULL; + int err; + + mbim_debug("%s(onoff=%d, sessionID=%d)", __func__, onoff, sessionID); + /* alloc memory then populate APN USERNAME PASSWORD */ + int buflen = mbim_populate_connect_data(&set_connect); + if (buflen < 0) { + return ENOMEM; + } + + set_connect->SessionId = htole32(sessionID); + if (onoff == 0) + set_connect->ActivationCommand = htole32(MBIMActivationCommandDeactivate); + else + set_connect->ActivationCommand = htole32(MBIMActivationCommandActivate); + + set_connect->Compression = htole32(MBIMCompressionNone); + set_connect->AuthProtocol = htole32(mbim_auth); + set_connect->IPType = htole32(mbim_iptype); + memcpy(set_connect->ContextType.uuid, str2uuid(UUID_MBIMContextTypeInternet), 16); + + pRequest = compose_basic_connect_command(MBIM_CID_CONNECT, MBIM_CID_CMD_TYPE_SET, set_connect, sizeof(MBIM_SET_CONNECT_T) + buflen); + mbim_free(set_connect); + err = mbim_send_command(pRequest, &pCmdDone, mbim_default_timeout*10); + mbim_check_err(err, pRequest, pCmdDone); + + if (le32toh(pCmdDone->InformationBufferLength)) { + MBIM_CONNECT_T *pInfo = (MBIM_CONNECT_T *)pCmdDone->InformationBuffer; + ActivationState = le32toh(pInfo->ActivationState); + } + + mbim_free(pRequest); mbim_free(pCmdDone); + return err; +} + +static int mbim_ip_config(int sessionID) { + MBIM_MESSAGE_HEADER *pRequest = NULL; + MBIM_COMMAND_DONE_T *pCmdDone = NULL; + MBIM_IP_CONFIGURATION_INFO_T ip_info; + int err; + + mbim_debug("%s(sessionID=%d)", __func__, sessionID); + ip_info.SessionId = htole32(sessionID); + pRequest = compose_basic_connect_command(MBIM_CID_IP_CONFIGURATION, MBIM_CID_CMD_TYPE_QUERY, &ip_info, sizeof(ip_info)); + err = mbim_send_command(pRequest, &pCmdDone, mbim_default_timeout); + mbim_check_err(err, pRequest, pCmdDone); + + if (le32toh(pCmdDone->InformationBufferLength)) { + UINT8 prefix, *ipv4=NULL, *ipv6=NULL, *gw=NULL, *dns1=NULL, *dns2=NULL; + UINT32 mtu = 1500; + MBIM_IP_CONFIGURATION_INFO_T *pInfo = (MBIM_IP_CONFIGURATION_INFO_T *)pCmdDone->InformationBuffer; + + if (mbim_verbose == 0) mbim_dump_ipconfig(pInfo, "<"); + + /* IPv4 network configration */ + if (le32toh(pInfo->IPv4ConfigurationAvailable)&0x1) { + MBIM_IPV4_ELEMENT_T *pAddress = (MBIM_IPV4_ELEMENT_T *)(&pInfo->DataBuffer[le32toh(pInfo->IPv4AddressOffset)-sizeof(MBIM_IP_CONFIGURATION_INFO_T)]); + prefix = le32toh(pAddress->OnLinkPrefixLength); + ipv4 = pAddress->IPv4Address; + + if (le32toh(pInfo->IPv4ConfigurationAvailable)&0x2) + gw = (UINT8 *)(&pInfo->DataBuffer[le32toh(pInfo->IPv4GatewayOffset)-sizeof(MBIM_IP_CONFIGURATION_INFO_T)]); + + if (le32toh(pInfo->IPv4ConfigurationAvailable)&0x4) { + dns1 = (UINT8 *)(&pInfo->DataBuffer[le32toh(pInfo->IPv4DnsServerOffset)-sizeof(MBIM_IP_CONFIGURATION_INFO_T)]); + if (le32toh(pInfo->IPv4DnsServerCount) == 2) + dns2 = dns1 + 4; + } + + if (le32toh(pInfo->IPv4ConfigurationAvailable)&0x8) + mtu = le32toh(pInfo->IPv4Mtu); + + mbim_ifconfig(4, mbim_netcard, ipv4, gw, dns1, dns2, prefix, mtu); + } + + /* IPv6 network configration */ + if (le32toh(pInfo->IPv6ConfigurationAvailable)&0x1) { + MBIM_IPV6_ELEMENT_T *pAddress = (MBIM_IPV6_ELEMENT_T *)(&pInfo->DataBuffer[le32toh(pInfo->IPv6AddressOffset)-sizeof(MBIM_IP_CONFIGURATION_INFO_T)]); + prefix = le32toh(pAddress->OnLinkPrefixLength); + ipv6 = pAddress->IPv6Address; + + if (le32toh(pInfo->IPv6ConfigurationAvailable)&0x2) + gw = (UINT8 *)(&pInfo->DataBuffer[le32toh(pInfo->IPv6GatewayOffset)-sizeof(MBIM_IP_CONFIGURATION_INFO_T)]); + + if (le32toh(pInfo->IPv6ConfigurationAvailable)&0x4) { + dns1 = (UINT8 *)(&pInfo->DataBuffer[le32toh(pInfo->IPv6DnsServerOffset)-sizeof(MBIM_IP_CONFIGURATION_INFO_T)]); + if (le32toh(pInfo->IPv6DnsServerCount) == 2) + dns2 = dns1 + 16; + } + + if (le32toh(pInfo->IPv6ConfigurationAvailable)&0x8) + mtu = le32toh(pInfo->IPv6Mtu); + + mbim_ifconfig(6, mbim_netcard, ipv6, gw, dns1, dns2, prefix, mtu); + } + } + return err; +} + +static void ql_sigaction(int signo) { + mbim_debug("MBIM catch signo %d", signo); + if (SIGTERM == signo || SIGHUP == signo || SIGINT == signo) { + mbim_quit++; + _notify_state_chage(); + } +} + +static void mbim_reset_state(void) { + ReadyState = oldReadyState = MBIMSubscriberReadyStateNotInitialized; + RegisterState = oldRegisterState = MBIMRegisterStateUnknown; + PacketServiceState = oldPacketServiceState = MBIMPacketServiceStateUnknown; + ActivationState = oldActivationState = MBIMActivationStateUnknown; + mbim_ms_version = 1; +} + +static int mbim_update_state(PROFILE_T *profile) { + int chages = 0; + + if (oldReadyState != ReadyState) { + mbim_debug("SubscriberReadyState %s -> %s ", MBIMSubscriberReadyStateStr(oldReadyState), MBIMSubscriberReadyStateStr(ReadyState)); + oldReadyState = ReadyState; chages++; + } + if (oldRegisterState != RegisterState) { + mbim_debug("RegisterState %s -> %s ", MBIMRegisterStateStr(oldRegisterState), MBIMRegisterStateStr(RegisterState)); + oldRegisterState = RegisterState; chages++; + } + if (oldPacketServiceState != PacketServiceState) { + mbim_debug("PacketServiceState %s -> %s ", MBIMPacketServiceStateStr(oldPacketServiceState), MBIMPacketServiceStateStr(PacketServiceState)); + oldPacketServiceState = PacketServiceState; chages++; + } + if (ActivationState != oldActivationState) { + ql_set_driver_link_state(profile, ActivationState == MBIMActivationStateActivated); + mbim_debug("ActivationState %s -> %s ", MBIMActivationStateStr(oldActivationState), MBIMActivationStateStr(ActivationState)); + oldActivationState = ActivationState; chages++; + } + + return chages; +} + +int mbim_main(PROFILE_T *profile) +{ + int retval; + int sessionID = 0; + + signal(SIGINT, ql_sigaction); + signal(SIGTERM, ql_sigaction); + signal(SIGHUP, ql_sigaction); + + profile->qmap_mode = 1; + + if (profile->qmichannel) + mbim_dev = profile->qmichannel; + if (profile->qmapnet_adapter) { + mbim_netcard = profile->qmapnet_adapter; + real_netcard = profile->usbnet_adapter; + } else if(profile->usbnet_adapter) { + mbim_netcard = profile->usbnet_adapter; + } + if (profile->apn) + mbim_apn = profile->apn; + if (profile->user) + mbim_user = profile->user; + if (profile->password) + mbim_passwd = profile->password; + if (profile->auth) + mbim_auth = profile->auth; + if (profile->enable_ipv4) + mbim_iptype = MBIMContextIPTypeIPv4; + if (profile->enable_ipv6) + mbim_iptype = MBIMContextIPTypeIPv6; + if (profile->enable_ipv4 && profile->enable_ipv6) + mbim_iptype = MBIMContextIPTypeIPv4AndIPv6; + mbim_verbose = debug_qmi; + mbim_debug("apn %s, user %s, passwd %s, auth %d", mbim_apn, mbim_user, mbim_passwd, mbim_auth); + mbim_debug("IP Proto %s", MBIMContextIPTypeStr(mbim_iptype)); + + /* set relative time, for pthread_cond_timedwait */ + cond_setclock_attr(&mbim_state_cond, CLOCK_MONOTONIC); + cond_setclock_attr(&mbim_command_cond, CLOCK_MONOTONIC); + + mbim_fd = open(mbim_dev, O_RDWR | O_NONBLOCK | O_NOCTTY); + if (mbim_fd <= 0) { + mbim_debug("fail to open (%s), errno: %d (%s)", mbim_dev, errno, strerror(errno)); + goto exit; + } + pthread_create(&read_tid, NULL, mbim_read_thread, (void *)mbim_dev); + mbim_open_state = 0; + + while (mbim_quit == 0 && read_tid != 0) { + uint32_t wait_time = 24*60*60; + + if (mbim_open_state == 0) { + mbim_ifconfig(4, mbim_netcard, NULL, NULL, NULL, NULL, 0, 0); + TransactionId = 1; + retval = mbim_open_device(4096); + if (retval) goto exit; + mbim_open_state = 1; + mbim_reset_state(); + retval = mbim_device_caps_query(); + if (retval) goto exit; + retval = mbim_device_services_query(); + if (retval) goto exit; + retval = mbim_set_radio_state(MBIMRadioOn); + if (retval) goto exit; + } + + if (ReadyState != MBIMSubscriberReadyStateInitialized) { + retval = mbim_subscriber_status_query(); + if (retval) goto exit; + mbim_update_state(profile); + } + if (ReadyState != MBIMSubscriberReadyStateInitialized) goto _wait_state_change; + + if (RegisterState != MBIMRegisterStateHome && RegisterState != MBIMRegisterStateRoaming) { + retval = mbim_register_state_query(); + if (retval) goto exit; + mbim_update_state(profile); + } + if (RegisterState != MBIMRegisterStateHome && RegisterState != MBIMRegisterStateRoaming) goto _wait_state_change; + + if (PacketServiceState != MBIMPacketServiceStateAttached) { + retval = mbim_packet_service_query(); + if (retval) goto exit; + mbim_update_state(profile); + if ((PacketServiceState == MBIMPacketServiceStateUnknown || PacketServiceState == MBIMPacketServiceStateDetached) + && (RegisterState == MBIMRegisterStateHome || RegisterState == MBIMRegisterStateRoaming)) { + retval = mbim_packet_service_set(MBIMPacketServiceActionAttach); //at+cgatt=0/1 + if (retval) goto exit; + } + mbim_update_state(profile); + } + if (PacketServiceState != MBIMPacketServiceStateAttached) goto _wait_state_change; + + if (ActivationState == MBIMActivationStateUnknown) { + retval = mbim_query_connect(sessionID); + if (retval) goto exit; + mbim_update_state(profile); + } + + if (ActivationState != MBIMActivationStateActivated && ActivationState != MBIMActivationStateActivating) { + retval = mbim_set_connect(1, sessionID); + if (retval) goto exit; + mbim_update_state(profile); + if (ActivationState == MBIMActivationStateActivated) { + retval = mbim_ip_config(sessionID); + if (retval) goto exit; + mbim_update_state(profile); + } + else { + wait_time = 30; //retry call mbim_set_connect 30 seconds later + } + } + if (ActivationState != MBIMActivationStateActivated) goto _wait_state_change; + +_wait_state_change: + wait_state_change(wait_time); + do { + mbim_update_state(profile); + } while (mbim_quit == 0 && read_tid != 0 && wait_state_change(1) != ETIMEDOUT); + } + +exit: + if (read_tid > 0) { + if (ActivationState == MBIMActivationStateActivated || ActivationState == MBIMActivationStateActivating) + mbim_set_connect(0, sessionID); + mbim_close_device(); + while (read_tid) { + pthread_kill(read_tid, SIGTERM); + usleep(10*1000); + } + mbim_ifconfig(4, mbim_netcard, NULL, NULL, NULL, NULL, 0, 0); + } + + if (mbim_fd > 0) { + close(mbim_fd); + } + pthread_mutex_destroy(&mbim_command_mutex); + pthread_mutex_destroy(&mbim_state_mutex); + pthread_cond_destroy(&mbim_command_cond); + pthread_cond_destroy(&mbim_state_cond); + + mbim_debug("MBIM CM exit...\n"); + return 0; +} diff --git a/root/package/link4all/quectel-CM/quectel1.6.0.15_src/qmap_bridge_mode.c b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/qmap_bridge_mode.c new file mode 100644 index 00000000..25ea0928 --- /dev/null +++ b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/qmap_bridge_mode.c @@ -0,0 +1,409 @@ +/****************************************************************************** + @file qmap_bridge_mode.c + @brief Connectivity bridge manager. + + DESCRIPTION + Connectivity Management Tool for USB network adapter of Quectel wireless cellular modules. + + INITIALIZATION AND SEQUENCING REQUIREMENTS + None. + + --------------------------------------------------------------------------- + Copyright (c) 2016 - 2020 Quectel Wireless Solution, Co., Ltd. All Rights Reserved. + Quectel Wireless Solution Proprietary and Confidential. + --------------------------------------------------------------------------- +******************************************************************************/ +#include "QMIThread.h" + +static size_t ql_fread(const char *filename, void *buf, size_t size) { + FILE *fp = fopen(filename , "r"); + size_t n = 0; + + memset(buf, 0x00, size); + + if (fp) { + n = fread(buf, 1, size, fp); + if (n <= 0 || n == size) { + dbg_time("warnning: fail to fread(%s), fread=%zu, buf_size=%zu: (%s)", filename, n, size, strerror(errno)); + } + fclose(fp); + } + + return n > 0 ? n : 0; +} + +static size_t ql_fwrite(const char *filename, const void *buf, size_t size) { + FILE *fp = fopen(filename , "w"); + size_t n = 0; + + if (fp) { + n = fwrite(buf, 1, size, fp); + if (n != size) { + dbg_time("warnning: fail to fwrite(%s), fwrite=%zu, buf_size=%zu: (%s)", filename, n, size, strerror(errno)); + } + fclose(fp); + } + + return n > 0 ? n : 0; +} + +int ql_bridge_mode_detect(PROFILE_T *profile) { + const char *ifname = profile->qmapnet_adapter ? profile->qmapnet_adapter : profile->usbnet_adapter; + const char *driver; + char bridge_mode[128]; + char bridge_ipv4[128]; + char ipv4[128]; + char buf[64]; + size_t n; + int in_bridge = 0; + + driver = profile->driver_name; + snprintf(bridge_mode, sizeof(bridge_mode), "/sys/class/net/%s/bridge_mode", ifname); + snprintf(bridge_ipv4, sizeof(bridge_ipv4), "/sys/class/net/%s/bridge_ipv4", ifname); + + if (access(bridge_ipv4, R_OK)) { + if (errno != ENOENT) { + dbg_time("fail to access %s, errno: %d (%s)", bridge_mode, errno, strerror(errno)); + return 0; + } + + snprintf(bridge_mode, sizeof(bridge_mode), "/sys/module/%s/parameters/bridge_mode", driver); + snprintf(bridge_ipv4, sizeof(bridge_ipv4), "/sys/module/%s/parameters/bridge_ipv4", driver); + + if (access(bridge_mode, R_OK)) { + if (errno != ENOENT) { + dbg_time("fail to access %s, errno: %d (%s)", bridge_mode, errno, strerror(errno)); + } + return 0; + } + } + + n = ql_fread(bridge_mode, buf, sizeof(buf)); + if (n > 0) { + in_bridge = (buf[0] != '0'); + } + if (!in_bridge) + return 0; + + memset(ipv4, 0, sizeof(ipv4)); + + if (strstr(bridge_ipv4, "/sys/class/net/") || profile->qmap_mode == 0 || profile->qmap_mode == 1) { + snprintf(ipv4, sizeof(ipv4), "0x%x", profile->ipv4.Address); + dbg_time("echo '%s' > %s", ipv4, bridge_ipv4); + ql_fwrite(bridge_ipv4, ipv4, strlen(ipv4)); + } + else { + snprintf(ipv4, sizeof(ipv4), "0x%x:%d", profile->ipv4.Address, profile->muxid); + dbg_time("echo '%s' > %s", ipv4, bridge_ipv4); + ql_fwrite(bridge_ipv4, ipv4, strlen(ipv4)); + } + + return in_bridge; +} + +int ql_enable_qmi_wwan_rawip_mode(PROFILE_T *profile) { + char filename[256]; + char buf[4]; + size_t n; + FILE *fp; + + if (!qmidev_is_qmiwwan(profile->qmichannel)) + return 0; + + snprintf(filename, sizeof(filename), "/sys/class/net/%s/qmi/rawip", profile->usbnet_adapter); + n = ql_fread(filename, buf, sizeof(buf)); + + if (n == 0) + return 0; + + if (buf[0] == '1' || buf[0] == 'Y') + return 0; + + fp = fopen(filename , "w"); + if (fp == NULL) { + dbg_time("Fail to fopen(%s, \"w\"), errno: %d (%s)", filename, errno, strerror(errno)); + return 1; + } + + buf[0] = 'Y'; + n = fwrite(buf, 1, 1, fp); + if (n != 1) { + dbg_time("Fail to fwrite(%s), errno: %d (%s)", filename, errno, strerror(errno)); + fclose(fp); + return 1; + } + fclose(fp); + + return 0; +} + +int ql_driver_type_detect(PROFILE_T *profile) { + if (qmidev_is_gobinet(profile->qmichannel)) { + profile->qmi_ops = &gobi_qmidev_ops; + } + else { + profile->qmi_ops = &qmiwwan_qmidev_ops; + } + qmidev_send = profile->qmi_ops->send; + + return 0; +} + +void ql_set_driver_bridge_mode(PROFILE_T *profile) { + char enable[8]; + char filename[256]; + + if(profile->qmap_mode) + snprintf(filename, sizeof(filename), "/sys/class/net/%s/bridge_mode", profile->qmapnet_adapter); + else + snprintf(filename, sizeof(filename), "/sys/class/net/%s/bridge_mode", profile->usbnet_adapter); + snprintf(enable, sizeof(enable), "%d\n", profile->enable_bridge); + ql_fwrite(filename, enable, sizeof(enable)); +} + +static int ql_qmi_qmap_mode_detect(PROFILE_T *profile) { + char buf[128]; + int n; + char qmap_netcard[128]; + struct { + char filename[255 * 2]; + char linkname[255 * 2]; + } *pl; + + pl = (typeof(pl)) malloc(sizeof(*pl)); + + if (profile->qmapnet_adapter) { + free(profile->qmapnet_adapter);; + profile->qmapnet_adapter = NULL; + } + + snprintf(pl->linkname, sizeof(pl->linkname), "/sys/class/net/%s/device/driver", profile->usbnet_adapter); + n = readlink(pl->linkname, pl->filename, sizeof(pl->filename)); + pl->filename[n] = '\0'; + while (pl->filename[n] != '/') + n--; + strset(profile->driver_name, &pl->filename[n+1]); + + ql_get_driver_rmnet_info(profile, &profile->rmnet_info); + if (profile->rmnet_info.size) { + profile->qmap_mode = profile->rmnet_info.qmap_mode; + if (profile->qmap_mode) { + int offset_id = profile->pdp - 1; + + if (profile->qmap_mode == 1) + offset_id = 0; + profile->muxid = profile->rmnet_info.mux_id[offset_id]; + profile->qmapnet_adapter = strdup( profile->rmnet_info.ifname[offset_id]); + profile->qmap_size = profile->rmnet_info.rx_urb_size; + profile->qmap_version = profile->rmnet_info.qmap_version; + } + + goto _out; + } + + snprintf(pl->filename, sizeof(pl->filename), "/sys/class/net/%s/qmap_mode", profile->usbnet_adapter); + if (access(pl->filename, R_OK)) { + if (errno != ENOENT) { + dbg_time("fail to access %s, errno: %d (%s)", pl->filename, errno, strerror(errno)); + goto _out; + } + + snprintf(pl->filename, sizeof(pl->filename), "/sys/module/%s/parameters/qmap_mode", profile->driver_name); + if (access(pl->filename, R_OK)) { + if (errno != ENOENT) { + dbg_time("fail to access %s, errno: %d (%s)", pl->filename, errno, strerror(errno)); + goto _out; + } + + snprintf(pl->filename, sizeof(pl->filename), "/sys/class/net/%s/device/driver/module/parameters/qmap_mode", profile->usbnet_adapter); + if (access(pl->filename, R_OK)) { + if (errno != ENOENT) { + dbg_time("fail to access %s, errno: %d (%s)", pl->filename, errno, strerror(errno)); + goto _out; + } + } + } + } + + if (!access(pl->filename, R_OK)) { + n = ql_fread(pl->filename, buf, sizeof(buf)); + if (n > 0) { + profile->qmap_mode = atoi(buf); + + if (profile->qmap_mode > 1) { + profile->muxid = profile->pdp + 0x80; //muxis is 0x8X for PDN-X + sprintf(qmap_netcard, "%s.%d", profile->usbnet_adapter, profile->pdp); + profile->qmapnet_adapter = strdup(qmap_netcard); + } if (profile->qmap_mode == 1) { + profile->muxid = 0x81; + profile->qmapnet_adapter = strdup(profile->usbnet_adapter); + } + } + } + else if (qmidev_is_qmiwwan(profile->qmichannel)) { + snprintf(pl->filename, sizeof(pl->filename), "/sys/class/net/qmimux%d", profile->pdp - 1); + if (access(pl->filename, R_OK)) { + if (errno != ENOENT) { + dbg_time("fail to access %s, errno: %d (%s)", pl->filename, errno, strerror(errno)); + } + goto _out; + } + + //upstream Kernel Style QMAP qmi_wwan.c + snprintf(pl->filename, sizeof(pl->filename), "/sys/class/net/%s/qmi/add_mux", profile->usbnet_adapter); + n = ql_fread(pl->filename, buf, sizeof(buf)); + if (n >= 5) { + dbg_time("If use QMAP by /sys/class/net/%s/qmi/add_mux", profile->usbnet_adapter); + dbg_time("File:%s Line:%d Please make sure add next patch to qmi_wwan.c", __func__, __LINE__); + /* + diff --git a/drivers/net/usb/qmi_wwan.c b/drivers/net/usb/qmi_wwan.c + index 74bebbd..db8a777 100644 + --- a/drivers/net/usb/qmi_wwan.c + +++ b/drivers/net/usb/qmi_wwan.c + @@ -379,6 +379,24 @@ static ssize_t add_mux_store(struct device *d, struct device_attribute *attr, c + if (!ret) { + info->flags |= QMI_WWAN_FLAG_MUX; + ret = len; + +#if 1 //Add by Quectel + + if (le16_to_cpu(dev->udev->descriptor.idVendor) == 0x2c7c) { + + int idProduct = le16_to_cpu(dev->udev->descriptor.idProduct); + + + + if (idProduct == 0x0121 || idProduct == 0x0125 || idProduct == 0x0435) //MDM9x07 + + dev->rx_urb_size = 4*1024; + + else if (idProduct == 0x0306) //MDM9x40 + + dev->rx_urb_size = 16*1024; + + else if (idProduct == 0x0512) //SDX20 + + dev->rx_urb_size = 32*1024; + + else if (idProduct == 0x0620) //SDX24 + + dev->rx_urb_size = 32*1024; + + else if (idProduct == 0x0800) //SDX55 + + dev->rx_urb_size = 32*1024; + + else + + dev->rx_urb_size = 32*1024; + + } + +#endif + } + err: + rtnl_unlock(); + */ + profile->qmap_mode = n/5; //0x11\n0x22\n0x33\n + if (profile->qmap_mode > 1) { + //PDN-X map to qmimux-X + profile->muxid = (buf[5*(profile->pdp - 1) + 2] - '0')*16 + (buf[5*(profile->pdp - 1) + 3] - '0'); + sprintf(qmap_netcard, "qmimux%d", profile->pdp - 1); + profile->qmapnet_adapter = strdup(qmap_netcard); + } else if (profile->qmap_mode == 1) { + profile->muxid = (buf[5*0 + 2] - '0')*16 + (buf[5*0 + 3] - '0'); + sprintf(qmap_netcard, "qmimux%d", 0); + profile->qmapnet_adapter = strdup(qmap_netcard); + } + } + } + +_out: + if (profile->qmap_mode) { + if (profile->qmap_size == 0) { + profile->qmap_size = 16*1024; + snprintf(pl->filename, sizeof(pl->filename), "/sys/class/net/%s/qmap_size", profile->usbnet_adapter); + if (!access(pl->filename, R_OK)) { + size_t n; + char buf[32]; + n = ql_fread(pl->filename, buf, sizeof(buf)); + if (n > 0) { + profile->qmap_size = atoi(buf); + } + } + } + + if (profile->qmap_version == 0) { + profile->qmap_version = WDA_DL_DATA_AGG_QMAP_ENABLED; + } + + dbg_time("qmap_mode = %d, qmap_version = %d, qmap_size = %d, muxid = 0x%02x, qmap_netcard = %s", + profile->qmap_mode, profile->qmap_version, profile->qmap_size, profile->muxid, profile->qmapnet_adapter); + } + ql_set_driver_bridge_mode(profile); + free(pl); + + return 0; +} + + +static int ql_mbim_qmap_mode_detect(PROFILE_T *profile) { + char buf[128]; + int n; + char qmap_netcard[128]; + struct { + char filename[255 * 2]; + char linkname[255 * 2]; + } *pl; + + pl = (typeof(pl)) malloc(sizeof(*pl)); + + if (profile->qmapnet_adapter) { + free(profile->qmapnet_adapter);; + profile->qmapnet_adapter = NULL; + } + + snprintf(pl->linkname, sizeof(pl->linkname), "/sys/class/net/%s/device/driver", profile->usbnet_adapter); + n = readlink(pl->linkname, pl->filename, sizeof(pl->filename)); + pl->filename[n] = '\0'; + while (pl->filename[n] != '/') + n--; + strset(profile->driver_name, &pl->filename[n+1]); + + ql_get_driver_rmnet_info(profile, &profile->rmnet_info); + if (profile->rmnet_info.size) { + profile->qmap_mode = profile->rmnet_info.qmap_mode; + if (profile->qmap_mode) { + int offset_id = profile->pdp - 1; + + if (profile->qmap_mode == 1) + offset_id = 0; + profile->muxid = profile->rmnet_info.mux_id[offset_id]; + profile->qmapnet_adapter = strdup( profile->rmnet_info.ifname[offset_id]); + profile->qmap_size = profile->rmnet_info.rx_urb_size; + profile->qmap_version = profile->rmnet_info.qmap_version; + } + + goto _out; + } + + snprintf(pl->filename, sizeof(pl->filename), "/sys/module/%s/parameters/qmap_mode", profile->driver_name); + if (access(pl->filename, R_OK)) { + if (errno != ENOENT) { + dbg_time("fail to access %s, errno: %d (%s)", pl->filename, errno, strerror(errno)); + goto _out; + } + } else { + n = ql_fread(pl->filename, buf, sizeof(buf)); + if (n > 0) { + profile->qmap_mode = atoi(buf); + + if (profile->qmap_mode > 1) { + profile->muxid = profile->pdp + 0x80; //muxis is 0x8X for PDN-X + sprintf(qmap_netcard, "%s.%d", profile->usbnet_adapter, profile->pdp); + profile->qmapnet_adapter = strdup(qmap_netcard); + } + } + } + +_out: + if (profile->qmap_mode) { + dbg_time("mbim_qmap_mode = %d, vlan_id = 0x%02x, qmap_netcard = %s", + profile->qmap_mode, profile->muxid, profile->qmapnet_adapter); + } + + free(pl); + + return 0; +} + +int ql_qmap_mode_detect(PROFILE_T *profile) { + if (profile->software_interface == SOFTWARE_MBIM) { + return ql_mbim_qmap_mode_detect(profile); + } else if (profile->software_interface == SOFTWARE_QMI) { + return ql_qmi_qmap_mode_detect(profile); + } + return 0; +} diff --git a/root/package/link4all/quectel-CM/quectel1.6.0.15_src/quectel-qmi-proxy.c b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/quectel-qmi-proxy.c new file mode 100644 index 00000000..39d76a37 --- /dev/null +++ b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/quectel-qmi-proxy.c @@ -0,0 +1,1546 @@ +/****************************************************************************** + @file quectel-qmi-proxy.c + @brief The qmi proxy. + + DESCRIPTION + Connectivity Management Tool for USB network adapter of Quectel wireless cellular modules. + + INITIALIZATION AND SEQUENCING REQUIREMENTS + None. + + --------------------------------------------------------------------------- + Copyright (c) 2016 - 2020 Quectel Wireless Solution, Co., Ltd. All Rights Reserved. + Quectel Wireless Solution Proprietary and Confidential. + --------------------------------------------------------------------------- +******************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef MIN +#define MIN(a, b) ((a) < (b)? (a): (b)) +#endif + +#ifndef htole32 +#if __BYTE_ORDER == __LITTLE_ENDIAN +#define htole16(x) (uint16_t)(x) +#define le16toh(x) (uint16_t)(x) +#define letoh16(x) (uint16_t)(x) +#define htole32(x) (uint32_t)(x) +#define le32toh(x) (uint32_t)(x) +#define letoh32(x) (uint32_t)(x) +#define htole64(x) (uint64_t)(x) +#define le64toh(x) (uint64_t)(x) +#define letoh64(x) (uint64_t)(x) +#else +static __inline uint16_t __bswap16(uint16_t __x) { + return (__x<<8) | (__x>>8); +} + +static __inline uint32_t __bswap32(uint32_t __x) { + return (__x>>24) | (__x>>8&0xff00) | (__x<<8&0xff0000) | (__x<<24); +} + +static __inline uint64_t __bswap64(uint64_t __x) { + return (__bswap32(__x)+0ULL<<32) | (__bswap32(__x>>32)); +} + +#define htole16(x) __bswap16(x) +#define le16toh(x) __bswap16(x) +#define letoh16(x) __bswap16(x) +#define htole32(x) __bswap32(x) +#define le32toh(x) __bswap32(x) +#define letoh32(x) __bswap32(x) +#define htole64(x) __bswap64(x) +#define le64toh(x) __bswap64(x) +#define letoh64(x) __bswap64(x) +#endif +#endif + +#define dprintf(fmt, arg...) do { printf(fmt, ##arg); } while(0) +#define SYSCHECK(c) do{if((c)<0) {dprintf("%s %d error: '%s' (code: %d)\n", __func__, __LINE__, strerror(errno), errno); return -1;}}while(0) +#define cfmakenoblock(fd) do{fcntl(fd, F_SETFL, fcntl(fd,F_GETFL) | O_NONBLOCK);}while(0) + +#define qmidev_is_pciemhi(_qmichannel) (strncmp(_qmichannel, "/dev/mhi_", strlen("/dev/mhi_")) == 0) + +typedef struct _QCQMI_HDR +{ + uint8_t IFType; + uint16_t Length; + uint8_t CtlFlags; // reserved + uint8_t QMIType; + uint8_t ClientId; +} __attribute__ ((packed)) QCQMI_HDR, *PQCQMI_HDR; + +typedef struct _QMICTL_SYNC_REQ_MSG +{ + uint8_t CtlFlags; // QMICTL_FLAG_REQUEST + uint8_t TransactionId; + uint16_t QMICTLType; // QMICTL_CTL_SYNC_REQ + uint16_t Length; // 0 +} __attribute__ ((packed)) QMICTL_SYNC_REQ_MSG, *PQMICTL_SYNC_REQ_MSG; + +typedef struct _QMICTL_SYNC_RESP_MSG +{ + uint8_t CtlFlags; // QMICTL_FLAG_RESPONSE + uint8_t TransactionId; + uint16_t QMICTLType; // QMICTL_CTL_SYNC_RESP + uint16_t Length; + uint8_t TLVType; // QCTLV_TYPE_RESULT_CODE + uint16_t TLVLength; // 0x0004 + uint16_t QMIResult; + uint16_t QMIError; +} __attribute__ ((packed)) QMICTL_SYNC_RESP_MSG, *PQMICTL_SYNC_RESP_MSG; + +typedef struct _QMICTL_SYNC_IND_MSG +{ + uint8_t CtlFlags; // QMICTL_FLAG_INDICATION + uint8_t TransactionId; + uint16_t QMICTLType; // QMICTL_REVOKE_CLIENT_ID_IND + uint16_t Length; +} __attribute__ ((packed)) QMICTL_SYNC_IND_MSG, *PQMICTL_SYNC_IND_MSG; + +typedef struct _QMICTL_GET_CLIENT_ID_REQ_MSG +{ + uint8_t CtlFlags; // QMICTL_FLAG_REQUEST + uint8_t TransactionId; + uint16_t QMICTLType; // QMICTL_GET_CLIENT_ID_REQ + uint16_t Length; + uint8_t TLVType; // QCTLV_TYPE_REQUIRED_PARAMETER + uint16_t TLVLength; // 1 + uint8_t QMIType; // QMUX type +} __attribute__ ((packed)) QMICTL_GET_CLIENT_ID_REQ_MSG, *PQMICTL_GET_CLIENT_ID_REQ_MSG; + +typedef struct _QMICTL_GET_CLIENT_ID_RESP_MSG +{ + uint8_t CtlFlags; // QMICTL_FLAG_RESPONSE + uint8_t TransactionId; + uint16_t QMICTLType; // QMICTL_GET_CLIENT_ID_RESP + uint16_t Length; + uint8_t TLVType; // QCTLV_TYPE_RESULT_CODE + uint16_t TLVLength; // 0x0004 + uint16_t QMIResult; // result code + uint16_t QMIError; // error code + uint8_t TLV2Type; // QCTLV_TYPE_REQUIRED_PARAMETER + uint16_t TLV2Length; // 2 + uint8_t QMIType; + uint8_t ClientId; +} __attribute__ ((packed)) QMICTL_GET_CLIENT_ID_RESP_MSG, *PQMICTL_GET_CLIENT_ID_RESP_MSG; + +typedef struct _QMICTL_RELEASE_CLIENT_ID_REQ_MSG +{ + uint8_t CtlFlags; // QMICTL_FLAG_REQUEST + uint8_t TransactionId; + uint16_t QMICTLType; // QMICTL_RELEASE_CLIENT_ID_REQ + uint16_t Length; + uint8_t TLVType; // QCTLV_TYPE_REQUIRED_PARAMETER + uint16_t TLVLength; // 0x0002 + uint8_t QMIType; + uint8_t ClientId; +} __attribute__ ((packed)) QMICTL_RELEASE_CLIENT_ID_REQ_MSG, *PQMICTL_RELEASE_CLIENT_ID_REQ_MSG; + +typedef struct _QMICTL_RELEASE_CLIENT_ID_RESP_MSG +{ + uint8_t CtlFlags; // QMICTL_FLAG_RESPONSE + uint8_t TransactionId; + uint16_t QMICTLType; // QMICTL_RELEASE_CLIENT_ID_RESP + uint16_t Length; + uint8_t TLVType; // QCTLV_TYPE_RESULT_CODE + uint16_t TLVLength; // 0x0004 + uint16_t QMIResult; // result code + uint16_t QMIError; // error code + uint8_t TLV2Type; // QCTLV_TYPE_REQUIRED_PARAMETER + uint16_t TLV2Length; // 2 + uint8_t QMIType; + uint8_t ClientId; +} __attribute__ ((packed)) QMICTL_RELEASE_CLIENT_ID_RESP_MSG, *PQMICTL_RELEASE_CLIENT_ID_RESP_MSG; + +// QMICTL Control Flags +#define QMICTL_CTL_FLAG_CMD 0x00 +#define QMICTL_CTL_FLAG_RSP 0x01 +#define QMICTL_CTL_FLAG_IND 0x02 + +typedef struct _QCQMICTL_MSG_HDR +{ + uint8_t CtlFlags; // 00-cmd, 01-rsp, 10-ind + uint8_t TransactionId; + uint16_t QMICTLType; + uint16_t Length; +} __attribute__ ((packed)) QCQMICTL_MSG_HDR, *PQCQMICTL_MSG_HDR; + +#define QCQMICTL_MSG_HDR_SIZE sizeof(QCQMICTL_MSG_HDR) + +typedef struct _QCQMICTL_MSG_HDR_RESP +{ + uint8_t CtlFlags; // 00-cmd, 01-rsp, 10-ind + uint8_t TransactionId; + uint16_t QMICTLType; + uint16_t Length; + uint8_t TLVType; // 0x02 - result code + uint16_t TLVLength; // 4 + uint16_t QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + uint16_t QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} __attribute__ ((packed)) QCQMICTL_MSG_HDR_RESP, *PQCQMICTL_MSG_HDR_RESP; + +typedef struct _QMICTL_GET_VERSION_REQ_MSG +{ + uint8_t CtlFlags; // QMICTL_FLAG_REQUEST + uint8_t TransactionId; + uint16_t QMICTLType; // QMICTL_GET_VERSION_REQ + uint16_t Length; // 0 + uint8_t TLVType; // QCTLV_TYPE_REQUIRED_PARAMETER + uint16_t TLVLength; // var + uint8_t QMUXTypes; // List of one byte QMUX_TYPE values + // 0xFF returns a list of versions for all + // QMUX_TYPEs implemented on the device +} __attribute__ ((packed)) QMICTL_GET_VERSION_REQ_MSG, *PQMICTL_GET_VERSION_REQ_MSG; + +typedef struct _QMUX_TYPE_VERSION_STRUCT +{ + uint8_t QMUXType; + uint16_t MajorVersion; + uint16_t MinorVersion; +} __attribute__ ((packed)) QMUX_TYPE_VERSION_STRUCT, *PQMUX_TYPE_VERSION_STRUCT; + +typedef struct _QMICTL_GET_VERSION_RESP_MSG +{ + uint8_t CtlFlags; // QMICTL_FLAG_RESPONSE + uint8_t TransactionId; + uint16_t QMICTLType; // QMICTL_GET_VERSION_RESP + uint16_t Length; + uint8_t TLVType; // QCTLV_TYPE_RESULT_CODE + uint16_t TLVLength; // 0x0004 + uint16_t QMIResult; + uint16_t QMIError; + uint8_t TLV2Type; // QCTLV_TYPE_REQUIRED_PARAMETER + uint16_t TLV2Length; // var + uint8_t NumElements; // Num of QMUX_TYPE_VERSION_STRUCT + QMUX_TYPE_VERSION_STRUCT TypeVersion[0]; +} __attribute__ ((packed)) QMICTL_GET_VERSION_RESP_MSG, *PQMICTL_GET_VERSION_RESP_MSG; + + +typedef struct _QMICTL_MSG +{ + union + { + // Message Header + QCQMICTL_MSG_HDR QMICTLMsgHdr; + QCQMICTL_MSG_HDR_RESP QMICTLMsgHdrRsp; + + // QMICTL Message + //QMICTL_SET_INSTANCE_ID_REQ_MSG SetInstanceIdReq; + //QMICTL_SET_INSTANCE_ID_RESP_MSG SetInstanceIdRsp; + QMICTL_GET_VERSION_REQ_MSG GetVersionReq; + QMICTL_GET_VERSION_RESP_MSG GetVersionRsp; + QMICTL_GET_CLIENT_ID_REQ_MSG GetClientIdReq; + QMICTL_GET_CLIENT_ID_RESP_MSG GetClientIdRsp; + //QMICTL_RELEASE_CLIENT_ID_REQ_MSG ReleaseClientIdReq; + QMICTL_RELEASE_CLIENT_ID_RESP_MSG ReleaseClientIdRsp; + //QMICTL_REVOKE_CLIENT_ID_IND_MSG RevokeClientIdInd; + //QMICTL_INVALID_CLIENT_ID_IND_MSG InvalidClientIdInd; + //QMICTL_SET_DATA_FORMAT_REQ_MSG SetDataFormatReq; + //QMICTL_SET_DATA_FORMAT_RESP_MSG SetDataFormatRsp; + QMICTL_SYNC_REQ_MSG SyncReq; + QMICTL_SYNC_RESP_MSG SyncRsp; + QMICTL_SYNC_IND_MSG SyncInd; + }; +} __attribute__ ((packed)) QMICTL_MSG, *PQMICTL_MSG; + +typedef struct _QCQMUX_MSG_HDR +{ + uint8_t CtlFlags; // 0: single QMUX Msg; 1: + uint16_t TransactionId; + uint16_t Type; + uint16_t Length; + uint8_t payload[0]; +} __attribute__ ((packed)) QCQMUX_MSG_HDR, *PQCQMUX_MSG_HDR; + +typedef struct _QCQMUX_MSG_HDR_RESP +{ + uint8_t CtlFlags; // 0: single QMUX Msg; 1: + uint16_t TransactionId; + uint16_t Type; + uint16_t Length; + uint8_t TLVType; // 0x02 - result code + uint16_t TLVLength; // 4 + uint16_t QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + uint16_t QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} __attribute__ ((packed)) QCQMUX_MSG_HDR_RESP, *PQCQMUX_MSG_HDR_RESP; + +typedef struct _QMIWDS_ADMIN_SET_DATA_FORMAT +{ + uint16_t Type; // QMUX type 0x0000 + uint16_t Length; +} __attribute__ ((packed)) QMIWDS_ADMIN_SET_DATA_FORMAT, *PQMIWDS_ADMIN_SET_DATA_FORMAT; + +typedef struct _QMIWDS_ADMIN_SET_DATA_FORMAT_TLV_QOS +{ + uint8_t TLVType; + uint16_t TLVLength; + uint8_t QOSSetting; +} __attribute__ ((packed)) QMIWDS_ADMIN_SET_DATA_FORMAT_TLV_QOS, *PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV_QOS; + +typedef struct _QMIWDS_ADMIN_SET_DATA_FORMAT_TLV +{ + uint8_t TLVType; + uint16_t TLVLength; + uint32_t Value; +} __attribute__ ((packed)) QMIWDS_ADMIN_SET_DATA_FORMAT_TLV, *PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV; + +typedef struct _QMIWDS_ENDPOINT_TLV +{ + uint8_t TLVType; + uint16_t TLVLength; + uint32_t ep_type; + uint32_t iface_id; +} __attribute__ ((packed)) QMIWDS_ENDPOINT_TLV, *PQMIWDS_ENDPOINT_TLV; + +#define QUECTEL_UL_DATA_AGG +typedef uint32_t UINT; +typedef struct { + UINT size; + UINT rx_urb_size; + UINT ep_type; + UINT iface_id; + UINT MuxId; + UINT ul_data_aggregation_max_datagrams; //0x17 + UINT ul_data_aggregation_max_size ;//0x18 + UINT dl_minimum_padding; //0x1A +} QMAP_SETTING; + +typedef struct { + unsigned int size; + unsigned int rx_urb_size; + unsigned int ep_type; + unsigned int iface_id; + unsigned int qmap_mode; + unsigned int qmap_version; + unsigned int dl_minimum_padding; + char ifname[8][16]; + unsigned char mux_id[8]; +} RMNET_INFO; + +typedef struct _QMIWDS_ADMIN_SET_DATA_FORMAT_REQ_MSG +{ + uint8_t CtlFlags; // 0: single QMUX Msg; 1: + uint16_t TransactionId; + uint16_t Type; + uint16_t Length; + QMIWDS_ADMIN_SET_DATA_FORMAT_TLV_QOS QosDataFormatTlv; + QMIWDS_ADMIN_SET_DATA_FORMAT_TLV UnderlyingLinkLayerProtocolTlv; + QMIWDS_ADMIN_SET_DATA_FORMAT_TLV UplinkDataAggregationProtocolTlv; + QMIWDS_ADMIN_SET_DATA_FORMAT_TLV DownlinkDataAggregationProtocolTlv; + QMIWDS_ADMIN_SET_DATA_FORMAT_TLV DownlinkDataAggregationMaxDatagramsTlv; + QMIWDS_ADMIN_SET_DATA_FORMAT_TLV DownlinkDataAggregationMaxSizeTlv; + QMIWDS_ENDPOINT_TLV epTlv; +#ifdef QUECTEL_UL_DATA_AGG + QMIWDS_ADMIN_SET_DATA_FORMAT_TLV DlMinimumPassingTlv; + QMIWDS_ADMIN_SET_DATA_FORMAT_TLV UplinkDataAggregationMaxDatagramsTlv; + QMIWDS_ADMIN_SET_DATA_FORMAT_TLV UplinkDataAggregationMaxSizeTlv; +#endif +} __attribute__ ((packed)) QMIWDS_ADMIN_SET_DATA_FORMAT_REQ_MSG, *PQMIWDS_ADMIN_SET_DATA_FORMAT_REQ_MSG; + +typedef struct _QCQMUX_TLV +{ + uint8_t Type; + uint16_t Length; + uint8_t Value[0]; +} __attribute__ ((packed)) QCQMUX_TLV, *PQCQMUX_TLV; + +typedef struct _QMUX_MSG +{ + union + { + // Message Header + QCQMUX_MSG_HDR QMUXMsgHdr; + QCQMUX_MSG_HDR_RESP QMUXMsgHdrResp; + QMIWDS_ADMIN_SET_DATA_FORMAT_REQ_MSG SetDataFormatReq; + }; +} __attribute__ ((packed)) QMUX_MSG, *PQMUX_MSG; + +typedef struct _QCQMIMSG { + QCQMI_HDR QMIHdr; + union { + QMICTL_MSG CTLMsg; + QMUX_MSG MUXMsg; + }; +} __attribute__ ((packed)) QCQMIMSG, *PQCQMIMSG; + + +// QMUX Message Definitions -- QMI SDU +#define QMUX_CTL_FLAG_SINGLE_MSG 0x00 +#define QMUX_CTL_FLAG_COMPOUND_MSG 0x01 +#define QMUX_CTL_FLAG_TYPE_CMD 0x00 +#define QMUX_CTL_FLAG_TYPE_RSP 0x02 +#define QMUX_CTL_FLAG_TYPE_IND 0x04 +#define QMUX_CTL_FLAG_MASK_COMPOUND 0x01 +#define QMUX_CTL_FLAG_MASK_TYPE 0x06 // 00-cmd, 01-rsp, 10-ind + +#define USB_CTL_MSG_TYPE_QMI 0x01 + +#define QMICTL_FLAG_REQUEST 0x00 +#define QMICTL_FLAG_RESPONSE 0x01 +#define QMICTL_FLAG_INDICATION 0x02 + +// QMICTL Type +#define QMICTL_SET_INSTANCE_ID_REQ 0x0020 +#define QMICTL_SET_INSTANCE_ID_RESP 0x0020 +#define QMICTL_GET_VERSION_REQ 0x0021 +#define QMICTL_GET_VERSION_RESP 0x0021 +#define QMICTL_GET_CLIENT_ID_REQ 0x0022 +#define QMICTL_GET_CLIENT_ID_RESP 0x0022 +#define QMICTL_RELEASE_CLIENT_ID_REQ 0x0023 +#define QMICTL_RELEASE_CLIENT_ID_RESP 0x0023 +#define QMICTL_REVOKE_CLIENT_ID_IND 0x0024 +#define QMICTL_INVALID_CLIENT_ID_IND 0x0025 +#define QMICTL_SET_DATA_FORMAT_REQ 0x0026 +#define QMICTL_SET_DATA_FORMAT_RESP 0x0026 +#define QMICTL_SYNC_REQ 0x0027 +#define QMICTL_SYNC_RESP 0x0027 +#define QMICTL_SYNC_IND 0x0027 + +#define QCTLV_TYPE_REQUIRED_PARAMETER 0x01 + +// Define QMI Type +typedef enum _QMI_SERVICE_TYPE +{ + QMUX_TYPE_CTL = 0x00, + QMUX_TYPE_WDS = 0x01, + QMUX_TYPE_DMS = 0x02, + QMUX_TYPE_NAS = 0x03, + QMUX_TYPE_QOS = 0x04, + QMUX_TYPE_WMS = 0x05, + QMUX_TYPE_PDS = 0x06, + QMUX_TYPE_UIM = 0x0B, + QMUX_TYPE_WDS_IPV6 = 0x11, + QMUX_TYPE_WDS_ADMIN = 0x1A, + QMUX_TYPE_MAX = 0xFF, + QMUX_TYPE_ALL = 0xFF +} QMI_SERVICE_TYPE; + +#define QMIWDS_ADMIN_SET_DATA_FORMAT_REQ 0x0020 +#define QMIWDS_ADMIN_SET_DATA_FORMAT_RESP 0x0020 + +struct qlistnode +{ + struct qlistnode *next; + struct qlistnode *prev; +}; + +#define qnode_to_item(node, container, member) \ + (container *) (((char*) (node)) - offsetof(container, member)) + +#define qlist_for_each(node, list) \ + for (node = (list)->next; node != (list); node = node->next) + +#define qlist_empty(list) ((list) == (list)->next) +#define qlist_head(list) ((list)->next) +#define qlist_tail(list) ((list)->prev) + +typedef struct { + struct qlistnode qnode; + uint8_t ClientFd; + QCQMIMSG qmi[0]; +} QMI_PROXY_MSG; + +typedef struct { + struct qlistnode qnode; + uint8_t QMIType; + uint8_t ClientId; + unsigned AccessTime; +} QMI_PROXY_CLINET; + +typedef struct { + struct qlistnode qnode; + struct qlistnode client_qnode; + uint8_t ClientFd; + unsigned AccessTime; +} QMI_PROXY_CONNECTION; + +static void qlist_init(struct qlistnode *node) +{ + node->next = node; + node->prev = node; +} + +static void qlist_add_tail(struct qlistnode *head, struct qlistnode *item) +{ + item->next = head; + item->prev = head->prev; + head->prev->next = item; + head->prev = item; +} + +static void qlist_remove(struct qlistnode *item) +{ + item->next->prev = item->prev; + item->prev->next = item->next; +} + +static int qmi_proxy_quit = 0; +static pthread_t thread_id = 0; +static int cdc_wdm_fd = -1; +static int qmi_proxy_server_fd = -1; +static struct qlistnode qmi_proxy_connection; +static struct qlistnode qmi_proxy_ctl_msg; +static PQCQMIMSG s_pCtlReq; +static PQCQMIMSG s_pCtlRsq; +static pthread_mutex_t s_ctlmutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t s_ctlcond = PTHREAD_COND_INITIALIZER; + +static void ql_get_driver_rmnet_info(char *ifname, RMNET_INFO *rmnet_info) { + int ifc_ctl_sock; + struct ifreq ifr; + int rc; + int request = 0x89F3; + unsigned char data[512]; + + memset(rmnet_info, 0x00, sizeof(*rmnet_info)); + + ifc_ctl_sock = socket(AF_INET, SOCK_DGRAM, 0); + if (ifc_ctl_sock <= 0) { + dprintf("socket() failed: %s\n", strerror(errno)); + return; + } + + memset(&ifr, 0, sizeof(struct ifreq)); + strncpy(ifr.ifr_name, ifname, IFNAMSIZ); + ifr.ifr_name[IFNAMSIZ - 1] = 0; + ifr.ifr_ifru.ifru_data = (void *)data; + + rc = ioctl(ifc_ctl_sock, request, &ifr); + if (rc < 0) { + dprintf("ioctl(0x%x, rmnet_info) failed: %s, rc=%d", request, strerror(errno), rc); + } + else { + memcpy(rmnet_info, data, sizeof(*rmnet_info)); + } + + close(ifc_ctl_sock); +} + +static void ql_set_driver_qmap_setting(char *ifname, QMAP_SETTING *qmap_settings) { + int ifc_ctl_sock; + struct ifreq ifr; + int rc; + int request = 0x89F2; + + ifc_ctl_sock = socket(AF_INET, SOCK_DGRAM, 0); + if (ifc_ctl_sock <= 0) { + dprintf("socket() failed: %s\n", strerror(errno)); + return; + } + + memset(&ifr, 0, sizeof(struct ifreq)); + strncpy(ifr.ifr_name, ifname, IFNAMSIZ); + ifr.ifr_name[IFNAMSIZ - 1] = 0; + ifr.ifr_ifru.ifru_data = (void *)qmap_settings; + + rc = ioctl(ifc_ctl_sock, request, &ifr); + if (rc < 0) { + dprintf("ioctl(0x%x, qmap_settings) failed: %s, rc=%d\n", request, strerror(errno), rc); + } + + close(ifc_ctl_sock); +} + +static void setTimespecRelative(struct timespec *p_ts, long long msec) +{ + struct timeval tv; + + gettimeofday(&tv, (struct timezone *) NULL); + + /* what's really funny about this is that I know + pthread_cond_timedwait just turns around and makes this + a relative time again */ + p_ts->tv_sec = tv.tv_sec + (msec / 1000); + p_ts->tv_nsec = (tv.tv_usec + (msec % 1000) * 1000L ) * 1000L; + if (p_ts->tv_nsec >= 1000000000UL) { + p_ts->tv_sec += 1; + p_ts->tv_nsec -= 1000000000UL; + } +} + +static int pthread_cond_timeout_np(pthread_cond_t *cond, pthread_mutex_t * mutex, unsigned msecs) { + if (msecs != 0) { + unsigned i; + unsigned t = msecs/4; + int ret = 0; + + if (t == 0) + t = 1; + + for (i = 0; i < msecs; i += t) { + struct timespec ts; + setTimespecRelative(&ts, t); + ret = pthread_cond_timedwait(cond, mutex, &ts); //to advoid system time change + if (ret != ETIMEDOUT) { + if(ret) dprintf("ret=%d, msecs=%u, t=%u", ret, msecs, t); + break; + } + } + + return ret; + } else { + return pthread_cond_wait(cond, mutex); + } +} + +static int create_local_server(const char *name) { + int sockfd = -1; + int reuse_addr = 1; + struct sockaddr_un sockaddr; + socklen_t alen; + + /*Create server socket*/ + SYSCHECK(sockfd = socket(AF_LOCAL, SOCK_STREAM, 0)); + + memset(&sockaddr, 0, sizeof(sockaddr)); + sockaddr.sun_family = AF_LOCAL; + sockaddr.sun_path[0] = 0; + memcpy(sockaddr.sun_path + 1, name, strlen(name) ); + + alen = strlen(name) + offsetof(struct sockaddr_un, sun_path) + 1; + SYSCHECK(setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse_addr,sizeof(reuse_addr))); + if(bind(sockfd, (struct sockaddr *)&sockaddr, alen) < 0) { + close(sockfd); + dprintf("%s bind %s errno: %d (%s)\n", __func__, name, errno, strerror(errno)); + return -1; + } + + dprintf("local server: %s sockfd = %d\n", name, sockfd); + cfmakenoblock(sockfd); + listen(sockfd, 1); + + return sockfd; +} + +static void accept_qmi_connection(int serverfd) { + int clientfd = -1; + unsigned char addr[128]; + socklen_t alen = sizeof(addr); + QMI_PROXY_CONNECTION *qmi_con; + + clientfd = accept(serverfd, (struct sockaddr *)addr, &alen); + + qmi_con = (QMI_PROXY_CONNECTION *)malloc(sizeof(QMI_PROXY_CONNECTION)); + if (qmi_con) { + qlist_init(&qmi_con->qnode); + qlist_init(&qmi_con->client_qnode); + qmi_con->ClientFd= clientfd; + qmi_con->AccessTime = 0; + dprintf("+++ ClientFd=%d\n", qmi_con->ClientFd); + qlist_add_tail(&qmi_proxy_connection, &qmi_con->qnode); + } + + cfmakenoblock(clientfd); +} + +static void cleanup_qmi_connection(int clientfd) { + struct qlistnode *con_node, *qmi_node; + + qlist_for_each(con_node, &qmi_proxy_connection) { + QMI_PROXY_CONNECTION *qmi_con = qnode_to_item(con_node, QMI_PROXY_CONNECTION, qnode); + + if (qmi_con->ClientFd == clientfd) { + while (!qlist_empty(&qmi_con->client_qnode)) { + QMI_PROXY_CLINET *qmi_client = qnode_to_item(qlist_head(&qmi_con->client_qnode), QMI_PROXY_CLINET, qnode); + + dprintf("xxx ClientFd=%d QMIType=%d ClientId=%d\n", qmi_con->ClientFd, qmi_client->QMIType, qmi_client->ClientId); + + qlist_remove(&qmi_client->qnode); + free(qmi_client); + } + + qlist_for_each(qmi_node, &qmi_proxy_ctl_msg) { + QMI_PROXY_MSG *qmi_msg = qnode_to_item(qmi_node, QMI_PROXY_MSG, qnode); + + if (qmi_msg->ClientFd == qmi_con->ClientFd) { + qlist_remove(&qmi_msg->qnode); + free(qmi_msg); + break; + } + } + + dprintf("--- ClientFd=%d\n", qmi_con->ClientFd); + close(qmi_con->ClientFd); + qlist_remove(&qmi_con->qnode); + free(qmi_con); + break; + } + } +} + +static void get_client_id(QMI_PROXY_CONNECTION *qmi_con, PQMICTL_GET_CLIENT_ID_RESP_MSG pClient) { + if (pClient->QMIResult == 0 && pClient->QMIError == 0) { + QMI_PROXY_CLINET *qmi_client = (QMI_PROXY_CLINET *)malloc(sizeof(QMI_PROXY_CLINET)); + + qlist_init(&qmi_client->qnode); + qmi_client->QMIType = pClient->QMIType; + qmi_client->ClientId = pClient->ClientId; + qmi_client->AccessTime = 0; + + dprintf("+++ ClientFd=%d QMIType=%d ClientId=%d\n", qmi_con->ClientFd, qmi_client->QMIType, qmi_client->ClientId); + qlist_add_tail(&qmi_con->client_qnode, &qmi_client->qnode); + } +} + +static void release_client_id(QMI_PROXY_CONNECTION *qmi_con, PQMICTL_RELEASE_CLIENT_ID_RESP_MSG pClient) { + struct qlistnode *client_node; + + if (pClient->QMIResult == 0 && pClient->QMIError == 0) { + qlist_for_each (client_node, &qmi_con->client_qnode) { + QMI_PROXY_CLINET *qmi_client = qnode_to_item(client_node, QMI_PROXY_CLINET, qnode); + + if (pClient->QMIType == qmi_client->QMIType && pClient->ClientId == qmi_client->ClientId) { + dprintf("--- ClientFd=%d QMIType=%d ClientId=%d\n", qmi_con->ClientFd, qmi_client->QMIType, qmi_client->ClientId); + qlist_remove(&qmi_client->qnode); + free(qmi_client); + break; + } + } + } +} + +static int verbose_debug = 0; +static int send_qmi_to_cdc_wdm(PQCQMIMSG pQMI) { + struct pollfd pollfds[]= {{cdc_wdm_fd, POLLOUT, 0}}; + ssize_t ret = 0; + + do { + ret = poll(pollfds, sizeof(pollfds)/sizeof(pollfds[0]), 5000); + } while ((ret < 0) && (errno == EINTR)); + + if (pollfds[0].revents & POLLOUT) { + ssize_t size = le16toh(pQMI->QMIHdr.Length) + 1; + ret = write(cdc_wdm_fd, pQMI, size); + if (verbose_debug) + { + ssize_t i; + printf("w %d %zd: ", cdc_wdm_fd, ret); + for (i = 0; i < size; i++) + printf("%02x ", ((uint8_t *)pQMI)[i]); + printf("\n"); + } + } + + return ret; +} + +static int send_qmi_to_client(PQCQMIMSG pQMI, int clientFd) { + struct pollfd pollfds[]= {{clientFd, POLLOUT, 0}}; + ssize_t ret = 0; + + do { + ret = poll(pollfds, sizeof(pollfds)/sizeof(pollfds[0]), 5000); + } while ((ret < 0) && (errno == EINTR)); + + if (pollfds[0].revents & POLLOUT) { + ssize_t size = le16toh(pQMI->QMIHdr.Length) + 1; + ret = write(clientFd, pQMI, size); + if (verbose_debug) + { + ssize_t i; + printf("w %d %zd: ", clientFd, ret); + for (i = 0; i < 16; i++) + printf("%02x ", ((uint8_t *)pQMI)[i]); + printf("\n"); + } + } + + return ret; +} + +static int modem_reset_flag = 0; +static void recv_qmi(PQCQMIMSG pQMI, unsigned size) { + struct qlistnode *con_node, *client_node; + + if (qmi_proxy_server_fd <= 0) { + pthread_mutex_lock(&s_ctlmutex); + + if (s_pCtlReq != NULL) { + if (pQMI->QMIHdr.QMIType == QMUX_TYPE_CTL + && s_pCtlReq->CTLMsg.QMICTLMsgHdrRsp.TransactionId == pQMI->CTLMsg.QMICTLMsgHdrRsp.TransactionId) { + s_pCtlRsq = malloc(size); + memcpy(s_pCtlRsq, pQMI, size); + pthread_cond_signal(&s_ctlcond); + } + else if (pQMI->QMIHdr.QMIType != QMUX_TYPE_CTL + && s_pCtlReq->MUXMsg.QMUXMsgHdr.TransactionId == pQMI->MUXMsg.QMUXMsgHdr.TransactionId) { + s_pCtlRsq = malloc(size); + memcpy(s_pCtlRsq, pQMI, size); + pthread_cond_signal(&s_ctlcond); + } + } + + pthread_mutex_unlock(&s_ctlmutex); + } + else if (pQMI->QMIHdr.QMIType == QMUX_TYPE_CTL) { + if (pQMI->CTLMsg.QMICTLMsgHdr.CtlFlags == QMICTL_CTL_FLAG_RSP) { + if (!qlist_empty(&qmi_proxy_ctl_msg)) { + QMI_PROXY_MSG *qmi_msg = qnode_to_item(qlist_head(&qmi_proxy_ctl_msg), QMI_PROXY_MSG, qnode); + + qlist_for_each(con_node, &qmi_proxy_connection) { + QMI_PROXY_CONNECTION *qmi_con = qnode_to_item(con_node, QMI_PROXY_CONNECTION, qnode); + + if (qmi_con->ClientFd == qmi_msg->ClientFd) { + send_qmi_to_client(pQMI, qmi_msg->ClientFd); + + if (le16toh(pQMI->CTLMsg.QMICTLMsgHdrRsp.QMICTLType) == QMICTL_GET_CLIENT_ID_RESP) + get_client_id(qmi_con, &pQMI->CTLMsg.GetClientIdRsp); + else if ((le16toh(pQMI->CTLMsg.QMICTLMsgHdrRsp.QMICTLType) == QMICTL_RELEASE_CLIENT_ID_RESP) || + (le16toh(pQMI->CTLMsg.QMICTLMsgHdrRsp.QMICTLType) == QMICTL_REVOKE_CLIENT_ID_IND)) { + release_client_id(qmi_con, &pQMI->CTLMsg.ReleaseClientIdRsp); + if (le16toh(pQMI->CTLMsg.QMICTLMsgHdrRsp.QMICTLType) == QMICTL_REVOKE_CLIENT_ID_IND) + modem_reset_flag = 1; + } + else { + } + } + } + + qlist_remove(&qmi_msg->qnode); + free(qmi_msg); + } + } + + if (!qlist_empty(&qmi_proxy_ctl_msg)) { + QMI_PROXY_MSG *qmi_msg = qnode_to_item(qlist_head(&qmi_proxy_ctl_msg), QMI_PROXY_MSG, qnode); + + qlist_for_each(con_node, &qmi_proxy_connection) { + QMI_PROXY_CONNECTION *qmi_con = qnode_to_item(con_node, QMI_PROXY_CONNECTION, qnode); + + if (qmi_con->ClientFd == qmi_msg->ClientFd) { + send_qmi_to_cdc_wdm(qmi_msg->qmi); + } + } + } + } + else { + qlist_for_each(con_node, &qmi_proxy_connection) { + QMI_PROXY_CONNECTION *qmi_con = qnode_to_item(con_node, QMI_PROXY_CONNECTION, qnode); + + qlist_for_each(client_node, &qmi_con->client_qnode) { + QMI_PROXY_CLINET *qmi_client = qnode_to_item(client_node, QMI_PROXY_CLINET, qnode); + if (pQMI->QMIHdr.QMIType == qmi_client->QMIType) { + if (pQMI->QMIHdr.ClientId == 0 || pQMI->QMIHdr.ClientId == qmi_client->ClientId) { + send_qmi_to_client(pQMI, qmi_con->ClientFd); + } + } + } + } + } +} + +static int send_qmi(PQCQMIMSG pQMI, unsigned size, int clientfd) { + if (qmi_proxy_server_fd <= 0) { + send_qmi_to_cdc_wdm(pQMI); + } + else if (pQMI->QMIHdr.QMIType == QMUX_TYPE_CTL) { + QMI_PROXY_MSG *qmi_msg; + + if (qlist_empty(&qmi_proxy_ctl_msg)) + send_qmi_to_cdc_wdm(pQMI); + + qmi_msg = malloc(sizeof(QMI_PROXY_MSG) + size); + qlist_init(&qmi_msg->qnode); + qmi_msg->ClientFd = clientfd; + memcpy(qmi_msg->qmi, pQMI, size); + qlist_add_tail(&qmi_proxy_ctl_msg, &qmi_msg->qnode); + } + else { + send_qmi_to_cdc_wdm(pQMI); + } + + return 0; +} + +static int send_qmi_timeout(PQCQMIMSG pRequest, PQCQMIMSG *ppResponse, unsigned mseconds) { + int ret; + + pthread_mutex_lock(&s_ctlmutex); + + s_pCtlReq = pRequest; + s_pCtlRsq = NULL; + if (ppResponse) *ppResponse = NULL; + + send_qmi_to_cdc_wdm(pRequest); + ret = pthread_cond_timeout_np(&s_ctlcond, &s_ctlmutex, mseconds); + if (!ret) { + if (s_pCtlRsq && ppResponse) { + *ppResponse = s_pCtlRsq; + } else if (s_pCtlRsq) { + free(s_pCtlRsq); + } + } else { + dprintf("%s ret=%d\n", __func__, ret); + } + + s_pCtlReq = NULL; + pthread_mutex_unlock(&s_ctlmutex); + + return ret; +} + +static PQCQMUX_TLV qmi_find_tlv (PQCQMIMSG pQMI, uint8_t TLVType) { + int Length = 0; + + while (Length < le16toh(pQMI->MUXMsg.QMUXMsgHdr.Length)) { + PQCQMUX_TLV pTLV = (PQCQMUX_TLV)(&pQMI->MUXMsg.QMUXMsgHdr.payload[Length]); + + //dprintf("TLV {%02x, %04x}\n", pTLV->Type, pTLV->Length); + if (pTLV->Type == TLVType) { + return pTLV; + } + + Length += (le16toh((pTLV->Length)) + sizeof(QCQMUX_TLV)); + } + + return NULL; +} + +static int qmi_proxy_init(char *qmi_device, char *ifname) { + unsigned i; + int ret; + QCQMIMSG _QMI; + PQCQMIMSG pQMI = &_QMI; + PQCQMIMSG pRsp; + uint8_t TransactionId = 0xC1; + uint8_t WDAClientId = 0; + unsigned rx_urb_size = 0; + unsigned ep_type, iface_id; + unsigned qmap_version; + QMAP_SETTING qmap_settings = {0}; + RMNET_INFO rmnet_info; + + dprintf("%s enter\n", __func__); + + pQMI->QMIHdr.IFType = USB_CTL_MSG_TYPE_QMI; + pQMI->QMIHdr.CtlFlags = 0x00; + pQMI->QMIHdr.QMIType = QMUX_TYPE_CTL; + pQMI->QMIHdr.ClientId= 0x00; + + pQMI->CTLMsg.QMICTLMsgHdr.CtlFlags = QMICTL_FLAG_REQUEST; + + for (i = 0; i < 10; i++) { + pQMI->CTLMsg.SyncReq.TransactionId = TransactionId++; + pQMI->CTLMsg.SyncReq.QMICTLType = QMICTL_SYNC_REQ; + pQMI->CTLMsg.SyncReq.Length = 0; + + pQMI->QMIHdr.Length = + htole16(le16toh(pQMI->CTLMsg.QMICTLMsgHdr.Length) + sizeof(QCQMI_HDR) + sizeof(QCQMICTL_MSG_HDR) - 1); + + ret = send_qmi_timeout(pQMI, NULL, 1000); + if (!ret) + break; + } + + if (ret) + goto qmi_proxy_init_fail; + + pQMI->CTLMsg.GetVersionReq.TransactionId = TransactionId++; + pQMI->CTLMsg.GetVersionReq.QMICTLType = htole16(QMICTL_GET_VERSION_REQ); + pQMI->CTLMsg.GetVersionReq.Length = htole16(0x0004); + pQMI->CTLMsg.GetVersionReq.TLVType= QCTLV_TYPE_REQUIRED_PARAMETER; + pQMI->CTLMsg.GetVersionReq.TLVLength = htole16(0x0001); + pQMI->CTLMsg.GetVersionReq.QMUXTypes = QMUX_TYPE_ALL; + + pQMI->QMIHdr.Length = htole16(le16toh(pQMI->CTLMsg.QMICTLMsgHdr.Length) + sizeof(QCQMI_HDR) + sizeof(QCQMICTL_MSG_HDR) - 1); + + ret = send_qmi_timeout(pQMI, &pRsp, 3000); + if (ret || (pRsp == NULL)) + goto qmi_proxy_init_fail; + + if (pRsp) { + uint8_t NumElements = 0; + if (pRsp->CTLMsg.QMICTLMsgHdrRsp.QMUXResult ||pRsp->CTLMsg.QMICTLMsgHdrRsp.QMUXError) { + dprintf("QMICTL_GET_VERSION_REQ QMUXResult=%d, QMUXError=%d\n", + le16toh(pRsp->CTLMsg.QMICTLMsgHdrRsp.QMUXResult), le16toh(pRsp->CTLMsg.QMICTLMsgHdrRsp.QMUXError)); + goto qmi_proxy_init_fail; + } + + for (NumElements = 0; NumElements < pRsp->CTLMsg.GetVersionRsp.NumElements; NumElements++) { + if (verbose_debug) + dprintf("QMUXType = %02x Version = %d.%d\n", + pRsp->CTLMsg.GetVersionRsp.TypeVersion[NumElements].QMUXType, + le16toh(pRsp->CTLMsg.GetVersionRsp.TypeVersion[NumElements].MajorVersion), + le16toh(pRsp->CTLMsg.GetVersionRsp.TypeVersion[NumElements].MinorVersion)); + } + } + free(pRsp); + + pQMI->CTLMsg.GetClientIdReq.TransactionId = TransactionId++; + pQMI->CTLMsg.GetClientIdReq.QMICTLType = htole16(QMICTL_GET_CLIENT_ID_REQ); + pQMI->CTLMsg.GetClientIdReq.Length = htole16(0x0004); + pQMI->CTLMsg.GetClientIdReq.TLVType= QCTLV_TYPE_REQUIRED_PARAMETER; + pQMI->CTLMsg.GetClientIdReq.TLVLength = htole16(0x0001); + pQMI->CTLMsg.GetClientIdReq.QMIType = QMUX_TYPE_WDS_ADMIN; + + pQMI->QMIHdr.Length = htole16(le16toh(pQMI->CTLMsg.QMICTLMsgHdr.Length) + sizeof(QCQMI_HDR) + sizeof(QCQMICTL_MSG_HDR) - 1); + + ret = send_qmi_timeout(pQMI, &pRsp, 3000); + if (ret || (pRsp == NULL)) + goto qmi_proxy_init_fail; + + if (pRsp) { + if (pRsp->CTLMsg.QMICTLMsgHdrRsp.QMUXResult ||pRsp->CTLMsg.QMICTLMsgHdrRsp.QMUXError) { + dprintf("QMICTL_GET_CLIENT_ID_REQ QMUXResult=%d, QMUXError=%d\n", + le16toh(pRsp->CTLMsg.QMICTLMsgHdrRsp.QMUXResult), le16toh(pRsp->CTLMsg.QMICTLMsgHdrRsp.QMUXError)); + goto qmi_proxy_init_fail; + } + + WDAClientId = pRsp->CTLMsg.GetClientIdRsp.ClientId; + if (verbose_debug) dprintf("WDAClientId = %d\n", WDAClientId); + } + free(pRsp); + + rx_urb_size = 16*1024; //must same as rx_urb_size defined in GobiNet&qmi_wwan driver //SDX24&SDX55 support 32KB + if(qmidev_is_pciemhi(qmi_device)) + ep_type = 0x03; + else + ep_type = 0x02; + iface_id = 0x04; + qmap_version = 5; + + qmap_settings.size = sizeof(qmap_settings); + qmap_settings.ul_data_aggregation_max_datagrams = 16; + qmap_settings.ul_data_aggregation_max_size = 8*1024; + qmap_settings.dl_minimum_padding = 11; //16->11 + + ql_get_driver_rmnet_info(ifname, &rmnet_info); + if (rmnet_info.size) { + rx_urb_size = rmnet_info.rx_urb_size; + ep_type = rmnet_info.ep_type; + iface_id = rmnet_info.iface_id; + qmap_version = rmnet_info.qmap_version; + qmap_settings.dl_minimum_padding = rmnet_info.dl_minimum_padding; + } + qmap_settings.dl_minimum_padding = 0; //no effect when register to real netowrk + + pQMI->QMIHdr.IFType = USB_CTL_MSG_TYPE_QMI; + pQMI->QMIHdr.CtlFlags = 0x00; + pQMI->QMIHdr.QMIType = QMUX_TYPE_WDS_ADMIN; + pQMI->QMIHdr.ClientId= WDAClientId; + + pQMI->MUXMsg.QMUXMsgHdr.CtlFlags = QMICTL_FLAG_REQUEST; + pQMI->MUXMsg.QMUXMsgHdr.TransactionId = htole16(TransactionId++); + + pQMI->MUXMsg.SetDataFormatReq.Type = htole16(QMIWDS_ADMIN_SET_DATA_FORMAT_REQ); + pQMI->MUXMsg.SetDataFormatReq.Length = htole16(sizeof(QMIWDS_ADMIN_SET_DATA_FORMAT_REQ_MSG) - sizeof(QCQMUX_MSG_HDR)); + +//Indicates whether the Quality of Service(QOS) data format is used by the client. + pQMI->MUXMsg.SetDataFormatReq.QosDataFormatTlv.TLVType = 0x10; + pQMI->MUXMsg.SetDataFormatReq.QosDataFormatTlv.TLVLength = htole16(0x0001); + pQMI->MUXMsg.SetDataFormatReq.QosDataFormatTlv.QOSSetting = 0; /* no-QOS header */ +//Underlying Link Layer Protocol + pQMI->MUXMsg.SetDataFormatReq.UnderlyingLinkLayerProtocolTlv.TLVType = 0x11; + pQMI->MUXMsg.SetDataFormatReq.UnderlyingLinkLayerProtocolTlv.TLVLength = htole16(4); + pQMI->MUXMsg.SetDataFormatReq.UnderlyingLinkLayerProtocolTlv.Value = htole32(0x02); /* Set IP mode */ +//Uplink (UL) data aggregation protocol to be used for uplink data transfer. + pQMI->MUXMsg.SetDataFormatReq.UplinkDataAggregationProtocolTlv.TLVType = 0x12; + pQMI->MUXMsg.SetDataFormatReq.UplinkDataAggregationProtocolTlv.TLVLength = htole16(4); + pQMI->MUXMsg.SetDataFormatReq.UplinkDataAggregationProtocolTlv.Value = htole32(qmap_version); //UL QMAP is enabled +//Downlink (DL) data aggregation protocol to be used for downlink data transfer + pQMI->MUXMsg.SetDataFormatReq.DownlinkDataAggregationProtocolTlv.TLVType = 0x13; + pQMI->MUXMsg.SetDataFormatReq.DownlinkDataAggregationProtocolTlv.TLVLength = htole16(4); + pQMI->MUXMsg.SetDataFormatReq.DownlinkDataAggregationProtocolTlv.Value = htole32(qmap_version); //UL QMAP is enabled +//Maximum number of datagrams in a single aggregated packet on downlink + pQMI->MUXMsg.SetDataFormatReq.DownlinkDataAggregationMaxDatagramsTlv.TLVType = 0x15; + pQMI->MUXMsg.SetDataFormatReq.DownlinkDataAggregationMaxDatagramsTlv.TLVLength = htole16(4); + pQMI->MUXMsg.SetDataFormatReq.DownlinkDataAggregationMaxDatagramsTlv.Value = htole32((rx_urb_size>2048)?(rx_urb_size/1024):1); +//Maximum size in bytes of a single aggregated packet allowed on downlink + pQMI->MUXMsg.SetDataFormatReq.DownlinkDataAggregationMaxSizeTlv.TLVType = 0x16; + pQMI->MUXMsg.SetDataFormatReq.DownlinkDataAggregationMaxSizeTlv.TLVLength = htole16(4); + pQMI->MUXMsg.SetDataFormatReq.DownlinkDataAggregationMaxSizeTlv.Value = htole32(rx_urb_size); +//Peripheral End Point ID + pQMI->MUXMsg.SetDataFormatReq.epTlv.TLVType = 0x17; + pQMI->MUXMsg.SetDataFormatReq.epTlv.TLVLength = htole16(8); + pQMI->MUXMsg.SetDataFormatReq.epTlv.ep_type = htole32(ep_type); // DATA_EP_TYPE_BAM_DMUX + pQMI->MUXMsg.SetDataFormatReq.epTlv.iface_id = htole32(iface_id); + +#ifdef QUECTEL_UL_DATA_AGG +//Maximum number of datagrams in a single aggregated packet on uplink + pQMI->MUXMsg.SetDataFormatReq.DlMinimumPassingTlv.TLVType = 0x19; + pQMI->MUXMsg.SetDataFormatReq.DlMinimumPassingTlv.TLVLength = htole16(4); + pQMI->MUXMsg.SetDataFormatReq.DlMinimumPassingTlv.Value = htole32(qmap_settings.dl_minimum_padding); + +//Maximum number of datagrams in a single aggregated packet on uplink + pQMI->MUXMsg.SetDataFormatReq.UplinkDataAggregationMaxDatagramsTlv.TLVType = 0x1B; + pQMI->MUXMsg.SetDataFormatReq.UplinkDataAggregationMaxDatagramsTlv.TLVLength = htole16(4); + pQMI->MUXMsg.SetDataFormatReq.UplinkDataAggregationMaxDatagramsTlv.Value = htole32(qmap_settings.ul_data_aggregation_max_datagrams); + +//Maximum size in bytes of a single aggregated packet allowed on downlink + pQMI->MUXMsg.SetDataFormatReq.UplinkDataAggregationMaxSizeTlv.TLVType = 0x1C; + pQMI->MUXMsg.SetDataFormatReq.UplinkDataAggregationMaxSizeTlv.TLVLength = htole16(4); + pQMI->MUXMsg.SetDataFormatReq.UplinkDataAggregationMaxSizeTlv.Value = htole32(qmap_settings.ul_data_aggregation_max_size); +#endif + + pQMI->QMIHdr.Length = htole16(le16toh(pQMI->MUXMsg.QMUXMsgHdr.Length) + sizeof(QCQMI_HDR) + sizeof(QCQMUX_MSG_HDR) - 1); + + ret = send_qmi_timeout(pQMI, &pRsp, 3000); + if (ret || (pRsp == NULL)) + goto qmi_proxy_init_fail; + + if (pRsp) { + PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV pFormat; + + if (pRsp->MUXMsg.QMUXMsgHdrResp.QMUXResult || pRsp->MUXMsg.QMUXMsgHdrResp.QMUXError) { + dprintf("QMIWDS_ADMIN_SET_DATA_FORMAT_REQ QMUXResult=%d, QMUXError=%d\n", + le16toh(pRsp->MUXMsg.QMUXMsgHdrResp.QMUXResult), le16toh(pRsp->MUXMsg.QMUXMsgHdrResp.QMUXError)); + goto qmi_proxy_init_fail; + } + + pFormat = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV)qmi_find_tlv(pRsp, 0x11); + if (pFormat) + dprintf("link_prot %d\n", le32toh(pFormat->Value)); + pFormat = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV)qmi_find_tlv(pRsp, 0x12); + if (pFormat) + dprintf("ul_data_aggregation_protocol %d\n", le32toh(pFormat->Value)); + pFormat = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV)qmi_find_tlv(pRsp, 0x13); + if (pFormat) + dprintf("dl_data_aggregation_protocol %d\n", le32toh(pFormat->Value)); + pFormat = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV)qmi_find_tlv(pRsp, 0x15); + if (pFormat) + dprintf("dl_data_aggregation_max_datagrams %d\n", le32toh(pFormat->Value)); + pFormat = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV)qmi_find_tlv(pRsp, 0x16); + if (pFormat) { + dprintf("dl_data_aggregation_max_size %d\n", le32toh(pFormat->Value)); + rx_urb_size = le32toh(pFormat->Value); + } + pFormat = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV)qmi_find_tlv(pRsp, 0x17); + if (pFormat) { + qmap_settings.ul_data_aggregation_max_datagrams = MIN(qmap_settings.ul_data_aggregation_max_datagrams, le32toh(pFormat->Value)); + dprintf("ul_data_aggregation_max_datagrams %d\n", qmap_settings.ul_data_aggregation_max_datagrams); + } + pFormat = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV)qmi_find_tlv(pRsp, 0x18); + if (pFormat) { + qmap_settings.ul_data_aggregation_max_size = MIN(qmap_settings.ul_data_aggregation_max_size, le32toh(pFormat->Value)); + dprintf("ul_data_aggregation_max_size %d\n", qmap_settings.ul_data_aggregation_max_size); + } + + if (qmap_settings.ul_data_aggregation_max_datagrams > 1) { + ql_set_driver_qmap_setting(ifname, &qmap_settings); + } + } + free(pRsp); + + dprintf("%s finished, rx_urb_size is %u\n", __func__, rx_urb_size); + return 0; + +qmi_proxy_init_fail: + dprintf("%s failed\n", __func__); + return -1; +} + +static void qmi_start_server(const char* servername) { + qmi_proxy_server_fd = create_local_server(servername); + printf("%s: qmi_proxy_server_fd = %d\n", __func__, qmi_proxy_server_fd); + if (qmi_proxy_server_fd == -1) { + dprintf("%s Failed to create %s, errno: %d (%s)\n", __func__, "quectel-qmi-proxy", errno, strerror(errno)); + } +} + +static void qmi_close_server(const char* servername) { + if (qmi_proxy_server_fd != -1) { + dprintf("%s %s close server\n", __func__, servername); + close(qmi_proxy_server_fd); + qmi_proxy_server_fd = -1; + } +} + +static void *qmi_proxy_loop(void *param) +{ + static uint8_t qmi_buf[2048]; + PQCQMIMSG pQMI = (PQCQMIMSG)qmi_buf; + struct qlistnode *con_node; + QMI_PROXY_CONNECTION *qmi_con; + + dprintf("%s enter thread_id %p\n", __func__, (void *)pthread_self()); + + qlist_init(&qmi_proxy_connection); + qlist_init(&qmi_proxy_ctl_msg); + + while (cdc_wdm_fd > 0 && qmi_proxy_quit == 0) { + struct pollfd pollfds[2+64]; + int ne, ret, nevents = 0; + ssize_t nreads; + + pollfds[nevents].fd = cdc_wdm_fd; + pollfds[nevents].events = POLLIN; + pollfds[nevents].revents= 0; + nevents++; + + if (qmi_proxy_server_fd > 0) { + pollfds[nevents].fd = qmi_proxy_server_fd; + pollfds[nevents].events = POLLIN; + pollfds[nevents].revents= 0; + nevents++; + } + + qlist_for_each(con_node, &qmi_proxy_connection) { + qmi_con = qnode_to_item(con_node, QMI_PROXY_CONNECTION, qnode); + + pollfds[nevents].fd = qmi_con->ClientFd; + pollfds[nevents].events = POLLIN; + pollfds[nevents].revents= 0; + nevents++; + + if (nevents == (sizeof(pollfds)/sizeof(pollfds[0]))) + break; + } + +#if 0 + dprintf("poll "); + for (ne = 0; ne < nevents; ne++) { + dprintf("%d ", pollfds[ne].fd); + } + dprintf("\n"); +#endif + + do { + //ret = poll(pollfds, nevents, -1); + ret = poll(pollfds, nevents, (qmi_proxy_server_fd > 0) ? -1 : 200); + } while (ret < 0 && errno == EINTR && qmi_proxy_quit == 0); + + if (ret < 0) { + dprintf("%s poll=%d, errno: %d (%s)\n", __func__, ret, errno, strerror(errno)); + goto qmi_proxy_loop_exit; + } + + for (ne = 0; ne < nevents; ne++) { + int fd = pollfds[ne].fd; + short revents = pollfds[ne].revents; + + if (revents & (POLLERR | POLLHUP | POLLNVAL)) { + dprintf("%s poll fd = %d, revents = %04x\n", __func__, fd, revents); + if (fd == cdc_wdm_fd) { + goto qmi_proxy_loop_exit; + } else if(fd == qmi_proxy_server_fd) { + + } else { + cleanup_qmi_connection(fd); + } + + continue; + } + + if (!(pollfds[ne].revents & POLLIN)) { + continue; + } + + if (fd == qmi_proxy_server_fd) { + accept_qmi_connection(fd); + } + else if (fd == cdc_wdm_fd) { + nreads = read(fd, pQMI, sizeof(qmi_buf)); + if (verbose_debug) + { + ssize_t i; + printf("r %d %zd: ", fd, nreads); + for (i = 0; i < nreads; i++) + printf("%02x ", ((uint8_t *)pQMI)[i]); + printf("\n"); + } + if (nreads <= 0) { + dprintf("%s read=%d errno: %d (%s)\n", __func__, (int)nreads, errno, strerror(errno)); + goto qmi_proxy_loop_exit; + } + + if (nreads != (le16toh(pQMI->QMIHdr.Length) + 1)) { + dprintf("%s nreads=%d, pQCQMI->QMIHdr.Length = %d\n", __func__, (int)nreads, le16toh(pQMI->QMIHdr.Length)); + continue; + } + + recv_qmi(pQMI, nreads); + if (modem_reset_flag) + goto qmi_proxy_loop_exit; + } + else { + nreads = read(fd, pQMI, sizeof(qmi_buf)); + if (verbose_debug) + { + ssize_t i; + printf("r %d %zd: ", fd, nreads); + for (i = 0; i < 16; i++) + printf("%02x ", ((uint8_t *)pQMI)[i]); + printf("\n"); + } + if (nreads <= 0) { + dprintf("%s read=%d errno: %d (%s)", __func__, (int)nreads, errno, strerror(errno)); + cleanup_qmi_connection(fd); + break; + } + + if (nreads != (le16toh(pQMI->QMIHdr.Length) + 1)) { + dprintf("%s nreads=%d, pQCQMI->QMIHdr.Length = %d\n", __func__, (int)nreads, le16toh(pQMI->QMIHdr.Length)); + continue; + } + + send_qmi(pQMI, nreads, fd); + } + } + } + +qmi_proxy_loop_exit: + while (!qlist_empty(&qmi_proxy_connection)) { + QMI_PROXY_CONNECTION *qmi_con = qnode_to_item(qlist_head(&qmi_proxy_connection), QMI_PROXY_CONNECTION, qnode); + + cleanup_qmi_connection(qmi_con->ClientFd); + } + + dprintf("%s exit, thread_id %p\n", __func__, (void *)pthread_self()); + + return NULL; +} + +static int dir_get_child(const char *dirname, char *buff, unsigned bufsize) +{ + struct dirent *entptr = NULL; + DIR *dirptr = opendir(dirname); + if (!dirptr) + goto error; + while ((entptr = readdir(dirptr))) { + if (entptr->d_name[0] == '.') + continue; + snprintf(buff, bufsize, "%s", entptr->d_name); + break; + } + + closedir(dirptr); + return 0; +error: + buff[0] = '\0'; + if (dirptr) closedir(dirptr); + return -1; +} + +static int mhidevice_detect(char *device_name, char *ifname) { + if (!access("/sys/class/net/pcie_mhi0", F_OK)) + strcpy(ifname, "pcie_mhi0"); + else if (!access("/sys/class/net/rmnet_mhi0", F_OK)) + strcpy(ifname, "rmnet_mhi0"); + else { + goto error; + } + + if (!access("/dev/mhi_QMI0", F_OK)) { + strcpy(device_name, "/dev/mhi_QMI0"); + } + else { + goto error; + } + + return 0; +error: + return -1; +} + +static int qmidevice_detect(char *device_name, char *ifname) { + struct dirent* ent = NULL; + DIR *pDir; + + char dir[255] = "/sys/bus/usb/devices"; + pDir = opendir(dir); + if (pDir) { + struct { + char subdir[255 * 3]; + char qmifile[255]; + char ifname[255]; + } *pl; + char qmidevice[255] = {'\0'}; + + pl = (typeof(pl)) malloc(sizeof(*pl)); + memset(pl, 0x00, sizeof(*pl)); + + while ((ent = readdir(pDir)) != NULL) { + char idVendor[4+1] = {0}; + char idProduct[4+1] = {0}; + int fd = 0; + + memset(pl, 0x00, sizeof(*pl)); + snprintf(pl->subdir, sizeof(pl->subdir), "%s/%s/idVendor", dir, ent->d_name); + fd = open(pl->subdir, O_RDONLY); + if (fd > 0) { + read(fd, idVendor, 4); + close(fd); + } + + snprintf(pl->subdir, sizeof(pl->subdir), "%s/%s/idProduct", dir, ent->d_name); + fd = open(pl->subdir, O_RDONLY); + if (fd > 0) { + read(fd, idProduct, 4); + close(fd); + } + + if (strncasecmp(idVendor, "05c6", 4) && strncasecmp(idVendor, "2c7c", 4)) + continue; + + dprintf("Find %s/%s idVendor=%s idProduct=%s\n", dir, ent->d_name, idVendor, idProduct); + snprintf(pl->subdir, sizeof(pl->subdir), "%s/%s:1.4/usbmisc", dir, ent->d_name); + if (access(pl->subdir, R_OK)) { + snprintf(pl->subdir, sizeof(pl->subdir), "%s/%s:1.4/usb", dir, ent->d_name); + if (access(pl->subdir, R_OK)) { + dprintf("no GobiQMI/usbmic/usb found in %s/%s:1.4\n", dir, ent->d_name); + continue; + } + } + + dir_get_child(pl->subdir, pl->qmifile, sizeof(pl->qmifile)); + snprintf(qmidevice, sizeof(qmidevice), "/dev/%.*s", 100, pl->qmifile); + strcpy(device_name, qmidevice); + + snprintf(pl->subdir, sizeof(pl->subdir), "%s/%s:1.4/net", dir, ent->d_name); + dir_get_child(pl->subdir, pl->ifname, sizeof(pl->ifname)); + strcpy(ifname, pl->ifname); + } + closedir(pDir); + free(pl); + if (device_name[0] == '\0' || ifname[0] == '\0') { + goto error; + } + return 0; + } + +error: + return -1; +} + +static void usage(void) { + dprintf(" -d A valid qmi device\n" + " default /dev/cdc-wdm0, but cdc-wdm0 may be invalid\n" + " -i netcard name\n" + " -v Will show all details\n"); +} + +static void sig_action(int sig) { + if (qmi_proxy_quit == 0) { + qmi_proxy_quit = 1; + if (thread_id) + pthread_kill(thread_id, sig); + } +} + +int main(int argc, char *argv[]) { + int opt; + char cdc_wdm[32+1] = {'\0'}; + char ifname[32+1] = {'\0'}; + int retry_times = 0; + char servername[64] = {0}; + + optind = 1; + + signal(SIGINT, sig_action); + + while ( -1 != (opt = getopt(argc, argv, "d:i:vh"))) { + switch (opt) { + case 'd': + strcpy(cdc_wdm, optarg); + break; + case 'v': + verbose_debug = 1; + break; + case 'i': + strcpy(ifname, optarg); + break; + default: + usage(); + return 0; + } + } + + if (cdc_wdm[0] == '\0' || ifname[0] == '\0') { + if(qmidevice_detect(cdc_wdm, ifname) && mhidevice_detect(cdc_wdm, ifname)) { + dprintf("network interface '%s' or qmidev '%s' is not exist\n", ifname, cdc_wdm); + return -1; + } + + } + + if (cdc_wdm[0] == '\0' || ifname[0] == '\0') { + dprintf("network interface '%s' or qmidev '%s' is not exist\n", ifname, cdc_wdm); + return -1; + } + + sprintf(servername, "quectel-qmi-proxy%d", cdc_wdm[strlen(cdc_wdm)-1]-'0'); + + if (access(cdc_wdm, R_OK | W_OK)) { + dprintf("Fail to access %s, errno: %d (%s). break\n", cdc_wdm, errno, strerror(errno)); + return -1; + } + + while (qmi_proxy_quit == 0) { + if (access(cdc_wdm, R_OK | W_OK)) { + dprintf("Fail to access %s, errno: %d (%s). continue\n", cdc_wdm, errno, strerror(errno)); + // wait device + sleep(3); + continue; + } + + dprintf("Will use cdc-wdm='%s', ifname='%s'\n", cdc_wdm, ifname); + + cdc_wdm_fd = open(cdc_wdm, O_RDWR | O_NONBLOCK | O_NOCTTY); + if (cdc_wdm_fd == -1) { + dprintf("Failed to open %s, errno: %d (%s). break\n", cdc_wdm, errno, strerror(errno)); + return -1; + } + cfmakenoblock(cdc_wdm_fd); + + /* no qmi_proxy_loop lives, create one */ + pthread_create(&thread_id, NULL, qmi_proxy_loop, NULL); + /* try to redo init if failed, init function must be successfully */ + while (qmi_proxy_init(cdc_wdm, ifname) != 0) { + if (retry_times < 5) { + dprintf("fail to init proxy, try again in 2 seconds.\n"); + sleep(2); + retry_times++; + } else { + dprintf("has failed too much times, restart the modem and have a try...\n"); + break; + } + /* break loop if modem is detached */ + if (access(cdc_wdm, F_OK|R_OK|W_OK)) + break; + } + retry_times = 0; + qmi_start_server(servername); + pthread_join(thread_id, NULL); + + /* close local server at last */ + qmi_close_server(servername); + close(cdc_wdm_fd); + /* DO RESTART IN 20s IF MODEM RESET ITSELF */ + if (modem_reset_flag) { + unsigned int time_to_wait = 20; + while (time_to_wait) { + time_to_wait = sleep(time_to_wait); + } + modem_reset_flag = 0; + } + } + + return 0; +} diff --git a/root/package/link4all/quectel-CM/quectel1.6.0.15_src/udhcpc.c b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/udhcpc.c new file mode 100644 index 00000000..3184e2e7 --- /dev/null +++ b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/udhcpc.c @@ -0,0 +1,675 @@ +/****************************************************************************** + @file udhcpc.c + @brief call DHCP tools to obtain IP address. + + DESCRIPTION + Connectivity Management Tool for USB network adapter of Quectel wireless cellular modules. + + INITIALIZATION AND SEQUENCING REQUIREMENTS + None. + + --------------------------------------------------------------------------- + Copyright (c) 2016 - 2020 Quectel Wireless Solution, Co., Ltd. All Rights Reserved. + Quectel Wireless Solution Proprietary and Confidential. + --------------------------------------------------------------------------- +******************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "util.h" +#include "QMIThread.h" + +static int ql_system(const char *shell_cmd) { + dbg_time("%s", shell_cmd); + return system(shell_cmd); +} + +static void ifc_init_ifr(const char *name, struct ifreq *ifr) +{ + memset(ifr, 0, sizeof(struct ifreq)); + strncpy(ifr->ifr_name, name, IFNAMSIZ); + ifr->ifr_name[IFNAMSIZ - 1] = 0; +} + +static void ql_set_mtu(const char *ifname, int ifru_mtu) { + int inet_sock; + struct ifreq ifr; + + inet_sock = socket(AF_INET, SOCK_DGRAM, 0); + + if (inet_sock > 0) { + ifc_init_ifr(ifname, &ifr); + + if (!ioctl(inet_sock, SIOCGIFMTU, &ifr)) { + if (ifr.ifr_ifru.ifru_mtu != ifru_mtu) { + dbg_time("change mtu %d -> %d", ifr.ifr_ifru.ifru_mtu , ifru_mtu); + ifr.ifr_ifru.ifru_mtu = ifru_mtu; + ioctl(inet_sock, SIOCSIFMTU, &ifr); + } + } + + close(inet_sock); + } +} + +static int ifc_get_addr(const char *name, in_addr_t *addr) +{ + int inet_sock; + struct ifreq ifr; + int ret = 0; + + inet_sock = socket(AF_INET, SOCK_DGRAM, 0); + + ifc_init_ifr(name, &ifr); + if (addr != NULL) { + ret = ioctl(inet_sock, SIOCGIFADDR, &ifr); + if (ret < 0) { + *addr = 0; + } else { + *addr = ((struct sockaddr_in*) &ifr.ifr_addr)->sin_addr.s_addr; + } + } + close(inet_sock); + return ret; +} + +static short ifc_get_flags(const char *ifname) +{ + int inet_sock; + struct ifreq ifr; + int ret = 0; + + inet_sock = socket(AF_INET, SOCK_DGRAM, 0); + + if (inet_sock > 0) { + ifc_init_ifr(ifname, &ifr); + + if (!ioctl(inet_sock, SIOCGIFFLAGS, &ifr)) { + ret = ifr.ifr_ifru.ifru_flags; + } + + close(inet_sock); + } + + return ret; +} + +static int ql_raw_ip_mode_check(const char *ifname) { + int fd; + in_addr_t addr; + char raw_ip[128]; + char shell_cmd[128]; + char mode[2] = "X"; + int mode_change = 0; + + ifc_get_addr(ifname, &addr); + if (addr) + return 0; + + snprintf(raw_ip, sizeof(raw_ip), "/sys/class/net/%s/qmi/raw_ip", ifname); + if (access(raw_ip, F_OK)) + return 0; + + fd = open(raw_ip, O_RDWR | O_NONBLOCK | O_NOCTTY); + if (fd < 0) { + dbg_time("%s %d fail to open(%s), errno:%d (%s)", __FILE__, __LINE__, raw_ip, errno, strerror(errno)); + return 0; + } + + read(fd, mode, 2); + if (mode[0] == '0' || mode[0] == 'N') { + dbg_time("File:%s Line:%d udhcpc fail to get ip address, try next:", __func__, __LINE__); + snprintf(shell_cmd, sizeof(shell_cmd), "ifconfig %s down", ifname); + ql_system(shell_cmd); + dbg_time("echo Y > /sys/class/net/%s/qmi/raw_ip", ifname); + mode[0] = 'Y'; + write(fd, mode, 2); + mode_change = 1; + snprintf(shell_cmd, sizeof(shell_cmd), "ifconfig %s up", ifname); + ql_system(shell_cmd); + } + + close(fd); + return mode_change; +} + +static void* udhcpc_thread_function(void* arg) { + FILE * udhcpc_fp; + char *udhcpc_cmd = (char *)arg; + + if (udhcpc_cmd == NULL) + return NULL; + + dbg_time("%s", udhcpc_cmd); + udhcpc_fp = popen(udhcpc_cmd, "r"); + free(udhcpc_cmd); + if (udhcpc_fp) { + char buf[0xff]; + + buf[sizeof(buf)-1] = '\0'; + while((fgets(buf, sizeof(buf)-1, udhcpc_fp)) != NULL) { + if ((strlen(buf) > 1) && (buf[strlen(buf) - 1] == '\n')) + buf[strlen(buf) - 1] = '\0'; + dbg_time("%s", buf); + } + + pclose(udhcpc_fp); + } + + return NULL; +} + +//#define USE_DHCLIENT +#ifdef USE_DHCLIENT +static int dhclient_alive = 0; +#endif +static int dibbler_client_alive = 0; + +void ql_get_driver_rmnet_info(PROFILE_T *profile, RMNET_INFO *rmnet_info) { + int ifc_ctl_sock; + struct ifreq ifr; + int rc; + int request = 0x89F3; + unsigned char data[512]; + + memset(rmnet_info, 0x00, sizeof(*rmnet_info)); + + ifc_ctl_sock = socket(AF_INET, SOCK_DGRAM, 0); + if (ifc_ctl_sock <= 0) { + dbg_time("socket() failed: %s\n", strerror(errno)); + return; + } + + memset(&ifr, 0, sizeof(struct ifreq)); + strncpy(ifr.ifr_name, profile->usbnet_adapter, IFNAMSIZ); + ifr.ifr_name[IFNAMSIZ - 1] = 0; + ifr.ifr_ifru.ifru_data = (void *)data; + + rc = ioctl(ifc_ctl_sock, request, &ifr); + if (rc < 0) { + dbg_time("ioctl(0x%x, qmap_settings) failed: %s, rc=%d", request, strerror(errno), rc); + } + else { + memcpy(rmnet_info, data, sizeof(*rmnet_info)); + } + + close(ifc_ctl_sock); +} + +void ql_set_driver_qmap_setting(PROFILE_T *profile, QMAP_SETTING *qmap_settings) { + int ifc_ctl_sock; + struct ifreq ifr; + int rc; + int request = 0x89F2; + + ifc_ctl_sock = socket(AF_INET, SOCK_DGRAM, 0); + if (ifc_ctl_sock <= 0) { + dbg_time("socket() failed: %s\n", strerror(errno)); + return; + } + + memset(&ifr, 0, sizeof(struct ifreq)); + strncpy(ifr.ifr_name, profile->usbnet_adapter, IFNAMSIZ); + ifr.ifr_name[IFNAMSIZ - 1] = 0; + ifr.ifr_ifru.ifru_data = (void *)qmap_settings; + + rc = ioctl(ifc_ctl_sock, request, &ifr); + if (rc < 0) { + dbg_time("ioctl(0x%x, qmap_settings) failed: %s, rc=%d", request, strerror(errno), rc); + } + + close(ifc_ctl_sock); +} + +void ql_set_driver_link_state(PROFILE_T *profile, int link_state) { + char link_file[128]; + int fd; + int new_state = 0; + + snprintf(link_file, sizeof(link_file), "/sys/class/net/%s/link_state", profile->usbnet_adapter); + fd = open(link_file, O_RDWR | O_NONBLOCK | O_NOCTTY); + if (fd == -1) { + if (errno != ENOENT) + dbg_time("Fail to access %s, errno: %d (%s)", link_file, errno, strerror(errno)); + return; + } + + if (profile->qmap_mode <= 1) + new_state = !!link_state; + else { + //0x80 means link off this pdp + new_state = (link_state ? 0x00 : 0x80) + profile->pdp; + } + + snprintf(link_file, sizeof(link_file), "%d\n", new_state); + write(fd, link_file, sizeof(link_file)); + + if (link_state == 0 && profile->qmapnet_adapter && strcmp(profile->qmapnet_adapter, profile->usbnet_adapter)) { + size_t rc; + + lseek(fd, 0, SEEK_SET); + rc = read(fd, link_file, sizeof(link_file)); + if (rc > 1 && (!strncasecmp(link_file, "0\n", 2) || !strncasecmp(link_file, "0x0\n", 4))) { + snprintf(link_file, sizeof(link_file), "ifconfig %s down", profile->usbnet_adapter); + ql_system(link_file); + } + } + + close(fd); +} + +static const char *ipv4Str(const uint32_t Address) { + static char str[] = {"255.225.255.255"}; + uint8_t *ip = (uint8_t *)&Address; + + snprintf(str, sizeof(str), "%d.%d.%d.%d", ip[3], ip[2], ip[1], ip[0]); + return str; +} + +static const char *ipv6Str(const UCHAR Address[16]) { + static char str[64]; + uint16_t ip[8]; + int i; + for (i = 0; i < 8; i++) { + ip[i] = (Address[i*2]<<8) + Address[i*2+1]; + } + + snprintf(str, sizeof(str), "%x:%x:%x:%x:%x:%x:%x:%x", + ip[0], ip[1], ip[2], ip[3], ip[4], ip[5], ip[6], ip[7]); + + return str; +} + +//#define QL_OPENWER_NETWORK_SETUP +#ifdef QL_OPENWER_NETWORK_SETUP +static const char *openwrt_lan = "br-lan"; +static const char *openwrt_wan = "wwan0"; + +static int ql_openwrt_system(const char *cmd) { + int i; + int ret = 1; + char shell_cmd[128]; + + snprintf(shell_cmd, sizeof(shell_cmd), "%s 2>1 > /dev/null", cmd); + + for (i = 0; i < 15; i++) { + dbg_time("%s", cmd); + ret = system(shell_cmd); + if (!ret) + break; + sleep(1); + } + + return ret; +} + +static int ql_openwrt_is_wan(const char *ifname) { + if (openwrt_lan == NULL) { + system("uci show network.wan.ifname"); + } + + if (strcmp(ifname, openwrt_wan)) + return 0; + + return 1; +} + +static void ql_openwrt_setup_wan(const char *ifname, const IPV4_T *ipv4) { + FILE *fp = NULL; + char config[64]; + + snprintf(config, sizeof(config), "/tmp/rmnet_%s_ipv4config", ifname); + + if (ipv4 == NULL) { + if (ql_openwrt_is_wan(ifname)) + ql_openwrt_system("ifdown wan"); + return; + } + + fp = fopen(config, "w"); + if (fp == NULL) + return; + + fprintf(fp, "IFNAME=\"%s\"\n", ifname); + fprintf(fp, "PUBLIC_IP=\"%s\"\n", ipv4Str(ipv4->Address)); + fprintf(fp, "NETMASK=\"%s\"\n", ipv4Str(ipv4->SubnetMask)); + fprintf(fp, "GATEWAY=\"%s\"\n", ipv4Str(ipv4->Gateway)); + fprintf(fp, "DNSSERVERS=\"%s", ipv4Str(ipv4->DnsPrimary)); + if (ipv4->DnsSecondary != 0) + fprintf(fp, " %s", ipv4Str(ipv4->DnsSecondary)); + fprintf(fp, "\"\n"); + + fclose(fp); + + if (!ql_openwrt_is_wan(ifname)) + return; + + ql_openwrt_system("ifup wan"); +} + +static void ql_openwrt_setup_wan6(const char *ifname, const IPV6_T *ipv6) { + FILE *fp = NULL; + char config[64]; + int first_ifup; + + snprintf(config, sizeof(config), "/tmp/rmnet_%s_ipv6config", ifname); + + if (ipv6 == NULL) { + if (ql_openwrt_is_wan(ifname)) + ql_openwrt_system("ifdown wan6"); + return; + } + + first_ifup = (access(config, F_OK) != 0); + + fp = fopen(config, "w"); + if (fp == NULL) + return; + + fprintf(fp, "IFNAME=\"%s\"\n", ifname); + fprintf(fp, "PUBLIC_IP=\"%s\"\n", ipv6Str(ipv6->Address)); + fprintf(fp, "NETMASK=\"%s\"\n", ipv6Str(ipv6->SubnetMask)); + fprintf(fp, "GATEWAY=\"%s\"\n", ipv6Str(ipv6->Gateway)); + fprintf(fp, "PrefixLength=\"%d\"\n", ipv6->PrefixLengthIPAddr); + fprintf(fp, "DNSSERVERS=\"%s", ipv6Str(ipv6->DnsPrimary)); + if (ipv6->DnsSecondary[0]) + fprintf(fp, " %s", ipv6Str(ipv6->DnsSecondary)); + fprintf(fp, "\"\n"); + + fclose(fp); + + if (!ql_openwrt_is_wan(ifname)) + return; + + if (first_ifup) + ql_openwrt_system("ifup wan6"); + else + ql_openwrt_system("/etc/init.d/network restart"); //make PC to release old IPV6 address, and RS new IPV6 address + +#if 1 //TODO? why need this? + if (openwrt_lan) { + int i; + char shell_cmd[128]; + UCHAR Address[16] = {0}; + + ql_openwrt_system(("ifstatus lan")); + + for (i = 0; i < (ipv6->PrefixLengthIPAddr/8); i++) + Address[i] = ipv6->Address[i]; + + snprintf(shell_cmd, sizeof(shell_cmd), "ip route del %s/%u dev %s", ipv6Str(Address), ipv6->PrefixLengthIPAddr, ifname); + ql_openwrt_system(shell_cmd); + + snprintf(shell_cmd, sizeof(shell_cmd), "ip route add %s/%u dev %s", ipv6Str(Address), ipv6->PrefixLengthIPAddr, openwrt_lan); + ql_system(shell_cmd); + } +#endif +} +#endif + +void udhcpc_start(PROFILE_T *profile) { + char *ifname = profile->usbnet_adapter; + char shell_cmd[128]; + + ql_set_driver_link_state(profile, 1); + + if (profile->qmapnet_adapter) { + ifname = profile->qmapnet_adapter; + } + + if (profile->rawIP && profile->ipv4.Address && profile->ipv4.Mtu) { + ql_set_mtu(ifname, (profile->ipv4.Mtu)); + } + + if (strcmp(ifname, profile->usbnet_adapter)) { + snprintf(shell_cmd, sizeof(shell_cmd), "ifconfig %s up", profile->usbnet_adapter); + ql_system(shell_cmd); + if (ifc_get_flags(ifname)&IFF_UP) { + snprintf(shell_cmd, sizeof(shell_cmd), "ifconfig %s down", ifname); + ql_system(shell_cmd); + } + } + + snprintf(shell_cmd, sizeof(shell_cmd), "ifconfig %s up", ifname); + ql_system(shell_cmd); + + if (profile->ipv4.Address) { + if (profile->PCSCFIpv4Addr1) + dbg_time("pcscf1: %s", ipv4Str(profile->PCSCFIpv4Addr1)); + if (profile->PCSCFIpv4Addr2) + dbg_time("pcscf2: %s", ipv4Str(profile->PCSCFIpv4Addr2)); + } + + if (profile->ipv6.Address[0] && profile->ipv6.PrefixLengthIPAddr) { + if (profile->PCSCFIpv6Addr1[0]) + dbg_time("pcscf1: %s", ipv6Str(profile->PCSCFIpv6Addr1)); + if (profile->PCSCFIpv6Addr2[0]) + dbg_time("pcscf2: %s", ipv6Str(profile->PCSCFIpv6Addr2)); + } + +#if 1 //for bridge mode, only one public IP, so do udhcpc manually + if (ql_bridge_mode_detect(profile)) { + return; + } +#endif + +//because must use udhcpc to obtain IP when working on ETH mode, +//so it is better also use udhcpc to obtain IP when working on IP mode. +//use the same policy for all modules +#if 0 + if (profile->rawIP != 0) //mdm9x07/ec25,ec20 R2.0 + { + if (profile->ipv4.Address) { + unsigned char *ip = (unsigned char *)&profile->ipv4.Address; + unsigned char *gw = (unsigned char *)&profile->ipv4.Gateway; + unsigned char *netmask = (unsigned char *)&profile->ipv4.SubnetMask; + unsigned char *dns1 = (unsigned char *)&profile->ipv4.DnsPrimary; + unsigned char *dns2 = (unsigned char *)&profile->ipv4.DnsSecondary; + + snprintf(shell_cmd, sizeof(shell_cmd), "ifconfig %s %d.%d.%d.%d netmask %d.%d.%d.%d",ifname, + ip[3], ip[2], ip[1], ip[0], netmask[3], netmask[2], netmask[1], netmask[0]); + ql_system(shell_cmd); + + //Resetting default routes + snprintf(shell_cmd, sizeof(shell_cmd), "route del default gw 0.0.0.0 dev %s", ifname); + while(!system(shell_cmd)); + + snprintf(shell_cmd, sizeof(shell_cmd), "route add default gw %d.%d.%d.%d dev %s metric 0", gw[3], gw[2], gw[1], gw[0], ifname); + ql_system(shell_cmd); + + //Adding DNS + if (profile->ipv4.DnsSecondary == 0) + profile->ipv4.DnsSecondary = profile->ipv4.DnsPrimary; + + if (dns1[0]) { + dbg_time("Adding DNS %d.%d.%d.%d %d.%d.%d.%d", dns1[3], dns1[2], dns1[1], dns1[0], dns2[3], dns2[2], dns2[1], dns2[0]); + snprintf(shell_cmd, sizeof(shell_cmd), "echo -n \"nameserver %d.%d.%d.%d\nnameserver %d.%d.%d.%d\n\" > /etc/resolv.conf", + dns1[3], dns1[2], dns1[1], dns1[0], dns2[3], dns2[2], dns2[1], dns2[0]); + system(shell_cmd); + } + } + + + if (profile->ipv6.Address[0] && profile->ipv6.PrefixLengthIPAddr) { + unsigned char *ip = (unsigned char *)profile->ipv6.Address; +#if 1 + snprintf(shell_cmd, sizeof(shell_cmd), "ifconfig %s inet6 add %02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x/%d", + ifname, ip[0], ip[1], ip[2], ip[3], ip[4], ip[5], ip[6], ip[7], ip[8], ip[9], ip[10], ip[11], ip[12], ip[13], ip[14], ip[15], profile->ipv6.PrefixLengthIPAddr); +#else + snprintf(shell_cmd, sizeof(shell_cmd), "ip -6 addr add %02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x/%d dev %s", + ip[0], ip[1], ip[2], ip[3], ip[4], ip[5], ip[6], ip[7], ip[8], ip[9], ip[10], ip[11], ip[12], ip[13], ip[14], ip[15], profile->ipv6.PrefixLengthIPAddr, ifname); +#endif + ql_system(shell_cmd); + snprintf(shell_cmd, sizeof(shell_cmd), "route -A inet6 add default dev %s", ifname); + ql_system(shell_cmd); + } + return; + } +#endif + +/* Do DHCP using busybox tools */ + { + char udhcpc_cmd[128]; + pthread_attr_t udhcpc_thread_attr; + pthread_t udhcpc_thread_id; + + pthread_attr_init(&udhcpc_thread_attr); + pthread_attr_setdetachstate(&udhcpc_thread_attr, PTHREAD_CREATE_DETACHED); + + if (profile->ipv4.Address) { +#ifdef USE_DHCLIENT + snprintf(udhcpc_cmd, sizeof(udhcpc_cmd), "dhclient -4 -d --no-pid %s", ifname); + dhclient_alive++; +#else + if (access("/usr/share/udhcpc/default.script", X_OK)) { + dbg_time("Fail to access /usr/share/udhcpc/default.script, errno: %d (%s)", errno, strerror(errno)); + } + + //-f,--foreground Run in foreground + //-b,--background Background if lease is not obtained + //-n,--now Exit if lease is not obtained + //-q,--quit Exit after obtaining lease + //-t,--retries N Send up to N discover packets (default 3) + snprintf(udhcpc_cmd, sizeof(udhcpc_cmd), "busybox udhcpc -f -n -q -t 5 -i %s", ifname); +#endif + +#if 1 //for OpenWrt + if (!access("/lib/netifd/dhcp.script", X_OK) && !access("/sbin/ifup", X_OK) && !access("/sbin/ifstatus", X_OK)) { + dbg_time("you are use OpenWrt?"); + dbg_time("should not calling udhcpc manually?"); + dbg_time("should modify /etc/config/network as below?"); + dbg_time("config interface wan"); + dbg_time("\toption ifname %s", ifname); + dbg_time("\toption proto dhcp"); + dbg_time("should use \"/sbin/ifstaus wan\" to check %s 's status?", ifname); + } +#endif + +#ifdef USE_DHCLIENT + pthread_create(&udhcpc_thread_id, &udhcpc_thread_attr, udhcpc_thread_function, (void*)strdup(udhcpc_cmd)); + sleep(1); +#else + pthread_create(&udhcpc_thread_id, NULL, udhcpc_thread_function, (void*)strdup(udhcpc_cmd)); + pthread_join(udhcpc_thread_id, NULL); + if (ql_raw_ip_mode_check(ifname)) { + pthread_create(&udhcpc_thread_id, NULL, udhcpc_thread_function, (void*)strdup(udhcpc_cmd)); + pthread_join(udhcpc_thread_id, NULL); + } +#endif +#ifdef QL_OPENWER_NETWORK_SETUP + ql_openwrt_setup_wan(ifname, &profile->ipv4); +#endif + } + + if (profile->ipv6.Address[0] && profile->ipv6.PrefixLengthIPAddr) { +#if 1 + //module do not support DHCPv6, only support 'Router Solicit' + //and it seem if enable /proc/sys/net/ipv6/conf/all/forwarding, Kernel do not send RS + const char *forward_file = "/proc/sys/net/ipv6/conf/all/forwarding"; + int forward_fd = open(forward_file, O_RDONLY); + if (forward_fd > 0) { + char forward_state[2]; + read(forward_fd, forward_state, 2); + if (forward_state[0] == '1') { + //dbg_time("%s enabled, kernel maybe donot send 'Router Solicit'", forward_file); + } + close(forward_fd); + } + + snprintf(shell_cmd, sizeof(shell_cmd), "ip -%d address add %s/%u dev %s", + 6, ipv6Str(profile->ipv6.Address), profile->ipv6.PrefixLengthIPAddr, ifname); + ql_system(shell_cmd); + + //ping6 www.qq.com + snprintf(shell_cmd, sizeof(shell_cmd), "ip -%d route add default via %s dev %s", + 6, ipv6Str(profile->ipv6.Gateway), ifname); + ql_system(shell_cmd); + + if (profile->ipv6.DnsPrimary[0] || profile->ipv6.DnsSecondary[0]) { + char dns1str[64], dns2str[64]; + + if (profile->ipv6.DnsPrimary[0]) { + strcpy(dns1str, ipv6Str(profile->ipv6.DnsPrimary)); + } + + if (profile->ipv6.DnsSecondary[0]) { + strcpy(dns2str, ipv6Str(profile->ipv6.DnsSecondary)); + } + + update_resolv_conf(6, ifname, profile->ipv6.DnsPrimary[0] ? dns1str : NULL, profile->ipv6.DnsSecondary ? dns2str : NULL); +#ifdef QL_OPENWER_NETWORK_SETUP + ql_openwrt_setup_wan6(ifname, &profile->ipv6); +#endif + } +#else +#ifdef USE_DHCLIENT + snprintf(udhcpc_cmd, sizeof(udhcpc_cmd), "dhclient -6 -d --no-pid %s", ifname); + dhclient_alive++; +#else + /* + DHCPv6: Dibbler - a portable DHCPv6 + 1. download from http://klub.com.pl/dhcpv6/ + 2. cross-compile + 2.1 ./configure --host=arm-linux-gnueabihf + 2.2 copy dibbler-client to your board + 3. mkdir -p /var/log/dibbler/ /var/lib/ on your board + 4. create /etc/dibbler/client.conf on your board, the content is + log-mode short + log-level 7 + iface wwan0 { + ia + option dns-server + } + 5. run "dibbler-client start" to get ipV6 address + 6. run "route -A inet6 add default dev wwan0" to add default route + */ + snprintf(shell_cmd, sizeof(shell_cmd), "route -A inet6 add default %s", ifname); + ql_system(shell_cmd); + snprintf(udhcpc_cmd, sizeof(udhcpc_cmd), "dibbler-client run"); + dibbler_client_alive++; +#endif + + pthread_create(&udhcpc_thread_id, &udhcpc_thread_attr, udhcpc_thread_function, (void*)strdup(udhcpc_cmd)); +#endif + } + } +} + +void udhcpc_stop(PROFILE_T *profile) { + char *ifname = profile->usbnet_adapter; + char shell_cmd[128]; + + ql_set_driver_link_state(profile, 0); + + if (profile->qmapnet_adapter) { + ifname = profile->qmapnet_adapter; + } + +#ifdef USE_DHCLIENT + if (dhclient_alive) { + system("killall dhclient"); + dhclient_alive = 0; + } +#endif + if (dibbler_client_alive) { + system("killall dibbler-client"); + dibbler_client_alive = 0; + } + +//it seems when call netif_carrier_on(), and netcard 's IP is "0.0.0.0", will cause netif_queue_stopped() + snprintf(shell_cmd, sizeof(shell_cmd), "ifconfig %s 0.0.0.0", ifname); + ql_system(shell_cmd); + snprintf(shell_cmd, sizeof(shell_cmd), "ifconfig %s down", ifname); + ql_system(shell_cmd); + +#ifdef QL_OPENWER_NETWORK_SETUP + ql_openwrt_setup_wan(ifname, NULL); + ql_openwrt_setup_wan6(ifname, NULL); +#endif +} diff --git a/root/package/link4all/quectel-CM/quectel1.6.0.15_src/udhcpc_netlink.c b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/udhcpc_netlink.c new file mode 100644 index 00000000..6124c4f7 --- /dev/null +++ b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/udhcpc_netlink.c @@ -0,0 +1,179 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libmnl/ifutils.h" +#include "libmnl/dhcp/dhcp.h" +#include "util.h" +#include "QMIThread.h" + +static int ql_raw_ip_mode_check(const char *ifname) +{ + int fd; + char raw_ip[128]; + char mode[2] = "X"; + int mode_change = 0; + + snprintf(raw_ip, sizeof(raw_ip), "/sys/class/net/%s/qmi/raw_ip", ifname); + if (access(raw_ip, F_OK)) + return 0; + + fd = open(raw_ip, O_RDWR | O_NONBLOCK | O_NOCTTY); + if (fd < 0) + { + dbg_time("%s %d fail to open(%s), errno:%d (%s)", __FILE__, __LINE__, raw_ip, errno, strerror(errno)); + return 0; + } + + read(fd, mode, 2); + if (mode[0] == '0' || mode[0] == 'N') + { + if_link_down(ifname); + dbg_time("echo Y > /sys/class/net/%s/qmi/raw_ip", ifname); + mode[0] = 'Y'; + write(fd, mode, 2); + mode_change = 1; + if_link_up(ifname); + } + + close(fd); + return mode_change; +} + +static void ql_set_driver_link_state(PROFILE_T *profile, int link_state) +{ + char link_file[128]; + int fd; + int new_state = 0; + + snprintf(link_file, sizeof(link_file), "/sys/class/net/%s/link_state", profile->usbnet_adapter); + fd = open(link_file, O_RDWR | O_NONBLOCK | O_NOCTTY); + if (fd == -1) + { + if (errno != ENOENT) + dbg_time("Fail to access %s, errno: %d (%s)", link_file, errno, strerror(errno)); + return; + } + + if (profile->qmap_mode <= 1) + new_state = !!link_state; + else + { + //0x80 means link off this pdp + new_state = (link_state ? 0x00 : 0x80) + profile->pdp; + } + + snprintf(link_file, sizeof(link_file), "%d\n", new_state); + write(fd, link_file, sizeof(link_file)); + + if (link_state == 0 && profile->qmap_mode > 1) + { + size_t rc; + + lseek(fd, 0, SEEK_SET); + rc = read(fd, link_file, sizeof(link_file)); + if (rc > 1 && (!strcasecmp(link_file, "0\n") || !strcasecmp(link_file, "0x0\n"))) + { + if_link_down(profile->usbnet_adapter); + } + } + + close(fd); +} + +void udhcpc_start(PROFILE_T *profile) +{ + char *ifname = profile->usbnet_adapter; + + ql_set_driver_link_state(profile, 1); + ql_raw_ip_mode_check(ifname); + + if (profile->qmapnet_adapter) + { + ifname = profile->qmapnet_adapter; + } + if (profile->rawIP && profile->ipv4.Address && profile->ipv4.Mtu) + { + if_set_mtu(ifname, (profile->ipv4.Mtu)); + } + + if (strcmp(ifname, profile->usbnet_adapter)) + { + if_link_up(profile->usbnet_adapter); + } + + if_link_up(ifname); + +#if 1 //for bridge mode, only one public IP, so do udhcpc manually + if (ql_bridge_mode_detect(profile)) + { + return; + } +#endif + // if use DHCP(should make with ${DHCP} src files) + // do_dhcp(ifname); + // return 0; + /* IPv4 Addr Info */ + if (profile->ipv4.Address) + { + dbg_time("IPv4 MTU: %d", profile->ipv4.Mtu); + dbg_time("IPv4 Address: %s", ipaddr_to_string_v4(ntohl(profile->ipv4.Address))); + dbg_time("IPv4 Netmask: %d", mask_to_prefix_v4(ntohl(profile->ipv4.SubnetMask))); + dbg_time("IPv4 Gateway: %s", ipaddr_to_string_v4(ntohl(profile->ipv4.Gateway))); + dbg_time("IPv4 DNS1: %s", ipaddr_to_string_v4(ntohl(profile->ipv4.DnsPrimary))); + dbg_time("IPv4 DNS2: %s", ipaddr_to_string_v4(ntohl(profile->ipv4.DnsSecondary))); + if_set_network_v4(ifname, ntohl(profile->ipv4.Address), + mask_to_prefix_v4(profile->ipv4.SubnetMask), + ntohl(profile->ipv4.Gateway), + ntohl(profile->ipv4.DnsPrimary), + ntohl(profile->ipv4.DnsSecondary)); + } + + if (profile->ipv6.Address[0] && profile->ipv6.PrefixLengthIPAddr) + { + //module do not support DHCPv6, only support 'Router Solicit' + //and it seem if enable /proc/sys/net/ipv6/conf/all/forwarding, Kernel do not send RS + const char *forward_file = "/proc/sys/net/ipv6/conf/all/forwarding"; + int forward_fd = open(forward_file, O_RDONLY); + if (forward_fd > 0) + { + char forward_state[2]; + read(forward_fd, forward_state, 2); + if (forward_state[0] == '1') + { + dbg_time("%s enabled, kernel maybe donot send 'Router Solicit'", forward_file); + } + close(forward_fd); + } + + dbg_time("IPv6 MTU: %d", profile->ipv6.Mtu); + dbg_time("IPv6 Address: %s", ipaddr_to_string_v6(profile->ipv6.Address)); + dbg_time("IPv6 Netmask: %d", profile->ipv6.PrefixLengthIPAddr); + dbg_time("IPv6 Gateway: %s", ipaddr_to_string_v6(profile->ipv6.Gateway)); + dbg_time("IPv6 DNS1: %s", ipaddr_to_string_v6(profile->ipv6.DnsPrimary)); + dbg_time("IPv6 DNS2: %s", ipaddr_to_string_v6(profile->ipv6.DnsSecondary)); + if_set_network_v6(ifname, profile->ipv6.Address, profile->ipv6.PrefixLengthIPAddr, + profile->ipv6.Gateway, profile->ipv6.DnsPrimary, profile->ipv6.DnsSecondary); + } +} + +void udhcpc_stop(PROFILE_T *profile) +{ + char *ifname = profile->usbnet_adapter; + + ql_set_driver_link_state(profile, 0); + + if (profile->qmapnet_adapter) + { + ifname = profile->qmapnet_adapter; + } + + if_link_down(ifname); + if_flush_v4_addr(ifname); + if_flush_v6_addr(ifname); +} diff --git a/root/package/link4all/quectel-CM/quectel1.6.0.15_src/util.c b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/util.c new file mode 100644 index 00000000..cc0105b4 --- /dev/null +++ b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/util.c @@ -0,0 +1,301 @@ +/****************************************************************************** + @file util.c + @brief some utils for this QCM tool. + + DESCRIPTION + Connectivity Management Tool for USB network adapter of Quectel wireless cellular modules. + + INITIALIZATION AND SEQUENCING REQUIREMENTS + None. + + --------------------------------------------------------------------------- + Copyright (c) 2016 - 2020 Quectel Wireless Solution, Co., Ltd. All Rights Reserved. + Quectel Wireless Solution Proprietary and Confidential. + --------------------------------------------------------------------------- +******************************************************************************/ + +#include +#if defined(__STDC__) +#include +#define __V(x) x +#else +#include +#define __V(x) (va_alist) va_dcl +#define const +#define volatile +#endif + +#include + +#include "QMIThread.h" + +static void setTimespecRelative(struct timespec *p_ts, long long msec) +{ + struct timeval tv; + + gettimeofday(&tv, (struct timezone *) NULL); + + /* what's really funny about this is that I know + pthread_cond_timedwait just turns around and makes this + a relative time again */ + p_ts->tv_sec = tv.tv_sec + (msec / 1000); + p_ts->tv_nsec = (tv.tv_usec + (msec % 1000) * 1000L ) * 1000L; + if (p_ts->tv_nsec >= 1000000000UL) { + p_ts->tv_sec += 1; + p_ts->tv_nsec -= 1000000000UL; + } +} + +int pthread_cond_timeout_np(pthread_cond_t *cond, pthread_mutex_t * mutex, unsigned msecs) { + if (msecs != 0) { + unsigned i; + unsigned t = msecs/4; + int ret = 0; + + if (t == 0) + t = 1; + + for (i = 0; i < msecs; i += t) { + struct timespec ts; + setTimespecRelative(&ts, t); + ret = pthread_cond_timedwait(cond, mutex, &ts); //to advoid system time change + if (ret != ETIMEDOUT) { + if(ret) dbg_time("ret=%d, msecs=%u, t=%u", ret, msecs, t); + break; + } + } + + return ret; + } else { + return pthread_cond_wait(cond, mutex); + } +} + +void cond_setclock_attr(pthread_cond_t *cond, clockid_t clock) +{ +#if 0 //very old uclibc do not support pthread_condattr_setclock + /* set relative time, for pthread_cond_timedwait */ + pthread_condattr_t attr; + pthread_condattr_init (&attr); + pthread_condattr_setclock(&attr, clock); + pthread_cond_init(cond, &attr); + pthread_condattr_destroy (&attr); +#endif +} + +const char * get_time(void) { + static char time_buf[50]; + struct timeval tv; + time_t time; + suseconds_t millitm; + struct tm *ti; + + gettimeofday (&tv, NULL); + + time= tv.tv_sec; + millitm = (tv.tv_usec + 500) / 1000; + + if (millitm == 1000) { + ++time; + millitm = 0; + } + + ti = localtime(&time); + sprintf(time_buf, "%02d-%02d_%02d:%02d:%02d:%03d", ti->tm_mon+1, ti->tm_mday, ti->tm_hour, ti->tm_min, ti->tm_sec, (int)millitm); + return time_buf; +} + +unsigned long clock_msec(void) +{ + struct timespec tm; + clock_gettime( CLOCK_MONOTONIC, &tm); + return (unsigned long)(tm.tv_sec*1000 + (tm.tv_nsec/1000000)); +} + +FILE *logfilefp = NULL; + +const int i = 1; +#define is_bigendian() ( (*(char*)&i) == 0 ) + +USHORT le16_to_cpu(USHORT v16) { + USHORT tmp = v16; + if (is_bigendian()) { + unsigned char *s = (unsigned char *)(&v16); + unsigned char *d = (unsigned char *)(&tmp); + d[0] = s[1]; + d[1] = s[0]; + } + return tmp; +} + +UINT le32_to_cpu (UINT v32) { + UINT tmp = v32; + if (is_bigendian()) { + unsigned char *s = (unsigned char *)(&v32); + unsigned char *d = (unsigned char *)(&tmp); + d[0] = s[3]; + d[1] = s[2]; + d[2] = s[1]; + d[3] = s[0]; + } + return tmp; +} + +UINT ql_swap32(UINT v32) { + UINT tmp = v32; + { + unsigned char *s = (unsigned char *)(&v32); + unsigned char *d = (unsigned char *)(&tmp); + d[0] = s[3]; + d[1] = s[2]; + d[2] = s[1]; + d[3] = s[0]; + } + return tmp; +} + +USHORT cpu_to_le16(USHORT v16) { + USHORT tmp = v16; + if (is_bigendian()) { + unsigned char *s = (unsigned char *)(&v16); + unsigned char *d = (unsigned char *)(&tmp); + d[0] = s[1]; + d[1] = s[0]; + } + return tmp; +} + +UINT cpu_to_le32 (UINT v32) { + UINT tmp = v32; + if (is_bigendian()) { + unsigned char *s = (unsigned char *)(&v32); + unsigned char *d = (unsigned char *)(&tmp); + d[0] = s[3]; + d[1] = s[2]; + d[2] = s[1]; + d[3] = s[0]; + } + return tmp; +} + +void update_resolv_conf(int iptype, const char *ifname, const char *dns1, const char *dns2) { + const char *dns_file = "/etc/resolv.conf"; + FILE *dns_fp; + char dns_line[256]; + #define MAX_DNS 16 + char *dns_info[MAX_DNS]; + char dns_tag[64]; + int dns_match = 0; + int i; + + snprintf(dns_tag, sizeof(dns_tag), "# IPV%d %s", iptype, ifname); + + for (i = 0; i < MAX_DNS; i++) + dns_info[i] = NULL; + + dns_fp = fopen(dns_file, "r"); + if (dns_fp) { + i = 0; + dns_line[sizeof(dns_line)-1] = '\0'; + + while((fgets(dns_line, sizeof(dns_line)-1, dns_fp)) != NULL) { + if ((strlen(dns_line) > 1) && (dns_line[strlen(dns_line) - 1] == '\n')) + dns_line[strlen(dns_line) - 1] = '\0'; + //dbg_time("%s", dns_line); + if (strstr(dns_line, dns_tag)) { + dns_match++; + continue; + } + dns_info[i++] = strdup(dns_line); + if (i == MAX_DNS) + break; + } + + fclose(dns_fp); + } + else if (errno != ENOENT) { + dbg_time("fopen %s fail, errno:%d (%s)", dns_file, errno, strerror(errno)); + return; + } + + if (dns1 == NULL && dns_match == 0) + return; + + dns_fp = fopen(dns_file, "w"); + if (dns_fp) { + if (dns1) + fprintf(dns_fp, "nameserver %s %s\n", dns1, dns_tag); + if (dns2) + fprintf(dns_fp, "nameserver %s %s\n", dns2, dns_tag); + + for (i = 0; i < MAX_DNS && dns_info[i]; i++) + fprintf(dns_fp, "%s\n", dns_info[i]); + fclose(dns_fp); + } + else { + dbg_time("fopen %s fail, errno:%d (%s)", dns_file, errno, strerror(errno)); + } + + for (i = 0; i < MAX_DNS && dns_info[i]; i++) + free(dns_info[i]); +} + +pid_t getpid_by_pdp(int pdp, const char* program_name) +{ + glob_t gt; + int ret; + char filter[5] = {0}; + pid_t pid; + + sprintf(filter, "-n %d", pdp); + ret = glob("/proc/*/cmdline", GLOB_NOSORT, NULL, >); + if (ret != 0) { + dbg_time("glob error, errno = %d(%s)", errno, strerror(errno)); + return -1; + } else { + int i = 0, fd = -1; + size_t nreads; + char cmdline[512] = {0}; + + for (i = 0; i < gt.gl_pathc; i++) { + fd = open(gt.gl_pathv[i], O_RDONLY); + if (fd == -1) { + dbg_time("open %s failed, errno = %d(%s)", gt.gl_pathv[i], errno, strerror(errno)); + globfree(>); + return -1; + } + + nreads = read(fd, cmdline, sizeof(cmdline)); + if (nreads > 0) { + int pos = 0; + while (pos < nreads-1) { + if (cmdline[pos] == '\0') + cmdline[pos] = ' '; // space + pos++; + } + // printf("%s\n", cmdline); + } + + if (strstr(cmdline, program_name) && strstr(cmdline, filter)) { + char path[64] = {0}; + char pidstr[64] = {0}; + char *p; + + dbg_time("%s: %s", gt.gl_pathv[i], cmdline); + strcpy(path, gt.gl_pathv[i]); + p = strstr(gt.gl_pathv[i], "/cmdline"); + *p = '\0'; + while (*(--p) != '/') ; + + strcpy(pidstr, p+1); + pid = atoi(pidstr); + globfree(>); + + return pid; + } + } + } + + globfree(>); + return -1; +} diff --git a/root/package/link4all/quectel-CM/quectel1.6.0.15_src/util.h b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/util.h new file mode 100644 index 00000000..392d4014 --- /dev/null +++ b/root/package/link4all/quectel-CM/quectel1.6.0.15_src/util.h @@ -0,0 +1,52 @@ +/** + @file + util.h + + @brief + This file provides the definitions, and declares some common APIs for list-algorithm. + + */ + +#ifndef _UTILS_H_ +#define _UTILS_H_ + +#include +#include + +struct listnode +{ + struct listnode *next; + struct listnode *prev; +}; + +#define node_to_item(node, container, member) \ + (container *) (((char*) (node)) - offsetof(container, member)) + +#define list_declare(name) \ + struct listnode name = { \ + .next = &name, \ + .prev = &name, \ + } + +#define list_for_each(node, list) \ + for (node = (list)->next; node != (list); node = node->next) + +#define list_for_each_reverse(node, list) \ + for (node = (list)->prev; node != (list); node = node->prev) + +void list_init(struct listnode *list); +void list_add_tail(struct listnode *list, struct listnode *item); +void list_add_head(struct listnode *head, struct listnode *item); +void list_remove(struct listnode *item); + +#define list_empty(list) ((list) == (list)->next) +#define list_head(list) ((list)->next) +#define list_tail(list) ((list)->prev) + +int epoll_register(int epoll_fd, int fd, unsigned int events); +int epoll_deregister(int epoll_fd, int fd); +const char * get_time(void); +unsigned long clock_msec(void); +pid_t getpid_by_pdp(int, const char*); + +#endif diff --git a/root/package/link4all/quectel-CM/simcom-cm/GobiNetCM.c b/root/package/link4all/quectel-CM/simcom-cm/GobiNetCM.c new file mode 100644 index 00000000..9a5de1f5 --- /dev/null +++ b/root/package/link4all/quectel-CM/simcom-cm/GobiNetCM.c @@ -0,0 +1,217 @@ +#include +#include +#include +#include +#include +#include "QMIThread.h" + +#ifdef CONFIG_GOBINET + +// 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 + +int GobiNetSendQMI(PQCQMIMSG pRequest) { + int ret, fd; + + fd = qmiclientId[pRequest->QMIHdr.QMIType]; + + if (fd <= 0) { + dbg_time("%s QMIType: %d has no clientID", __func__, pRequest->QMIHdr.QMIType); + return -ENODEV; + } + + // Always ready to write + if (1 == 1) { + ssize_t nwrites = le16_to_cpu(pRequest->QMIHdr.Length) + 1 - sizeof(QCQMI_HDR); + ret = write(fd, &pRequest->MUXMsg, nwrites); + if (ret == nwrites) { + ret = 0; + } else { + dbg_time("%s write=%d, errno: %d (%s)", __func__, ret, errno, strerror(errno)); + } + } else { + dbg_time("%s poll=%d, errno: %d (%s)", __func__, ret, errno, strerror(errno)); + } + + return ret; +} + +static int GobiNetGetClientID(const char *qcqmi, UCHAR QMIType) { + int ClientId; + ClientId = open(qcqmi, O_RDWR | O_NONBLOCK | O_NOCTTY); + if (ClientId == -1) { + dbg_time("failed to open %s, errno: %d (%s)", qcqmi, errno, strerror(errno)); + return -1; + } + fcntl(cdc_wdm_fd, F_SETFD, FD_CLOEXEC) ; + + if (ioctl(ClientId, IOCTL_QMI_GET_SERVICE_FILE, QMIType) != 0) { + dbg_time("failed to get ClientID for 0x%02x errno: %d (%s)", QMIType, errno, strerror(errno)); + close(ClientId); + ClientId = 0; + } + + switch (QMIType) { + case QMUX_TYPE_WDS: dbg_time("Get clientWDS = %d", ClientId); break; + case QMUX_TYPE_DMS: dbg_time("Get clientDMS = %d", ClientId); break; + case QMUX_TYPE_NAS: dbg_time("Get clientNAS = %d", ClientId); break; + case QMUX_TYPE_QOS: dbg_time("Get clientQOS = %d", ClientId); break; + case QMUX_TYPE_WMS: dbg_time("Get clientWMS = %d", ClientId); break; + case QMUX_TYPE_PDS: dbg_time("Get clientPDS = %d", ClientId); break; + case QMUX_TYPE_UIM: dbg_time("Get clientUIM = %d", ClientId); break; + case QMUX_TYPE_WDS_ADMIN: dbg_time("Get clientWDA = %d", ClientId); + break; + default: break; + } + + return ClientId; +} + +int GobiNetDeInit(void) { + unsigned int i; + for (i = 0; i < sizeof(qmiclientId)/sizeof(qmiclientId[0]); i++) + { + if (qmiclientId[i] != 0) + { + close(qmiclientId[i]); + qmiclientId[i] = 0; + } + } + + return 0; +} + +void * GobiNetThread(void *pData) { + PROFILE_T *profile = (PROFILE_T *)pData; + const char *qcqmi = (const char *)profile->qmichannel; + + qmiclientId[QMUX_TYPE_WDS] = GobiNetGetClientID(qcqmi, QMUX_TYPE_WDS); + if (profile->IsDualIPSupported) + qmiclientId[QMUX_TYPE_WDS_IPV6] = GobiNetGetClientID(qcqmi, QMUX_TYPE_WDS); + qmiclientId[QMUX_TYPE_DMS] = GobiNetGetClientID(qcqmi, QMUX_TYPE_DMS); + qmiclientId[QMUX_TYPE_NAS] = GobiNetGetClientID(qcqmi, QMUX_TYPE_NAS); + qmiclientId[QMUX_TYPE_UIM] = GobiNetGetClientID(qcqmi, QMUX_TYPE_UIM); + qmiclientId[QMUX_TYPE_WDS_ADMIN] = GobiNetGetClientID(qcqmi, QMUX_TYPE_WDS_ADMIN); + + //donot check clientWDA, there is only one client for WDA, if quectel-CM is killed by SIGKILL, i cannot get client ID for WDA again! + if (qmiclientId[QMUX_TYPE_WDS] == 0) /*|| (clientWDA == -1)*/ { + GobiNetDeInit(); + dbg_time("%s Failed to open %s, errno: %d (%s)", __func__, qcqmi, errno, strerror(errno)); + qmidevice_send_event_to_main(RIL_INDICATE_DEVICE_DISCONNECTED); + pthread_exit(NULL); + return NULL; + } + + qmidevice_send_event_to_main(RIL_INDICATE_DEVICE_CONNECTED); + + while (1) { + struct pollfd pollfds[16] = {{qmidevice_control_fd[1], POLLIN, 0}}; + int ne, ret, nevents = 1; + unsigned int i; + + for (i = 0; i < sizeof(qmiclientId)/sizeof(qmiclientId[0]); i++) + { + if (qmiclientId[i] != 0) + { + pollfds[nevents].fd = qmiclientId[i]; + pollfds[nevents].events = POLLIN; + pollfds[nevents].revents = 0; + nevents++; + } + } + + do { + ret = poll(pollfds, nevents, -1); + } while ((ret < 0) && (errno == EINTR)); + + if (ret <= 0) { + dbg_time("%s poll=%d, errno: %d (%s)", __func__, ret, errno, strerror(errno)); + break; + } + + for (ne = 0; ne < nevents; ne++) { + int fd = pollfds[ne].fd; + short revents = pollfds[ne].revents; + + if (revents & (POLLERR | POLLHUP | POLLNVAL)) { + dbg_time("%s poll err/hup/inval", __func__); + dbg_time("epoll fd = %d, events = 0x%04x", fd, revents); + if (fd == qmidevice_control_fd[1]) { + } else { + } + if (revents & (POLLERR | POLLHUP | POLLNVAL)) + goto __GobiNetThread_quit; + } + + if ((revents & POLLIN) == 0) + continue; + + if (fd == qmidevice_control_fd[1]) { + int triger_event; + if (read(fd, &triger_event, sizeof(triger_event)) == sizeof(triger_event)) { + //DBG("triger_event = 0x%x", triger_event); + switch (triger_event) { + case RIL_REQUEST_QUIT: + goto __GobiNetThread_quit; + break; + case SIGTERM: + case SIGHUP: + case SIGINT: + QmiThreadRecvQMI(NULL); + break; + default: + break; + } + } + continue; + } + + { + ssize_t nreads; + UCHAR QMIBuf[512]; + PQCQMIMSG pResponse = (PQCQMIMSG)QMIBuf; + + nreads = read(fd, &pResponse->MUXMsg, sizeof(QMIBuf) - sizeof(QCQMI_HDR)); + if (nreads <= 0) + { + dbg_time("%s read=%d errno: %d (%s)", __func__, (int)nreads, errno, strerror(errno)); + break; + } + + for (i = 0; i < sizeof(qmiclientId)/sizeof(qmiclientId[0]); i++) + { + if (qmiclientId[i] == fd) + { + pResponse->QMIHdr.QMIType = i; + } + } + + pResponse->QMIHdr.IFType = USB_CTL_MSG_TYPE_QMI; + pResponse->QMIHdr.Length = cpu_to_le16(nreads + sizeof(QCQMI_HDR) - 1); + pResponse->QMIHdr.CtlFlags = 0x00; + pResponse->QMIHdr.ClientId = fd & 0xFF; + + QmiThreadRecvQMI(pResponse); + } + } + } + +__GobiNetThread_quit: + GobiNetDeInit(); + qmidevice_send_event_to_main(RIL_INDICATE_DEVICE_DISCONNECTED); + QmiThreadRecvQMI(NULL); //main thread may pending on QmiThreadSendQMI() + dbg_time("%s exit", __func__); + pthread_exit(NULL); + return NULL; +} + +#else +int GobiNetSendQMI(PQCQMIMSG pRequest) {return -1;} +void * GobiNetThread(void *pData) {dbg_time("please set CONFIG_GOBINET"); return NULL;} +#endif diff --git a/root/package/link4all/quectel-CM/simcom-cm/MPQCTL.h b/root/package/link4all/quectel-CM/simcom-cm/MPQCTL.h new file mode 100644 index 00000000..679ba2fe --- /dev/null +++ b/root/package/link4all/quectel-CM/simcom-cm/MPQCTL.h @@ -0,0 +1,376 @@ +/*=========================================================================== + + M P Q C T L. H +DESCRIPTION: + + This module contains QMI QCTL module. + +INITIALIZATION AND SEQUENCING REQUIREMENTS: + +Copyright (C) 2011 by Qualcomm Technologies, Incorporated. All Rights Reserved. +===========================================================================*/ + +#ifndef MPQCTL_H +#define MPQCTL_H + +#include "MPQMI.h" + +#pragma pack(push, 1) + +// ================= QMICTL ================== + +// QMICTL Control Flags +#define QMICTL_CTL_FLAG_CMD 0x00 +#define QMICTL_CTL_FLAG_RSP 0x01 +#define QMICTL_CTL_FLAG_IND 0x02 + +#if 0 +typedef struct _QMICTL_TRANSACTION_ITEM +{ + LIST_ENTRY List; + UCHAR TransactionId; // QMICTL transaction id + PVOID Context; // Adapter or IocDev + PIRP Irp; +} QMICTL_TRANSACTION_ITEM, *PQMICTL_TRANSACTION_ITEM; +#endif + +typedef struct _QCQMICTL_MSG_HDR +{ + UCHAR CtlFlags; // 00-cmd, 01-rsp, 10-ind + UCHAR TransactionId; + USHORT QMICTLType; + USHORT Length; +} __attribute__ ((packed)) QCQMICTL_MSG_HDR, *PQCQMICTL_MSG_HDR; + +#define QCQMICTL_MSG_HDR_SIZE sizeof(QCQMICTL_MSG_HDR) + +typedef struct _QCQMICTL_MSG_HDR_RESP +{ + UCHAR CtlFlags; // 00-cmd, 01-rsp, 10-ind + UCHAR TransactionId; + USHORT QMICTLType; + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} __attribute__ ((packed)) QCQMICTL_MSG_HDR_RESP, *PQCQMICTL_MSG_HDR_RESP; + +typedef struct _QCQMICTL_MSG +{ + UCHAR CtlFlags; // 00-cmd, 01-rsp, 10-ind + UCHAR TransactionId; + USHORT QMICTLType; + USHORT Length; + UCHAR Payload; +} __attribute__ ((packed)) QCQMICTL_MSG, *PQCQMICTL_MSG; + +// TLV Header +typedef struct _QCQMICTL_TLV_HDR +{ + UCHAR TLVType; + USHORT TLVLength; +} __attribute__ ((packed)) QCQMICTL_TLV_HDR, *PQCQMICTL_TLV_HDR; + +#define QCQMICTL_TLV_HDR_SIZE sizeof(QCQMICTL_TLV_HDR) + +// QMICTL Type +#define QMICTL_SET_INSTANCE_ID_REQ 0x0020 +#define QMICTL_SET_INSTANCE_ID_RESP 0x0020 +#define QMICTL_GET_VERSION_REQ 0x0021 +#define QMICTL_GET_VERSION_RESP 0x0021 +#define QMICTL_GET_CLIENT_ID_REQ 0x0022 +#define QMICTL_GET_CLIENT_ID_RESP 0x0022 +#define QMICTL_RELEASE_CLIENT_ID_REQ 0x0023 +#define QMICTL_RELEASE_CLIENT_ID_RESP 0x0023 +#define QMICTL_REVOKE_CLIENT_ID_IND 0x0024 +#define QMICTL_INVALID_CLIENT_ID_IND 0x0025 +#define QMICTL_SET_DATA_FORMAT_REQ 0x0026 +#define QMICTL_SET_DATA_FORMAT_RESP 0x0026 +#define QMICTL_SYNC_REQ 0x0027 +#define QMICTL_SYNC_RESP 0x0027 +#define QMICTL_SYNC_IND 0x0027 + +#define QMICTL_FLAG_REQUEST 0x00 +#define QMICTL_FLAG_RESPONSE 0x01 +#define QMICTL_FLAG_INDICATION 0x02 + +// QMICTL Message Definitions + +typedef struct _QMICTL_SET_INSTANCE_ID_REQ_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_REQUEST + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_SET_INSTANCE_ID_REQ + USHORT Length; // 4 + UCHAR TLVType; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLVLength; // 1 + UCHAR Value; // Host-unique QMI instance for this device driver +} __attribute__ ((packed)) QMICTL_SET_INSTANCE_ID_REQ_MSG, *PQMICTL_SET_INSTANCE_ID_REQ_MSG; + +typedef struct _QMICTL_SET_INSTANCE_ID_RESP_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_RESPONSE + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_SET_INSTANCE_ID_RESP + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_RESULT_CODE + USHORT TLVLength; // 0x0004 + USHORT QMIResult; + USHORT QMIError; + UCHAR TLV2Type; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLV2Length; // 0x0002 + USHORT QMI_ID; // Upper byte is assigned by MSM, + // lower assigned by host +} __attribute__ ((packed)) QMICTL_SET_INSTANCE_ID_RESP_MSG, *PQMICTL_SET_INSTANCE_ID_RESP_MSG; + +typedef struct _QMICTL_GET_VERSION_REQ_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_REQUEST + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_GET_VERSION_REQ + USHORT Length; // 0 + UCHAR TLVType; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLVLength; // var + UCHAR QMUXTypes; // List of one byte QMUX_TYPE values + // 0xFF returns a list of versions for all + // QMUX_TYPEs implemented on the device +} __attribute__ ((packed)) QMICTL_GET_VERSION_REQ_MSG, *PQMICTL_GET_VERSION_REQ_MSG; + +typedef struct _QMUX_TYPE_VERSION_STRUCT +{ + UCHAR QMUXType; + USHORT MajorVersion; + USHORT MinorVersion; +} __attribute__ ((packed)) QMUX_TYPE_VERSION_STRUCT, *PQMUX_TYPE_VERSION_STRUCT; + +typedef struct _ADDENDUM_VERSION_PREAMBLE +{ + UCHAR LabelLength; + UCHAR Label; +} __attribute__ ((packed)) ADDENDUM_VERSION_PREAMBLE, *PADDENDUM_VERSION_PREAMBLE; + +#define QMICTL_GET_VERSION_RSP_TLV_TYPE_VERSION 0x01 +#define QMICTL_GET_VERSION_RSP_TLV_TYPE_ADD_VERSION 0x10 + +typedef struct _QMICTL_GET_VERSION_RESP_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_RESPONSE + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_GET_VERSION_RESP + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_RESULT_CODE + USHORT TLVLength; // 0x0004 + USHORT QMIResult; + USHORT QMIError; + UCHAR TLV2Type; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLV2Length; // var + UCHAR NumElements; // Num of QMUX_TYPE_VERSION_STRUCT + QMUX_TYPE_VERSION_STRUCT TypeVersion; +} __attribute__ ((packed)) QMICTL_GET_VERSION_RESP_MSG, *PQMICTL_GET_VERSION_RESP_MSG; + +typedef struct _QMICTL_GET_CLIENT_ID_REQ_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_REQUEST + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_GET_CLIENT_ID_REQ + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLVLength; // 1 + UCHAR QMIType; // QMUX type +} __attribute__ ((packed)) QMICTL_GET_CLIENT_ID_REQ_MSG, *PQMICTL_GET_CLIENT_ID_REQ_MSG; + +typedef struct _QMICTL_GET_CLIENT_ID_RESP_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_RESPONSE + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_GET_CLIENT_ID_RESP + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_RESULT_CODE + USHORT TLVLength; // 0x0004 + USHORT QMIResult; // result code + USHORT QMIError; // error code + UCHAR TLV2Type; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLV2Length; // 2 + UCHAR QMIType; + UCHAR ClientId; +} __attribute__ ((packed)) QMICTL_GET_CLIENT_ID_RESP_MSG, *PQMICTL_GET_CLIENT_ID_RESP_MSG; + +typedef struct _QMICTL_RELEASE_CLIENT_ID_REQ_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_REQUEST + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_RELEASE_CLIENT_ID_REQ + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLVLength; // 0x0002 + UCHAR QMIType; + UCHAR ClientId; +} __attribute__ ((packed)) QMICTL_RELEASE_CLIENT_ID_REQ_MSG, *PQMICTL_RELEASE_CLIENT_ID_REQ_MSG; + +typedef struct _QMICTL_RELEASE_CLIENT_ID_RESP_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_RESPONSE + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_RELEASE_CLIENT_ID_RESP + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_RESULT_CODE + USHORT TLVLength; // 0x0004 + USHORT QMIResult; // result code + USHORT QMIError; // error code + UCHAR TLV2Type; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLV2Length; // 2 + UCHAR QMIType; + UCHAR ClientId; +} __attribute__ ((packed)) QMICTL_RELEASE_CLIENT_ID_RESP_MSG, *PQMICTL_RELEASE_CLIENT_ID_RESP_MSG; + +typedef struct _QMICTL_REVOKE_CLIENT_ID_IND_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_INDICATION + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_REVOKE_CLIENT_ID_IND + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLVLength; // 0x0002 + UCHAR QMIType; + UCHAR ClientId; +} __attribute__ ((packed)) QMICTL_REVOKE_CLIENT_ID_IND_MSG, *PQMICTL_REVOKE_CLIENT_ID_IND_MSG; + +typedef struct _QMICTL_INVALID_CLIENT_ID_IND_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_INDICATION + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_REVOKE_CLIENT_ID_IND + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLVLength; // 0x0002 + UCHAR QMIType; + UCHAR ClientId; +} __attribute__ ((packed)) QMICTL_INVALID_CLIENT_ID_IND_MSG, *PQMICTL_INVALID_CLIENT_ID_IND_MSG; + +typedef struct _QMICTL_SET_DATA_FORMAT_REQ_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_REQUEST + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_SET_DATA_FORMAT_REQ + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLVLength; // 1 + UCHAR DataFormat; // 0-default; 1-QoS hdr present +} __attribute__ ((packed)) QMICTL_SET_DATA_FORMAT_REQ_MSG, *PQMICTL_SET_DATA_FORMAT_REQ_MSG; + +#ifdef QC_IP_MODE +#define SET_DATA_FORMAT_TLV_TYPE_LINK_PROTO 0x10 +#define SET_DATA_FORMAT_LINK_PROTO_ETH 0x0001 +#define SET_DATA_FORMAT_LINK_PROTO_IP 0x0002 +typedef struct _QMICTL_SET_DATA_FORMAT_TLV_LINK_PROT +{ + UCHAR TLVType; // Link-Layer Protocol + USHORT TLVLength; // 2 + USHORT LinkProt; // 0x1: ETH; 0x2: IP +} QMICTL_SET_DATA_FORMAT_TLV_LINK_PROT, *PQMICTL_SET_DATA_FORMAT_TLV_LINK_PROT; + +#ifdef QCMP_UL_TLP +#define SET_DATA_FORMAT_TLV_TYPE_UL_TLP 0x11 +typedef struct _QMICTL_SET_DATA_FORMAT_TLV_UL_TLP +{ + UCHAR TLVType; // 0x11, Uplink TLP Setting + USHORT TLVLength; // 1 + UCHAR UlTlpSetting; // 0x0: Disable; 0x01: Enable +} QMICTL_SET_DATA_FORMAT_TLV_UL_TLP, *PQMICTL_SET_DATA_FORMAT_TLV_UL_TLP; +#endif // QCMP_UL_TLP + +#ifdef QCMP_DL_TLP +#define SET_DATA_FORMAT_TLV_TYPE_DL_TLP 0x13 +typedef struct _QMICTL_SET_DATA_FORMAT_TLV_DL_TLP +{ + UCHAR TLVType; // 0x11, Uplink TLP Setting + USHORT TLVLength; // 1 + UCHAR DlTlpSetting; // 0x0: Disable; 0x01: Enable +} QMICTL_SET_DATA_FORMAT_TLV_DL_TLP, *PQMICTL_SET_DATA_FORMAT_TLV_DL_TLP; +#endif // QCMP_DL_TLP + +#endif // QC_IP_MODE + +#ifdef MP_QCQOS_ENABLED +#define SET_DATA_FORMAT_TLV_TYPE_QOS_SETTING 0x12 +typedef struct _QMICTL_SET_DATA_FORMAT_TLV_QOS_SETTING +{ + UCHAR TLVType; // 0x12, QoS setting + USHORT TLVLength; // 1 + UCHAR QosSetting; // 0x0: Disable; 0x01: Enable +} QMICTL_SET_DATA_FORMAT_TLV_QOS_SETTING, *PQMICTL_SET_DATA_FORMAT_TLV_QOS_SETTING; +#endif // MP_QCQOS_ENABLED + +typedef struct _QMICTL_SET_DATA_FORMAT_RESP_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_RESPONSE + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_SET_DATA_FORMAT_RESP + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_RESULT_CODE + USHORT TLVLength; // 0x0004 + USHORT QMIResult; // result code + USHORT QMIError; // error code +} __attribute__ ((packed)) QMICTL_SET_DATA_FORMAT_RESP_MSG, *PQMICTL_SET_DATA_FORMAT_RESP_MSG; + +typedef struct _QMICTL_SYNC_REQ_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_REQUEST + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_CTL_SYNC_REQ + USHORT Length; // 0 +} __attribute__ ((packed)) QMICTL_SYNC_REQ_MSG, *PQMICTL_SYNC_REQ_MSG; + +typedef struct _QMICTL_SYNC_RESP_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_RESPONSE + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_CTL_SYNC_RESP + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_RESULT_CODE + USHORT TLVLength; // 0x0004 + USHORT QMIResult; + USHORT QMIError; +} __attribute__ ((packed)) QMICTL_SYNC_RESP_MSG, *PQMICTL_SYNC_RESP_MSG; + +typedef struct _QMICTL_SYNC_IND_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_INDICATION + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_REVOKE_CLIENT_ID_IND + USHORT Length; +} __attribute__ ((packed)) QMICTL_SYNC_IND_MSG, *PQMICTL_SYNC_IND_MSG; + +typedef struct _QMICTL_MSG +{ + union + { + // Message Header + QCQMICTL_MSG_HDR QMICTLMsgHdr; + QCQMICTL_MSG_HDR_RESP QMICTLMsgHdrRsp; + + // QMICTL Message + QMICTL_SET_INSTANCE_ID_REQ_MSG SetInstanceIdReq; + QMICTL_SET_INSTANCE_ID_RESP_MSG SetInstanceIdRsp; + QMICTL_GET_VERSION_REQ_MSG GetVersionReq; + QMICTL_GET_VERSION_RESP_MSG GetVersionRsp; + QMICTL_GET_CLIENT_ID_REQ_MSG GetClientIdReq; + QMICTL_GET_CLIENT_ID_RESP_MSG GetClientIdRsp; + QMICTL_RELEASE_CLIENT_ID_REQ_MSG ReleaseClientIdReq; + QMICTL_RELEASE_CLIENT_ID_RESP_MSG ReleaseClientIdRsp; + QMICTL_REVOKE_CLIENT_ID_IND_MSG RevokeClientIdInd; + QMICTL_INVALID_CLIENT_ID_IND_MSG InvalidClientIdInd; + QMICTL_SET_DATA_FORMAT_REQ_MSG SetDataFormatReq; + QMICTL_SET_DATA_FORMAT_RESP_MSG SetDataFormatRsp; + QMICTL_SYNC_REQ_MSG SyncReq; + QMICTL_SYNC_RESP_MSG SyncRsp; + QMICTL_SYNC_IND_MSG SyncInd; + }; +} __attribute__ ((packed)) QMICTL_MSG, *PQMICTL_MSG; + +#endif // MPQCTL_H diff --git a/root/package/link4all/quectel-CM/simcom-cm/MPQMI.h b/root/package/link4all/quectel-CM/simcom-cm/MPQMI.h new file mode 100644 index 00000000..f45da466 --- /dev/null +++ b/root/package/link4all/quectel-CM/simcom-cm/MPQMI.h @@ -0,0 +1,221 @@ +/*=========================================================================== + + M P Q M I. H +DESCRIPTION: + + This module contains forward references to the QMI module. + +INITIALIZATION AND SEQUENCING REQUIREMENTS: + +Copyright (C) 2011 by Qualcomm Technologies, Incorporated. All Rights Reserved. +===========================================================================*/ +/*=========================================================================== + + EDIT HISTORY FOR FILE + $Header: //depot/QMI/win/qcdrivers/ndis/MPQMI.h#3 $ + +when who what, where, why +-------- --- ---------------------------------------------------------- +11/20/04 hg Initial version. +===========================================================================*/ + +#ifndef USBQMI_H +#define USBQMI_H + +typedef char CHAR; +typedef unsigned char UCHAR; +typedef unsigned short USHORT; +typedef int INT; +typedef unsigned int UINT; +typedef long LONG; +typedef unsigned int ULONG; +typedef unsigned long long ULONG64; +typedef char *PCHAR; +typedef unsigned char *PUCHAR; +typedef int *PINT; +typedef int BOOL; + +#define TRUE (1 == 1) +#define FALSE (1 != 1) + +#define QMICTL_SUPPORTED_MAJOR_VERSION 1 +#define QMICTL_SUPPORTED_MINOR_VERSION 0 + +#pragma pack(push, 1) + +// ========= USB Control Message ========== + +#define USB_CTL_MSG_TYPE_QMI 0x01 + +// USB Control Message +typedef struct _QCUSB_CTL_MSG_HDR +{ + UCHAR IFType; +} __attribute__ ((packed)) QCUSB_CTL_MSG_HDR, *PQCUSB_CTL_MSG_HDR; + +#define QCUSB_CTL_MSG_HDR_SIZE sizeof(QCUSB_CTL_MSG_HDR) + +typedef struct _QCUSB_CTL_MSG +{ + UCHAR IFType; + UCHAR Message; +} __attribute__ ((packed)) QCUSB_CTL_MSG, *PQCUSB_CTL_MSG; + +#define QCTLV_TYPE_REQUIRED_PARAMETER 0x01 +#define QCTLV_TYPE_RESULT_CODE 0x02 + +// ================= QMI ================== + +// Define QMI Type +typedef enum _QMI_SERVICE_TYPE +{ + QMUX_TYPE_CTL = 0x00, + QMUX_TYPE_WDS = 0x01, + QMUX_TYPE_DMS = 0x02, + QMUX_TYPE_NAS = 0x03, + QMUX_TYPE_QOS = 0x04, + QMUX_TYPE_WMS = 0x05, + QMUX_TYPE_PDS = 0x06, + QMUX_TYPE_UIM = 0x0B, + QMUX_TYPE_WDS_IPV6 = 0x11, + QMUX_TYPE_WDS_ADMIN = 0x1A, + QMUX_TYPE_MAX = 0xFF, + QMUX_TYPE_ALL = 0xFF +} QMI_SERVICE_TYPE; + +typedef enum _QMI_RESULT_CODE_TYPE +{ + QMI_RESULT_SUCCESS = 0x0000, + QMI_RESULT_FAILURE = 0x0001 +} QMI_RESULT_CODE_TYPE; + +typedef enum _QMI_ERROR_CODE_TYPE +{ + QMI_ERR_NONE = 0x0000, + QMI_ERR_INTERNAL = 0x0003, + QMI_ERR_CLIENT_IDS_EXHAUSTED = 0x0005, + QMI_ERR_DENIED = 0x0006, + QMI_ERR_INVALID_CLIENT_IDS = 0x0007, + QMI_ERR_NO_BATTERY = 0x0008, + QMI_ERR_INVALID_HANDLE = 0x0009, + QMI_ERR_INVALID_PROFILE = 0x000A, + QMI_ERR_STORAGE_EXCEEDED = 0x000B, + QMI_ERR_INCORRECT_PIN = 0x000C, + QMI_ERR_NO_NETWORK = 0x000D, + QMI_ERR_PIN_LOCKED = 0x000E, + QMI_ERR_OUT_OF_CALL = 0x000F, + QMI_ERR_NOT_PROVISIONED = 0x0010, + QMI_ERR_ARG_TOO_LONG = 0x0013, + QMI_ERR_DEVICE_IN_USE = 0x0017, + QMI_ERR_OP_DEVICE_UNSUPPORTED = 0x0019, + QMI_ERR_NO_EFFECT = 0x001A, + QMI_ERR_INVALID_ARG = 0x0020, + QMI_ERR_NO_MEMORY = 0x0021, + QMI_ERR_PIN_BLOCKED = 0x0023, + QMI_ERR_PIN_PERM_BLOCKED = 0x0024, + QMI_ERR_INVALID_INDEX = 0x0031, + QMI_ERR_NO_ENTRY = 0x0032, + QMI_ERR_EXTENDED_INTERNAL = 0x0051, + QMI_ERR_ACCESS_DENIED = 0x0052 +} QMI_ERROR_CODE_TYPE; + +#define QCQMI_CTL_FLAG_SERVICE 0x80 +#define QCQMI_CTL_FLAG_CTL_POINT 0x00 + +typedef struct _QCQMI_HDR +{ + UCHAR IFType; + USHORT Length; + UCHAR CtlFlags; // reserved + UCHAR QMIType; + UCHAR ClientId; +} __attribute__ ((packed)) QCQMI_HDR, *PQCQMI_HDR; + +#define QCQMI_HDR_SIZE (sizeof(QCQMI_HDR)-1) + +typedef struct _QCQMI +{ + UCHAR IFType; + USHORT Length; + UCHAR CtlFlags; // reserved + UCHAR QMIType; + UCHAR ClientId; + UCHAR SDU; +} __attribute__ ((packed)) QCQMI, *PQCQMI; + +typedef struct _QMI_SERVICE_VERSION +{ + USHORT Major; + USHORT Minor; + USHORT AddendumMajor; + USHORT AddendumMinor; +} __attribute__ ((packed)) QMI_SERVICE_VERSION, *PQMI_SERVICE_VERSION; + +// ================= QMUX ================== + +#define QMUX_MSG_OVERHEAD_BYTES 4 // Type(USHORT) Length(USHORT) -- header + +#define QMUX_BROADCAST_CID 0xFF + +typedef struct _QCQMUX_HDR +{ + UCHAR CtlFlags; // 0: single QMUX Msg; 1: + USHORT TransactionId; +} __attribute__ ((packed)) QCQMUX_HDR, *PQCQMUX_HDR; + +typedef struct _QCQMUX +{ + UCHAR CtlFlags; // 0: single QMUX Msg; 1: + USHORT TransactionId; + UCHAR Message; // Type(2), Length(2), Value +} __attribute__ ((packed)) QCQMUX, *PQCQMUX; + +#define QCQMUX_HDR_SIZE sizeof(QCQMUX_HDR) + +typedef struct _QCQMUX_MSG_HDR +{ + USHORT Type; + USHORT Length; +} __attribute__ ((packed)) QCQMUX_MSG_HDR, *PQCQMUX_MSG_HDR; + +#define QCQMUX_MSG_HDR_SIZE sizeof(QCQMUX_MSG_HDR) + +typedef struct _QCQMUX_MSG_HDR_RESP +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} __attribute__ ((packed)) QCQMUX_MSG_HDR_RESP, *PQCQMUX_MSG_HDR_RESP; + +typedef struct _QCQMUX_TLV +{ + UCHAR Type; + USHORT Length; + UCHAR Value; +} __attribute__ ((packed)) QCQMUX_TLV, *PQCQMUX_TLV; + +typedef struct _QMI_TLV_HDR +{ + UCHAR TLVType; + USHORT TLVLength; +} __attribute__ ((packed)) QMI_TLV_HDR, *PQMI_TLV_HDR; + +// QMUX Message Definitions -- QMI SDU +#define QMUX_CTL_FLAG_SINGLE_MSG 0x00 +#define QMUX_CTL_FLAG_COMPOUND_MSG 0x01 +#define QMUX_CTL_FLAG_TYPE_CMD 0x00 +#define QMUX_CTL_FLAG_TYPE_RSP 0x02 +#define QMUX_CTL_FLAG_TYPE_IND 0x04 +#define QMUX_CTL_FLAG_MASK_COMPOUND 0x01 +#define QMUX_CTL_FLAG_MASK_TYPE 0x06 // 00-cmd, 01-rsp, 10-ind + +#pragma pack(pop) + +#endif // USBQMI_H diff --git a/root/package/link4all/quectel-CM/simcom-cm/MPQMUX.c b/root/package/link4all/quectel-CM/simcom-cm/MPQMUX.c new file mode 100644 index 00000000..178208ac --- /dev/null +++ b/root/package/link4all/quectel-CM/simcom-cm/MPQMUX.c @@ -0,0 +1,424 @@ +#include "QMIThread.h" +static char line[1024]; +static pthread_mutex_t dumpQMIMutex = PTHREAD_MUTEX_INITIALIZER; +#undef dbg +#define dbg( format, arg... ) do {if (strlen(line) < sizeof(line)) snprintf(&line[strlen(line)], sizeof(line) - strlen(line), format, ## arg);} while (0) + +PQMI_TLV_HDR GetTLV (PQCQMUX_MSG_HDR pQMUXMsgHdr, int TLVType); + +typedef struct { + UINT type; + const char *name; +} QMI_NAME_T; + +#define qmi_name_item(type) {type, #type} + +static const QMI_NAME_T qmi_IFType[] = { +{USB_CTL_MSG_TYPE_QMI, "USB_CTL_MSG_TYPE_QMI"}, +}; + +static const QMI_NAME_T qmi_CtlFlags[] = { +qmi_name_item(QMICTL_CTL_FLAG_CMD), +qmi_name_item(QCQMI_CTL_FLAG_SERVICE), +}; + +static const QMI_NAME_T qmi_QMIType[] = { +qmi_name_item(QMUX_TYPE_CTL), +qmi_name_item(QMUX_TYPE_WDS), +qmi_name_item(QMUX_TYPE_DMS), +qmi_name_item(QMUX_TYPE_NAS), +qmi_name_item(QMUX_TYPE_QOS), +qmi_name_item(QMUX_TYPE_WMS), +qmi_name_item(QMUX_TYPE_PDS), +qmi_name_item(QMUX_TYPE_WDS_ADMIN), +}; + +static const QMI_NAME_T qmi_ctl_CtlFlags[] = { +qmi_name_item(QMICTL_FLAG_REQUEST), +qmi_name_item(QMICTL_FLAG_RESPONSE), +qmi_name_item(QMICTL_FLAG_INDICATION), +}; + +static const QMI_NAME_T qmux_ctl_QMICTLType[] = { +// QMICTL Type +qmi_name_item(QMICTL_SET_INSTANCE_ID_REQ), // 0x0020 +qmi_name_item(QMICTL_SET_INSTANCE_ID_RESP), // 0x0020 +qmi_name_item(QMICTL_GET_VERSION_REQ), // 0x0021 +qmi_name_item(QMICTL_GET_VERSION_RESP), // 0x0021 +qmi_name_item(QMICTL_GET_CLIENT_ID_REQ), // 0x0022 +qmi_name_item(QMICTL_GET_CLIENT_ID_RESP), // 0x0022 +qmi_name_item(QMICTL_RELEASE_CLIENT_ID_REQ), // 0x0023 +qmi_name_item(QMICTL_RELEASE_CLIENT_ID_RESP), // 0x0023 +qmi_name_item(QMICTL_REVOKE_CLIENT_ID_IND), // 0x0024 +qmi_name_item(QMICTL_INVALID_CLIENT_ID_IND), // 0x0025 +qmi_name_item(QMICTL_SET_DATA_FORMAT_REQ), // 0x0026 +qmi_name_item(QMICTL_SET_DATA_FORMAT_RESP), // 0x0026 +qmi_name_item(QMICTL_SYNC_REQ), // 0x0027 +qmi_name_item(QMICTL_SYNC_RESP), // 0x0027 +qmi_name_item(QMICTL_SYNC_IND), // 0x0027 +}; + +static const QMI_NAME_T qmux_CtlFlags[] = { +qmi_name_item(QMUX_CTL_FLAG_TYPE_CMD), +qmi_name_item(QMUX_CTL_FLAG_TYPE_RSP), +qmi_name_item(QMUX_CTL_FLAG_TYPE_IND), +}; + + +static const QMI_NAME_T qmux_wds_Type[] = { +qmi_name_item(QMIWDS_SET_EVENT_REPORT_REQ), // 0x0001 +qmi_name_item(QMIWDS_SET_EVENT_REPORT_RESP), // 0x0001 +qmi_name_item(QMIWDS_EVENT_REPORT_IND), // 0x0001 +qmi_name_item(QMIWDS_START_NETWORK_INTERFACE_REQ), // 0x0020 +qmi_name_item(QMIWDS_START_NETWORK_INTERFACE_RESP), // 0x0020 +qmi_name_item(QMIWDS_STOP_NETWORK_INTERFACE_REQ), // 0x0021 +qmi_name_item(QMIWDS_STOP_NETWORK_INTERFACE_RESP), // 0x0021 +qmi_name_item(QMIWDS_GET_PKT_SRVC_STATUS_REQ), // 0x0022 +qmi_name_item(QMIWDS_GET_PKT_SRVC_STATUS_RESP), // 0x0022 +qmi_name_item(QMIWDS_GET_PKT_SRVC_STATUS_IND), // 0x0022 +qmi_name_item(QMIWDS_GET_CURRENT_CHANNEL_RATE_REQ), // 0x0023 +qmi_name_item(QMIWDS_GET_CURRENT_CHANNEL_RATE_RESP), // 0x0023 +qmi_name_item(QMIWDS_GET_PKT_STATISTICS_REQ), // 0x0024 +qmi_name_item(QMIWDS_GET_PKT_STATISTICS_RESP), // 0x0024 +qmi_name_item(QMIWDS_MODIFY_PROFILE_SETTINGS_REQ), // 0x0028 +qmi_name_item(QMIWDS_MODIFY_PROFILE_SETTINGS_RESP), // 0x0028 +qmi_name_item(QMIWDS_GET_PROFILE_SETTINGS_REQ), // 0x002B +qmi_name_item(QMIWDS_GET_PROFILE_SETTINGS_RESP), // 0x002BD +qmi_name_item(QMIWDS_GET_DEFAULT_SETTINGS_REQ), // 0x002C +qmi_name_item(QMIWDS_GET_DEFAULT_SETTINGS_RESP), // 0x002C +qmi_name_item(QMIWDS_GET_RUNTIME_SETTINGS_REQ), // 0x002D +qmi_name_item(QMIWDS_GET_RUNTIME_SETTINGS_RESP), // 0x002D +qmi_name_item(QMIWDS_GET_MIP_MODE_REQ), // 0x002F +qmi_name_item(QMIWDS_GET_MIP_MODE_RESP), // 0x002F +qmi_name_item(QMIWDS_GET_DATA_BEARER_REQ), // 0x0037 +qmi_name_item(QMIWDS_GET_DATA_BEARER_RESP), // 0x0037 +qmi_name_item(QMIWDS_DUN_CALL_INFO_REQ), // 0x0038 +qmi_name_item(QMIWDS_DUN_CALL_INFO_RESP), // 0x0038 +qmi_name_item(QMIWDS_DUN_CALL_INFO_IND), // 0x0038 +qmi_name_item(QMIWDS_SET_CLIENT_IP_FAMILY_PREF_REQ), // 0x004D +qmi_name_item(QMIWDS_SET_CLIENT_IP_FAMILY_PREF_RESP), // 0x004D +qmi_name_item(QMIWDS_SET_AUTO_CONNECT_REQ), // 0x0051 +qmi_name_item(QMIWDS_SET_AUTO_CONNECT_RESP), // 0x0051 +qmi_name_item(QMIWDS_BIND_MUX_DATA_PORT_REQ), // 0x00A2 +qmi_name_item(QMIWDS_BIND_MUX_DATA_PORT_RESP), // 0x00A2 +}; + +static const QMI_NAME_T qmux_dms_Type[] = { +// ======================= DMS ============================== +qmi_name_item(QMIDMS_SET_EVENT_REPORT_REQ), // 0x0001 +qmi_name_item(QMIDMS_SET_EVENT_REPORT_RESP), // 0x0001 +qmi_name_item(QMIDMS_EVENT_REPORT_IND), // 0x0001 +qmi_name_item(QMIDMS_GET_DEVICE_CAP_REQ), // 0x0020 +qmi_name_item(QMIDMS_GET_DEVICE_CAP_RESP), // 0x0020 +qmi_name_item(QMIDMS_GET_DEVICE_MFR_REQ), // 0x0021 +qmi_name_item(QMIDMS_GET_DEVICE_MFR_RESP), // 0x0021 +qmi_name_item(QMIDMS_GET_DEVICE_MODEL_ID_REQ), // 0x0022 +qmi_name_item(QMIDMS_GET_DEVICE_MODEL_ID_RESP), // 0x0022 +qmi_name_item(QMIDMS_GET_DEVICE_REV_ID_REQ), // 0x0023 +qmi_name_item(QMIDMS_GET_DEVICE_REV_ID_RESP), // 0x0023 +qmi_name_item(QMIDMS_GET_MSISDN_REQ), // 0x0024 +qmi_name_item(QMIDMS_GET_MSISDN_RESP), // 0x0024 +qmi_name_item(QMIDMS_GET_DEVICE_SERIAL_NUMBERS_REQ), // 0x0025 +qmi_name_item(QMIDMS_GET_DEVICE_SERIAL_NUMBERS_RESP), // 0x0025 +qmi_name_item(QMIDMS_UIM_SET_PIN_PROTECTION_REQ), // 0x0027 +qmi_name_item(QMIDMS_UIM_SET_PIN_PROTECTION_RESP), // 0x0027 +qmi_name_item(QMIDMS_UIM_VERIFY_PIN_REQ), // 0x0028 +qmi_name_item(QMIDMS_UIM_VERIFY_PIN_RESP), // 0x0028 +qmi_name_item(QMIDMS_UIM_UNBLOCK_PIN_REQ), // 0x0029 +qmi_name_item(QMIDMS_UIM_UNBLOCK_PIN_RESP), // 0x0029 +qmi_name_item(QMIDMS_UIM_CHANGE_PIN_REQ), // 0x002A +qmi_name_item(QMIDMS_UIM_CHANGE_PIN_RESP), // 0x002A +qmi_name_item(QMIDMS_UIM_GET_PIN_STATUS_REQ), // 0x002B +qmi_name_item(QMIDMS_UIM_GET_PIN_STATUS_RESP), // 0x002B +qmi_name_item(QMIDMS_GET_DEVICE_HARDWARE_REV_REQ), // 0x002C +qmi_name_item(QMIDMS_GET_DEVICE_HARDWARE_REV_RESP), // 0x002C +qmi_name_item(QMIDMS_GET_OPERATING_MODE_REQ), // 0x002D +qmi_name_item(QMIDMS_GET_OPERATING_MODE_RESP), // 0x002D +qmi_name_item(QMIDMS_SET_OPERATING_MODE_REQ), // 0x002E +qmi_name_item(QMIDMS_SET_OPERATING_MODE_RESP), // 0x002E +qmi_name_item(QMIDMS_GET_ACTIVATED_STATUS_REQ), // 0x0031 +qmi_name_item(QMIDMS_GET_ACTIVATED_STATUS_RESP), // 0x0031 +qmi_name_item(QMIDMS_ACTIVATE_AUTOMATIC_REQ), // 0x0032 +qmi_name_item(QMIDMS_ACTIVATE_AUTOMATIC_RESP), // 0x0032 +qmi_name_item(QMIDMS_ACTIVATE_MANUAL_REQ), // 0x0033 +qmi_name_item(QMIDMS_ACTIVATE_MANUAL_RESP), // 0x0033 +qmi_name_item(QMIDMS_UIM_GET_ICCID_REQ), // 0x003C +qmi_name_item(QMIDMS_UIM_GET_ICCID_RESP), // 0x003C +qmi_name_item(QMIDMS_UIM_GET_CK_STATUS_REQ), // 0x0040 +qmi_name_item(QMIDMS_UIM_GET_CK_STATUS_RESP), // 0x0040 +qmi_name_item(QMIDMS_UIM_SET_CK_PROTECTION_REQ), // 0x0041 +qmi_name_item(QMIDMS_UIM_SET_CK_PROTECTION_RESP), // 0x0041 +qmi_name_item(QMIDMS_UIM_UNBLOCK_CK_REQ), // 0x0042 +qmi_name_item(QMIDMS_UIM_UNBLOCK_CK_RESP), // 0x0042 +qmi_name_item(QMIDMS_UIM_GET_IMSI_REQ), // 0x0043 +qmi_name_item(QMIDMS_UIM_GET_IMSI_RESP), // 0x0043 +qmi_name_item(QMIDMS_UIM_GET_STATE_REQ), // 0x0044 +qmi_name_item(QMIDMS_UIM_GET_STATE_RESP), // 0x0044 +qmi_name_item(QMIDMS_GET_BAND_CAP_REQ), // 0x0045 +qmi_name_item(QMIDMS_GET_BAND_CAP_RESP), // 0x0045 +}; + +static const QMI_NAME_T qmux_nas_Type[] = { +// ======================= NAS ============================== +qmi_name_item(QMINAS_SET_EVENT_REPORT_REQ), // 0x0002 +qmi_name_item(QMINAS_SET_EVENT_REPORT_RESP), // 0x0002 +qmi_name_item(QMINAS_EVENT_REPORT_IND), // 0x0002 +qmi_name_item(QMINAS_GET_SIGNAL_STRENGTH_REQ), // 0x0020 +qmi_name_item(QMINAS_GET_SIGNAL_STRENGTH_RESP), // 0x0020 +qmi_name_item(QMINAS_PERFORM_NETWORK_SCAN_REQ), // 0x0021 +qmi_name_item(QMINAS_PERFORM_NETWORK_SCAN_RESP), // 0x0021 +qmi_name_item(QMINAS_INITIATE_NW_REGISTER_REQ), // 0x0022 +qmi_name_item(QMINAS_INITIATE_NW_REGISTER_RESP), // 0x0022 +qmi_name_item(QMINAS_INITIATE_ATTACH_REQ), // 0x0023 +qmi_name_item(QMINAS_INITIATE_ATTACH_RESP), // 0x0023 +qmi_name_item(QMINAS_GET_SERVING_SYSTEM_REQ), // 0x0024 +qmi_name_item(QMINAS_GET_SERVING_SYSTEM_RESP), // 0x0024 +qmi_name_item(QMINAS_SERVING_SYSTEM_IND), // 0x0024 +qmi_name_item(QMINAS_GET_HOME_NETWORK_REQ), // 0x0025 +qmi_name_item(QMINAS_GET_HOME_NETWORK_RESP), // 0x0025 +qmi_name_item(QMINAS_GET_PREFERRED_NETWORK_REQ), // 0x0026 +qmi_name_item(QMINAS_GET_PREFERRED_NETWORK_RESP), // 0x0026 +qmi_name_item(QMINAS_SET_PREFERRED_NETWORK_REQ), // 0x0027 +qmi_name_item(QMINAS_SET_PREFERRED_NETWORK_RESP), // 0x0027 +qmi_name_item(QMINAS_GET_FORBIDDEN_NETWORK_REQ), // 0x0028 +qmi_name_item(QMINAS_GET_FORBIDDEN_NETWORK_RESP), // 0x0028 +qmi_name_item(QMINAS_SET_FORBIDDEN_NETWORK_REQ), // 0x0029 +qmi_name_item(QMINAS_SET_FORBIDDEN_NETWORK_RESP), // 0x0029 +qmi_name_item(QMINAS_SET_TECHNOLOGY_PREF_REQ), // 0x002A +qmi_name_item(QMINAS_SET_TECHNOLOGY_PREF_RESP), // 0x002A +qmi_name_item(QMINAS_GET_RF_BAND_INFO_REQ), // 0x0031 +qmi_name_item(QMINAS_GET_RF_BAND_INFO_RESP), // 0x0031 +qmi_name_item(QMINAS_GET_PLMN_NAME_REQ), // 0x0044 +qmi_name_item(QMINAS_GET_PLMN_NAME_RESP), // 0x0044 +qmi_name_item(QUECTEL_PACKET_TRANSFER_START_IND), // 0X100 +qmi_name_item(QUECTEL_PACKET_TRANSFER_END_IND), // 0X101 +qmi_name_item(QMINAS_GET_SYS_INFO_REQ), // 0x004D +qmi_name_item(QMINAS_GET_SYS_INFO_RESP), // 0x004D +qmi_name_item(QMINAS_SYS_INFO_IND), // 0x004D +}; + +static const QMI_NAME_T qmux_wms_Type[] = { +// ======================= WMS ============================== +qmi_name_item(QMIWMS_SET_EVENT_REPORT_REQ), // 0x0001 +qmi_name_item(QMIWMS_SET_EVENT_REPORT_RESP), // 0x0001 +qmi_name_item(QMIWMS_EVENT_REPORT_IND), // 0x0001 +qmi_name_item(QMIWMS_RAW_SEND_REQ), // 0x0020 +qmi_name_item(QMIWMS_RAW_SEND_RESP), // 0x0020 +qmi_name_item(QMIWMS_RAW_WRITE_REQ), // 0x0021 +qmi_name_item(QMIWMS_RAW_WRITE_RESP), // 0x0021 +qmi_name_item(QMIWMS_RAW_READ_REQ), // 0x0022 +qmi_name_item(QMIWMS_RAW_READ_RESP), // 0x0022 +qmi_name_item(QMIWMS_MODIFY_TAG_REQ), // 0x0023 +qmi_name_item(QMIWMS_MODIFY_TAG_RESP), // 0x0023 +qmi_name_item(QMIWMS_DELETE_REQ), // 0x0024 +qmi_name_item(QMIWMS_DELETE_RESP), // 0x0024 +qmi_name_item(QMIWMS_GET_MESSAGE_PROTOCOL_REQ), // 0x0030 +qmi_name_item(QMIWMS_GET_MESSAGE_PROTOCOL_RESP), // 0x0030 +qmi_name_item(QMIWMS_LIST_MESSAGES_REQ), // 0x0031 +qmi_name_item(QMIWMS_LIST_MESSAGES_RESP), // 0x0031 +qmi_name_item(QMIWMS_GET_SMSC_ADDRESS_REQ), // 0x0034 +qmi_name_item(QMIWMS_GET_SMSC_ADDRESS_RESP), // 0x0034 +qmi_name_item(QMIWMS_SET_SMSC_ADDRESS_REQ), // 0x0035 +qmi_name_item(QMIWMS_SET_SMSC_ADDRESS_RESP), // 0x0035 +qmi_name_item(QMIWMS_GET_STORE_MAX_SIZE_REQ), // 0x0036 +qmi_name_item(QMIWMS_GET_STORE_MAX_SIZE_RESP), // 0x0036 +}; + +static const QMI_NAME_T qmux_wds_admin_Type[] = { +qmi_name_item(QMIWDS_ADMIN_SET_DATA_FORMAT_REQ), // 0x0020 +qmi_name_item(QMIWDS_ADMIN_SET_DATA_FORMAT_RESP), // 0x0020 +qmi_name_item(QMIWDS_ADMIN_GET_DATA_FORMAT_REQ), // 0x0021 +qmi_name_item(QMIWDS_ADMIN_GET_DATA_FORMAT_RESP), // 0x0021 +qmi_name_item(QMIWDS_ADMIN_SET_QMAP_SETTINGS_REQ), // 0x002B +qmi_name_item(QMIWDS_ADMIN_SET_QMAP_SETTINGS_RESP), // 0x002B +qmi_name_item(QMIWDS_ADMIN_GET_QMAP_SETTINGS_REQ), // 0x002C +qmi_name_item(QMIWDS_ADMIN_GET_QMAP_SETTINGS_RESP), // 0x002C +}; + +static const QMI_NAME_T qmux_uim_Type[] = { +qmi_name_item( QMIUIM_READ_TRANSPARENT_REQ), // 0x0020 +qmi_name_item( QMIUIM_READ_TRANSPARENT_RESP), // 0x0020 +qmi_name_item( QMIUIM_READ_TRANSPARENT_IND), // 0x0020 +qmi_name_item( QMIUIM_READ_RECORD_REQ), // 0x0021 +qmi_name_item( QMIUIM_READ_RECORD_RESP), // 0x0021 +qmi_name_item( QMIUIM_READ_RECORD_IND), // 0x0021 +qmi_name_item( QMIUIM_WRITE_TRANSPARENT_REQ), // 0x0022 +qmi_name_item( QMIUIM_WRITE_TRANSPARENT_RESP), // 0x0022 +qmi_name_item( QMIUIM_WRITE_TRANSPARENT_IND), // 0x0022 +qmi_name_item( QMIUIM_WRITE_RECORD_REQ), // 0x0023 +qmi_name_item( QMIUIM_WRITE_RECORD_RESP), // 0x0023 +qmi_name_item( QMIUIM_WRITE_RECORD_IND), // 0x0023 +qmi_name_item( QMIUIM_SET_PIN_PROTECTION_REQ), // 0x0025 +qmi_name_item( QMIUIM_SET_PIN_PROTECTION_RESP), // 0x0025 +qmi_name_item( QMIUIM_SET_PIN_PROTECTION_IND), // 0x0025 +qmi_name_item( QMIUIM_VERIFY_PIN_REQ), // 0x0026 +qmi_name_item( QMIUIM_VERIFY_PIN_RESP), // 0x0026 +qmi_name_item( QMIUIM_VERIFY_PIN_IND), // 0x0026 +qmi_name_item( QMIUIM_UNBLOCK_PIN_REQ), // 0x0027 +qmi_name_item( QMIUIM_UNBLOCK_PIN_RESP), // 0x0027 +qmi_name_item( QMIUIM_UNBLOCK_PIN_IND), // 0x0027 +qmi_name_item( QMIUIM_CHANGE_PIN_REQ), // 0x0028 +qmi_name_item( QMIUIM_CHANGE_PIN_RESP), // 0x0028 +qmi_name_item( QMIUIM_CHANGE_PIN_IND), // 0x0028 +qmi_name_item( QMIUIM_DEPERSONALIZATION_REQ), // 0x0029 +qmi_name_item( QMIUIM_DEPERSONALIZATION_RESP), // 0x0029 +qmi_name_item( QMIUIM_EVENT_REG_REQ), // 0x002E +qmi_name_item( QMIUIM_EVENT_REG_RESP), // 0x002E +qmi_name_item( QMIUIM_GET_CARD_STATUS_REQ), // 0x002F +qmi_name_item( QMIUIM_GET_CARD_STATUS_RESP), // 0x002F +qmi_name_item( QMIUIM_STATUS_CHANGE_IND), // 0x0032 +}; + +static const char * qmi_name_get(const QMI_NAME_T *table, size_t size, int type, const char *tag) { + static char unknow[40]; + size_t i; + + if (qmux_CtlFlags == table) { + if (!strcmp(tag, "_REQ")) + tag = "_CMD"; + else if (!strcmp(tag, "_RESP")) + tag = "_RSP"; + } + + for (i = 0; i < size; i++) { + if (table[i].type == (UINT)type) { + if (!tag || (strstr(table[i].name, tag))) + return table[i].name; + } + } + sprintf(unknow, "unknow_%x", type); + return unknow; +} + +#define QMI_NAME(table, type) qmi_name_get(table, sizeof(table) / sizeof(table[0]), type, 0) +#define QMUX_NAME(table, type, tag) qmi_name_get(table, sizeof(table) / sizeof(table[0]), type, tag) + +void dump_tlv(PQCQMUX_MSG_HDR pQMUXMsgHdr) { + int TLVFind = 0; + int i; + //dbg("QCQMUX_TLV-----------------------------------\n"); + //dbg("{Type,\tLength,\tValue}\n"); + + while (1) { + PQMI_TLV_HDR TLVHdr = GetTLV(pQMUXMsgHdr, 0x1000 + (++TLVFind)); + if (TLVHdr == NULL) + break; + + //if ((TLVHdr->TLVType == 0x02) && ((USHORT *)(TLVHdr+1))[0]) + { + dbg("{%02x,\t%04x,\t", TLVHdr->TLVType, le16_to_cpu(TLVHdr->TLVLength)); + for (i = 0; i < le16_to_cpu(TLVHdr->TLVLength); i++) { + dbg("%02x ", ((UCHAR *)(TLVHdr+1))[i]); + } + dbg("}\n"); + } + } // while +} + +void dump_ctl(PQCQMICTL_MSG_HDR CTLHdr) { + const char *tag; + + //dbg("QCQMICTL_MSG--------------------------------------------\n"); + //dbg("CtlFlags: %02x\t\t%s\n", CTLHdr->CtlFlags, QMI_NAME(qmi_ctl_CtlFlags, CTLHdr->CtlFlags)); + dbg("TransactionId: %02x\n", CTLHdr->TransactionId); + switch (CTLHdr->CtlFlags) { + case QMICTL_FLAG_REQUEST: tag = "_REQ"; break; + case QMICTL_FLAG_RESPONSE: tag = "_RESP"; break; + case QMICTL_FLAG_INDICATION: tag = "_IND"; break; + default: tag = 0; break; + } + dbg("QMICTLType: %04x\t%s\n", le16_to_cpu(CTLHdr->QMICTLType), + QMUX_NAME(qmux_ctl_QMICTLType, le16_to_cpu(CTLHdr->QMICTLType), tag)); + dbg("Length: %04x\n", le16_to_cpu(CTLHdr->Length)); + + dump_tlv((PQCQMUX_MSG_HDR)(&CTLHdr->QMICTLType)); +} + +int dump_qmux(QMI_SERVICE_TYPE serviceType, PQCQMUX_HDR QMUXHdr) { + PQCQMUX_MSG_HDR QMUXMsgHdr = (PQCQMUX_MSG_HDR) (QMUXHdr + 1); + CHAR *tag; + + //dbg("QCQMUX--------------------------------------------\n"); + switch (QMUXHdr->CtlFlags&QMUX_CTL_FLAG_MASK_TYPE) { + case QMUX_CTL_FLAG_TYPE_CMD: tag = "_REQ"; break; + case QMUX_CTL_FLAG_TYPE_RSP: tag = "_RESP"; break; + case QMUX_CTL_FLAG_TYPE_IND: tag = "_IND"; break; + default: tag = 0; break; + } + //dbg("CtlFlags: %02x\t\t%s\n", QMUXHdr->CtlFlags, QMUX_NAME(qmux_CtlFlags, QMUXHdr->CtlFlags, tag)); + dbg("TransactionId: %04x\n", le16_to_cpu(QMUXHdr->TransactionId)); + + //dbg("QCQMUX_MSG_HDR-----------------------------------\n"); + switch (serviceType) { + case QMUX_TYPE_DMS: + dbg("Type: %04x\t%s\n", le16_to_cpu(QMUXMsgHdr->Type), + QMUX_NAME(qmux_dms_Type, le16_to_cpu(QMUXMsgHdr->Type), tag)); + break; + case QMUX_TYPE_NAS: + dbg("Type: %04x\t%s\n", le16_to_cpu(QMUXMsgHdr->Type), + QMUX_NAME(qmux_nas_Type, le16_to_cpu(QMUXMsgHdr->Type), tag)); + break; + case QMUX_TYPE_WDS: + dbg("Type: %04x\t%s\n", le16_to_cpu(QMUXMsgHdr->Type), + QMUX_NAME(qmux_wds_Type, le16_to_cpu(QMUXMsgHdr->Type), tag)); + break; + case QMUX_TYPE_WMS: + dbg("Type: %04x\t%s\n", le16_to_cpu(QMUXMsgHdr->Type), + QMUX_NAME(qmux_wms_Type, le16_to_cpu(QMUXMsgHdr->Type), tag)); + break; + case QMUX_TYPE_WDS_ADMIN: + dbg("Type: %04x\t%s\n", le16_to_cpu(QMUXMsgHdr->Type), + QMUX_NAME(qmux_wds_admin_Type, le16_to_cpu(QMUXMsgHdr->Type), tag)); + break; + case QMUX_TYPE_UIM: + dbg("Type: %04x\t%s\n", le16_to_cpu(QMUXMsgHdr->Type), + QMUX_NAME(qmux_uim_Type, le16_to_cpu(QMUXMsgHdr->Type), tag)); + break; + case QMUX_TYPE_PDS: + case QMUX_TYPE_QOS: + case QMUX_TYPE_CTL: + default: + dbg("Type: %04x\t%s\n", le16_to_cpu(QMUXMsgHdr->Type), "PDS/QOS/CTL/unknown!"); + break; + } + dbg("Length: %04x\n", le16_to_cpu(QMUXMsgHdr->Length)); + + dump_tlv(QMUXMsgHdr); + + return 0; +} + +void dump_qmi(void *dataBuffer, int dataLen) +{ + PQCQMI_HDR QMIHdr = (PQCQMI_HDR)dataBuffer; + PQCQMUX_HDR QMUXHdr = (PQCQMUX_HDR) (QMIHdr + 1); + PQCQMICTL_MSG_HDR CTLHdr = (PQCQMICTL_MSG_HDR) (QMIHdr + 1); + + int i; + + if (!debug_qmi) + return; + + pthread_mutex_lock(&dumpQMIMutex); + line[0] = 0; + for (i = 0; i < dataLen; i++) { + dbg("%02x ", ((unsigned char *)dataBuffer)[i]); + } + dbg_time("%s", line); + line[0] = 0; + + //dbg("QCQMI_HDR-----------------------------------------"); + //dbg("IFType: %02x\t\t%s", QMIHdr->IFType, QMI_NAME(qmi_IFType, QMIHdr->IFType)); + //dbg("Length: %04x", le16_to_cpu(QMIHdr->Length)); + //dbg("CtlFlags: %02x\t\t%s", QMIHdr->CtlFlags, QMI_NAME(qmi_CtlFlags, QMIHdr->CtlFlags)); + //dbg("QMIType: %02x\t\t%s", QMIHdr->QMIType, QMI_NAME(qmi_QMIType, QMIHdr->QMIType)); + //dbg("ClientId: %02x", QMIHdr->ClientId); + + if (QMIHdr->QMIType == QMUX_TYPE_CTL) { + dump_ctl(CTLHdr); + } else { + dump_qmux(QMIHdr->QMIType, QMUXHdr); + } + dbg_time("%s", line); + pthread_mutex_unlock(&dumpQMIMutex); +} diff --git a/root/package/link4all/quectel-CM/simcom-cm/MPQMUX.h b/root/package/link4all/quectel-CM/simcom-cm/MPQMUX.h new file mode 100644 index 00000000..109bb31b --- /dev/null +++ b/root/package/link4all/quectel-CM/simcom-cm/MPQMUX.h @@ -0,0 +1,3300 @@ +/*=========================================================================== + + M P Q M U X. H +DESCRIPTION: + + This file provides support for QMUX. + +INITIALIZATION AND SEQUENCING REQUIREMENTS: + +Copyright (C) 2011 by Qualcomm Technologies, Incorporated. All Rights Reserved. +===========================================================================*/ + +#ifndef MPQMUX_H +#define MPQMUX_H + +#include "MPQMI.h" + +#pragma pack(push, 1) + +#define QMIWDS_SET_EVENT_REPORT_REQ 0x0001 +#define QMIWDS_SET_EVENT_REPORT_RESP 0x0001 +#define QMIWDS_EVENT_REPORT_IND 0x0001 +#define QMIWDS_START_NETWORK_INTERFACE_REQ 0x0020 +#define QMIWDS_START_NETWORK_INTERFACE_RESP 0x0020 +#define QMIWDS_STOP_NETWORK_INTERFACE_REQ 0x0021 +#define QMIWDS_STOP_NETWORK_INTERFACE_RESP 0x0021 +#define QMIWDS_GET_PKT_SRVC_STATUS_REQ 0x0022 +#define QMIWDS_GET_PKT_SRVC_STATUS_RESP 0x0022 +#define QMIWDS_GET_PKT_SRVC_STATUS_IND 0x0022 +#define QMIWDS_GET_CURRENT_CHANNEL_RATE_REQ 0x0023 +#define QMIWDS_GET_CURRENT_CHANNEL_RATE_RESP 0x0023 +#define QMIWDS_GET_PKT_STATISTICS_REQ 0x0024 +#define QMIWDS_GET_PKT_STATISTICS_RESP 0x0024 +#define QMIWDS_MODIFY_PROFILE_SETTINGS_REQ 0x0028 +#define QMIWDS_MODIFY_PROFILE_SETTINGS_RESP 0x0028 +#define QMIWDS_GET_PROFILE_SETTINGS_REQ 0x002B +#define QMIWDS_GET_PROFILE_SETTINGS_RESP 0x002B +#define QMIWDS_GET_DEFAULT_SETTINGS_REQ 0x002C +#define QMIWDS_GET_DEFAULT_SETTINGS_RESP 0x002C +#define QMIWDS_GET_RUNTIME_SETTINGS_REQ 0x002D +#define QMIWDS_GET_RUNTIME_SETTINGS_RESP 0x002D +#define QMIWDS_GET_MIP_MODE_REQ 0x002F +#define QMIWDS_GET_MIP_MODE_RESP 0x002F +#define QMIWDS_GET_DATA_BEARER_REQ 0x0037 +#define QMIWDS_GET_DATA_BEARER_RESP 0x0037 +#define QMIWDS_DUN_CALL_INFO_REQ 0x0038 +#define QMIWDS_DUN_CALL_INFO_RESP 0x0038 +#define QMIWDS_DUN_CALL_INFO_IND 0x0038 +#define QMIWDS_SET_CLIENT_IP_FAMILY_PREF_REQ 0x004D +#define QMIWDS_SET_CLIENT_IP_FAMILY_PREF_RESP 0x004D +#define QMIWDS_SET_AUTO_CONNECT_REQ 0x0051 +#define QMIWDS_SET_AUTO_CONNECT_RESP 0x0051 +#define QMIWDS_BIND_MUX_DATA_PORT_REQ 0x00A2 +#define QMIWDS_BIND_MUX_DATA_PORT_RESP 0x00A2 + + +// Stats masks +#define QWDS_STAT_MASK_TX_PKT_OK 0x00000001 +#define QWDS_STAT_MASK_RX_PKT_OK 0x00000002 +#define QWDS_STAT_MASK_TX_PKT_ER 0x00000004 +#define QWDS_STAT_MASK_RX_PKT_ER 0x00000008 +#define QWDS_STAT_MASK_TX_PKT_OF 0x00000010 +#define QWDS_STAT_MASK_RX_PKT_OF 0x00000020 + +// TLV Types for xfer statistics +#define TLV_WDS_TX_GOOD_PKTS 0x10 +#define TLV_WDS_RX_GOOD_PKTS 0x11 +#define TLV_WDS_TX_ERROR 0x12 +#define TLV_WDS_RX_ERROR 0x13 +#define TLV_WDS_TX_OVERFLOW 0x14 +#define TLV_WDS_RX_OVERFLOW 0x15 +#define TLV_WDS_CHANNEL_RATE 0x16 +#define TLV_WDS_DATA_BEARER 0x17 +#define TLV_WDS_DORMANCY_STATUS 0x18 + +#define QWDS_PKT_DATA_DISCONNECTED 0x01 +#define QWDS_PKT_DATA_CONNECTED 0x02 +#define QWDS_PKT_DATA_SUSPENDED 0x03 +#define QWDS_PKT_DATA_AUTHENTICATING 0x04 + +#define QMIWDS_ADMIN_SET_DATA_FORMAT_REQ 0x0020 +#define QMIWDS_ADMIN_SET_DATA_FORMAT_RESP 0x0020 +#define QMIWDS_ADMIN_GET_DATA_FORMAT_REQ 0x0021 +#define QMIWDS_ADMIN_GET_DATA_FORMAT_RESP 0x0021 +#define QMIWDS_ADMIN_SET_QMAP_SETTINGS_REQ 0x002B +#define QMIWDS_ADMIN_SET_QMAP_SETTINGS_RESP 0x002B +#define QMIWDS_ADMIN_GET_QMAP_SETTINGS_REQ 0x002C +#define QMIWDS_ADMIN_GET_QMAP_SETTINGS_RESP 0x002C + +#define NETWORK_DESC_ENCODING_OCTET 0x00 +#define NETWORK_DESC_ENCODING_EXTPROTOCOL 0x01 +#define NETWORK_DESC_ENCODING_7BITASCII 0x02 +#define NETWORK_DESC_ENCODING_IA5 0x03 +#define NETWORK_DESC_ENCODING_UNICODE 0x04 +#define NETWORK_DESC_ENCODING_SHIFTJIS 0x05 +#define NETWORK_DESC_ENCODING_KOREAN 0x06 +#define NETWORK_DESC_ENCODING_LATINH 0x07 +#define NETWORK_DESC_ENCODING_LATIN 0x08 +#define NETWORK_DESC_ENCODING_GSM7BIT 0x09 +#define NETWORK_DESC_ENCODING_GSMDATA 0x0A +#define NETWORK_DESC_ENCODING_UNKNOWN 0xFF + +typedef struct _QMIWDS_ADMIN_SET_DATA_FORMAT +{ + USHORT Type; // QMUX type 0x0000 + USHORT Length; +} __attribute__ ((packed)) QMIWDS_ADMIN_SET_DATA_FORMAT, *PQMIWDS_ADMIN_SET_DATA_FORMAT; + +typedef struct _QMIWDS_ADMIN_SET_DATA_FORMAT_TLV_QOS +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR QOSSetting; +} __attribute__ ((packed)) QMIWDS_ADMIN_SET_DATA_FORMAT_TLV_QOS, *PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV_QOS; + +typedef struct _QMIWDS_ADMIN_SET_DATA_FORMAT_TLV +{ + UCHAR TLVType; + USHORT TLVLength; + ULONG Value; +} __attribute__ ((packed)) QMIWDS_ADMIN_SET_DATA_FORMAT_TLV, *PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV; + +#if 0 +typedef struct _QMIWDS_ENDPOINT_TLV +{ + UCHAR TLVType; + USHORT TLVLength; + ULONG ep_type; + ULONG iface_id; +} QMIWDS_ENDPOINT_TLV, *PQMIWDS_ENDPOINT_TLV; + +typedef enum _QMI_RETURN_CODES { + QMI_SUCCESS = 0, + QMI_SUCCESS_NOT_COMPLETE, + QMI_FAILURE +}QMI_RETURN_CODES; + +typedef struct _QMIWDS_GET_PKT_SRVC_STATUS_REQ_MSG +{ + USHORT Type; // 0x0022 + USHORT Length; // 0x0000 +} QMIWDS_GET_PKT_SRVC_STATUS_REQ_MSG, *PQMIWDS_GET_PKT_SRVC_STATUS_REQ_MSG; + +typedef struct _QMIWDS_GET_PKT_SRVC_STATUS_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLVType2; + USHORT TLVLength2; + UCHAR ConnectionStatus; // 0x01: QWDS_PKT_DATAC_DISCONNECTED + // 0x02: QWDS_PKT_DATA_CONNECTED + // 0x03: QWDS_PKT_DATA_SUSPENDED + // 0x04: QWDS_PKT_DATA_AUTHENTICATING +} QMIWDS_GET_PKT_SRVC_STATUS_RESP_MSG, *PQMIWDS_GET_PKT_SRVC_STATUS_RESP_MSG; + +typedef struct _QMIWDS_GET_PKT_SRVC_STATUS_IND_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR ConnectionStatus; // 0x01: QWDS_PKT_DATAC_DISCONNECTED + // 0x02: QWDS_PKT_DATA_CONNECTED + // 0x03: QWDS_PKT_DATA_SUSPENDED + UCHAR ReconfigRequired; // 0x00: No need to reconfigure + // 0x01: Reconfiguration required +} QMIWDS_GET_PKT_SRVC_STATUS_IND_MSG, *PQMIWDS_GET_PKT_SRVC_STATUS_IND_MSG; + +typedef struct _WDS_PKT_SRVC_IP_FAMILY_TLV +{ + UCHAR TLVType; // 0x12 + USHORT TLVLength; // 1 + UCHAR IpFamily; // IPV4-0x04, IPV6-0x06 +} WDS_PKT_SRVC_IP_FAMILY_TLV, *PWDS_PKT_SRVC_IP_FAMILY_TLV; + +typedef struct _QMIWDS_DUN_CALL_INFO_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + ULONG Mask; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR ReportConnectionStatus; +} QMIWDS_DUN_CALL_INFO_REQ_MSG, *PQMIWDS_DUN_CALL_INFO_REQ_MSG; + +typedef struct _QMIWDS_DUN_CALL_INFO_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} QMIWDS_DUN_CALL_INFO_RESP_MSG, *PQMIWDS_DUN_CALL_INFO_RESP_MSG; + +typedef struct _QMIWDS_DUN_CALL_INFO_IND_MSG +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR ConnectionStatus; +} QMIWDS_DUN_CALL_INFO_IND_MSG, *PQMIWDS_DUN_CALL_INFO_IND_MSG; + +typedef struct _QMIWDS_GET_CURRENT_CHANNEL_RATE_REQ_MSG +{ + USHORT Type; // QMUX type 0x0040 + USHORT Length; +} QMIWDS_GET_CURRENT_CHANNEL_RATE_REQ_MSG, *PQMIWDS_GET_CURRENT_CHANNEL_RATE_REQ_MSG; + +typedef struct _QMIWDS_GET_CURRENT_CHANNEL_RATE_RESP_MSG +{ + USHORT Type; // QMUX type 0x0040 + USHORT Length; + UCHAR TLVType; // 0x02 + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + + UCHAR TLV2Type; // 0x01 + USHORT TLV2Length; // 16 + //ULONG CallHandle; // Context corresponding to reported channel + ULONG CurrentTxRate; // bps + ULONG CurrentRxRate; // bps + ULONG ServingSystemTxRate; // bps + ULONG ServingSystemRxRate; // bps + +} QMIWDS_GET_CURRENT_CHANNEL_RATE_RESP_MSG, *PQMIWDS_GET_CURRENT_CHANNEL_RATE_RESP; + +#define QWDS_EVENT_REPORT_MASK_RATES 0x01 +#define QWDS_EVENT_REPORT_MASK_STATS 0x02 + +#ifdef QCUSB_MUX_PROTOCOL +#error code not present +#endif // QCUSB_MUX_PROTOCOL + +typedef struct _QMIWDS_SET_EVENT_REPORT_REQ_MSG +{ + USHORT Type; // QMUX type 0x0042 + USHORT Length; + + UCHAR TLVType; // 0x10 -- current channel rate indicator + USHORT TLVLength; // 1 + UCHAR Mode; // 0-do not report; 1-report when rate changes + + UCHAR TLV2Type; // 0x11 + USHORT TLV2Length; // 5 + UCHAR StatsPeriod; // seconds between reports; 0-do not report + ULONG StatsMask; // + + UCHAR TLV3Type; // 0x12 -- current data bearer indicator + USHORT TLV3Length; // 1 + UCHAR Mode3; // 0-do not report; 1-report when changes + + UCHAR TLV4Type; // 0x13 -- dormancy status indicator + USHORT TLV4Length; // 1 + UCHAR DormancyStatus; // 0-do not report; 1-report when changes +} QMIWDS_SET_EVENT_REPORT_REQ_MSG, *PQMIWDS_SET_EVENT_REPORT_REQ_MSG; + +typedef struct _QMIWDS_SET_EVENT_REPORT_RESP_MSG +{ + USHORT Type; // QMUX type 0x0042 + USHORT Length; + + UCHAR TLVType; // 0x02 result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_NO_BATTERY + // QMI_ERR_FAULT +} QMIWDS_SET_EVENT_REPORT_RESP_MSG, *PQMIWDS_SET_EVENT_REPORT_RESP_MSG; + +typedef struct _QMIWDS_EVENT_REPORT_IND_MSG +{ + USHORT Type; // QMUX type 0x0001 + USHORT Length; +} QMIWDS_EVENT_REPORT_IND_MSG, *PQMIWDS_EVENT_REPORT_IND_MSG; + +// PQCTLV_PKT_STATISTICS + +typedef struct _QMIWDS_EVENT_REPORT_IND_CHAN_RATE_TLV +{ + UCHAR Type; + USHORT Length; // 8 + ULONG TxRate; + ULONG RxRate; +} QMIWDS_EVENT_REPORT_IND_CHAN_RATE_TLV, *PQMIWDS_EVENT_REPORT_IND_CHAN_RATE_TLV; + +#ifdef QCUSB_MUX_PROTOCOL +#error code not present +#endif // QCUSB_MUX_PROTOCOL + +typedef struct _QMIWDS_GET_PKT_STATISTICS_REQ_MSG +{ + USHORT Type; // QMUX type 0x0041 + USHORT Length; + UCHAR TLVType; // 0x01 + USHORT TLVLength; // 4 + ULONG StateMask; // 0x00000001 tx success packets + // 0x00000002 rx success packets + // 0x00000004 rx packet errors (checksum) + // 0x00000008 rx packets dropped (memory) + +} QMIWDS_GET_PKT_STATISTICS_REQ_MSG, *PQMIWDS_GET_PKT_STATISTICS_REQ_MSG; + +typedef struct _QMIWDS_GET_PKT_STATISTICS_RESP_MSG +{ + USHORT Type; // QMUX type 0x0041 + USHORT Length; + UCHAR TLVType; // 0x02 + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} QMIWDS_GET_PKT_STATISTICS_RESP_MSG, *PQMIWDS_GET_PKT_STATISTICS_RESP_MSG; + +// optional TLV for stats +typedef struct _QCTLV_PKT_STATISTICS +{ + UCHAR TLVType; // see above definitions for TLV types + USHORT TLVLength; // 4 + ULONG Count; +} QCTLV_PKT_STATISTICS, *PQCTLV_PKT_STATISTICS; +#endif + +//#ifdef QC_IP_MODE + +#define QMIWDS_GET_RUNTIME_SETTINGS_MASK_IPV4DNS_ADDR 0x0010 +#define QMIWDS_GET_RUNTIME_SETTINGS_MASK_IPV4_ADDR 0x0100 +#define QMIWDS_GET_RUNTIME_SETTINGS_MASK_IPV4GATEWAY_ADDR 0x0200 +#define QMIWDS_GET_RUNTIME_SETTINGS_MASK_MTU 0x2000 + +typedef struct _QMIWDS_GET_RUNTIME_SETTINGS_REQ_MSG +{ + USHORT Type; // QMIWDS_GET_RUNTIME_SETTINGS_REQ + USHORT Length; + UCHAR TLVType; // 0x10 + USHORT TLVLength; // 0x0004 + ULONG Mask; // mask, bit 8: IP addr -- 0x0100 +} __attribute__ ((packed)) QMIWDS_GET_RUNTIME_SETTINGS_REQ_MSG, *PQMIWDS_GET_RUNTIME_SETTINGS_REQ_MSG; + +typedef struct _QMIWDS_BIND_MUX_DATA_PORT_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + ULONG ep_type; + ULONG iface_id; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR MuxId; + UCHAR TLV3Type; + USHORT TLV3Length; + ULONG client_type; +} __attribute__ ((packed)) QMIWDS_BIND_MUX_DATA_PORT_REQ_MSG, *PQMIWDS_BIND_MUX_DATA_PORT_REQ_MSG; + +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4PRIMARYDNS 0x15 +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4SECONDARYDNS 0x16 +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4 0x1E +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4GATEWAY 0x20 +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4SUBNET 0x21 + +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6 0x25 +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6GATEWAY 0x26 +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6PRIMARYDNS 0x27 +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6SECONDARYDNS 0x28 +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_MTU 0x29 + +typedef struct _QMIWDS_GET_RUNTIME_SETTINGS_TLV_MTU +{ + UCHAR TLVType; // QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_MTU + USHORT TLVLength; // 4 + ULONG Mtu; // MTU +} __attribute__ ((packed)) QMIWDS_GET_RUNTIME_SETTINGS_TLV_MTU, *PQMIWDS_GET_RUNTIME_SETTINGS_TLV_MTU; + +typedef struct _QMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR +{ + UCHAR TLVType; // QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4 + USHORT TLVLength; // 4 + ULONG IPV4Address; // address +} __attribute__ ((packed)) QMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR, *PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR; + +typedef struct _QMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV6_ADDR +{ + UCHAR TLVType; // QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6 + USHORT TLVLength; // 16 + UCHAR IPV6Address[16]; // address + UCHAR PrefixLength; // prefix length +} __attribute__ ((packed)) QMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV6_ADDR, *PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV6_ADDR; + +typedef struct _QMIWDS_GET_RUNTIME_SETTINGS_RESP_MSG +{ + USHORT Type; // QMIWDS_GET_RUNTIME_SETTINGS_RESP + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_RESULT_CODE + USHORT TLVLength; // 0x0004 + USHORT QMUXResult; // result code + USHORT QMUXError; // error code +} __attribute__ ((packed)) QMIWDS_GET_RUNTIME_SETTINGS_RESP_MSG, *PQMIWDS_GET_RUNTIME_SETTINGS_RESP_MSG; + +//#endif // QC_IP_MODE + +typedef struct _QMIWDS_IP_FAMILY_TLV +{ + UCHAR TLVType; // 0x12 + USHORT TLVLength; // 1 + UCHAR IpFamily; // IPV4-0x04, IPV6-0x06 +} __attribute__ ((packed)) QMIWDS_IP_FAMILY_TLV, *PQMIWDS_IP_FAMILY_TLV; + +typedef struct _QMIWDS_PKT_SRVC_TLV +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR ConnectionStatus; + UCHAR ReconfigReqd; +} __attribute__ ((packed)) QMIWDS_PKT_SRVC_TLV, *PQMIWDS_PKT_SRVC_TLV; + +typedef struct _QMIWDS_CALL_END_REASON_TLV +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT CallEndReason; +} __attribute__ ((packed)) QMIWDS_CALL_END_REASON_TLV, *PQMIWDS_CALL_END_REASON_TLV; + +typedef struct _QMIWDS_CALL_END_REASON_V_TLV +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT CallEndReasonType; + USHORT CallEndReason; +} __attribute__ ((packed)) QMIWDS_CALL_END_REASON_V_TLV, *PQMIWDS_CALL_END_REASON_V_TLV; + +typedef struct _QMIWDS_SET_CLIENT_IP_FAMILY_PREF_REQ_MSG +{ + USHORT Type; // QMUX type 0x004D + USHORT Length; + UCHAR TLVType; // 0x01 + USHORT TLVLength; // 1 + UCHAR IpPreference; // IPV4-0x04, IPV6-0x06 +} __attribute__ ((packed)) QMIWDS_SET_CLIENT_IP_FAMILY_PREF_REQ_MSG, *PQMIWDS_SET_CLIENT_IP_FAMILY_PREF_REQ_MSG; + +typedef struct _QMIWDS_SET_CLIENT_IP_FAMILY_PREF_RESP_MSG +{ + USHORT Type; // QMUX type 0x0037 + USHORT Length; + UCHAR TLVType; // 0x02 + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS, QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INTERNAL, QMI_ERR_MALFORMED_MSG, QMI_ERR_INVALID_ARG +} __attribute__ ((packed)) QMIWDS_SET_CLIENT_IP_FAMILY_PREF_RESP_MSG, *PQMIWDS_SET_CLIENT_IP_FAMILY_PREF_RESP_MSG; + +typedef struct _QMIWDS_SET_AUTO_CONNECT_REQ_MSG +{ + USHORT Type; // QMUX type 0x0051 + USHORT Length; + UCHAR TLVType; // 0x01 + USHORT TLVLength; // 1 + UCHAR autoconnect_setting; // 0x00 ?C Disabled, 0x01 ?C Enabled, 0x02 ?C Paused (resume on power cycle) +} __attribute__ ((packed)) QMIWDS_SET_AUTO_CONNECT_REQ_MSG, *PQMIWDS_SET_AUTO_CONNECT_REQ_MSG; + +#if 0 +typedef struct _QMIWDS_GET_MIP_MODE_REQ_MSG +{ + USHORT Type; // QMUX type 0x0040 + USHORT Length; +} QMIWDS_GET_MIP_MODE_REQ_MSG, *PQMIWDS_GET_MIP_MODE_REQ_MSG; + +typedef struct _QMIWDS_GET_MIP_MODE_RESP_MSG +{ + USHORT Type; // QMUX type 0x0040 + USHORT Length; + UCHAR TLVType; // 0x02 + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + + UCHAR TLV2Type; // 0x01 + USHORT TLV2Length; // 20 + UCHAR MipMode; // +} QMIWDS_GET_MIP_MODE_RESP_MSG, *PQMIWDS_GET_MIP_MODE_RESP_MSG; +#endif + +typedef struct _QMIWDS_TECHNOLOGY_PREFERECE +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR TechPreference; +} __attribute__ ((packed)) QMIWDS_TECHNOLOGY_PREFERECE, *PQMIWDS_TECHNOLOGY_PREFERECE; + +typedef struct _QMIWDS_PROFILE_IDENTIFIER +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR ProfileIndex; +} __attribute__ ((packed)) QMIWDS_PROFILE_IDENTIFIER, *PQMIWDS_PROFILE_IDENTIFIER; + +#if 0 +typedef struct _QMIWDS_IPADDRESS +{ + UCHAR TLVType; + USHORT TLVLength; + ULONG IPv4Address; +}QMIWDS_IPADDRESS, *PQMIWDS_IPADDRESS; + +/* +typedef struct _QMIWDS_UMTS_QOS +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR TrafficClass; + ULONG MaxUplinkBitRate; + ULONG MaxDownlinkBitRate; + ULONG GuarUplinkBitRate; + ULONG GuarDownlinkBitRate; + UCHAR QOSDevOrder; + ULONG MAXSDUSize; + UCHAR SDUErrorRatio; + UCHAR ResidualBerRatio; + UCHAR DeliveryErrorSDUs; + ULONG TransferDelay; + ULONG TrafficHndPri; +}QMIWDS_UMTS_QOS, *PQMIWDS_UMTS_QOS; + +typedef struct _QMIWDS_GPRS_QOS +{ + UCHAR TLVType; + USHORT TLVLength; + ULONG PrecedenceClass; + ULONG DelayClass; + ULONG ReliabilityClass; + ULONG PeekThroClass; + ULONG MeanThroClass; +}QMIWDS_GPRS_QOS, *PQMIWDS_GPRS_QOS; +*/ +#endif + +typedef struct _QMIWDS_PROFILENAME +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR ProfileName; +} __attribute__ ((packed)) QMIWDS_PROFILENAME, *PQMIWDS_PROFILENAME; + +typedef struct _QMIWDS_PDPTYPE +{ + UCHAR TLVType; + USHORT TLVLength; +// 0 ?C PDP-IP (IPv4) +// 1 ?C PDP-PPP +// 2 ?C PDP-IPv6 +// 3 ?C PDP-IPv4v6 + UCHAR PdpType; +} __attribute__ ((packed)) QMIWDS_PDPTYPE, *PQMIWDS_PDPTYPE; + +typedef struct _QMIWDS_USERNAME +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR UserName; +} __attribute__ ((packed)) QMIWDS_USERNAME, *PQMIWDS_USERNAME; + +typedef struct _QMIWDS_PASSWD +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR Passwd; +} __attribute__ ((packed)) QMIWDS_PASSWD, *PQMIWDS_PASSWD; + +typedef struct _QMIWDS_AUTH_PREFERENCE +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR AuthPreference; +} __attribute__ ((packed)) QMIWDS_AUTH_PREFERENCE, *PQMIWDS_AUTH_PREFERENCE; + +typedef struct _QMIWDS_APNNAME +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR ApnName; +} __attribute__ ((packed)) QMIWDS_APNNAME, *PQMIWDS_APNNAME; + +typedef struct _QMIWDS_AUTOCONNECT +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR AutoConnect; +} __attribute__ ((packed)) QMIWDS_AUTOCONNECT, *PQMIWDS_AUTOCONNECT; + +typedef struct _QMIWDS_START_NETWORK_INTERFACE_REQ_MSG +{ + USHORT Type; + USHORT Length; +} __attribute__ ((packed)) QMIWDS_START_NETWORK_INTERFACE_REQ_MSG, *PQMIWDS_START_NETWORK_INTERFACE_REQ_MSG; + +typedef struct _QMIWDS_CALLENDREASON +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT Reason; +}__attribute__ ((packed)) QMIWDS_CALLENDREASON, *PQMIWDS_CALLENDREASON; + +typedef struct _QMIWDS_START_NETWORK_INTERFACE_RESP_MSG +{ + USHORT Type; // QMUX type 0x0040 + USHORT Length; + UCHAR TLVType; // 0x02 + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + + UCHAR TLV2Type; // 0x01 + USHORT TLV2Length; // 20 + ULONG Handle; // +} __attribute__ ((packed)) QMIWDS_START_NETWORK_INTERFACE_RESP_MSG, *PQMIWDS_START_NETWORK_INTERFACE_RESP_MSG; + +typedef struct _QMIWDS_STOP_NETWORK_INTERFACE_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + ULONG Handle; +} __attribute__ ((packed)) QMIWDS_STOP_NETWORK_INTERFACE_REQ_MSG, *PQMIWDS_STOP_NETWORK_INTERFACE_REQ_MSG; + +typedef struct _QMIWDS_STOP_NETWORK_INTERFACE_RESP_MSG +{ + USHORT Type; // QMUX type 0x0040 + USHORT Length; + UCHAR TLVType; // 0x02 + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + +} __attribute__ ((packed)) QMIWDS_STOP_NETWORK_INTERFACE_RESP_MSG, *PQMIWDS_STOP_NETWORK_INTERFACE_RESP_MSG; + +typedef struct _QMIWDS_GET_DEFAULT_SETTINGS_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR ProfileType; +} __attribute__ ((packed)) QMIWDS_GET_DEFAULT_SETTINGS_REQ_MSG, *PQMIWDS_GET_DEFAULT_SETTINGS_REQ_MSG; + +typedef struct _QMIWDS_GET_DEFAULT_SETTINGS_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} __attribute__ ((packed)) QMIWDS_GET_DEFAULT_SETTINGS_RESP_MSG, *PQMIWDS_GET_DEFAULT_SETTINGS_RESP_MSG; + +typedef struct _QMIWDS_MODIFY_PROFILE_SETTINGS_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR ProfileType; + UCHAR ProfileIndex; +} __attribute__ ((packed)) QMIWDS_MODIFY_PROFILE_SETTINGS_REQ_MSG, *PQMIWDS_MODIFY_PROFILE_SETTINGS_REQ_MSG; + +typedef struct _QMIWDS_MODIFY_PROFILE_SETTINGS_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} __attribute__ ((packed)) QMIWDS_MODIFY_PROFILE_SETTINGS_RESP_MSG, *PQMIWDS_MODIFY_PROFILE_SETTINGS_RESP_MSG; + +typedef struct _QMIWDS_GET_PROFILE_SETTINGS_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR ProfileType; + UCHAR ProfileIndex; +} __attribute__ ((packed)) QMIWDS_GET_PROFILE_SETTINGS_REQ_MSG, *PQMIWDS_GET_PROFILE_SETTINGS_REQ_MSG; + +#if 0 +typedef struct _QMIWDS_EVENT_REPORT_IND_DATA_BEARER_TLV +{ + UCHAR Type; + USHORT Length; + UCHAR DataBearer; +} QMIWDS_EVENT_REPORT_IND_DATA_BEARER_TLV, *PQMIWDS_EVENT_REPORT_IND_DATA_BEARER_TLV; + +typedef struct _QMIWDS_EVENT_REPORT_IND_DORMANCY_STATUS_TLV +{ + UCHAR Type; + USHORT Length; + UCHAR DormancyStatus; +} QMIWDS_EVENT_REPORT_IND_DORMANCY_STATUS_TLV, *PQMIWDS_EVENT_REPORT_IND_DORMANCY_STATUS_TLV; + + +typedef struct _QMIWDS_GET_DATA_BEARER_REQ_MSG +{ + USHORT Type; // QMUX type 0x0037 + USHORT Length; +} QMIWDS_GET_DATA_BEARER_REQ_MSG, *PQMIWDS_GET_DATA_BEARER_REQ_MSG; + +typedef struct _QMIWDS_GET_DATA_BEARER_RESP_MSG +{ + USHORT Type; // QMUX type 0x0037 + USHORT Length; + UCHAR TLVType; // 0x02 + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INTERNAL + // QMI_ERR_MALFORMED_MSG + // QMI_ERR_NO_MEMORY + // QMI_ERR_OUT_OF_CALL + // QMI_ERR_INFO_UNAVAILABLE + UCHAR TLV2Type; // 0x01 + USHORT TLV2Length; // + UCHAR Technology; // +} QMIWDS_GET_DATA_BEARER_RESP_MSG, *PQMIWDS_GET_DATA_BEARER_RESP_MSG; +#endif + +// ======================= DMS ============================== +#define QMIDMS_SET_EVENT_REPORT_REQ 0x0001 +#define QMIDMS_SET_EVENT_REPORT_RESP 0x0001 +#define QMIDMS_EVENT_REPORT_IND 0x0001 +#define QMIDMS_GET_DEVICE_CAP_REQ 0x0020 +#define QMIDMS_GET_DEVICE_CAP_RESP 0x0020 +#define QMIDMS_GET_DEVICE_MFR_REQ 0x0021 +#define QMIDMS_GET_DEVICE_MFR_RESP 0x0021 +#define QMIDMS_GET_DEVICE_MODEL_ID_REQ 0x0022 +#define QMIDMS_GET_DEVICE_MODEL_ID_RESP 0x0022 +#define QMIDMS_GET_DEVICE_REV_ID_REQ 0x0023 +#define QMIDMS_GET_DEVICE_REV_ID_RESP 0x0023 +#define QMIDMS_GET_MSISDN_REQ 0x0024 +#define QMIDMS_GET_MSISDN_RESP 0x0024 +#define QMIDMS_GET_DEVICE_SERIAL_NUMBERS_REQ 0x0025 +#define QMIDMS_GET_DEVICE_SERIAL_NUMBERS_RESP 0x0025 +#define QMIDMS_UIM_SET_PIN_PROTECTION_REQ 0x0027 +#define QMIDMS_UIM_SET_PIN_PROTECTION_RESP 0x0027 +#define QMIDMS_UIM_VERIFY_PIN_REQ 0x0028 +#define QMIDMS_UIM_VERIFY_PIN_RESP 0x0028 +#define QMIDMS_UIM_UNBLOCK_PIN_REQ 0x0029 +#define QMIDMS_UIM_UNBLOCK_PIN_RESP 0x0029 +#define QMIDMS_UIM_CHANGE_PIN_REQ 0x002A +#define QMIDMS_UIM_CHANGE_PIN_RESP 0x002A +#define QMIDMS_UIM_GET_PIN_STATUS_REQ 0x002B +#define QMIDMS_UIM_GET_PIN_STATUS_RESP 0x002B +#define QMIDMS_GET_DEVICE_HARDWARE_REV_REQ 0x002C +#define QMIDMS_GET_DEVICE_HARDWARE_REV_RESP 0x002C +#define QMIDMS_GET_OPERATING_MODE_REQ 0x002D +#define QMIDMS_GET_OPERATING_MODE_RESP 0x002D +#define QMIDMS_SET_OPERATING_MODE_REQ 0x002E +#define QMIDMS_SET_OPERATING_MODE_RESP 0x002E +#define QMIDMS_GET_ACTIVATED_STATUS_REQ 0x0031 +#define QMIDMS_GET_ACTIVATED_STATUS_RESP 0x0031 +#define QMIDMS_ACTIVATE_AUTOMATIC_REQ 0x0032 +#define QMIDMS_ACTIVATE_AUTOMATIC_RESP 0x0032 +#define QMIDMS_ACTIVATE_MANUAL_REQ 0x0033 +#define QMIDMS_ACTIVATE_MANUAL_RESP 0x0033 +#define QMIDMS_UIM_GET_ICCID_REQ 0x003C +#define QMIDMS_UIM_GET_ICCID_RESP 0x003C +#define QMIDMS_UIM_GET_CK_STATUS_REQ 0x0040 +#define QMIDMS_UIM_GET_CK_STATUS_RESP 0x0040 +#define QMIDMS_UIM_SET_CK_PROTECTION_REQ 0x0041 +#define QMIDMS_UIM_SET_CK_PROTECTION_RESP 0x0041 +#define QMIDMS_UIM_UNBLOCK_CK_REQ 0x0042 +#define QMIDMS_UIM_UNBLOCK_CK_RESP 0x0042 +#define QMIDMS_UIM_GET_IMSI_REQ 0x0043 +#define QMIDMS_UIM_GET_IMSI_RESP 0x0043 +#define QMIDMS_UIM_GET_STATE_REQ 0x0044 +#define QMIDMS_UIM_GET_STATE_RESP 0x0044 +#define QMIDMS_GET_BAND_CAP_REQ 0x0045 +#define QMIDMS_GET_BAND_CAP_RESP 0x0045 + +#if 0 +typedef struct _QMIDMS_GET_DEVICE_MFR_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; +} QMIDMS_GET_DEVICE_MFR_REQ_MSG, *PQMIDMS_GET_DEVICE_MFR_REQ_MSG; + +typedef struct _QMIDMS_GET_DEVICE_MFR_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR TLV2Type; // 0x01 - required parameter + USHORT TLV2Length; // length of the mfr string + UCHAR DeviceManufacturer; // first byte of string +} QMIDMS_GET_DEVICE_MFR_RESP_MSG, *PQMIDMS_GET_DEVICE_MFR_RESP_MSG; + +typedef struct _QMIDMS_GET_DEVICE_MODEL_ID_REQ_MSG +{ + USHORT Type; // QMUX type 0x0004 + USHORT Length; +} QMIDMS_GET_DEVICE_MODEL_ID_REQ_MSG, *PQMIDMS_GET_DEVICE_MODEL_ID_REQ_MSG; + +typedef struct _QMIDMS_GET_DEVICE_MODEL_ID_RESP_MSG +{ + USHORT Type; // QMUX type 0x0004 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR TLV2Type; // 0x01 - required parameter + USHORT TLV2Length; // length of the modem id string + UCHAR DeviceModelID; // device model id +} QMIDMS_GET_DEVICE_MODEL_ID_RESP_MSG, *PQMIDMS_GET_DEVICE_MODEL_ID_RESP_MSG; +#endif + +typedef struct _QMIDMS_GET_DEVICE_REV_ID_REQ_MSG +{ + USHORT Type; // QMUX type 0x0005 + USHORT Length; +} __attribute__ ((packed)) QMIDMS_GET_DEVICE_REV_ID_REQ_MSG, *PQMIDMS_GET_DEVICE_REV_ID_REQ_MSG; + +typedef struct _DEVICE_REV_ID +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR RevisionID; +} __attribute__ ((packed)) DEVICE_REV_ID, *PDEVICE_REV_ID; + +#if 0 +typedef struct _QMIDMS_GET_DEVICE_REV_ID_RESP_MSG +{ + USHORT Type; // QMUX type 0x0023 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} QMIDMS_GET_DEVICE_REV_ID_RESP_MSG, *PQMIDMS_GET_DEVICE_REV_ID_RESP_MSG; + +typedef struct _QMIDMS_GET_MSISDN_REQ_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; +} QMIDMS_GET_MSISDN_REQ_MSG, *PQMIDMS_GET_MSISDN_REQ_MSG; + +typedef struct _QCTLV_DEVICE_VOICE_NUMBERS +{ + UCHAR TLVType; // as defined above + USHORT TLVLength; // 4/7/7 + UCHAR VoideNumberString; // ESN, IMEI, or MEID + +} QCTLV_DEVICE_VOICE_NUMBERS, *PQCTLV_DEVICE_VOICE_NUMBERS; + + +typedef struct _QMIDMS_GET_MSISDN_RESP_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG +} QMIDMS_GET_MSISDN_RESP_MSG, *PQMIDMS_GET_MSISDN_RESP_MSG; +#endif + +typedef struct _QMIDMS_UIM_GET_IMSI_REQ_MSG +{ + USHORT Type; + USHORT Length; +} __attribute__ ((packed)) QMIDMS_UIM_GET_IMSI_REQ_MSG, *PQMIDMS_UIM_GET_IMSI_REQ_MSG; + +typedef struct _QMIDMS_UIM_GET_IMSI_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR IMSI; +} __attribute__ ((packed)) QMIDMS_UIM_GET_IMSI_RESP_MSG, *PQMIDMS_UIM_GET_IMSI_RESP_MSG; + +#if 0 +typedef struct _QMIDMS_GET_DEVICE_SERIAL_NUMBERS_REQ_MSG +{ + USHORT Type; // QMUX type 0x0007 + USHORT Length; +} QMIDMS_GET_DEVICE_SERIAL_NUMBERS_REQ_MSG, *PQMIDMS_GET_DEVICE_SERIAL_NUMBERS_REQ_MSG; + +#define QCTLV_TYPE_SER_NUM_ESN 0x10 +#define QCTLV_TYPE_SER_NUM_IMEI 0x11 +#define QCTLV_TYPE_SER_NUM_MEID 0x12 + +typedef struct _QCTLV_DEVICE_SERIAL_NUMBER +{ + UCHAR TLVType; // as defined above + USHORT TLVLength; // 4/7/7 + UCHAR SerialNumberString; // ESN, IMEI, or MEID + +} QCTLV_DEVICE_SERIAL_NUMBER, *PQCTLV_DEVICE_SERIAL_NUMBER; + +typedef struct _QMIDMS_GET_DEVICE_SERIAL_NUMBERS_RESP_MSG +{ + USHORT Type; // QMUX type 0x0007 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + // followed by optional TLV +} QMIDMS_GET_DEVICE_SERIAL_NUMBERS_RESP_MSG, *PQMIDMS_GET_DEVICE_SERIAL_NUMBERS_RESP; + +typedef struct _QMIDMS_GET_DMS_BAND_CAP +{ + USHORT Type; + USHORT Length; +} QMIDMS_GET_BAND_CAP_REQ_MSG, *PQMIDMS_GET_BAND_CAP_REQ_MSG; + +typedef struct _QMIDMS_GET_BAND_CAP_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_NONE + // QMI_ERR_INTERNAL + // QMI_ERR_MALFORMED_MSG + // QMI_ERR_NO_MEMORY + + UCHAR TLV2Type; // 0x01 + USHORT TLV2Length; // 2 + ULONG64 BandCap; +} QMIDMS_GET_BAND_CAP_RESP_MSG, *PQMIDMS_GET_BAND_CAP_RESP; + +typedef struct _QMIDMS_GET_DEVICE_CAP_REQ_MSG +{ + USHORT Type; // QMUX type 0x0002 + USHORT Length; +} QMIDMS_GET_DEVICE_CAP_REQ_MSG, *PQMIDMS_GET_DEVICE_CAP_REQ_MSG; + +typedef struct _QMIDMS_GET_DEVICE_CAP_RESP_MSG +{ + USHORT Type; // QMUX type 0x0002 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMUX_RESULT_SUCCESS + // QMUX_RESULT_FAILURE + USHORT QMUXError; // QMUX_ERR_INVALID_ARG + // QMUX_ERR_NO_MEMORY + // QMUX_ERR_INTERNAL + // QMUX_ERR_FAULT + UCHAR TLV2Type; // 0x01 + USHORT TLV2Length; // 2 + + ULONG MaxTxChannelRate; + ULONG MaxRxChannelRate; + UCHAR VoiceCap; + UCHAR SimCap; + + UCHAR RadioIfListCnt; // #elements in radio interface list + UCHAR RadioIfList; // N 1-byte elements +} QMIDMS_GET_DEVICE_CAP_RESP_MSG, *PQMIDMS_GET_DEVICE_CAP_RESP_MSG; + +typedef struct _QMIDMS_GET_ACTIVATED_STATUS_REQ_MSG +{ + USHORT Type; // QMUX type 0x0002 + USHORT Length; +} QMIDMS_GET_ACTIVATED_STATUS_REQ_MSG, *PQMIDMS_GET_ACTIVATES_STATUD_REQ_MSG; + +typedef struct _QMIDMS_GET_ACTIVATED_STATUS_RESP_MSG +{ + USHORT Type; // QMUX type 0x0002 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMUX_RESULT_SUCCESS + // QMUX_RESULT_FAILURE + USHORT QMUXError; // QMUX_ERR_INVALID_ARG + // QMUX_ERR_NO_MEMORY + // QMUX_ERR_INTERNAL + // QMUX_ERR_FAULT + UCHAR TLV2Type; // 0x01 + USHORT TLV2Length; // 2 + + USHORT ActivatedStatus; +} QMIDMS_GET_ACTIVATED_STATUS_RESP_MSG, *PQMIDMS_GET_ACTIVATED_STATUS_RESP_MSG; + +typedef struct _QMIDMS_GET_OPERATING_MODE_REQ_MSG +{ + USHORT Type; // QMUX type 0x0002 + USHORT Length; +} QMIDMS_GET_OPERATING_MODE_REQ_MSG, *PQMIDMS_GET_OPERATING_MODE_REQ_MSG; + +typedef struct _OFFLINE_REASON +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT OfflineReason; +} OFFLINE_REASON, *POFFLINE_REASON; + +typedef struct _HARDWARE_RESTRICTED_MODE +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR HardwareControlledMode; +} HARDWARE_RESTRICTED_MODE, *PHARDWARE_RESTRICTED_MODE; + +typedef struct _QMIDMS_GET_OPERATING_MODE_RESP_MSG +{ + USHORT Type; // QMUX type 0x0002 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMUX_RESULT_SUCCESS + // QMUX_RESULT_FAILURE + USHORT QMUXError; // QMUX_ERR_INVALID_ARG + // QMUX_ERR_NO_MEMORY + // QMUX_ERR_INTERNAL + // QMUX_ERR_FAULT + UCHAR TLV2Type; // 0x01 + USHORT TLV2Length; // 2 + + UCHAR OperatingMode; +} QMIDMS_GET_OPERATING_MODE_RESP_MSG, *PQMIDMS_GET_OPERATING_MODE_RESP_MSG; + +typedef struct _QMIDMS_UIM_GET_ICCID_REQ_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; +} QMIDMS_UIM_GET_ICCID_REQ_MSG, *PQMIDMS_UIM_GET_ICCID_REQ_MSG; + +typedef struct _QMIDMS_UIM_GET_ICCID_RESP_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR TLV2Type; // 0x01 - required parameter + USHORT TLV2Length; // var + UCHAR ICCID; // String of voice number +} QMIDMS_UIM_GET_ICCID_RESP_MSG, *PQMIDMS_UIM_GET_ICCID_RESP_MSG; +#endif + +typedef struct _QMIDMS_SET_OPERATING_MODE_REQ_MSG +{ + USHORT Type; // QMUX type 0x0002 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + UCHAR OperatingMode; +} __attribute__ ((packed)) QMIDMS_SET_OPERATING_MODE_REQ_MSG, *PQMIDMS_SET_OPERATING_MODE_REQ_MSG; + +typedef struct _QMIDMS_SET_OPERATING_MODE_RESP_MSG +{ + USHORT Type; // QMUX type 0x0002 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMUX_RESULT_SUCCESS + // QMUX_RESULT_FAILURE + USHORT QMUXError; // QMUX_ERR_INVALID_ARG + // QMUX_ERR_NO_MEMORY + // QMUX_ERR_INTERNAL + // QMUX_ERR_FAULT +} __attribute__ ((packed)) QMIDMS_SET_OPERATING_MODE_RESP_MSG, *PQMIDMS_SET_OPERATING_MODE_RESP_MSG; + +#if 0 +typedef struct _QMIDMS_ACTIVATE_AUTOMATIC_REQ_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // + UCHAR ActivateCodelen; + UCHAR ActivateCode; +} QMIDMS_ACTIVATE_AUTOMATIC_REQ_MSG, *PQMIDMS_ACTIVATE_AUTOMATIC_REQ_MSG; + +typedef struct _QMIDMS_ACTIVATE_AUTOMATIC_RESP_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} QMIDMS_ACTIVATE_AUTOMATIC_RESP_MSG, *PQMIDMS_ACTIVATE_AUTOMATIC_RESP_MSG; + + +typedef struct _SPC_MSG +{ + UCHAR SPC[6]; + USHORT SID; +} SPC_MSG, *PSPC_MSG; + +typedef struct _MDN_MSG +{ + UCHAR MDNLEN; + UCHAR MDN; +} MDN_MSG, *PMDN_MSG; + +typedef struct _MIN_MSG +{ + UCHAR MINLEN; + UCHAR MIN; +} MIN_MSG, *PMIN_MSG; + +typedef struct _PRL_MSG +{ + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // + USHORT PRLLEN; + UCHAR PRL; +} PRL_MSG, *PPRL_MSG; + +typedef struct _MN_HA_KEY_MSG +{ + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // + UCHAR MN_HA_KEY_LEN; + UCHAR MN_HA_KEY; +} MN_HA_KEY_MSG, *PMN_HA_KEY_MSG; + +typedef struct _MN_AAA_KEY_MSG +{ + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // + UCHAR MN_AAA_KEY_LEN; + UCHAR MN_AAA_KEY; +} MN_AAA_KEY_MSG, *PMN_AAA_KEY_MSG; + +typedef struct _QMIDMS_ACTIVATE_MANUAL_REQ_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // + UCHAR Value; +} QMIDMS_ACTIVATE_MANUAL_REQ_MSG, *PQMIDMS_ACTIVATE_MANUAL_REQ_MSG; + +typedef struct _QMIDMS_ACTIVATE_MANUAL_RESP_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} QMIDMS_ACTIVATE_MANUAL_RESP_MSG, *PQMIDMS_ACTIVATE_MANUAL_RESP_MSG; +#endif + +typedef struct _QMIDMS_UIM_GET_STATE_REQ_MSG +{ + USHORT Type; + USHORT Length; +} __attribute__ ((packed)) QMIDMS_UIM_GET_STATE_REQ_MSG, *PQMIDMS_UIM_GET_STATE_REQ_MSG; + +typedef struct _QMIDMS_UIM_GET_STATE_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR UIMState; +} __attribute__ ((packed)) QMIDMS_UIM_GET_STATE_RESP_MSG, *PQMIDMS_UIM_GET_STATE_RESP_MSG; + +typedef struct _QMIDMS_UIM_GET_PIN_STATUS_REQ_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; +} __attribute__ ((packed)) QMIDMS_UIM_GET_PIN_STATUS_REQ_MSG, *PQMIDMS_UIM_GET_PIN_STATUS_REQ_MSG; + +typedef struct _QMIDMS_UIM_PIN_STATUS +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR PINStatus; + UCHAR PINVerifyRetriesLeft; + UCHAR PINUnblockRetriesLeft; +} __attribute__ ((packed)) QMIDMS_UIM_PIN_STATUS, *PQMIDMS_UIM_PIN_STATUS; + +#define QMI_PIN_STATUS_NOT_INIT 0 +#define QMI_PIN_STATUS_NOT_VERIF 1 +#define QMI_PIN_STATUS_VERIFIED 2 +#define QMI_PIN_STATUS_DISABLED 3 +#define QMI_PIN_STATUS_BLOCKED 4 +#define QMI_PIN_STATUS_PERM_BLOCKED 5 +#define QMI_PIN_STATUS_UNBLOCKED 6 +#define QMI_PIN_STATUS_CHANGED 7 + + +typedef struct _QMIDMS_UIM_GET_PIN_STATUS_RESP_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR PinStatus; +} __attribute__ ((packed)) QMIDMS_UIM_GET_PIN_STATUS_RESP_MSG, *PQMIDMS_UIM_GET_PIN_STATUS_RESP_MSG; + +#if 0 +typedef struct _QMIDMS_UIM_GET_CK_STATUS_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR Facility; +} QMIDMS_UIM_GET_CK_STATUS_REQ_MSG, *PQMIDMS_UIM_GET_CK_STATUS_REQ_MSG; + + +typedef struct _QMIDMS_UIM_CK_STATUS +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR FacilityStatus; + UCHAR FacilityVerifyRetriesLeft; + UCHAR FacilityUnblockRetriesLeft; +} QMIDMS_UIM_CK_STATUS, *PQMIDMS_UIM_CK_STATUS; + +typedef struct _QMIDMS_UIM_CK_OPERATION_STATUS +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR OperationBlocking; +} QMIDMS_UIM_CK_OPERATION_STATUS, *PQMIDMS_UIM_CK_OPERATION_STATUS; + +typedef struct _QMIDMS_UIM_GET_CK_STATUS_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR CkStatus; +} QMIDMS_UIM_GET_CK_STATUS_RESP_MSG, *PQMIDMS_UIM_GET_CK_STATUS_RESP_MSG; +#endif + +typedef struct _QMIDMS_UIM_VERIFY_PIN_REQ_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + UCHAR PINID; + UCHAR PINLen; + UCHAR PINValue; +} __attribute__ ((packed)) QMIDMS_UIM_VERIFY_PIN_REQ_MSG, *PQMIDMS_UIM_VERIFY_PIN_REQ_MSG; + +typedef struct _QMIDMS_UIM_VERIFY_PIN_RESP_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR PINVerifyRetriesLeft; + UCHAR PINUnblockRetriesLeft; +} __attribute__ ((packed)) QMIDMS_UIM_VERIFY_PIN_RESP_MSG, *PQMIDMS_UIM_VERIFY_PIN_RESP_MSG; + +#if 0 +typedef struct _QMIDMS_UIM_SET_PIN_PROTECTION_REQ_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + UCHAR PINID; + UCHAR ProtectionSetting; + UCHAR PINLen; + UCHAR PINValue; +} QMIDMS_UIM_SET_PIN_PROTECTION_REQ_MSG, *PQMIDMS_UIM_SET_PIN_PROTECTION_REQ_MSG; + +typedef struct _QMIDMS_UIM_SET_PIN_PROTECTION_RESP_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR PINVerifyRetriesLeft; + UCHAR PINUnblockRetriesLeft; +} QMIDMS_UIM_SET_PIN_PROTECTION_RESP_MSG, *PQMIDMS_UIM_SET_PIN_PROTECTION_RESP_MSG; + +typedef struct _QMIDMS_UIM_SET_CK_PROTECTION_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR Facility; + UCHAR FacilityState; + UCHAR FacliltyLen; + UCHAR FacliltyValue; +} QMIDMS_UIM_SET_CK_PROTECTION_REQ_MSG, *PQMIDMS_UIM_SET_CK_PROTECTION_REQ_MSG; + +typedef struct _QMIDMS_UIM_SET_CK_PROTECTION_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR FacilityRetriesLeft; +} QMIDMS_UIM_SET_CK_PROTECTION_RESP_MSG, *PQMIDMS_UIM_SET_CK_PROTECTION_RESP_MSG; + + +typedef struct _UIM_PIN +{ + UCHAR PinLength; + UCHAR PinValue; +} UIM_PIN, *PUIM_PIN; + +typedef struct _QMIDMS_UIM_CHANGE_PIN_REQ_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + UCHAR PINID; + UCHAR PinDetails; +} QMIDMS_UIM_CHANGE_PIN_REQ_MSG, *PQMIDMS_UIM_CHANGE_PIN_REQ_MSG; + +typedef struct QMIDMS_UIM_CHANGE_PIN_RESP_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR PINVerifyRetriesLeft; + UCHAR PINUnblockRetriesLeft; +} QMIDMS_UIM_CHANGE_PIN_RESP_MSG, *PQMIDMS_UIM_CHANGE_PIN_RESP_MSG; + +typedef struct _UIM_PUK +{ + UCHAR PukLength; + UCHAR PukValue; +} UIM_PUK, *PUIM_PUK; + +typedef struct _QMIDMS_UIM_UNBLOCK_PIN_REQ_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + UCHAR PINID; + UCHAR PinDetails; +} QMIDMS_UIM_UNBLOCK_PIN_REQ_MSG, *PQMIDMS_UIM_BLOCK_PIN_REQ_MSG; + +typedef struct QMIDMS_UIM_UNBLOCK_PIN_RESP_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR PINVerifyRetriesLeft; + UCHAR PINUnblockRetriesLeft; +} QMIDMS_UIM_UNBLOCK_PIN_RESP_MSG, *PQMIDMS_UIM_UNBLOCK_PIN_RESP_MSG; + +typedef struct _QMIDMS_UIM_UNBLOCK_CK_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR Facility; + UCHAR FacliltyUnblockLen; + UCHAR FacliltyUnblockValue; +} QMIDMS_UIM_UNBLOCK_CK_REQ_MSG, *PQMIDMS_UIM_BLOCK_CK_REQ_MSG; + +typedef struct QMIDMS_UIM_UNBLOCK_CK_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR FacilityUnblockRetriesLeft; +} QMIDMS_UIM_UNBLOCK_CK_RESP_MSG, *PQMIDMS_UIM_UNBLOCK_CK_RESP_MSG; + +typedef struct _QMIDMS_SET_EVENT_REPORT_REQ_MSG +{ + USHORT Type; + USHORT Length; +} QMIDMS_SET_EVENT_REPORT_REQ_MSG, *PQMIDMS_SET_EVENT_REPORT_REQ_MSG; + +typedef struct _QMIDMS_SET_EVENT_REPORT_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG +} QMIDMS_SET_EVENT_REPORT_RESP_MSG, *PQMIDMS_SET_EVENT_REPORT_RESP_MSG; + +typedef struct _PIN_STATUS +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR ReportPinState; +} PIN_STATUS, *PPIN_STATUS; + +typedef struct _POWER_STATUS +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR PowerStatus; + UCHAR BatteryLvl; +} POWER_STATUS, *PPOWER_STATUS; + +typedef struct _ACTIVATION_STATE +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT ActivationState; +} ACTIVATION_STATE, *PACTIVATION_STATE; + +typedef struct _ACTIVATION_STATE_REQ +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR ActivationState; +} ACTIVATION_STATE_REQ, *PACTIVATION_STATE_REQ; + +typedef struct _OPERATING_MODE +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR OperatingMode; +} OPERATING_MODE, *POPERATING_MODE; + +typedef struct _UIM_STATE +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR UIMState; +} UIM_STATE, *PUIM_STATE; + +typedef struct _WIRELESS_DISABLE_STATE +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR WirelessDisableState; +} WIRELESS_DISABLE_STATE, *PWIRELESS_DISABLE_STATE; + +typedef struct _QMIDMS_EVENT_REPORT_IND_MSG +{ + USHORT Type; + USHORT Length; +} QMIDMS_EVENT_REPORT_IND_MSG, *PQMIDMS_EVENT_REPORT_IND_MSG; +#endif + +// ============================ END OF DMS =============================== + +// ======================= QOS ============================== +typedef struct _MPIOC_DEV_INFO MPIOC_DEV_INFO, *PMPIOC_DEV_INFO; + +#define QMI_QOS_SET_EVENT_REPORT_REQ 0x0001 +#define QMI_QOS_SET_EVENT_REPORT_RESP 0x0001 +#define QMI_QOS_EVENT_REPORT_IND 0x0001 + +#if 0 +typedef struct _QMI_QOS_SET_EVENT_REPORT_REQ_MSG +{ + USHORT Type; // QMUX type 0x0001 + USHORT Length; + // UCHAR TLVType; // 0x01 - physical link state + // USHORT TLVLength; // 1 + // UCHAR PhyLinkStatusRpt; // 0-enable; 1-disable + UCHAR TLVType2; // 0x02 = global flow reporting + USHORT TLVLength2; // 1 + UCHAR GlobalFlowRpt; // 1-enable; 0-disable +} QMI_QOS_SET_EVENT_REPORT_REQ_MSG, *PQMI_QOS_SET_EVENT_REPORT_REQ_MSG; + +typedef struct _QMI_QOS_SET_EVENT_REPORT_RESP_MSG +{ + USHORT Type; // QMUX type 0x0010 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMUX_RESULT_SUCCESS + // QMUX_RESULT_FAILURE + USHORT QMUXError; // QMUX_ERR_INVALID_ARG + // QMUX_ERR_NO_MEMORY + // QMUX_ERR_INTERNAL + // QMUX_ERR_FAULT +} QMI_QOS_SET_EVENT_REPORT_RESP_MSG, *PQMI_QOS_SET_EVENT_REPORT_RESP_MSG; + +typedef struct _QMI_QOS_EVENT_REPORT_IND_MSG +{ + USHORT Type; // QMUX type 0x0001 + USHORT Length; + UCHAR TLVs; +} QMI_QOS_EVENT_REPORT_IND_MSG, *PQMI_QOS_EVENT_REPORT_IND_MSG; + +#define QOS_EVENT_RPT_IND_FLOW_ACTIVATED 0x01 +#define QOS_EVENT_RPT_IND_FLOW_MODIFIED 0x02 +#define QOS_EVENT_RPT_IND_FLOW_DELETED 0x03 +#define QOS_EVENT_RPT_IND_FLOW_SUSPENDED 0x04 +#define QOS_EVENT_RPT_IND_FLOW_ENABLED 0x05 +#define QOS_EVENT_RPT_IND_FLOW_DISABLED 0x06 + +#define QOS_EVENT_RPT_IND_TLV_PHY_LINK_STATE_TYPE 0x01 +#define QOS_EVENT_RPT_IND_TLV_GLOBAL_FL_RPT_STATE 0x10 +#define QOS_EVENT_RPT_IND_TLV_GLOBAL_FL_RPT_TYPE 0x10 +#define QOS_EVENT_RPT_IND_TLV_TX_FLOW_TYPE 0x11 +#define QOS_EVENT_RPT_IND_TLV_RX_FLOW_TYPE 0x12 +#define QOS_EVENT_RPT_IND_TLV_TX_FILTER_TYPE 0x13 +#define QOS_EVENT_RPT_IND_TLV_RX_FILTER_TYPE 0x14 +#define QOS_EVENT_RPT_IND_TLV_FLOW_SPEC 0x10 +#define QOS_EVENT_RPT_IND_TLV_FILTER_SPEC 0x10 + +typedef struct _QOS_EVENT_RPT_IND_TLV_PHY_LINK_STATE +{ + UCHAR TLVType; // 0x01 + USHORT TLVLength; // 1 + UCHAR PhyLinkState; // 0-dormant, 1-active +} QOS_EVENT_RPT_IND_TLV_PHY_LINK_STATE, *PQOS_EVENT_RPT_IND_TLV_PHY_LINK_STATE; + +typedef struct _QOS_EVENT_RPT_IND_TLV_GLOBAL_FL_RPT +{ + UCHAR TLVType; // 0x10 + USHORT TLVLength; // 6 + ULONG QosId; + UCHAR NewFlow; // 1: newly added flow; 0: existing flow + UCHAR StateChange; // 1: activated; 2: modified; 3: deleted; + // 4: suspended(delete); 5: enabled; 6: disabled +} QOS_EVENT_RPT_IND_TLV_GLOBAL_FL_RPT, *PQOS_EVENT_RPT_IND_TLV_GLOBAL_FL_RPT; + +// QOS Flow + +typedef struct _QOS_EVENT_RPT_IND_TLV_FLOW +{ + UCHAR TLVType; // 0x10-TX flow; 0x11-RX flow + USHORT TLVLength; // var + // embedded TLV's +} QOS_EVENT_RPT_IND_TLV_TX_FLOW, *PQOS_EVENT_RPT_IND_TLV_TX_FLOW; + +#define QOS_FLOW_TLV_IP_FLOW_IDX_TYPE 0x10 +#define QOS_FLOW_TLV_IP_FLOW_TRAFFIC_CLASS_TYPE 0x11 +#define QOS_FLOW_TLV_IP_FLOW_DATA_RATE_MIN_MAX_TYPE 0x12 +#define QOS_FLOW_TLV_IP_FLOW_DATA_RATE_TOKEN_BUCKET_TYPE 0x13 +#define QOS_FLOW_TLV_IP_FLOW_LATENCY_TYPE 0x14 +#define QOS_FLOW_TLV_IP_FLOW_JITTER_TYPE 0x15 +#define QOS_FLOW_TLV_IP_FLOW_PKT_ERR_RATE_TYPE 0x16 +#define QOS_FLOW_TLV_IP_FLOW_MIN_PKT_SIZE_TYPE 0x17 +#define QOS_FLOW_TLV_IP_FLOW_MAX_PKT_SIZE_TYPE 0x18 +#define QOS_FLOW_TLV_IP_FLOW_3GPP_BIT_ERR_RATE_TYPE 0x19 +#define QOS_FLOW_TLV_IP_FLOW_3GPP_TRAF_PRIORITY_TYPE 0x1A +#define QOS_FLOW_TLV_IP_FLOW_3GPP2_PROFILE_ID_TYPE 0x1B + +typedef struct _QOS_FLOW_TLV_IP_FLOW_IDX +{ + UCHAR TLVType; // 0x10 + USHORT TLVLength; // 1 + UCHAR IpFlowIndex; +} QOS_FLOW_TLV_IP_FLOW_IDX, *PQOS_FLOW_TLV_IP_FLOW_IDX; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_TRAFFIC_CLASS +{ + UCHAR TLVType; // 0x11 + USHORT TLVLength; // 1 + UCHAR TrafficClass; +} QOS_FLOW_TLV_IP_FLOW_TRAFFIC_CLASS, *PQOS_FLOW_TLV_IP_FLOW_TRAFFIC_CLASS; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_DATA_RATE_MIN_MAX +{ + UCHAR TLVType; // 0x12 + USHORT TLVLength; // 8 + ULONG DataRateMax; + ULONG GuaranteedRate; +} QOS_FLOW_TLV_IP_FLOW_DATA_RATE_MIN_MAX, *PQOS_FLOW_TLV_IP_FLOW_DATA_RATE_MIN_MAX; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_DATA_RATE_TOKEN_BUCKET +{ + UCHAR TLVType; // 0x13 + USHORT TLVLength; // 12 + ULONG PeakRate; + ULONG TokenRate; + ULONG BucketSize; +} QOS_FLOW_TLV_IP_FLOW_DATA_RATE_TOKEN_BUCKET, *PQOS_FLOW_TLV_IP_FLOW_DATA_RATE_TOKEN_BUCKET; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_LATENCY +{ + UCHAR TLVType; // 0x14 + USHORT TLVLength; // 4 + ULONG IpFlowLatency; +} QOS_FLOW_TLV_IP_FLOW_LATENCY, *PQOS_FLOW_TLV_IP_FLOW_LATENCY; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_JITTER +{ + UCHAR TLVType; // 0x15 + USHORT TLVLength; // 4 + ULONG IpFlowJitter; +} QOS_FLOW_TLV_IP_FLOW_JITTER, *PQOS_FLOW_TLV_IP_FLOW_JITTER; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_PKT_ERR_RATE +{ + UCHAR TLVType; // 0x16 + USHORT TLVLength; // 4 + USHORT ErrRateMultiplier; + USHORT ErrRateExponent; +} QOS_FLOW_TLV_IP_FLOW_PKT_ERR_RATE, *PQOS_FLOW_TLV_IP_FLOW_PKT_ERR_RATE; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_MIN_PKT_SIZE +{ + UCHAR TLVType; // 0x17 + USHORT TLVLength; // 4 + ULONG MinPolicedPktSize; +} QOS_FLOW_TLV_IP_FLOW_MIN_PKT_SIZE, *PQOS_FLOW_TLV_IP_FLOW_MIN_PKT_SIZE; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_MAX_PKT_SIZE +{ + UCHAR TLVType; // 0x18 + USHORT TLVLength; // 4 + ULONG MaxAllowedPktSize; +} QOS_FLOW_TLV_IP_FLOW_MAX_PKT_SIZE, *PQOS_FLOW_TLV_IP_FLOW_MAX_PKT_SIZE; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_3GPP_BIT_ERR_RATE +{ + UCHAR TLVType; // 0x19 + USHORT TLVLength; // 1 + UCHAR ResidualBitErrorRate; +} QOS_FLOW_TLV_IP_FLOW_3GPP_BIT_ERR_RATE, *PQOS_FLOW_TLV_IP_FLOW_3GPP_BIT_ERR_RATE; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_3GPP_TRAF_PRIORITY +{ + UCHAR TLVType; // 0x1A + USHORT TLVLength; // 1 + UCHAR TrafficHandlingPriority; +} QOS_FLOW_TLV_IP_FLOW_3GPP_TRAF_PRIORITY, *PQOS_FLOW_TLV_IP_FLOW_3GPP_TRAF_PRIORITY; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_3GPP2_PROFILE_ID +{ + UCHAR TLVType; // 0x1B + USHORT TLVLength; // 2 + USHORT ProfileId; +} QOS_FLOW_TLV_IP_FLOW_3GPP2_PROFILE_ID, *PQOS_FLOW_TLV_IP_FLOW_3GPP2_PROFILE_ID; + +// QOS Filter + +#define QOS_FILTER_TLV_IP_FILTER_IDX_TYPE 0x10 +#define QOS_FILTER_TLV_IP_VERSION_TYPE 0x11 +#define QOS_FILTER_TLV_IPV4_SRC_ADDR_TYPE 0x12 +#define QOS_FILTER_TLV_IPV4_DEST_ADDR_TYPE 0x13 +#define QOS_FILTER_TLV_NEXT_HDR_PROTOCOL_TYPE 0x14 +#define QOS_FILTER_TLV_IPV4_TYPE_OF_SERVICE_TYPE 0x15 +#define QOS_FILTER_TLV_TCP_UDP_PORT_SRC_TCP_TYPE 0x1B +#define QOS_FILTER_TLV_TCP_UDP_PORT_DEST_TCP_TYPE 0x1C +#define QOS_FILTER_TLV_TCP_UDP_PORT_SRC_UDP_TYPE 0x1D +#define QOS_FILTER_TLV_TCP_UDP_PORT_DEST_UDP_TYPE 0x1E +#define QOS_FILTER_TLV_ICMP_FILTER_MSG_TYPE_TYPE 0x1F +#define QOS_FILTER_TLV_ICMP_FILTER_MSG_CODE_TYPE 0x20 +#define QOS_FILTER_TLV_TCP_UDP_PORT_SRC_TYPE 0x24 +#define QOS_FILTER_TLV_TCP_UDP_PORT_DEST_TYPE 0x25 + +typedef struct _QOS_EVENT_RPT_IND_TLV_FILTER +{ + UCHAR TLVType; // 0x12-TX filter; 0x13-RX filter + USHORT TLVLength; // var + // embedded TLV's +} QOS_EVENT_RPT_IND_TLV_RX_FILTER, *PQOS_EVENT_RPT_IND_TLV_RX_FILTER; + +typedef struct _QOS_FILTER_TLV_IP_FILTER_IDX +{ + UCHAR TLVType; // 0x10 + USHORT TLVLength; // 1 + UCHAR IpFilterIndex; +} QOS_FILTER_TLV_IP_FILTER_IDX, *PQOS_FILTER_TLV_IP_FILTER_IDX; + +typedef struct _QOS_FILTER_TLV_IP_VERSION +{ + UCHAR TLVType; // 0x11 + USHORT TLVLength; // 1 + UCHAR IpVersion; +} QOS_FILTER_TLV_IP_VERSION, *PQOS_FILTER_TLV_IP_VERSION; + +typedef struct _QOS_FILTER_TLV_IPV4_SRC_ADDR +{ + UCHAR TLVType; // 0x12 + USHORT TLVLength; // 8 + ULONG IpSrcAddr; + ULONG IpSrcSubnetMask; +} QOS_FILTER_TLV_IPV4_SRC_ADDR, *PQOS_FILTER_TLV_IPV4_SRC_ADDR; + +typedef struct _QOS_FILTER_TLV_IPV4_DEST_ADDR +{ + UCHAR TLVType; // 0x13 + USHORT TLVLength; // 8 + ULONG IpDestAddr; + ULONG IpDestSubnetMask; +} QOS_FILTER_TLV_IPV4_DEST_ADDR, *PQOS_FILTER_TLV_IPV4_DEST_ADDR; + +typedef struct _QOS_FILTER_TLV_NEXT_HDR_PROTOCOL +{ + UCHAR TLVType; // 0x14 + USHORT TLVLength; // 1 + UCHAR NextHdrProtocol; +} QOS_FILTER_TLV_NEXT_HDR_PROTOCOL, *PQOS_FILTER_TLV_NEXT_HDR_PROTOCOL; + +typedef struct _QOS_FILTER_TLV_IPV4_TYPE_OF_SERVICE +{ + UCHAR TLVType; // 0x15 + USHORT TLVLength; // 2 + UCHAR Ipv4TypeOfService; + UCHAR Ipv4TypeOfServiceMask; +} QOS_FILTER_TLV_IPV4_TYPE_OF_SERVICE, *PQOS_FILTER_TLV_IPV4_TYPE_OF_SERVICE; + +typedef struct _QOS_FILTER_TLV_TCP_UDP_PORT +{ + UCHAR TLVType; // source port: 0x1B-TCP; 0x1D-UDP + // dest port: 0x1C-TCP; 0x1E-UDP + USHORT TLVLength; // 4 + USHORT FilterPort; + USHORT FilterPortRange; +} QOS_FILTER_TLV_TCP_UDP_PORT, *PQOS_FILTER_TLV_TCP_UDP_PORT; + +typedef struct _QOS_FILTER_TLV_ICMP_FILTER_MSG_TYPE +{ + UCHAR TLVType; // 0x1F + USHORT TLVLength; // 1 + UCHAR IcmpFilterMsgType; +} QOS_FILTER_TLV_ICMP_FILTER_MSG_TYPE, *PQOS_FILTER_TLV_ICMP_FILTER_MSG_TYPE; + +typedef struct _QOS_FILTER_TLV_ICMP_FILTER_MSG_CODE +{ + UCHAR TLVType; // 0x20 + USHORT TLVLength; // 1 + UCHAR IcmpFilterMsgCode; +} QOS_FILTER_TLV_ICMP_FILTER_MSG_CODE, *PQOS_FILTER_TLV_ICMP_FILTER_MSG_CODE; + +#define QOS_FILTER_PRECEDENCE_INVALID 256 +#define QOS_FILTER_TLV_PRECEDENCE_TYPE 0x22 +#define QOS_FILTER_TLV_ID_TYPE 0x23 + +typedef struct _QOS_FILTER_TLV_PRECEDENCE +{ + UCHAR TLVType; // 0x22 + USHORT TLVLength; // 2 + USHORT Precedence; // precedence of the filter +} QOS_FILTER_TLV_PRECEDENCE, *PQOS_FILTER_TLV_PRECEDENCE; + +typedef struct _QOS_FILTER_TLV_ID +{ + UCHAR TLVType; // 0x23 + USHORT TLVLength; // 2 + USHORT FilterId; // filter ID +} QOS_FILTER_TLV_ID, *PQOS_FILTER_TLV_ID; + +#ifdef QCQOS_IPV6 + +#define QOS_FILTER_TLV_IPV6_SRC_ADDR_TYPE 0x16 +#define QOS_FILTER_TLV_IPV6_DEST_ADDR_TYPE 0x17 +#define QOS_FILTER_TLV_IPV6_NEXT_HDR_PROTOCOL_TYPE 0x14 // same as IPV4 +#define QOS_FILTER_TLV_IPV6_TRAFFIC_CLASS_TYPE 0x19 +#define QOS_FILTER_TLV_IPV6_FLOW_LABEL_TYPE 0x1A + +typedef struct _QOS_FILTER_TLV_IPV6_SRC_ADDR +{ + UCHAR TLVType; // 0x16 + USHORT TLVLength; // 17 + UCHAR IpSrcAddr[16]; + UCHAR IpSrcAddrPrefixLen; // [0..128] +} QOS_FILTER_TLV_IPV6_SRC_ADDR, *PQOS_FILTER_TLV_IPV6_SRC_ADDR; + +typedef struct _QOS_FILTER_TLV_IPV6_DEST_ADDR +{ + UCHAR TLVType; // 0x17 + USHORT TLVLength; // 17 + UCHAR IpDestAddr[16]; + UCHAR IpDestAddrPrefixLen; // [0..128] +} QOS_FILTER_TLV_IPV6_DEST_ADDR, *PQOS_FILTER_TLV_IPV6_DEST_ADDR; + +#define QOS_FILTER_IPV6_NEXT_HDR_PROTOCOL_TCP 0x06 +#define QOS_FILTER_IPV6_NEXT_HDR_PROTOCOL_UDP 0x11 + +typedef struct _QOS_FILTER_TLV_IPV6_TRAFFIC_CLASS +{ + UCHAR TLVType; // 0x19 + USHORT TLVLength; // 2 + UCHAR TrafficClass; + UCHAR TrafficClassMask; // compare the first 6 bits only +} QOS_FILTER_TLV_IPV6_TRAFFIC_CLASS, *PQOS_FILTER_TLV_IPV6_TRAFFIC_CLASS; + +typedef struct _QOS_FILTER_TLV_IPV6_FLOW_LABEL +{ + UCHAR TLVType; // 0x1A + USHORT TLVLength; // 4 + ULONG FlowLabel; +} QOS_FILTER_TLV_IPV6_FLOW_LABEL, *PQOS_FILTER_TLV_IPV6_FLOW_LABEL; + +#endif // QCQOS_IPV6 +#endif + +// ======================= WMS ============================== +#define QMIWMS_SET_EVENT_REPORT_REQ 0x0001 +#define QMIWMS_SET_EVENT_REPORT_RESP 0x0001 +#define QMIWMS_EVENT_REPORT_IND 0x0001 +#define QMIWMS_RAW_SEND_REQ 0x0020 +#define QMIWMS_RAW_SEND_RESP 0x0020 +#define QMIWMS_RAW_WRITE_REQ 0x0021 +#define QMIWMS_RAW_WRITE_RESP 0x0021 +#define QMIWMS_RAW_READ_REQ 0x0022 +#define QMIWMS_RAW_READ_RESP 0x0022 +#define QMIWMS_MODIFY_TAG_REQ 0x0023 +#define QMIWMS_MODIFY_TAG_RESP 0x0023 +#define QMIWMS_DELETE_REQ 0x0024 +#define QMIWMS_DELETE_RESP 0x0024 +#define QMIWMS_GET_MESSAGE_PROTOCOL_REQ 0x0030 +#define QMIWMS_GET_MESSAGE_PROTOCOL_RESP 0x0030 +#define QMIWMS_LIST_MESSAGES_REQ 0x0031 +#define QMIWMS_LIST_MESSAGES_RESP 0x0031 +#define QMIWMS_GET_SMSC_ADDRESS_REQ 0x0034 +#define QMIWMS_GET_SMSC_ADDRESS_RESP 0x0034 +#define QMIWMS_SET_SMSC_ADDRESS_REQ 0x0035 +#define QMIWMS_SET_SMSC_ADDRESS_RESP 0x0035 +#define QMIWMS_GET_STORE_MAX_SIZE_REQ 0x0036 +#define QMIWMS_GET_STORE_MAX_SIZE_RESP 0x0036 + + +#define WMS_MESSAGE_PROTOCOL_CDMA 0x00 +#define WMS_MESSAGE_PROTOCOL_WCDMA 0x01 + +#if 0 +typedef struct _QMIWMS_GET_MESSAGE_PROTOCOL_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; +} QMIWMS_GET_MESSAGE_PROTOCOL_REQ_MSG, *PQMIWMS_GET_MESSAGE_PROTOCOL_REQ_MSG; + +typedef struct _QMIWMS_GET_MESSAGE_PROTOCOL_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR MessageProtocol; +} QMIWMS_GET_MESSAGE_PROTOCOL_RESP_MSG, *PQMIWMS_GET_MESSAGE_PROTOCOL_RESP_MSG; + +typedef struct _QMIWMS_GET_STORE_MAX_SIZE_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR StorageType; +} QMIWMS_GET_STORE_MAX_SIZE_REQ_MSG, *PQMIWMS_GET_STORE_MAX_SIZE_REQ_MSG; + +typedef struct _QMIWMS_GET_STORE_MAX_SIZE_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLV2Type; + USHORT TLV2Length; + ULONG MemStoreMaxSize; +} QMIWMS_GET_STORE_MAX_SIZE_RESP_MSG, *PQMIWMS_GET_STORE_MAX_SIZE_RESP_MSG; + +typedef struct _REQUEST_TAG +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR TagType; +} REQUEST_TAG, *PREQUEST_TAG; + +typedef struct _QMIWMS_LIST_MESSAGES_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR StorageType; +} QMIWMS_LIST_MESSAGES_REQ_MSG, *PQMIWMS_LIST_MESSAGES_REQ_MSG; + +typedef struct _QMIWMS_MESSAGE +{ + ULONG MessageIndex; + UCHAR TagType; +} QMIWMS_MESSAGE, *PQMIWMS_MESSAGE; + +typedef struct _QMIWMS_LIST_MESSAGES_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLV2Type; + USHORT TLV2Length; + ULONG NumMessages; +} QMIWMS_LIST_MESSAGES_RESP_MSG, *PQMIWMS_LIST_MESSAGES_RESP_MSG; + +typedef struct _QMIWMS_RAW_READ_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR StorageType; + ULONG MemoryIndex; +} QMIWMS_RAW_READ_REQ_MSG, *PQMIWMS_RAW_READ_REQ_MSG; + +typedef struct _QMIWMS_RAW_READ_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR TagType; + UCHAR Format; + USHORT MessageLength; + UCHAR Message; +} QMIWMS_RAW_READ_RESP_MSG, *PQMIWMS_RAW_READ_RESP_MSG; + +typedef struct _QMIWMS_MODIFY_TAG_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR StorageType; + ULONG MemoryIndex; + UCHAR TagType; +} QMIWMS_MODIFY_TAG_REQ_MSG, *PQMIWMS_MODIFY_TAG_REQ_MSG; + +typedef struct _QMIWMS_MODIFY_TAG_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} QMIWMS_MODIFY_TAG_RESP_MSG, *PQMIWMS_MODIFY_TAG_RESP_MSG; + +typedef struct _QMIWMS_RAW_SEND_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR SmsFormat; + USHORT SmsLength; + UCHAR SmsMessage; +} QMIWMS_RAW_SEND_REQ_MSG, *PQMIWMS_RAW_SEND_REQ_MSG; + +typedef struct _RAW_SEND_CAUSE_CODE +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT CauseCode; +} RAW_SEND_CAUSE_CODE, *PRAW_SEND_CAUSE_CODE; + + +typedef struct _QMIWMS_RAW_SEND_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} QMIWMS_RAW_SEND_RESP_MSG, *PQMIWMS_RAW_SEND_RESP_MSG; + + +typedef struct _WMS_DELETE_MESSAGE_INDEX +{ + UCHAR TLVType; + USHORT TLVLength; + ULONG MemoryIndex; +} WMS_DELETE_MESSAGE_INDEX, *PWMS_DELETE_MESSAGE_INDEX; + +typedef struct _WMS_DELETE_MESSAGE_TAG +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR MessageTag; +} WMS_DELETE_MESSAGE_TAG, *PWMS_DELETE_MESSAGE_TAG; + +typedef struct _QMIWMS_DELETE_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR StorageType; +} QMIWMS_DELETE_REQ_MSG, *PQMIWMS_DELETE_REQ_MSG; + +typedef struct _QMIWMS_DELETE_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} QMIWMS_DELETE_RESP_MSG, *PQMIWMS_DELETE_RESP_MSG; + + +typedef struct _QMIWMS_GET_SMSC_ADDRESS_REQ_MSG +{ + USHORT Type; + USHORT Length; +} QMIWMS_GET_SMSC_ADDRESS_REQ_MSG, *PQMIWMS_GET_SMSC_ADDRESS_REQ_MSG; + +typedef struct _QMIWMS_SMSC_ADDRESS +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR SMSCAddressType[3]; + UCHAR SMSCAddressLength; + UCHAR SMSCAddressDigits; +} QMIWMS_SMSC_ADDRESS, *PQMIWMS_SMSC_ADDRESS; + + +typedef struct _QMIWMS_GET_SMSC_ADDRESS_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR SMSCAddress; +} QMIWMS_GET_SMSC_ADDRESS_RESP_MSG, *PQMIWMS_GET_SMSC_ADDRESS_RESP_MSG; + +typedef struct _QMIWMS_SET_SMSC_ADDRESS_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR SMSCAddress; +} QMIWMS_SET_SMSC_ADDRESS_REQ_MSG, *PQMIWMS_SET_SMSC_ADDRESS_REQ_MSG; + +typedef struct _QMIWMS_SET_SMSC_ADDRESS_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} QMIWMS_SET_SMSC_ADDRESS_RESP_MSG, *PQMIWMS_SET_SMSC_ADDRESS_RESP_MSG; + +typedef struct _QMIWMS_SET_EVENT_REPORT_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR ReportNewMessage; +} QMIWMS_SET_EVENT_REPORT_REQ_MSG, *PQMIWMS_SET_EVENT_REPORT_REQ_MSG; + +typedef struct _QMIWMS_SET_EVENT_REPORT_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} QMIWMS_SET_EVENT_REPORT_RESP_MSG, *PQMIWMS_SET_EVENT_REPORT_RESP_MSG; + +typedef struct _QMIWMS_EVENT_REPORT_IND_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR StorageType; + ULONG StorageIndex; +} QMIWMS_EVENT_REPORT_IND_MSG, *PQMIWMS_EVENT_REPORT_IND_MSG; +#endif + +// ======================= End of WMS ============================== + + +// ======================= NAS ============================== +#define QMINAS_SET_EVENT_REPORT_REQ 0x0002 +#define QMINAS_SET_EVENT_REPORT_RESP 0x0002 +#define QMINAS_EVENT_REPORT_IND 0x0002 +#define QMINAS_GET_SIGNAL_STRENGTH_REQ 0x0020 +#define QMINAS_GET_SIGNAL_STRENGTH_RESP 0x0020 +#define QMINAS_PERFORM_NETWORK_SCAN_REQ 0x0021 +#define QMINAS_PERFORM_NETWORK_SCAN_RESP 0x0021 +#define QMINAS_INITIATE_NW_REGISTER_REQ 0x0022 +#define QMINAS_INITIATE_NW_REGISTER_RESP 0x0022 +#define QMINAS_INITIATE_ATTACH_REQ 0x0023 +#define QMINAS_INITIATE_ATTACH_RESP 0x0023 +#define QMINAS_GET_SERVING_SYSTEM_REQ 0x0024 +#define QMINAS_GET_SERVING_SYSTEM_RESP 0x0024 +#define QMINAS_SERVING_SYSTEM_IND 0x0024 +#define QMINAS_GET_HOME_NETWORK_REQ 0x0025 +#define QMINAS_GET_HOME_NETWORK_RESP 0x0025 +#define QMINAS_GET_PREFERRED_NETWORK_REQ 0x0026 +#define QMINAS_GET_PREFERRED_NETWORK_RESP 0x0026 +#define QMINAS_SET_PREFERRED_NETWORK_REQ 0x0027 +#define QMINAS_SET_PREFERRED_NETWORK_RESP 0x0027 +#define QMINAS_GET_FORBIDDEN_NETWORK_REQ 0x0028 +#define QMINAS_GET_FORBIDDEN_NETWORK_RESP 0x0028 +#define QMINAS_SET_FORBIDDEN_NETWORK_REQ 0x0029 +#define QMINAS_SET_FORBIDDEN_NETWORK_RESP 0x0029 +#define QMINAS_SET_TECHNOLOGY_PREF_REQ 0x002A +#define QMINAS_SET_TECHNOLOGY_PREF_RESP 0x002A +#define QMINAS_GET_RF_BAND_INFO_REQ 0x0031 +#define QMINAS_GET_RF_BAND_INFO_RESP 0x0031 +#define QMINAS_GET_PLMN_NAME_REQ 0x0044 +#define QMINAS_GET_PLMN_NAME_RESP 0x0044 +#define QUECTEL_PACKET_TRANSFER_START_IND 0X100 +#define QUECTEL_PACKET_TRANSFER_END_IND 0X101 +#define QMINAS_GET_SYS_INFO_REQ 0x004D +#define QMINAS_GET_SYS_INFO_RESP 0x004D +#define QMINAS_SYS_INFO_IND 0x004D + +typedef struct _QMINAS_GET_HOME_NETWORK_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; +} __attribute__ ((packed)) QMINAS_GET_HOME_NETWORK_REQ_MSG, *PQMINAS_GET_HOME_NETWORK_REQ_MSG; + +typedef struct _HOME_NETWORK_SYSTEMID +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT SystemID; + USHORT NetworkID; +} __attribute__ ((packed)) HOME_NETWORK_SYSTEMID, *PHOME_NETWORK_SYSTEMID; + +typedef struct _HOME_NETWORK +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT MobileCountryCode; + USHORT MobileNetworkCode; + UCHAR NetworkDesclen; + UCHAR NetworkDesc; +} __attribute__ ((packed)) HOME_NETWORK, *PHOME_NETWORK; + +#if 0 +typedef struct _HOME_NETWORK_EXT +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT MobileCountryCode; + USHORT MobileNetworkCode; + UCHAR NetworkDescDisp; + UCHAR NetworkDescEncoding; + UCHAR NetworkDesclen; + UCHAR NetworkDesc; +} HOME_NETWORK_EXT, *PHOME_NETWORK_EXT; + +typedef struct _QMINAS_GET_HOME_NETWORK_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} QMINAS_GET_HOME_NETWORK_RESP_MSG, *PQMINAS_GET_HOME_NETWORK_RESP_MSG; + +typedef struct _QMINAS_GET_PREFERRED_NETWORK_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; +} QMINAS_GET_PREFERRED_NETWORK_REQ_MSG, *PQMINAS_GET_PREFERRED_NETWORK_REQ_MSG; + + +typedef struct _PREFERRED_NETWORK +{ + USHORT MobileCountryCode; + USHORT MobileNetworkCode; + USHORT RadioAccess; +} PREFERRED_NETWORK, *PPREFERRED_NETWORK; + +typedef struct _QMINAS_GET_PREFERRED_NETWORK_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR TLV2Type; // 0x01 - required parameter + USHORT TLV2Length; // length of the mfr string + USHORT NumPreferredNetwork; +} QMINAS_GET_PREFERRED_NETWORK_RESP_MSG, *PQMINAS_GET_PREFERRED_NETWORK_RESP_MSG; + +typedef struct _QMINAS_GET_FORBIDDEN_NETWORK_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; +} QMINAS_GET_FORBIDDEN_NETWORK_REQ_MSG, *PQMINAS_GET_FORBIDDEN_NETWORK_REQ_MSG; + +typedef struct _FORBIDDEN_NETWORK +{ + USHORT MobileCountryCode; + USHORT MobileNetworkCode; +} FORBIDDEN_NETWORK, *PFORBIDDEN_NETWORK; + +typedef struct _QMINAS_GET_FORBIDDEN_NETWORK_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR TLV2Type; // 0x01 - required parameter + USHORT TLV2Length; // length of the mfr string + USHORT NumForbiddenNetwork; +} QMINAS_GET_FORBIDDEN_NETWORK_RESP_MSG, *PQMINAS_GET_FORBIDDEN_NETWORK_RESP_MSG; + +typedef struct _QMINAS_GET_SERVING_SYSTEM_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; +} QMINAS_GET_SERVING_SYSTEM_REQ_MSG, *PQMINAS_GET_SERVING_SYSTEM_REQ_MSG; + +typedef struct _QMINAS_ROAMING_INDICATOR_MSG +{ + UCHAR TLVType; // 0x01 - required parameter + USHORT TLVLength; // length of the mfr string + UCHAR RoamingIndicator; +} QMINAS_ROAMING_INDICATOR_MSG, *PQMINAS_ROAMING_INDICATOR_MSG; +#endif + +typedef struct _QMINAS_DATA_CAP +{ + UCHAR TLVType; // 0x01 - required parameter + USHORT TLVLength; // length of the mfr string + UCHAR DataCapListLen; + UCHAR DataCap; +} __attribute__ ((packed)) QMINAS_DATA_CAP, *PQMINAS_DATA_CAP; + +typedef struct _QMINAS_CURRENT_PLMN_MSG +{ + UCHAR TLVType; // 0x01 - required parameter + USHORT TLVLength; // length of the mfr string + USHORT MobileCountryCode; + USHORT MobileNetworkCode; + UCHAR NetworkDesclen; + UCHAR NetworkDesc; +} __attribute__ ((packed)) QMINAS_CURRENT_PLMN_MSG, *PQMINAS_CURRENT_PLMN_MSG; + +typedef struct _QMINAS_GET_SERVING_SYSTEM_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} __attribute__ ((packed)) QMINAS_GET_SERVING_SYSTEM_RESP_MSG, *PQMINAS_GET_SERVING_SYSTEM_RESP_MSG; + +typedef struct _SERVING_SYSTEM +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR RegistrationState; + UCHAR CSAttachedState; + UCHAR PSAttachedState; + UCHAR RegistredNetwork; + UCHAR InUseRadioIF; + UCHAR RadioIF; +} __attribute__ ((packed)) SERVING_SYSTEM, *PSERVING_SYSTEM; + +typedef struct _QMINAS_GET_SYS_INFO_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} __attribute__ ((packed)) QMINAS_GET_SYS_INFO_RESP_MSG, *PQMINAS_GET_SYS_INFO_RESP_MSG; + +typedef struct _QMINAS_SYS_INFO_IND_MSG +{ + USHORT Type; + USHORT Length; +} __attribute__ ((packed)) QMINAS_SYS_INFO_IND_MSG, *PQMINAS_SYS_INFO_IND_MSG; + +typedef struct _SERVICE_STATUS_INFO +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR SrvStatus; + UCHAR IsPrefDataPath; +} __attribute__ ((packed)) SERVICE_STATUS_INFO, *PSERVICE_STATUS_INFO; + +typedef struct _CDMA_SYSTEM_INFO +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR SrvDomainValid; + UCHAR SrvDomain; + UCHAR SrvCapabilityValid; + UCHAR SrvCapability; + UCHAR RoamStatusValid; + UCHAR RoamStatus; + UCHAR IsSysForbiddenValid; + UCHAR IsSysForbidden; + UCHAR IsSysPrlMatchValid; + UCHAR IsSysPrlMatch; + UCHAR PRevInUseValid; + UCHAR PRevInUse; + UCHAR BSPRevValid; + UCHAR BSPRev; + UCHAR CCSSupportedValid; + UCHAR CCSSupported; + UCHAR CDMASysIdValid; + USHORT SID; + USHORT NID; + UCHAR BSInfoValid; + USHORT BaseID; + ULONG BaseLAT; + ULONG BaseLONG; + UCHAR PacketZoneValid; + USHORT PacketZone; + UCHAR NetworkIdValid; + UCHAR MCC[3]; + UCHAR MNC[3]; +} __attribute__ ((packed)) CDMA_SYSTEM_INFO, *PCDMA_SYSTEM_INFO; + +typedef struct _HDR_SYSTEM_INFO +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR SrvDomainValid; + UCHAR SrvDomain; + UCHAR SrvCapabilityValid; + UCHAR SrvCapability; + UCHAR RoamStatusValid; + UCHAR RoamStatus; + UCHAR IsSysForbiddenValid; + UCHAR IsSysForbidden; + UCHAR IsSysPrlMatchValid; + UCHAR IsSysPrlMatch; + UCHAR HdrPersonalityValid; + UCHAR HdrPersonality; + UCHAR HdrActiveProtValid; + UCHAR HdrActiveProt; + UCHAR is856SysIdValid; + UCHAR is856SysId[16]; +} __attribute__ ((packed)) HDR_SYSTEM_INFO, *PHDR_SYSTEM_INFO; + +typedef struct _GSM_SYSTEM_INFO +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR SrvDomainValid; + UCHAR SrvDomain; + UCHAR SrvCapabilityValid; + UCHAR SrvCapability; + UCHAR RoamStatusValid; + UCHAR RoamStatus; + UCHAR IsSysForbiddenValid; + UCHAR IsSysForbidden; + UCHAR LacValid; + USHORT Lac; + UCHAR CellIdValid; + ULONG CellId; + UCHAR RegRejectInfoValid; + UCHAR RejectSrvDomain; + UCHAR RejCause; + UCHAR NetworkIdValid; + UCHAR MCC[3]; + UCHAR MNC[3]; + UCHAR EgprsSuppValid; + UCHAR EgprsSupp; + UCHAR DtmSuppValid; + UCHAR DtmSupp; +} __attribute__ ((packed)) GSM_SYSTEM_INFO, *PGSM_SYSTEM_INFO; + +typedef struct _WCDMA_SYSTEM_INFO +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR SrvDomainValid; + UCHAR SrvDomain; + UCHAR SrvCapabilityValid; + UCHAR SrvCapability; + UCHAR RoamStatusValid; + UCHAR RoamStatus; + UCHAR IsSysForbiddenValid; + UCHAR IsSysForbidden; + UCHAR LacValid; + USHORT Lac; + UCHAR CellIdValid; + ULONG CellId; + UCHAR RegRejectInfoValid; + UCHAR RejectSrvDomain; + UCHAR RejCause; + UCHAR NetworkIdValid; + UCHAR MCC[3]; + UCHAR MNC[3]; + UCHAR HsCallStatusValid; + UCHAR HsCallStatus; + UCHAR HsIndValid; + UCHAR HsInd; + UCHAR PscValid; + UCHAR Psc; +} __attribute__ ((packed)) WCDMA_SYSTEM_INFO, *PWCDMA_SYSTEM_INFO; + +typedef struct _LTE_SYSTEM_INFO +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR SrvDomainValid; + UCHAR SrvDomain; + UCHAR SrvCapabilityValid; + UCHAR SrvCapability; + UCHAR RoamStatusValid; + UCHAR RoamStatus; + UCHAR IsSysForbiddenValid; + UCHAR IsSysForbidden; + UCHAR LacValid; + USHORT Lac; + UCHAR CellIdValid; + ULONG CellId; + UCHAR RegRejectInfoValid; + UCHAR RejectSrvDomain; + UCHAR RejCause; + UCHAR NetworkIdValid; + UCHAR MCC[3]; + UCHAR MNC[3]; + UCHAR TacValid; + USHORT Tac; +} __attribute__ ((packed)) LTE_SYSTEM_INFO, *PLTE_SYSTEM_INFO; + +typedef struct _TDSCDMA_SYSTEM_INFO +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR SrvDomainValid; + UCHAR SrvDomain; + UCHAR SrvCapabilityValid; + UCHAR SrvCapability; + UCHAR RoamStatusValid; + UCHAR RoamStatus; + UCHAR IsSysForbiddenValid; + UCHAR IsSysForbidden; + UCHAR LacValid; + USHORT Lac; + UCHAR CellIdValid; + ULONG CellId; + UCHAR RegRejectInfoValid; + UCHAR RejectSrvDomain; + UCHAR RejCause; + UCHAR NetworkIdValid; + UCHAR MCC[3]; + UCHAR MNC[3]; + UCHAR HsCallStatusValid; + UCHAR HsCallStatus; + UCHAR HsIndValid; + UCHAR HsInd; + UCHAR CellParameterIdValid; + USHORT CellParameterId; + UCHAR CellBroadcastCapValid; + ULONG CellBroadcastCap; + UCHAR CsBarStatusValid; + ULONG CsBarStatus; + UCHAR PsBarStatusValid; + ULONG PsBarStatus; + UCHAR CipherDomainValid; + UCHAR CipherDomain; +} __attribute__ ((packed)) TDSCDMA_SYSTEM_INFO, *PTDSCDMA_SYSTEM_INFO; + +#if 0 +typedef struct _QMINAS_SERVING_SYSTEM_IND_MSG +{ + USHORT Type; + USHORT Length; +} QMINAS_SERVING_SYSTEM_IND_MSG, *PQMINAS_SERVING_SYSTEM_IND_MSG; + +typedef struct _QMINAS_SET_PREFERRED_NETWORK_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT NumPreferredNetwork; + USHORT MobileCountryCode; + USHORT MobileNetworkCode; + USHORT RadioAccess; +} QMINAS_SET_PREFERRED_NETWORK_REQ_MSG, *PQMINAS_SET_PREFERRED_NETWORK_REQ_MSG; + +typedef struct _QMINAS_SET_PREFERRED_NETWORK_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} QMINAS_SET_PREFERRED_NETWORK_RESP_MSG, *PQMINAS_SET_PREFERRED_NETWORK_RESP_MSG; + +typedef struct _QMINAS_SET_FORBIDDEN_NETWORK_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT NumForbiddenNetwork; + USHORT MobileCountryCode; + USHORT MobileNetworkCode; +} QMINAS_SET_FORBIDDEN_NETWORK_REQ_MSG, *PQMINAS_SET_FORBIDDEN_NETWORK_REQ_MSG; + +typedef struct _QMINAS_SET_FORBIDDEN_NETWORK_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} QMINAS_SET_FORBIDDEN_NETWORK_RESP_MSG, *PQMINAS_SET_FORBIDDEN_NETWORK_RESP_MSG; + +typedef struct _QMINAS_PERFORM_NETWORK_SCAN_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; +} QMINAS_PERFORM_NETWORK_SCAN_REQ_MSG, *PQMINAS_PERFORM_NETWORK_SCAN_REQ_MSG; + +typedef struct _VISIBLE_NETWORK +{ + USHORT MobileCountryCode; + USHORT MobileNetworkCode; + UCHAR NetworkStatus; + UCHAR NetworkDesclen; +} VISIBLE_NETWORK, *PVISIBLE_NETWORK; + +typedef struct _QMINAS_PERFORM_NETWORK_SCAN_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} QMINAS_PERFORM_NETWORK_SCAN_RESP_MSG, *PQMINAS_PERFORM_NETWORK_SCAN_RESP_MSG; + +typedef struct _QMINAS_PERFORM_NETWORK_SCAN_NETWORK_INFO +{ + UCHAR TLVType; // 0x010 - required parameter + USHORT TLVLength; // length + USHORT NumNetworkInstances; +} QMINAS_PERFORM_NETWORK_SCAN_NETWORK_INFO, *PQMINAS_PERFORM_NETWORK_SCAN_NETWORK_INFO; + +typedef struct _QMINAS_PERFORM_NETWORK_SCAN_RAT_INFO +{ + UCHAR TLVType; // 0x011 - required parameter + USHORT TLVLength; // length + USHORT NumInst; +} QMINAS_PERFORM_NETWORK_SCAN_RAT_INFO, *PQMINAS_PERFORM_NETWORK_SCAN_RAT_INFO; + +typedef struct _QMINAS_PERFORM_NETWORK_SCAN_RAT +{ + USHORT MCC; + USHORT MNC; + UCHAR RAT; +} QMINAS_PERFORM_NETWORK_SCAN_RAT, *PQMINAS_PERFORM_NETWORK_SCAN_RAT; + + +typedef struct _QMINAS_MANUAL_NW_REGISTER +{ + UCHAR TLV2Type; // 0x02 - result code + USHORT TLV2Length; // 4 + USHORT MobileCountryCode; + USHORT MobileNetworkCode; + UCHAR RadioAccess; +} QMINAS_MANUAL_NW_REGISTER, *PQMINAS_MANUAL_NW_REGISTER; + +typedef struct _QMINAS_INITIATE_NW_REGISTER_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + UCHAR RegisterAction; +} QMINAS_INITIATE_NW_REGISTER_REQ_MSG, *PQMINAS_INITIATE_NW_REGISTER_REQ_MSG; + +typedef struct _QMINAS_INITIATE_NW_REGISTER_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} QMINAS_INITIATE_NW_REGISTER_RESP_MSG, *PQMINAS_INITIATE_NW_REGISTER_RESP_MSG; + +typedef struct _QMINAS_SET_TECHNOLOGY_PREF_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT TechPref; + UCHAR Duration; +} QMINAS_SET_TECHNOLOGY_PREF_REQ_MSG, *PQMINAS_SET_TECHNOLOGY_PREF_REQ_MSG; + +typedef struct _QMINAS_SET_TECHNOLOGY_PREF_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} QMINAS_SET_TECHNOLOGY_PREF_RESP_MSG, *PQMINAS_SET_TECHNOLOGY_PREF_RESP_MSG; + +typedef struct _QMINAS_GET_SIGNAL_STRENGTH_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; +} QMINAS_GET_SIGNAL_STRENGTH_REQ_MSG, *PQMINAS_GET_SIGNAL_STRENGTH_REQ_MSG; + +typedef struct _QMINAS_SIGNAL_STRENGTH +{ + CHAR SigStrength; + UCHAR RadioIf; +} QMINAS_SIGNAL_STRENGTH, *PQMINAS_SIGNAL_STRENGTH; + +typedef struct _QMINAS_SIGNAL_STRENGTH_LIST +{ + UCHAR TLV3Type; + USHORT TLV3Length; + USHORT NumInstance; +} QMINAS_SIGNAL_STRENGTH_LIST, *PQMINAS_SIGNAL_STRENGTH_LIST; + + +typedef struct _QMINAS_GET_SIGNAL_STRENGTH_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR TLV2Type; + USHORT TLV2Length; + CHAR SignalStrength; + UCHAR RadioIf; +} QMINAS_GET_SIGNAL_STRENGTH_RESP_MSG, *PQMINAS_GET_SIGNAL_STRENGTH_RESP_MSG; + + +typedef struct _QMINAS_SET_EVENT_REPORT_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR ReportSigStrength; + UCHAR NumTresholds; + CHAR TresholdList[2]; +} QMINAS_SET_EVENT_REPORT_REQ_MSG, *PQMINAS_SET_EVENT_REPORT_REQ_MSG; + +typedef struct _QMINAS_SET_EVENT_REPORT_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} QMINAS_SET_EVENT_REPORT_RESP_MSG, *PQMINAS_SET_EVENT_REPORT_RESP_MSG; + +typedef struct _QMINAS_SIGNAL_STRENGTH_TLV +{ + UCHAR TLVType; + USHORT TLVLength; + CHAR SigStrength; + UCHAR RadioIf; +} QMINAS_SIGNAL_STRENGTH_TLV, *PQMINAS_SIGNAL_STRENGTH_TLV; + +typedef struct _QMINAS_REJECT_CAUSE_TLV +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR ServiceDomain; + USHORT RejectCause; +} QMINAS_REJECT_CAUSE_TLV, *PQMINAS_REJECT_CAUSE_TLV; + +typedef struct _QMINAS_EVENT_REPORT_IND_MSG +{ + USHORT Type; + USHORT Length; +} QMINAS_EVENT_REPORT_IND_MSG, *PQMINAS_EVENT_REPORT_IND_MSG; + +typedef struct _QMINAS_GET_RF_BAND_INFO_REQ_MSG +{ + USHORT Type; + USHORT Length; +} QMINAS_GET_RF_BAND_INFO_REQ_MSG, *PQMINAS_GET_RF_BAND_INFO_REQ_MSG; + +typedef struct _QMINASRF_BAND_INFO +{ + UCHAR RadioIf; + USHORT ActiveBand; + USHORT ActiveChannel; +} QMINASRF_BAND_INFO, *PQMINASRF_BAND_INFO; + +typedef struct _QMINAS_GET_RF_BAND_INFO_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR NumInstances; +} QMINAS_GET_RF_BAND_INFO_RESP_MSG, *PQMINAS_GET_RF_BAND_INFO_RESP_MSG; + + +typedef struct _QMINAS_GET_PLMN_NAME_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT MCC; + USHORT MNC; +} QMINAS_GET_PLMN_NAME_REQ_MSG, *PQMINAS_GET_PLMN_NAME_REQ_MSG; + +typedef struct _QMINAS_GET_PLMN_NAME_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} QMINAS_GET_PLMN_NAME_RESP_MSG, *PQMINAS_GET_PLMN_NAME_RESP_MSG; + +typedef struct _QMINAS_GET_PLMN_NAME_SPN +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR SPN_Enc; + UCHAR SPN_Len; +} QMINAS_GET_PLMN_NAME_SPN, *PQMINAS_GET_PLMN_NAME_SPN; + +typedef struct _QMINAS_GET_PLMN_NAME_PLMN +{ + UCHAR PLMN_Enc; + UCHAR PLMN_Ci; + UCHAR PLMN_SpareBits; + UCHAR PLMN_Len; +} QMINAS_GET_PLMN_NAME_PLMN, *PQMINAS_GET_PLMN_NAME_PLMN; + +typedef struct _QMINAS_INITIATE_ATTACH_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR PsAttachAction; +} QMINAS_INITIATE_ATTACH_REQ_MSG, *PQMINAS_INITIATE_ATTACH_REQ_MSG; + +typedef struct _QMINAS_INITIATE_ATTACH_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} QMINAS_INITIATE_ATTACH_RESP_MSG, *PQMINAS_INITIATE_ATTACH_RESP_MSG; +#endif +// ======================= End of NAS ============================== + +// ======================= UIM ============================== +#define QMIUIM_READ_TRANSPARENT_REQ 0x0020 +#define QMIUIM_READ_TRANSPARENT_RESP 0x0020 +#define QMIUIM_READ_TRANSPARENT_IND 0x0020 +#define QMIUIM_READ_RECORD_REQ 0x0021 +#define QMIUIM_READ_RECORD_RESP 0x0021 +#define QMIUIM_READ_RECORD_IND 0x0021 +#define QMIUIM_WRITE_TRANSPARENT_REQ 0x0022 +#define QMIUIM_WRITE_TRANSPARENT_RESP 0x0022 +#define QMIUIM_WRITE_TRANSPARENT_IND 0x0022 +#define QMIUIM_WRITE_RECORD_REQ 0x0023 +#define QMIUIM_WRITE_RECORD_RESP 0x0023 +#define QMIUIM_WRITE_RECORD_IND 0x0023 +#define QMIUIM_SET_PIN_PROTECTION_REQ 0x0025 +#define QMIUIM_SET_PIN_PROTECTION_RESP 0x0025 +#define QMIUIM_SET_PIN_PROTECTION_IND 0x0025 +#define QMIUIM_VERIFY_PIN_REQ 0x0026 +#define QMIUIM_VERIFY_PIN_RESP 0x0026 +#define QMIUIM_VERIFY_PIN_IND 0x0026 +#define QMIUIM_UNBLOCK_PIN_REQ 0x0027 +#define QMIUIM_UNBLOCK_PIN_RESP 0x0027 +#define QMIUIM_UNBLOCK_PIN_IND 0x0027 +#define QMIUIM_CHANGE_PIN_REQ 0x0028 +#define QMIUIM_CHANGE_PIN_RESP 0x0028 +#define QMIUIM_CHANGE_PIN_IND 0x0028 +#define QMIUIM_DEPERSONALIZATION_REQ 0x0029 +#define QMIUIM_DEPERSONALIZATION_RESP 0x0029 +#define QMIUIM_EVENT_REG_REQ 0x002E +#define QMIUIM_EVENT_REG_RESP 0x002E +#define QMIUIM_GET_CARD_STATUS_REQ 0x002F +#define QMIUIM_GET_CARD_STATUS_RESP 0x002F +#define QMIUIM_STATUS_CHANGE_IND 0x0032 + + +typedef struct _QMIUIM_GET_CARD_STATUS_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} __attribute__ ((packed)) QMIUIM_GET_CARD_STATUS_RESP_MSG, *PQMIUIM_GET_CARD_STATUS_RESP_MSG; + +typedef struct _QMIUIM_CARD_STATUS +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT IndexGWPri; + USHORT Index1XPri; + USHORT IndexGWSec; + USHORT Index1XSec; + UCHAR NumSlot; + UCHAR CardState; + UCHAR UPINState; + UCHAR UPINRetries; + UCHAR UPUKRetries; + UCHAR ErrorCode; + UCHAR NumApp; + UCHAR AppType; + UCHAR AppState; + UCHAR PersoState; + UCHAR PersoFeature; + UCHAR PersoRetries; + UCHAR PersoUnblockRetries; + UCHAR AIDLength; +} __attribute__ ((packed)) QMIUIM_CARD_STATUS, *PQMIUIM_CARD_STATUS; + +typedef struct _QMIUIM_PIN_STATE +{ + UCHAR UnivPIN; + UCHAR PIN1State; + UCHAR PIN1Retries; + UCHAR PUK1Retries; + UCHAR PIN2State; + UCHAR PIN2Retries; + UCHAR PUK2Retries; +} __attribute__ ((packed)) QMIUIM_PIN_STATE, *PQMIUIM_PIN_STATE; + +typedef struct _QMIUIM_VERIFY_PIN_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR Session_Type; + UCHAR Aid_Len; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR PINID; + UCHAR PINLen; + UCHAR PINValue; +} __attribute__ ((packed)) QMIUIM_VERIFY_PIN_REQ_MSG, *PQMIUIM_VERIFY_PIN_REQ_MSG; + +typedef struct _QMIUIM_VERIFY_PIN_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR PINVerifyRetriesLeft; + UCHAR PINUnblockRetriesLeft; +} __attribute__ ((packed)) QMIUIM_VERIFY_PIN_RESP_MSG, *PQMIUIM_VERIFY_PIN_RESP_MSG; + +typedef struct _QMIUIM_READ_TRANSPARENT_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR Session_Type; + UCHAR Aid_Len; + UCHAR TLV2Type; + USHORT TLV2Length; + USHORT file_id; + UCHAR path_len; + UCHAR path[]; +} __attribute__ ((packed)) QMIUIM_READ_TRANSPARENT_REQ_MSG, *PQMIUIM_READ_TRANSPARENT_REQ_MSG; + +typedef struct _READ_TRANSPARENT_TLV +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT Offset; + USHORT Length; +} __attribute__ ((packed)) READ_TRANSPARENT_TLV, *PREAD_TRANSPARENT_TLV; + +typedef struct _QMIUIM_CONTENT +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT content_len; + UCHAR content[]; +} __attribute__ ((packed)) QMIUIM_CONTENT, *PQMIUIM_CONTENT; + +typedef struct _QMIUIM_READ_TRANSPARENT_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} __attribute__ ((packed)) QMIUIM_READ_TRANSPARENT_RESP_MSG, *PQMIUIM_READ_TRANSPARENT_RESP_MSG; + +typedef struct _QMUX_MSG +{ + QCQMUX_HDR QMUXHdr; + union + { + // Message Header + QCQMUX_MSG_HDR QMUXMsgHdr; + QCQMUX_MSG_HDR_RESP QMUXMsgHdrResp; + + // QMIWDS Message +#if 0 + QMIWDS_GET_PKT_SRVC_STATUS_REQ_MSG PacketServiceStatusReq; + QMIWDS_GET_PKT_SRVC_STATUS_RESP_MSG PacketServiceStatusRsp; + QMIWDS_GET_PKT_SRVC_STATUS_IND_MSG PacketServiceStatusInd; + QMIWDS_EVENT_REPORT_IND_MSG EventReportInd; + QMIWDS_GET_CURRENT_CHANNEL_RATE_REQ_MSG GetCurrChannelRateReq; + QMIWDS_GET_CURRENT_CHANNEL_RATE_RESP_MSG GetCurrChannelRateRsp; + QMIWDS_GET_PKT_STATISTICS_REQ_MSG GetPktStatsReq; + QMIWDS_GET_PKT_STATISTICS_RESP_MSG GetPktStatsRsp; + QMIWDS_SET_EVENT_REPORT_REQ_MSG EventReportReq; + QMIWDS_SET_EVENT_REPORT_RESP_MSG EventReportRsp; +#endif + //#ifdef QC_IP_MODE + QMIWDS_GET_RUNTIME_SETTINGS_REQ_MSG GetRuntimeSettingsReq; + QMIWDS_GET_RUNTIME_SETTINGS_RESP_MSG GetRuntimeSettingsRsp; + //#endif // QC_IP_MODE + QMIWDS_SET_CLIENT_IP_FAMILY_PREF_REQ_MSG SetClientIpFamilyPrefReq; + QMIWDS_SET_CLIENT_IP_FAMILY_PREF_RESP_MSG SetClientIpFamilyPrefResp; + QMIWDS_SET_AUTO_CONNECT_REQ_MSG SetAutoConnectReq; +#if 0 + QMIWDS_GET_MIP_MODE_REQ_MSG GetMipModeReq; + QMIWDS_GET_MIP_MODE_RESP_MSG GetMipModeResp; +#endif + QMIWDS_START_NETWORK_INTERFACE_REQ_MSG StartNwInterfaceReq; + QMIWDS_START_NETWORK_INTERFACE_RESP_MSG StartNwInterfaceResp; + QMIWDS_STOP_NETWORK_INTERFACE_REQ_MSG StopNwInterfaceReq; + QMIWDS_STOP_NETWORK_INTERFACE_RESP_MSG StopNwInterfaceResp; + QMIWDS_GET_DEFAULT_SETTINGS_REQ_MSG GetDefaultSettingsReq; + QMIWDS_GET_DEFAULT_SETTINGS_RESP_MSG GetDefaultSettingsResp; + QMIWDS_MODIFY_PROFILE_SETTINGS_REQ_MSG ModifyProfileSettingsReq; + QMIWDS_MODIFY_PROFILE_SETTINGS_RESP_MSG ModifyProfileSettingsResp; + QMIWDS_GET_PROFILE_SETTINGS_REQ_MSG GetProfileSettingsReq; +#if 0 + QMIWDS_GET_DATA_BEARER_REQ_MSG GetDataBearerReq; + QMIWDS_GET_DATA_BEARER_RESP_MSG GetDataBearerResp; + QMIWDS_DUN_CALL_INFO_REQ_MSG DunCallInfoReq; + QMIWDS_DUN_CALL_INFO_RESP_MSG DunCallInfoResp; +#endif + QMIWDS_BIND_MUX_DATA_PORT_REQ_MSG BindMuxDataPortReq; + + // QMIDMS Messages +#if 0 + QMIDMS_GET_DEVICE_MFR_REQ_MSG GetDeviceMfrReq; + QMIDMS_GET_DEVICE_MFR_RESP_MSG GetDeviceMfrRsp; + QMIDMS_GET_DEVICE_MODEL_ID_REQ_MSG GetDeviceModeIdReq; + QMIDMS_GET_DEVICE_MODEL_ID_RESP_MSG GetDeviceModeIdRsp; + QMIDMS_GET_DEVICE_REV_ID_REQ_MSG GetDeviceRevIdReq; + QMIDMS_GET_DEVICE_REV_ID_RESP_MSG GetDeviceRevIdRsp; + QMIDMS_GET_MSISDN_REQ_MSG GetMsisdnReq; + QMIDMS_GET_MSISDN_RESP_MSG GetMsisdnRsp; + QMIDMS_GET_DEVICE_SERIAL_NUMBERS_REQ_MSG GetDeviceSerialNumReq; + QMIDMS_GET_DEVICE_SERIAL_NUMBERS_RESP_MSG GetDeviceSerialNumRsp; + QMIDMS_GET_DEVICE_CAP_REQ_MSG GetDeviceCapReq; + QMIDMS_GET_DEVICE_CAP_RESP_MSG GetDeviceCapResp; + QMIDMS_GET_BAND_CAP_REQ_MSG GetBandCapReq; + QMIDMS_GET_BAND_CAP_RESP_MSG GetBandCapRsp; + QMIDMS_GET_ACTIVATED_STATUS_REQ_MSG GetActivatedStatusReq; + QMIDMS_GET_ACTIVATED_STATUS_RESP_MSG GetActivatedStatusResp; + QMIDMS_GET_OPERATING_MODE_REQ_MSG GetOperatingModeReq; + QMIDMS_GET_OPERATING_MODE_RESP_MSG GetOperatingModeResp; +#endif + QMIDMS_SET_OPERATING_MODE_REQ_MSG SetOperatingModeReq; + QMIDMS_SET_OPERATING_MODE_RESP_MSG SetOperatingModeResp; +#if 0 + QMIDMS_UIM_GET_ICCID_REQ_MSG GetICCIDReq; + QMIDMS_UIM_GET_ICCID_RESP_MSG GetICCIDResp; + QMIDMS_ACTIVATE_AUTOMATIC_REQ_MSG ActivateAutomaticReq; + QMIDMS_ACTIVATE_AUTOMATIC_RESP_MSG ActivateAutomaticResp; + QMIDMS_ACTIVATE_MANUAL_REQ_MSG ActivateManualReq; + QMIDMS_ACTIVATE_MANUAL_RESP_MSG ActivateManualResp; +#endif + QMIDMS_UIM_GET_PIN_STATUS_REQ_MSG UIMGetPinStatusReq; + QMIDMS_UIM_GET_PIN_STATUS_RESP_MSG UIMGetPinStatusResp; + QMIDMS_UIM_VERIFY_PIN_REQ_MSG UIMVerifyPinReq; + QMIDMS_UIM_VERIFY_PIN_RESP_MSG UIMVerifyPinResp; +#if 0 + QMIDMS_UIM_SET_PIN_PROTECTION_REQ_MSG UIMSetPinProtectionReq; + QMIDMS_UIM_SET_PIN_PROTECTION_RESP_MSG UIMSetPinProtectionResp; + QMIDMS_UIM_CHANGE_PIN_REQ_MSG UIMChangePinReq; + QMIDMS_UIM_CHANGE_PIN_RESP_MSG UIMChangePinResp; + QMIDMS_UIM_UNBLOCK_PIN_REQ_MSG UIMUnblockPinReq; + QMIDMS_UIM_UNBLOCK_PIN_RESP_MSG UIMUnblockPinResp; + QMIDMS_SET_EVENT_REPORT_REQ_MSG DmsSetEventReportReq; + QMIDMS_SET_EVENT_REPORT_RESP_MSG DmsSetEventReportResp; + QMIDMS_EVENT_REPORT_IND_MSG DmsEventReportInd; +#endif + QMIDMS_UIM_GET_STATE_REQ_MSG UIMGetStateReq; + QMIDMS_UIM_GET_STATE_RESP_MSG UIMGetStateResp; + QMIDMS_UIM_GET_IMSI_REQ_MSG UIMGetIMSIReq; + QMIDMS_UIM_GET_IMSI_RESP_MSG UIMGetIMSIResp; +#if 0 + QMIDMS_UIM_GET_CK_STATUS_REQ_MSG UIMGetCkStatusReq; + QMIDMS_UIM_GET_CK_STATUS_RESP_MSG UIMGetCkStatusResp; + QMIDMS_UIM_SET_CK_PROTECTION_REQ_MSG UIMSetCkProtectionReq; + QMIDMS_UIM_SET_CK_PROTECTION_RESP_MSG UIMSetCkProtectionResp; + QMIDMS_UIM_UNBLOCK_CK_REQ_MSG UIMUnblockCkReq; + QMIDMS_UIM_UNBLOCK_CK_RESP_MSG UIMUnblockCkResp; +#endif + + // QMIQOS Messages +#if 0 + QMI_QOS_SET_EVENT_REPORT_REQ_MSG QosSetEventReportReq; + QMI_QOS_SET_EVENT_REPORT_RESP_MSG QosSetEventReportRsp; + QMI_QOS_EVENT_REPORT_IND_MSG QosEventReportInd; +#endif + + // QMIWMS Messages +#if 0 + QMIWMS_GET_MESSAGE_PROTOCOL_REQ_MSG GetMessageProtocolReq; + QMIWMS_GET_MESSAGE_PROTOCOL_RESP_MSG GetMessageProtocolResp; + QMIWMS_GET_SMSC_ADDRESS_REQ_MSG GetSMSCAddressReq; + QMIWMS_GET_SMSC_ADDRESS_RESP_MSG GetSMSCAddressResp; + QMIWMS_SET_SMSC_ADDRESS_REQ_MSG SetSMSCAddressReq; + QMIWMS_SET_SMSC_ADDRESS_RESP_MSG SetSMSCAddressResp; + QMIWMS_GET_STORE_MAX_SIZE_REQ_MSG GetStoreMaxSizeReq; + QMIWMS_GET_STORE_MAX_SIZE_RESP_MSG GetStoreMaxSizeResp; + QMIWMS_LIST_MESSAGES_REQ_MSG ListMessagesReq; + QMIWMS_LIST_MESSAGES_RESP_MSG ListMessagesResp; + QMIWMS_RAW_READ_REQ_MSG RawReadMessagesReq; + QMIWMS_RAW_READ_RESP_MSG RawReadMessagesResp; + QMIWMS_SET_EVENT_REPORT_REQ_MSG WmsSetEventReportReq; + QMIWMS_SET_EVENT_REPORT_RESP_MSG WmsSetEventReportResp; + QMIWMS_EVENT_REPORT_IND_MSG WmsEventReportInd; + QMIWMS_DELETE_REQ_MSG WmsDeleteReq; + QMIWMS_DELETE_RESP_MSG WmsDeleteResp; + QMIWMS_RAW_SEND_REQ_MSG RawSendMessagesReq; + QMIWMS_RAW_SEND_RESP_MSG RawSendMessagesResp; + QMIWMS_MODIFY_TAG_REQ_MSG WmsModifyTagReq; + QMIWMS_MODIFY_TAG_RESP_MSG WmsModifyTagResp; +#endif + + // QMINAS Messages +#if 0 + QMINAS_GET_HOME_NETWORK_REQ_MSG GetHomeNetworkReq; + QMINAS_GET_HOME_NETWORK_RESP_MSG GetHomeNetworkResp; + QMINAS_GET_PREFERRED_NETWORK_REQ_MSG GetPreferredNetworkReq; + QMINAS_GET_PREFERRED_NETWORK_RESP_MSG GetPreferredNetworkResp; + QMINAS_GET_FORBIDDEN_NETWORK_REQ_MSG GetForbiddenNetworkReq; + QMINAS_GET_FORBIDDEN_NETWORK_RESP_MSG GetForbiddenNetworkResp; + QMINAS_GET_SERVING_SYSTEM_REQ_MSG GetServingSystemReq; +#endif + QMINAS_GET_SERVING_SYSTEM_RESP_MSG GetServingSystemResp; + QMINAS_GET_SYS_INFO_RESP_MSG GetSysInfoResp; + QMINAS_SYS_INFO_IND_MSG NasSysInfoInd; +#if 0 + QMINAS_SERVING_SYSTEM_IND_MSG NasServingSystemInd; + QMINAS_SET_PREFERRED_NETWORK_REQ_MSG SetPreferredNetworkReq; + QMINAS_SET_PREFERRED_NETWORK_RESP_MSG SetPreferredNetworkResp; + QMINAS_SET_FORBIDDEN_NETWORK_REQ_MSG SetForbiddenNetworkReq; + QMINAS_SET_FORBIDDEN_NETWORK_RESP_MSG SetForbiddenNetworkResp; + QMINAS_PERFORM_NETWORK_SCAN_REQ_MSG PerformNetworkScanReq; + QMINAS_PERFORM_NETWORK_SCAN_RESP_MSG PerformNetworkScanResp; + QMINAS_INITIATE_NW_REGISTER_REQ_MSG InitiateNwRegisterReq; + QMINAS_INITIATE_NW_REGISTER_RESP_MSG InitiateNwRegisterResp; + QMINAS_SET_TECHNOLOGY_PREF_REQ_MSG SetTechnologyPrefReq; + QMINAS_SET_TECHNOLOGY_PREF_RESP_MSG SetTechnologyPrefResp; + QMINAS_GET_SIGNAL_STRENGTH_REQ_MSG GetSignalStrengthReq; + QMINAS_GET_SIGNAL_STRENGTH_RESP_MSG GetSignalStrengthResp; + QMINAS_SET_EVENT_REPORT_REQ_MSG SetEventReportReq; + QMINAS_SET_EVENT_REPORT_RESP_MSG SetEventReportResp; + QMINAS_EVENT_REPORT_IND_MSG NasEventReportInd; + QMINAS_GET_RF_BAND_INFO_REQ_MSG GetRFBandInfoReq; + QMINAS_GET_RF_BAND_INFO_RESP_MSG GetRFBandInfoResp; + QMINAS_INITIATE_ATTACH_REQ_MSG InitiateAttachReq; + QMINAS_INITIATE_ATTACH_RESP_MSG InitiateAttachResp; + QMINAS_GET_PLMN_NAME_REQ_MSG GetPLMNNameReq; + QMINAS_GET_PLMN_NAME_RESP_MSG GetPLMNNameResp; +#endif + + // QMIUIM Messages + QMIUIM_GET_CARD_STATUS_RESP_MSG UIMGetCardStatus; + QMIUIM_VERIFY_PIN_REQ_MSG UIMUIMVerifyPinReq; + QMIUIM_VERIFY_PIN_RESP_MSG UIMUIMVerifyPinResp; +#if 0 + QMIUIM_SET_PIN_PROTECTION_REQ_MSG UIMUIMSetPinProtectionReq; + QMIUIM_SET_PIN_PROTECTION_RESP_MSG UIMUIMSetPinProtectionResp; + QMIUIM_CHANGE_PIN_REQ_MSG UIMUIMChangePinReq; + QMIUIM_CHANGE_PIN_RESP_MSG UIMUIMChangePinResp; + QMIUIM_UNBLOCK_PIN_REQ_MSG UIMUIMUnblockPinReq; + QMIUIM_UNBLOCK_PIN_RESP_MSG UIMUIMUnblockPinResp; +#endif + QMIUIM_READ_TRANSPARENT_REQ_MSG UIMUIMReadTransparentReq; + QMIUIM_READ_TRANSPARENT_RESP_MSG UIMUIMReadTransparentResp; + + }; +} __attribute__ ((packed)) QMUX_MSG, *PQMUX_MSG; + +#pragma pack(pop) + +#endif // MPQMUX_H diff --git a/root/package/link4all/quectel-CM/simcom-cm/Makefile b/root/package/link4all/quectel-CM/simcom-cm/Makefile new file mode 100644 index 00000000..0eb49e49 --- /dev/null +++ b/root/package/link4all/quectel-CM/simcom-cm/Makefile @@ -0,0 +1,24 @@ +#CROSS_COMPILE=arm-hisiv100nptl-linux- +# ifneq ($(CROSS_COMPILE),) +# CROSS-COMPILE:=$(CROSS_COMPILE) +# endif + +# CC:=$(CROSS-COMPILE)gcc +# LD:=$(CROSS-COMPILE)ld + +# release: clean +# $(CC) -Wall -s QmiWwanCM.c GobiNetCM.c main.c MPQMUX.c QMIThread.c util.c udhcpc.c -o simcom-cm -lpthread + +# debug: clean +# $(CC) -Wall -g QmiWwanCM.c GobiNetCM.c main.c MPQMUX.c QMIThread.c util.c udhcpc.c -o simcom-cm -lpthread + +# clean: +# rm -rf simcom-cm *~ + + +quectel-CM:clean + $(CC) -Wall -s QmiWwanCM.c GobiNetCM.c main.c MPQMUX.c QMIThread.c util.c udhcpc.c -o quectel-CM -lpthread + +clean: + rm -rf quectel-CM *~ + diff --git a/root/package/link4all/quectel-CM/simcom-cm/QMIThread.c b/root/package/link4all/quectel-CM/simcom-cm/QMIThread.c new file mode 100644 index 00000000..45aa76bc --- /dev/null +++ b/root/package/link4all/quectel-CM/simcom-cm/QMIThread.c @@ -0,0 +1,2020 @@ +#include "QMIThread.h" +extern char *strndup (const char *__string, size_t __n); + +int qmiclientId[QMUX_TYPE_WDS_ADMIN + 1]; //GobiNet use fd to indicate client ID, so type of qmiclientId must be int +static uint32_t WdsConnectionIPv4Handle = 0; +static uint32_t WdsConnectionIPv6Handle = 0; +static int s_is_cdma = 0; +static int s_hdr_personality = 0; // 0x01-HRPD, 0x02-eHRPD +static char *qstrcpy(char *to, const char *from) { //no __strcpy_chk + char *save = to; + for (; (*to = *from) != '\0'; ++from, ++to); + return(save); +} + +static int s_9x07 = -1; + +typedef USHORT (*CUSTOMQMUX)(PQMUX_MSG pMUXMsg, void *arg); + +// To retrieve the ith (Index) TLV +PQMI_TLV_HDR GetTLV (PQCQMUX_MSG_HDR pQMUXMsgHdr, int TLVType) { + int TLVFind = 0; + USHORT Length = le16_to_cpu(pQMUXMsgHdr->Length); + PQMI_TLV_HDR pTLVHdr = (PQMI_TLV_HDR)(pQMUXMsgHdr + 1); + + while (Length >= sizeof(QMI_TLV_HDR)) { + TLVFind++; + if (TLVType > 0x1000) { + if ((TLVFind + 0x1000) == TLVType) + return pTLVHdr; + } else if (pTLVHdr->TLVType == TLVType) { + return pTLVHdr; + } + + Length -= (le16_to_cpu((pTLVHdr->TLVLength)) + sizeof(QMI_TLV_HDR)); + pTLVHdr = (PQMI_TLV_HDR)(((UCHAR *)pTLVHdr) + le16_to_cpu(pTLVHdr->TLVLength) + sizeof(QMI_TLV_HDR)); + } + + return NULL; +} + +static USHORT GetQMUXTransactionId(void) { + static int TransactionId = 0; + if (++TransactionId > 0xFFFF) + TransactionId = 1; + return TransactionId; +} + +static PQCQMIMSG ComposeQMUXMsg(UCHAR QMIType, USHORT Type, CUSTOMQMUX customQmuxMsgFunction, void *arg) { + UCHAR QMIBuf[WDM_DEFAULT_BUFSIZE]; + PQCQMIMSG pRequest = (PQCQMIMSG)QMIBuf; + int Length; + + memset(QMIBuf, 0x00, sizeof(QMIBuf)); + pRequest->QMIHdr.IFType = USB_CTL_MSG_TYPE_QMI; + pRequest->QMIHdr.CtlFlags = 0x00; + pRequest->QMIHdr.QMIType = QMIType; + pRequest->QMIHdr.ClientId = qmiclientId[QMIType] & 0xFF; + + if (qmiclientId[QMIType] == 0) { + dbg_time("QMIType %d has no clientID", QMIType); + return NULL; + } + + pRequest->MUXMsg.QMUXHdr.CtlFlags = QMUX_CTL_FLAG_SINGLE_MSG | QMUX_CTL_FLAG_TYPE_CMD; + pRequest->MUXMsg.QMUXHdr.TransactionId = cpu_to_le16(GetQMUXTransactionId()); + pRequest->MUXMsg.QMUXMsgHdr.Type = cpu_to_le16(Type); + if (customQmuxMsgFunction) + pRequest->MUXMsg.QMUXMsgHdr.Length = cpu_to_le16(customQmuxMsgFunction(&pRequest->MUXMsg, arg) - sizeof(QCQMUX_MSG_HDR)); + else + pRequest->MUXMsg.QMUXMsgHdr.Length = cpu_to_le16(0x0000); + + pRequest->QMIHdr.Length = cpu_to_le16(le16_to_cpu(pRequest->MUXMsg.QMUXMsgHdr.Length) + sizeof(QCQMUX_MSG_HDR) + sizeof(QCQMUX_HDR) + + sizeof(QCQMI_HDR) - 1); + Length = le16_to_cpu(pRequest->QMIHdr.Length) + 1; + + pRequest = (PQCQMIMSG)malloc(Length); + if (pRequest == NULL) { + dbg_time("%s fail to malloc", __func__); + } else { + memcpy(pRequest, QMIBuf, Length); + } + + return pRequest; +} + +#if 0 +static USHORT NasSetEventReportReq(PQMUX_MSG pMUXMsg, void *arg) { + pMUXMsg->SetEventReportReq.TLVType = 0x10; + pMUXMsg->SetEventReportReq.TLVLength = 0x04; + pMUXMsg->SetEventReportReq.ReportSigStrength = 0x00; + pMUXMsg->SetEventReportReq.NumTresholds = 2; + pMUXMsg->SetEventReportReq.TresholdList[0] = -113; + pMUXMsg->SetEventReportReq.TresholdList[1] = -50; + return sizeof(QMINAS_SET_EVENT_REPORT_REQ_MSG); +} + +static USHORT WdsSetEventReportReq(PQMUX_MSG pMUXMsg, void *arg) { + pMUXMsg->EventReportReq.TLVType = 0x10; // 0x10 -- current channel rate indicator + pMUXMsg->EventReportReq.TLVLength = 0x0001; // 1 + pMUXMsg->EventReportReq.Mode = 0x00; // 0-do not report; 1-report when rate changes + + pMUXMsg->EventReportReq.TLV2Type = 0x11; // 0x11 + pMUXMsg->EventReportReq.TLV2Length = 0x0005; // 5 + pMUXMsg->EventReportReq.StatsPeriod = 0x00; // seconds between reports; 0-do not report + pMUXMsg->EventReportReq.StatsMask = 0x000000ff; // + + pMUXMsg->EventReportReq.TLV3Type = 0x12; // 0x12 -- current data bearer indicator + pMUXMsg->EventReportReq.TLV3Length = 0x0001; // 1 + pMUXMsg->EventReportReq.Mode3 = 0x01; // 0-do not report; 1-report when changes + + pMUXMsg->EventReportReq.TLV4Type = 0x13; // 0x13 -- dormancy status indicator + pMUXMsg->EventReportReq.TLV4Length = 0x0001; // 1 + pMUXMsg->EventReportReq.DormancyStatus = 0x00; // 0-do not report; 1-report when changes + return sizeof(QMIWDS_SET_EVENT_REPORT_REQ_MSG); +} + +static USHORT DmsSetEventReportReq(PQMUX_MSG pMUXMsg) { + PPIN_STATUS pPinState = (PPIN_STATUS)(&pMUXMsg->DmsSetEventReportReq + 1); + PUIM_STATE pUimState = (PUIM_STATE)(pPinState + 1); + // Pin State + pPinState->TLVType = 0x12; + pPinState->TLVLength = 0x01; + pPinState->ReportPinState = 0x01; + // UIM State + pUimState->TLVType = 0x15; + pUimState->TLVLength = 0x01; + pUimState->UIMState = 0x01; + return sizeof(QMIDMS_SET_EVENT_REPORT_REQ_MSG) + sizeof(PIN_STATUS) + sizeof(UIM_STATE); +} +#endif + +static USHORT WdsStartNwInterfaceReq(PQMUX_MSG pMUXMsg, void *arg) { + PQMIWDS_TECHNOLOGY_PREFERECE pTechPref; + PQMIWDS_AUTH_PREFERENCE pAuthPref; + PQMIWDS_USERNAME pUserName; + PQMIWDS_PASSWD pPasswd; + PQMIWDS_APNNAME pApnName; + PQMIWDS_IP_FAMILY_TLV pIpFamily; + USHORT TLVLength = 0; + UCHAR *pTLV; + PROFILE_T *profile = (PROFILE_T *)arg; + const char *profile_user = profile->user; + const char *profile_password = profile->password; + int profile_auth = profile->auth; + + if (s_is_cdma && (profile_user == NULL || profile_user[0] == '\0') && (profile_password == NULL || profile_password[0] == '\0')) { + profile_user = "ctnet@mycdma.cn"; + profile_password = "vnet.mobi"; + profile_auth = 2; //chap + } + + pTLV = (UCHAR *)(&pMUXMsg->StartNwInterfaceReq + 1); + pMUXMsg->StartNwInterfaceReq.Length = 0; + + // Set technology Preferece + pTechPref = (PQMIWDS_TECHNOLOGY_PREFERECE)(pTLV + TLVLength); + pTechPref->TLVType = 0x30; + pTechPref->TLVLength = cpu_to_le16(0x01); + if (s_is_cdma == 0) + pTechPref->TechPreference = 0x01; + else + pTechPref->TechPreference = 0x02; + TLVLength +=(le16_to_cpu(pTechPref->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + + // Set APN Name + if (profile->apn) { + pApnName = (PQMIWDS_APNNAME)(pTLV + TLVLength); + pApnName->TLVType = 0x14; + pApnName->TLVLength = cpu_to_le16(strlen(profile->apn)); + qstrcpy((char *)&pApnName->ApnName, profile->apn); + TLVLength +=(le16_to_cpu(pApnName->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + // Set User Name + if (profile_user) { + pUserName = (PQMIWDS_USERNAME)(pTLV + TLVLength); + pUserName->TLVType = 0x17; + pUserName->TLVLength = cpu_to_le16(strlen(profile_user)); + qstrcpy((char *)&pUserName->UserName, profile_user); + TLVLength += (le16_to_cpu(pUserName->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + // Set Password + if (profile_password) { + pPasswd = (PQMIWDS_PASSWD)(pTLV + TLVLength); + pPasswd->TLVType = 0x18; + pPasswd->TLVLength = cpu_to_le16(strlen(profile_password)); + qstrcpy((char *)&pPasswd->Passwd, profile_password); + TLVLength += (le16_to_cpu(pPasswd->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + // Set Auth Protocol + if (profile_user && profile_password) { + pAuthPref = (PQMIWDS_AUTH_PREFERENCE)(pTLV + TLVLength); + pAuthPref->TLVType = 0x16; + pAuthPref->TLVLength = cpu_to_le16(0x01); + pAuthPref->AuthPreference = profile_auth; // 0 ~ None, 1 ~ Pap, 2 ~ Chap, 3 ~ MsChapV2 + TLVLength += (le16_to_cpu(pAuthPref->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + // Add IP Family Preference + pIpFamily = (PQMIWDS_IP_FAMILY_TLV)(pTLV + TLVLength); + pIpFamily->TLVType = 0x19; + pIpFamily->TLVLength = cpu_to_le16(0x01); + pIpFamily->IpFamily = profile->curIpFamily; + TLVLength += (le16_to_cpu(pIpFamily->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + + //Set Profile Index + if (profile->pdp && !s_is_cdma) { //cdma only allow one pdp + PQMIWDS_PROFILE_IDENTIFIER pProfileIndex = (PQMIWDS_PROFILE_IDENTIFIER)(pTLV + TLVLength); + pProfileIndex->TLVLength = cpu_to_le16(0x01); + pProfileIndex->TLVType = 0x31; + pProfileIndex->ProfileIndex = profile->pdp; + if (s_is_cdma && s_hdr_personality == 0x02) { + pProfileIndex->TLVType = 0x32; //profile_index_3gpp2 + pProfileIndex->ProfileIndex = 101; + } + TLVLength += (le16_to_cpu(pProfileIndex->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + return sizeof(QMIWDS_START_NETWORK_INTERFACE_REQ_MSG) + TLVLength; +} + +static USHORT WdsStopNwInterfaceReq(PQMUX_MSG pMUXMsg, void *arg) { + pMUXMsg->StopNwInterfaceReq.TLVType = 0x01; + pMUXMsg->StopNwInterfaceReq.TLVLength = cpu_to_le16(0x04); + if (*((int *)arg) == IpFamilyV4) + pMUXMsg->StopNwInterfaceReq.Handle = cpu_to_le32(WdsConnectionIPv4Handle); + else + pMUXMsg->StopNwInterfaceReq.Handle = cpu_to_le32(WdsConnectionIPv6Handle); + return sizeof(QMIWDS_STOP_NETWORK_INTERFACE_REQ_MSG); +} + +static USHORT WdsSetClientIPFamilyPref(PQMUX_MSG pMUXMsg, void *arg) { + pMUXMsg->SetClientIpFamilyPrefReq.TLVType = 0x01; + pMUXMsg->SetClientIpFamilyPrefReq.TLVLength = cpu_to_le16(0x01); + pMUXMsg->SetClientIpFamilyPrefReq.IpPreference = *((UCHAR *)arg); + return sizeof(QMIWDS_SET_CLIENT_IP_FAMILY_PREF_REQ_MSG); +} + +static USHORT WdsSetAutoConnect(PQMUX_MSG pMUXMsg, void *arg) { + pMUXMsg->SetAutoConnectReq.TLVType = 0x01; + pMUXMsg->SetAutoConnectReq.TLVLength = cpu_to_le16(0x01); + pMUXMsg->SetAutoConnectReq.autoconnect_setting = *((UCHAR *)arg); + return sizeof(QMIWDS_SET_AUTO_CONNECT_REQ_MSG); +} + +static USHORT WdaSetDataFormat(PQMUX_MSG pMUXMsg, void *arg) { + PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV_QOS pWdsAdminQosTlv; + PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV linkProto; + PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV dlTlp; + + pWdsAdminQosTlv = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV_QOS)(&pMUXMsg->QMUXMsgHdr + 1); + pWdsAdminQosTlv->TLVType = 0x10; + pWdsAdminQosTlv->TLVLength = cpu_to_le16(0x0001); + pWdsAdminQosTlv->QOSSetting = 0; /* no-QOS header */ + + linkProto = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV)(pWdsAdminQosTlv + 1); + linkProto->TLVType = 0x11; + linkProto->TLVLength = cpu_to_le16(4); + linkProto->Value = cpu_to_le32(0x01); /* Set Ethernet mode */ + + dlTlp = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV)(linkProto + 1);; + dlTlp->TLVType = 0x13; + dlTlp->TLVLength = cpu_to_le16(4); + dlTlp->Value = cpu_to_le32(0x00); + + if (sizeof(*linkProto) != 7 ) + dbg_time("%s sizeof(*linkProto) = %d, is not 7!", __func__, sizeof(*linkProto) ); + + return sizeof(QCQMUX_MSG_HDR) + sizeof(*pWdsAdminQosTlv) + sizeof(*linkProto) + sizeof(*dlTlp); +} + +#ifdef CONFIG_SIM +static USHORT DmsUIMVerifyPinReqSend(PQMUX_MSG pMUXMsg, void *arg) { + pMUXMsg->UIMVerifyPinReq.TLVType = 0x01; + pMUXMsg->UIMVerifyPinReq.PINID = 0x01; //Pin1, not Puk + pMUXMsg->UIMVerifyPinReq.PINLen = strlen((const char *)arg); + qstrcpy((PCHAR)&pMUXMsg->UIMVerifyPinReq.PINValue, ((const char *)arg)); + pMUXMsg->UIMVerifyPinReq.TLVLength = cpu_to_le16(2 + strlen((const char *)arg)); + return sizeof(QMIDMS_UIM_VERIFY_PIN_REQ_MSG) + (strlen((const char *)arg) - 1); +} + +static USHORT UimVerifyPinReqSend(PQMUX_MSG pMUXMsg, void *arg) +{ + pMUXMsg->UIMUIMVerifyPinReq.TLVType = 0x01; + pMUXMsg->UIMUIMVerifyPinReq.TLVLength = cpu_to_le16(0x02); + pMUXMsg->UIMUIMVerifyPinReq.Session_Type = 0x00; + pMUXMsg->UIMUIMVerifyPinReq.Aid_Len = 0x00; + pMUXMsg->UIMUIMVerifyPinReq.TLV2Type = 0x02; + pMUXMsg->UIMUIMVerifyPinReq.TLV2Length = cpu_to_le16(2 + strlen((const char *)arg)); + pMUXMsg->UIMUIMVerifyPinReq.PINID = 0x01; //Pin1, not Puk + pMUXMsg->UIMUIMVerifyPinReq.PINLen= strlen((const char *)arg); + qstrcpy((PCHAR)&pMUXMsg->UIMUIMVerifyPinReq.PINValue, ((const char *)arg)); + return sizeof(QMIUIM_VERIFY_PIN_REQ_MSG) + (strlen((const char *)arg) - 1); +} + +#ifdef CONFIG_IMSI_ICCID +static USHORT UimReadTransparentIMSIReqSend(PQMUX_MSG pMUXMsg, void *arg) { + PREAD_TRANSPARENT_TLV pReadTransparent; + + pMUXMsg->UIMUIMReadTransparentReq.TLVType = 0x01; + pMUXMsg->UIMUIMReadTransparentReq.TLVLength = cpu_to_le16(0x02); + if (!strcmp((char *)arg, "EF_ICCID")) { + pMUXMsg->UIMUIMReadTransparentReq.Session_Type = 0x06; + pMUXMsg->UIMUIMReadTransparentReq.Aid_Len = 0x00; + + pMUXMsg->UIMUIMReadTransparentReq.TLV2Type = 0x02; + pMUXMsg->UIMUIMReadTransparentReq.file_id = cpu_to_le16(0x2FE2); + pMUXMsg->UIMUIMReadTransparentReq.path_len = 0x02; + pMUXMsg->UIMUIMReadTransparentReq.path[0] = 0x00; + pMUXMsg->UIMUIMReadTransparentReq.path[1] = 0x3F; + } + else if(!strcmp((char *)arg, "EF_IMSI")) { + pMUXMsg->UIMUIMReadTransparentReq.Session_Type = 0x00; + pMUXMsg->UIMUIMReadTransparentReq.Aid_Len = 0x00; + + pMUXMsg->UIMUIMReadTransparentReq.TLV2Type = 0x02; + pMUXMsg->UIMUIMReadTransparentReq.file_id = cpu_to_le16(0x6F07); + pMUXMsg->UIMUIMReadTransparentReq.path_len = 0x04; + pMUXMsg->UIMUIMReadTransparentReq.path[0] = 0x00; + pMUXMsg->UIMUIMReadTransparentReq.path[1] = 0x3F; + pMUXMsg->UIMUIMReadTransparentReq.path[2] = 0xFF; + pMUXMsg->UIMUIMReadTransparentReq.path[3] = 0x7F; + } + + pMUXMsg->UIMUIMReadTransparentReq.TLV2Length = cpu_to_le16(3 + pMUXMsg->UIMUIMReadTransparentReq.path_len); + + pReadTransparent = (PREAD_TRANSPARENT_TLV)(&pMUXMsg->UIMUIMReadTransparentReq.path[0] + pMUXMsg->UIMUIMReadTransparentReq.path_len); + pReadTransparent->TLVType = 0x03; + pReadTransparent->TLVLength = cpu_to_le16(0x04); + pReadTransparent->Offset = cpu_to_le16(0x00); + pReadTransparent->Length = cpu_to_le16(0x00); + + return (sizeof(QMIUIM_READ_TRANSPARENT_REQ_MSG) + pMUXMsg->UIMUIMReadTransparentReq.path_len + sizeof(READ_TRANSPARENT_TLV)); +} +#endif +#endif + +#ifdef CONFIG_APN +static USHORT WdsGetProfileSettingsReqSend(PQMUX_MSG pMUXMsg, void *arg) { + PROFILE_T *profile = (PROFILE_T *)arg; + pMUXMsg->GetProfileSettingsReq.Length = cpu_to_le16(sizeof(QMIWDS_GET_PROFILE_SETTINGS_REQ_MSG) - 4); + pMUXMsg->GetProfileSettingsReq.TLVType = 0x01; + pMUXMsg->GetProfileSettingsReq.TLVLength = cpu_to_le16(0x02); + pMUXMsg->GetProfileSettingsReq.ProfileType = 0x00; // 0 ~ 3GPP, 1 ~ 3GPP2 + pMUXMsg->GetProfileSettingsReq.ProfileIndex = profile->pdp; + return sizeof(QMIWDS_GET_PROFILE_SETTINGS_REQ_MSG); +} + +static USHORT WdsModifyProfileSettingsReq(PQMUX_MSG pMUXMsg, void *arg) { + USHORT TLVLength = 0; + UCHAR *pTLV; + PROFILE_T *profile = (PROFILE_T *)arg; + PQMIWDS_PDPTYPE pPdpType; + + pMUXMsg->ModifyProfileSettingsReq.Length = cpu_to_le16(sizeof(QMIWDS_MODIFY_PROFILE_SETTINGS_REQ_MSG) - 4); + pMUXMsg->ModifyProfileSettingsReq.TLVType = 0x01; + pMUXMsg->ModifyProfileSettingsReq.TLVLength = cpu_to_le16(0x02); + pMUXMsg->ModifyProfileSettingsReq.ProfileType = 0x00; // 0 ~ 3GPP, 1 ~ 3GPP2 + pMUXMsg->ModifyProfileSettingsReq.ProfileIndex = profile->pdp; + + pTLV = (UCHAR *)(&pMUXMsg->ModifyProfileSettingsReq + 1); + + pPdpType = (PQMIWDS_PDPTYPE)(pTLV + TLVLength); + pPdpType->TLVType = 0x11; + pPdpType->TLVLength = cpu_to_le16(0x01); +// 0 ?C PDP-IP (IPv4) +// 1 ?C PDP-PPP +// 2 ?C PDP-IPv6 +// 3 ?C PDP-IPv4v6 + pPdpType->PdpType = 3; + TLVLength +=(le16_to_cpu(pPdpType->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + + // Set APN Name + if (profile->apn) { + PQMIWDS_APNNAME pApnName = (PQMIWDS_APNNAME)(pTLV + TLVLength); + pApnName->TLVType = 0x14; + pApnName->TLVLength = cpu_to_le16(strlen(profile->apn)); + qstrcpy((char *)&pApnName->ApnName, profile->apn); + TLVLength +=(le16_to_cpu(pApnName->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + // Set User Name + if (profile->user) { + PQMIWDS_USERNAME pUserName = (PQMIWDS_USERNAME)(pTLV + TLVLength); + pUserName->TLVType = 0x1B; + pUserName->TLVLength = cpu_to_le16(strlen(profile->user)); + qstrcpy((char *)&pUserName->UserName, profile->user); + TLVLength += (le16_to_cpu(pUserName->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + // Set Password + if (profile->password) { + PQMIWDS_PASSWD pPasswd = (PQMIWDS_PASSWD)(pTLV + TLVLength); + pPasswd->TLVType = 0x1C; + pPasswd->TLVLength = cpu_to_le16(strlen(profile->password)); + qstrcpy((char *)&pPasswd->Passwd, profile->password); + TLVLength +=(le16_to_cpu(pPasswd->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + // Set Auth Protocol + if (profile->user && profile->password) { + PQMIWDS_AUTH_PREFERENCE pAuthPref = (PQMIWDS_AUTH_PREFERENCE)(pTLV + TLVLength); + pAuthPref->TLVType = 0x1D; + pAuthPref->TLVLength = cpu_to_le16(0x01); + pAuthPref->AuthPreference = profile->auth; // 0 ~ None, 1 ~ Pap, 2 ~ Chap, 3 ~ MsChapV2 + TLVLength += (le16_to_cpu(pAuthPref->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + return sizeof(QMIWDS_MODIFY_PROFILE_SETTINGS_REQ_MSG) + TLVLength; +} +#endif + +static USHORT WdsGetRuntimeSettingReq(PQMUX_MSG pMUXMsg, void *arg) { + pMUXMsg->GetRuntimeSettingsReq.TLVType = 0x10; + pMUXMsg->GetRuntimeSettingsReq.TLVLength = cpu_to_le16(0x04); + // the following mask also applies to IPV6 + pMUXMsg->GetRuntimeSettingsReq.Mask = cpu_to_le32(QMIWDS_GET_RUNTIME_SETTINGS_MASK_IPV4DNS_ADDR | + QMIWDS_GET_RUNTIME_SETTINGS_MASK_IPV4_ADDR | + QMIWDS_GET_RUNTIME_SETTINGS_MASK_MTU | + QMIWDS_GET_RUNTIME_SETTINGS_MASK_IPV4GATEWAY_ADDR); // | + // QMIWDS_GET_RUNTIME_SETTINGS_MASK_PCSCF_SV_ADDR | + // QMIWDS_GET_RUNTIME_SETTINGS_MASK_PCSCF_DOM_NAME; + + return sizeof(QMIWDS_GET_RUNTIME_SETTINGS_REQ_MSG); +} + +static PQCQMIMSG s_pRequest; +static PQCQMIMSG s_pResponse; +static pthread_mutex_t s_commandmutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t s_commandcond = PTHREAD_COND_INITIALIZER; + +static int is_response(const PQCQMIMSG pRequest, const PQCQMIMSG pResponse) { + if ((pRequest->QMIHdr.QMIType == pResponse->QMIHdr.QMIType) + && (pRequest->QMIHdr.ClientId == pResponse->QMIHdr.ClientId)) { + USHORT requestTID, responseTID; + if (pRequest->QMIHdr.QMIType == QMUX_TYPE_CTL) { + requestTID = pRequest->CTLMsg.QMICTLMsgHdr.TransactionId; + responseTID = pResponse->CTLMsg.QMICTLMsgHdr.TransactionId; + } else { + requestTID = le16_to_cpu(pRequest->MUXMsg.QMUXHdr.TransactionId); + responseTID = le16_to_cpu(pResponse->MUXMsg.QMUXHdr.TransactionId); + } + return (requestTID == responseTID); + } + return 0; +} + +int QmiThreadSendQMITimeout(PQCQMIMSG pRequest, PQCQMIMSG *ppResponse, unsigned msecs) { + int ret; + + if (!pRequest) + { + return -EINVAL; + } + + pthread_mutex_lock(&s_commandmutex); + + if (ppResponse) + *ppResponse = NULL; + + dump_qmi(pRequest, le16_to_cpu(pRequest->QMIHdr.Length) + 1); + + s_pRequest = pRequest; + s_pResponse = NULL; + + if (!strncmp(qmichannel, "/dev/qcqmi", strlen("/dev/qcqmi"))) + ret = GobiNetSendQMI(pRequest); + else + ret = QmiWwanSendQMI(pRequest); + + if (ret == 0) { + ret = pthread_cond_timeout_np(&s_commandcond, &s_commandmutex, msecs); + if (!ret) { + if (s_pResponse && ppResponse) { + *ppResponse = s_pResponse; + } else { + if (s_pResponse) { + free(s_pResponse); + s_pResponse = NULL; + } + } + } else { + dbg_time("%s pthread_cond_timeout_np=%d, errno: %d (%s)", __func__, ret, errno, strerror(errno)); + } + } + + pthread_mutex_unlock(&s_commandmutex); + + return ret; +} + +int QmiThreadSendQMI(PQCQMIMSG pRequest, PQCQMIMSG *ppResponse) { + return QmiThreadSendQMITimeout(pRequest, ppResponse, 30 * 1000); +} + +void QmiThreadRecvQMI(PQCQMIMSG pResponse) { + pthread_mutex_lock(&s_commandmutex); + if (pResponse == NULL) { + if (s_pRequest) { + free(s_pRequest); + s_pRequest = NULL; + s_pResponse = NULL; + pthread_cond_signal(&s_commandcond); + } + pthread_mutex_unlock(&s_commandmutex); + return; + } + dump_qmi(pResponse, le16_to_cpu(pResponse->QMIHdr.Length) + 1); + if (s_pRequest && is_response(s_pRequest, pResponse)) { + free(s_pRequest); + s_pRequest = NULL; + s_pResponse = malloc(le16_to_cpu(pResponse->QMIHdr.Length) + 1); + if (s_pResponse != NULL) { + memcpy(s_pResponse, pResponse, le16_to_cpu(pResponse->QMIHdr.Length) + 1); + } + pthread_cond_signal(&s_commandcond); + } else if ((pResponse->QMIHdr.QMIType == QMUX_TYPE_NAS) + && (le16_to_cpu(pResponse->MUXMsg.QMUXMsgHdrResp.Type) == QMINAS_SERVING_SYSTEM_IND)) { + qmidevice_send_event_to_main(RIL_UNSOL_RESPONSE_VOICE_NETWORK_STATE_CHANGED); + } else if ((pResponse->QMIHdr.QMIType == QMUX_TYPE_WDS) + && (le16_to_cpu(pResponse->MUXMsg.QMUXMsgHdrResp.Type) == QMIWDS_GET_PKT_SRVC_STATUS_IND)) { + qmidevice_send_event_to_main(RIL_UNSOL_DATA_CALL_LIST_CHANGED); + } else if ((pResponse->QMIHdr.QMIType == QMUX_TYPE_NAS) + && (le16_to_cpu(pResponse->MUXMsg.QMUXMsgHdrResp.Type) == QMINAS_SYS_INFO_IND)) { + qmidevice_send_event_to_main(RIL_UNSOL_RESPONSE_VOICE_NETWORK_STATE_CHANGED); + } else { + if (debug_qmi) + dbg_time("nobody care this qmi msg!!"); + } + pthread_mutex_unlock(&s_commandmutex); +} + +int requestSetEthMode(PROFILE_T *profile) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV linkProto; + + if (profile->IsDualIPSupported) { + UCHAR IpPreference, autoconnect_setting; + + IpPreference = IpFamilyV4; + pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS, QMIWDS_SET_CLIENT_IP_FAMILY_PREF_REQ, WdsSetClientIPFamilyPref, (void *)&IpPreference); + QmiThreadSendQMI(pRequest, &pResponse); + if (pResponse) free(pResponse); + + IpPreference = IpFamilyV6; + pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS_IPV6, QMIWDS_SET_CLIENT_IP_FAMILY_PREF_REQ, WdsSetClientIPFamilyPref, (void *)&IpPreference); + QmiThreadSendQMI(pRequest, &pResponse); + if (pResponse) free(pResponse); + + autoconnect_setting = 0; + pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS, QMIWDS_SET_AUTO_CONNECT_REQ , WdsSetAutoConnect, (void *)&autoconnect_setting); + QmiThreadSendQMI(pRequest, &pResponse); + if (pResponse) free(pResponse); + } + + pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS_ADMIN, QMIWDS_ADMIN_SET_DATA_FORMAT_REQ, WdaSetDataFormat, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + linkProto = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x11); + if (linkProto != NULL) { + profile->rawIP = (le32_to_cpu(linkProto->Value) == 2); + s_9x07 = profile->rawIP; + } + + free(pResponse); + return 0; +} + +#ifdef CONFIG_SIM +int requestGetPINStatus(SIM_Status *pSIMStatus) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + PQMIDMS_UIM_PIN_STATUS pPin1Status = NULL; + //PQMIDMS_UIM_PIN_STATUS pPin2Status = NULL; + + if (s_9x07 && qmiclientId[QMUX_TYPE_UIM]) + pRequest = ComposeQMUXMsg(QMUX_TYPE_UIM, QMIUIM_GET_CARD_STATUS_REQ, NULL, NULL); + else + pRequest = ComposeQMUXMsg(QMUX_TYPE_DMS, QMIDMS_UIM_GET_PIN_STATUS_REQ, NULL, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + pPin1Status = (PQMIDMS_UIM_PIN_STATUS)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x11); + //pPin2Status = (PQMIDMS_UIM_PIN_STATUS)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x12); + + if (pPin1Status != NULL) { + if (pPin1Status->PINStatus == QMI_PIN_STATUS_NOT_VERIF) { + *pSIMStatus = SIM_PIN; + } else if (pPin1Status->PINStatus == QMI_PIN_STATUS_BLOCKED) { + *pSIMStatus = SIM_PUK; + } else if (pPin1Status->PINStatus == QMI_PIN_STATUS_PERM_BLOCKED) { + *pSIMStatus = SIM_BAD; + } + } + + free(pResponse); + return 0; +} + +int requestGetSIMStatus(SIM_Status *pSIMStatus) { //RIL_REQUEST_GET_SIM_STATUS + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + const char * SIM_Status_String[] = { + "SIM_ABSENT", + "SIM_NOT_READY", + "SIM_READY", /* SIM_READY means the radio state is RADIO_STATE_SIM_READY */ + "SIM_PIN", + "SIM_PUK", + "SIM_NETWORK_PERSONALIZATION" + }; + +__requestGetSIMStatus: + if (s_9x07 && qmiclientId[QMUX_TYPE_UIM]) + pRequest = ComposeQMUXMsg(QMUX_TYPE_UIM, QMIUIM_GET_CARD_STATUS_REQ, NULL, NULL); + else + pRequest = ComposeQMUXMsg(QMUX_TYPE_DMS, QMIDMS_UIM_GET_STATE_REQ, NULL, NULL); + + err = QmiThreadSendQMI(pRequest, &pResponse); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + if (QMI_ERR_OP_DEVICE_UNSUPPORTED == le16_to_cpu(pResponse->MUXMsg.QMUXMsgHdrResp.QMUXError)) { + sleep(1); + goto __requestGetSIMStatus; + } + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + *pSIMStatus = SIM_ABSENT; + if (s_9x07 && qmiclientId[QMUX_TYPE_UIM]) + { + PQMIUIM_CARD_STATUS pCardStatus = NULL; + PQMIUIM_PIN_STATE pPINState = NULL; + UCHAR CardState = 0x01; + UCHAR PIN1State = QMI_PIN_STATUS_NOT_VERIF; + //UCHAR PIN1Retries; + //UCHAR PUK1Retries; + //UCHAR PIN2State; + //UCHAR PIN2Retries; + //UCHAR PUK2Retries; + + pCardStatus = (PQMIUIM_CARD_STATUS)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x10); + if (pCardStatus != NULL) + { + pPINState = (PQMIUIM_PIN_STATE)((PUCHAR)pCardStatus + sizeof(QMIUIM_CARD_STATUS) + pCardStatus->AIDLength); + CardState = pCardStatus->CardState; + if (pPINState->UnivPIN == 1) + { + PIN1State = pCardStatus->UPINState; + //PIN1Retries = pCardStatus->UPINRetries; + //PUK1Retries = pCardStatus->UPUKRetries; + } + else + { + PIN1State = pPINState->PIN1State; + //PIN1Retries = pPINState->PIN1Retries; + //PUK1Retries = pPINState->PUK1Retries; + } + //PIN2State = pPINState->PIN2State; + //PIN2Retries = pPINState->PIN2Retries; + //PUK2Retries = pPINState->PUK2Retries; + } + + *pSIMStatus = SIM_ABSENT; + if ((CardState == 0x01) && ((PIN1State == QMI_PIN_STATUS_VERIFIED)|| (PIN1State == QMI_PIN_STATUS_DISABLED))) + { + *pSIMStatus = SIM_READY; + } + else if (CardState == 0x01) + { + if (PIN1State == QMI_PIN_STATUS_NOT_VERIF) + { + *pSIMStatus = SIM_PIN; + } + if ( PIN1State == QMI_PIN_STATUS_BLOCKED) + { + *pSIMStatus = SIM_PUK; + } + else if (PIN1State == QMI_PIN_STATUS_PERM_BLOCKED) + { + *pSIMStatus = SIM_BAD; + } + else if (PIN1State == QMI_PIN_STATUS_NOT_INIT || PIN1State == QMI_PIN_STATUS_VERIFIED || PIN1State == QMI_PIN_STATUS_DISABLED) + { + *pSIMStatus = SIM_READY; + } + } + else if (CardState == 0x00 || CardState == 0x02) + { + } + else + { + } + } + else + { + //UIM state. Values: + // 0x00 UIM initialization completed + // 0x01 UIM is locked or the UIM failed + // 0x02 UIM is not present + // 0x03 Reserved + // 0xFF UIM state is currently + //unavailable + if (pResponse->MUXMsg.UIMGetStateResp.UIMState == 0x00) { + *pSIMStatus = SIM_READY; + } else if (pResponse->MUXMsg.UIMGetStateResp.UIMState == 0x01) { + *pSIMStatus = SIM_ABSENT; + err = requestGetPINStatus(pSIMStatus); + } else if ((pResponse->MUXMsg.UIMGetStateResp.UIMState == 0x02) || (pResponse->MUXMsg.UIMGetStateResp.UIMState == 0xFF)) { + *pSIMStatus = SIM_ABSENT; + } else { + *pSIMStatus = SIM_ABSENT; + } + } + dbg_time("%s SIMStatus: %s", __func__, SIM_Status_String[*pSIMStatus]); + + free(pResponse); + + return 0; +} + +int requestEnterSimPin(const CHAR *pPinCode) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + + if (s_9x07 && qmiclientId[QMUX_TYPE_UIM]) + pRequest = ComposeQMUXMsg(QMUX_TYPE_UIM, QMIUIM_VERIFY_PIN_REQ, UimVerifyPinReqSend, (void *)pPinCode); + else + pRequest = ComposeQMUXMsg(QMUX_TYPE_DMS, QMIDMS_UIM_VERIFY_PIN_REQ, DmsUIMVerifyPinReqSend, (void *)pPinCode); + err = QmiThreadSendQMI(pRequest, &pResponse); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + free(pResponse); + return 0; +} + +#ifdef CONFIG_IMSI_ICCID +int requestGetICCID(void) { //RIL_REQUEST_GET_IMSI + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + PQMIUIM_CONTENT pUimContent; + int err; + + if (s_9x07 && qmiclientId[QMUX_TYPE_UIM]) { + pRequest = ComposeQMUXMsg(QMUX_TYPE_UIM, QMIUIM_READ_TRANSPARENT_REQ, UimReadTransparentIMSIReqSend, (void *)"EF_ICCID"); + err = QmiThreadSendQMI(pRequest, &pResponse); + } else { + return 0; + } + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + pUimContent = (PQMIUIM_CONTENT)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x11); + if (pUimContent != NULL) { + static char DeviceICCID[32] = {'\0'}; + int i = 0, j = 0; + + for (i = 0, j = 0; i < le16_to_cpu(pUimContent->content_len); ++i) { + if ((pUimContent->content[i] & 0x0F) >= 0x0A) + DeviceICCID[j++] = 'A' + (pUimContent->content[i] & 0x0F); + else + DeviceICCID[j++] = '0' + (pUimContent->content[i] & 0x0F); + + if (((pUimContent->content[i] & 0xF0) >> 0x04) >= 0x0A) + DeviceICCID[j++] = 'A' + ((pUimContent->content[i] & 0xF0) >> 0x04); + else + DeviceICCID[j++] = '0' + ((pUimContent->content[i] & 0xF0) >> 0x04); + } + DeviceICCID[j] = '\0'; + + dbg_time("%s DeviceICCID: %s", __func__, DeviceICCID); + } + + free(pResponse); + return 0; +} + +int requestGetIMSI(void) { //RIL_REQUEST_GET_IMSI + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + PQMIUIM_CONTENT pUimContent; + int err; + + if (s_9x07 && qmiclientId[QMUX_TYPE_UIM]) { + pRequest = ComposeQMUXMsg(QMUX_TYPE_UIM, QMIUIM_READ_TRANSPARENT_REQ, UimReadTransparentIMSIReqSend, (void *)"EF_IMSI"); + err = QmiThreadSendQMI(pRequest, &pResponse); + } else { + return 0; + } + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + pUimContent = (PQMIUIM_CONTENT)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x11); + if (pUimContent != NULL) { + static char DeviceIMSI[32] = {'\0'}; + int i = 0, j = 0; + + for (i = 0, j = 0; i < le16_to_cpu(pUimContent->content[0]); ++i) { + if (i != 0) + DeviceIMSI[j++] = (pUimContent->content[i+1] & 0x0F) + '0'; + DeviceIMSI[j++] = ((pUimContent->content[i+1] & 0xF0) >> 0x04) + '0'; + } + DeviceIMSI[j] = '\0'; + + dbg_time("%s DeviceIMSI: %s", __func__, DeviceIMSI); + } + + free(pResponse); + return 0; +} +#endif +#endif + +#if 1 +static void quectel_convert_cdma_mcc_2_ascii_mcc( USHORT *p_mcc, USHORT mcc ) +{ + unsigned int d1, d2, d3, buf = mcc + 111; + + if ( mcc == 0x3FF ) // wildcard + { + *p_mcc = 3; + } + else + { + d3 = buf % 10; + buf = ( d3 == 0 ) ? (buf-10)/10 : buf/10; + + d2 = buf % 10; + buf = ( d2 == 0 ) ? (buf-10)/10 : buf/10; + + d1 = ( buf == 10 ) ? 0 : buf; + +//dbg_time("d1:%d, d2:%d,d3:%d",d1,d2,d3); + if ( d1<10 && d2<10 && d3<10 ) + { + *p_mcc = d1*100+d2*10+d3; +#if 0 + *(p_mcc+0) = '0' + d1; + *(p_mcc+1) = '0' + d2; + *(p_mcc+2) = '0' + d3; +#endif + } + else + { + //dbg_time( "invalid digits %d %d %d", d1, d2, d3 ); + *p_mcc = 0; + } + } +} + +static void quectel_convert_cdma_mnc_2_ascii_mnc( USHORT *p_mnc, USHORT imsi_11_12) +{ + unsigned int d1, d2, buf = imsi_11_12 + 11; + + if ( imsi_11_12 == 0x7F ) // wildcard + { + *p_mnc = 7; + } + else + { + d2 = buf % 10; + buf = ( d2 == 0 ) ? (buf-10)/10 : buf/10; + + d1 = ( buf == 10 ) ? 0 : buf; + + if ( d1<10 && d2<10 ) + { + *p_mnc = d1*10 + d2; + } + else + { + //dbg_time( "invalid digits %d %d", d1, d2, 0 ); + *p_mnc = 0; + } + } +} + +int requestGetHomeNetwork(USHORT *p_mcc, USHORT *p_mnc, USHORT *p_sid, USHORT *p_nid) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + PHOME_NETWORK pHomeNetwork; + PHOME_NETWORK_SYSTEMID pHomeNetworkSystemID; + + pRequest = ComposeQMUXMsg(QMUX_TYPE_NAS, QMINAS_GET_HOME_NETWORK_REQ, NULL, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + pHomeNetwork = (PHOME_NETWORK)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x01); + if (pHomeNetwork && p_mcc && p_mnc ) { + *p_mcc = le16_to_cpu(pHomeNetwork->MobileCountryCode); + *p_mnc = le16_to_cpu(pHomeNetwork->MobileNetworkCode); + //dbg_time("%s MobileCountryCode: %d, MobileNetworkCode: %d", __func__, *pMobileCountryCode, *pMobileNetworkCode); + } + + pHomeNetworkSystemID = (PHOME_NETWORK_SYSTEMID)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x10); + if (pHomeNetworkSystemID && p_sid && p_nid) { + *p_sid = le16_to_cpu(pHomeNetworkSystemID->SystemID); //china-hefei: sid 14451 + *p_nid = le16_to_cpu(pHomeNetworkSystemID->NetworkID); + //dbg_time("%s SystemID: %d, NetworkID: %d", __func__, *pSystemID, *pNetworkID); + } + + free(pResponse); + + return 0; +} +#endif + +#if 0 +// Lookup table for carriers known to produce SIMs which incorrectly indicate MNC length. +static const char * MCCMNC_CODES_HAVING_3DIGITS_MNC[] = { + "302370", "302720", "310260", + "405025", "405026", "405027", "405028", "405029", "405030", "405031", "405032", + "405033", "405034", "405035", "405036", "405037", "405038", "405039", "405040", + "405041", "405042", "405043", "405044", "405045", "405046", "405047", "405750", + "405751", "405752", "405753", "405754", "405755", "405756", "405799", "405800", + "405801", "405802", "405803", "405804", "405805", "405806", "405807", "405808", + "405809", "405810", "405811", "405812", "405813", "405814", "405815", "405816", + "405817", "405818", "405819", "405820", "405821", "405822", "405823", "405824", + "405825", "405826", "405827", "405828", "405829", "405830", "405831", "405832", + "405833", "405834", "405835", "405836", "405837", "405838", "405839", "405840", + "405841", "405842", "405843", "405844", "405845", "405846", "405847", "405848", + "405849", "405850", "405851", "405852", "405853", "405875", "405876", "405877", + "405878", "405879", "405880", "405881", "405882", "405883", "405884", "405885", + "405886", "405908", "405909", "405910", "405911", "405912", "405913", "405914", + "405915", "405916", "405917", "405918", "405919", "405920", "405921", "405922", + "405923", "405924", "405925", "405926", "405927", "405928", "405929", "405930", + "405931", "405932", "502142", "502143", "502145", "502146", "502147", "502148" +}; + +static const char * MCC_CODES_HAVING_3DIGITS_MNC[] = { + "302", //Canada + "310", //United States of America + "311", //United States of America + "312", //United States of America + "313", //United States of America + "314", //United States of America + "315", //United States of America + "316", //United States of America + "334", //Mexico + "338", //Jamaica + "342", //Barbados + "344", //Antigua and Barbuda + "346", //Cayman Islands + "348", //British Virgin Islands + "365", //Anguilla + "708", //Honduras (Republic of) + "722", //Argentine Republic + "732" //Colombia (Republic of) +}; + +int requestGetIMSI(const char **pp_imsi, USHORT *pMobileCountryCode, USHORT *pMobileNetworkCode) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + + if (pp_imsi) *pp_imsi = NULL; + if (pMobileCountryCode) *pMobileCountryCode = 0; + if (pMobileNetworkCode) *pMobileNetworkCode = 0; + + pRequest = ComposeQMUXMsg(QMUX_TYPE_DMS, QMIDMS_UIM_GET_IMSI_REQ, NULL, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + if (pMUXMsg->UIMGetIMSIResp.TLV2Type == 0x01 && le16_to_cpu(pMUXMsg->UIMGetIMSIResp.TLV2Length) >= 5) { + int mnc_len = 2; + unsigned i; + char tmp[4]; + + if (pp_imsi) *pp_imsi = strndup((const char *)(&pMUXMsg->UIMGetIMSIResp.IMSI), le16_to_cpu(pMUXMsg->UIMGetIMSIResp.TLV2Length)); + + for (i = 0; i < sizeof(MCCMNC_CODES_HAVING_3DIGITS_MNC)/sizeof(MCCMNC_CODES_HAVING_3DIGITS_MNC[0]); i++) { + if (!strncmp((const char *)(&pMUXMsg->UIMGetIMSIResp.IMSI), MCCMNC_CODES_HAVING_3DIGITS_MNC[i], 6)) { + mnc_len = 3; + break; + } + } + if (mnc_len == 2) { + for (i = 0; i < sizeof(MCC_CODES_HAVING_3DIGITS_MNC)/sizeof(MCC_CODES_HAVING_3DIGITS_MNC[0]); i++) { + if (!strncmp((const char *)(&pMUXMsg->UIMGetIMSIResp.IMSI), MCC_CODES_HAVING_3DIGITS_MNC[i], 3)) { + mnc_len = 3; + break; + } + } + } + + tmp[0] = (&pMUXMsg->UIMGetIMSIResp.IMSI)[0]; + tmp[1] = (&pMUXMsg->UIMGetIMSIResp.IMSI)[1]; + tmp[2] = (&pMUXMsg->UIMGetIMSIResp.IMSI)[2]; + tmp[3] = 0; + if (pMobileCountryCode) *pMobileCountryCode = atoi(tmp); + tmp[0] = (&pMUXMsg->UIMGetIMSIResp.IMSI)[3]; + tmp[1] = (&pMUXMsg->UIMGetIMSIResp.IMSI)[4]; + tmp[2] = 0; + if (mnc_len == 3) { + tmp[2] = (&pMUXMsg->UIMGetIMSIResp.IMSI)[6]; + } + if (pMobileNetworkCode) *pMobileNetworkCode = atoi(tmp); + } + + free(pResponse); + + return 0; +} +#endif + +struct wwan_data_class_str class2str[] = { + {WWAN_DATA_CLASS_NONE, "UNKNOWN"}, + {WWAN_DATA_CLASS_GPRS, "GPRS"}, + {WWAN_DATA_CLASS_EDGE, "EDGE"}, + {WWAN_DATA_CLASS_UMTS, "UMTS"}, + {WWAN_DATA_CLASS_HSDPA, "HSDPA"}, + {WWAN_DATA_CLASS_HSUPA, "HSUPA"}, + {WWAN_DATA_CLASS_LTE, "LTE"}, + {WWAN_DATA_CLASS_1XRTT, "1XRTT"}, + {WWAN_DATA_CLASS_1XEVDO, "1XEVDO"}, + {WWAN_DATA_CLASS_1XEVDO_REVA, "1XEVDO_REVA"}, + {WWAN_DATA_CLASS_1XEVDV, "1XEVDV"}, + {WWAN_DATA_CLASS_3XRTT, "3XRTT"}, + {WWAN_DATA_CLASS_1XEVDO_REVB, "1XEVDO_REVB"}, + {WWAN_DATA_CLASS_UMB, "UMB"}, + {WWAN_DATA_CLASS_CUSTOM, "CUSTOM"}, +}; + +CHAR *wwan_data_class2str(ULONG class) +{ + unsigned int i = 0; + for (i = 0; i < sizeof(class2str)/sizeof(class2str[0]); i++) { + if (class2str[i].class == class) { + return class2str[i].str; + } + } + return "UNKNOWN"; +} + +int requestRegistrationState2(UCHAR *pPSAttachedState) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + USHORT MobileCountryCode = 0; + USHORT MobileNetworkCode = 0; + const char *pDataCapStr = "UNKNOW"; + LONG remainingLen; + PSERVICE_STATUS_INFO pServiceStatusInfo; + int is_lte = 0; + PCDMA_SYSTEM_INFO pCdmaSystemInfo; + PHDR_SYSTEM_INFO pHdrSystemInfo; + PGSM_SYSTEM_INFO pGsmSystemInfo; + PWCDMA_SYSTEM_INFO pWcdmaSystemInfo; + PLTE_SYSTEM_INFO pLteSystemInfo; + PTDSCDMA_SYSTEM_INFO pTdscdmaSystemInfo; + UCHAR DeviceClass = 0; + ULONG DataCapList = 0; + + *pPSAttachedState = 0; + pRequest = ComposeQMUXMsg(QMUX_TYPE_NAS, QMINAS_GET_SYS_INFO_REQ, NULL, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + pServiceStatusInfo = (PSERVICE_STATUS_INFO)(((PCHAR)&pMUXMsg->GetSysInfoResp) + QCQMUX_MSG_HDR_SIZE); + remainingLen = le16_to_cpu(pMUXMsg->GetSysInfoResp.Length); + + s_is_cdma = 0; + s_hdr_personality = 0; + while (remainingLen > 0) { + switch (pServiceStatusInfo->TLVType) { + case 0x10: // CDMA + if (pServiceStatusInfo->SrvStatus == 0x02) { + DataCapList = WWAN_DATA_CLASS_1XRTT| + WWAN_DATA_CLASS_1XEVDO| + WWAN_DATA_CLASS_1XEVDO_REVA| + WWAN_DATA_CLASS_1XEVDV| + WWAN_DATA_CLASS_1XEVDO_REVB; + DeviceClass = DEVICE_CLASS_CDMA; + s_is_cdma = (0 == is_lte); + } + break; + case 0x11: // HDR + if (pServiceStatusInfo->SrvStatus == 0x02) { + DataCapList = WWAN_DATA_CLASS_3XRTT| + WWAN_DATA_CLASS_UMB; + DeviceClass = DEVICE_CLASS_CDMA; + s_is_cdma = (0 == is_lte); + } + break; + case 0x12: // GSM + if (pServiceStatusInfo->SrvStatus == 0x02) { + DataCapList = WWAN_DATA_CLASS_GPRS| + WWAN_DATA_CLASS_EDGE; + DeviceClass = DEVICE_CLASS_GSM; + } + break; + case 0x13: // WCDMA + if (pServiceStatusInfo->SrvStatus == 0x02) { + DataCapList = WWAN_DATA_CLASS_UMTS; + DeviceClass = DEVICE_CLASS_GSM; + } + break; + case 0x14: // LTE + if (pServiceStatusInfo->SrvStatus == 0x02) { + DataCapList = WWAN_DATA_CLASS_LTE; + DeviceClass = DEVICE_CLASS_GSM; + is_lte = 1; + s_is_cdma = 0; + } + break; + case 0x24: // TDSCDMA + if (pServiceStatusInfo->SrvStatus == 0x02) { + pDataCapStr = "TD-SCDMA"; + } + break; + case 0x15: // CDMA + // CDMA_SYSTEM_INFO + pCdmaSystemInfo = (PCDMA_SYSTEM_INFO)pServiceStatusInfo; + if (pCdmaSystemInfo->SrvDomainValid == 0x01) { + *pPSAttachedState = 0; + if (pCdmaSystemInfo->SrvDomain & 0x02) { + *pPSAttachedState = 1; + s_is_cdma = (0 == is_lte); + } + } +#if 0 + if (pCdmaSystemInfo->SrvCapabilityValid == 0x01) { + *pPSAttachedState = 0; + if (pCdmaSystemInfo->SrvCapability & 0x02) { + *pPSAttachedState = 1; + s_is_cdma = (0 == is_lte); + } + } +#endif + if (pCdmaSystemInfo->NetworkIdValid == 0x01) { + int i; + CHAR temp[10]; + strncpy(temp, (CHAR *)pCdmaSystemInfo->MCC, 3); + temp[3] = '\0'; + for (i = 0; i < 4; i++) { + if ((UCHAR)temp[i] == 0xFF) { + temp[i] = '\0'; + } + } + MobileCountryCode = (USHORT)atoi(temp); + + strncpy(temp, (CHAR *)pCdmaSystemInfo->MNC, 3); + temp[3] = '\0'; + for (i = 0; i < 4; i++) { + if ((UCHAR)temp[i] == 0xFF) { + temp[i] = '\0'; + } + } + MobileNetworkCode = (USHORT)atoi(temp); + } + break; + case 0x16: // HDR + // HDR_SYSTEM_INFO + pHdrSystemInfo = (PHDR_SYSTEM_INFO)pServiceStatusInfo; + if (pHdrSystemInfo->SrvDomainValid == 0x01) { + *pPSAttachedState = 0; + if (pHdrSystemInfo->SrvDomain & 0x02) { + *pPSAttachedState = 1; + s_is_cdma = (0 == is_lte); + } + } +#if 0 + if (pHdrSystemInfo->SrvCapabilityValid == 0x01) { + *pPSAttachedState = 0; + if (pHdrSystemInfo->SrvCapability & 0x02) { + *pPSAttachedState = 1; + s_is_cdma = (0 == is_lte); + } + } +#endif + if (*pPSAttachedState && pHdrSystemInfo->HdrPersonalityValid == 0x01) { + if (pHdrSystemInfo->HdrPersonality == 0x03) + s_hdr_personality = 0x02; + //else if (pHdrSystemInfo->HdrPersonality == 0x02) + // s_hdr_personality = 0x01; + } + USHORT cmda_mcc = 0, cdma_mnc = 0; + if(!requestGetHomeNetwork(&cmda_mcc, &cdma_mnc,NULL, NULL) && cmda_mcc) { + quectel_convert_cdma_mcc_2_ascii_mcc(&MobileCountryCode, cmda_mcc); + quectel_convert_cdma_mnc_2_ascii_mnc(&MobileNetworkCode, cdma_mnc); + } + break; + case 0x17: // GSM + // GSM_SYSTEM_INFO + pGsmSystemInfo = (PGSM_SYSTEM_INFO)pServiceStatusInfo; + if (pGsmSystemInfo->SrvDomainValid == 0x01) { + *pPSAttachedState = 0; + if (pGsmSystemInfo->SrvDomain & 0x02) { + *pPSAttachedState = 1; + } + } +#if 0 + if (pGsmSystemInfo->SrvCapabilityValid == 0x01) { + *pPSAttachedState = 0; + if (pGsmSystemInfo->SrvCapability & 0x02) { + *pPSAttachedState = 1; + } + } +#endif + if (pGsmSystemInfo->NetworkIdValid == 0x01) { + int i; + CHAR temp[10]; + strncpy(temp, (CHAR *)pGsmSystemInfo->MCC, 3); + temp[3] = '\0'; + for (i = 0; i < 4; i++) { + if ((UCHAR)temp[i] == 0xFF) { + temp[i] = '\0'; + } + } + MobileCountryCode = (USHORT)atoi(temp); + + strncpy(temp, (CHAR *)pGsmSystemInfo->MNC, 3); + temp[3] = '\0'; + for (i = 0; i < 4; i++) { + if ((UCHAR)temp[i] == 0xFF) { + temp[i] = '\0'; + } + } + MobileNetworkCode = (USHORT)atoi(temp); + } + break; + case 0x18: // WCDMA + // WCDMA_SYSTEM_INFO + pWcdmaSystemInfo = (PWCDMA_SYSTEM_INFO)pServiceStatusInfo; + if (pWcdmaSystemInfo->SrvDomainValid == 0x01) { + *pPSAttachedState = 0; + if (pWcdmaSystemInfo->SrvDomain & 0x02) { + *pPSAttachedState = 1; + } + } +#if 0 + if (pWcdmaSystemInfo->SrvCapabilityValid == 0x01) { + *pPSAttachedState = 0; + if (pWcdmaSystemInfo->SrvCapability & 0x02) { + *pPSAttachedState = 1; + } + } +#endif + if (pWcdmaSystemInfo->NetworkIdValid == 0x01) { + int i; + CHAR temp[10]; + strncpy(temp, (CHAR *)pWcdmaSystemInfo->MCC, 3); + temp[3] = '\0'; + for (i = 0; i < 4; i++) { + if ((UCHAR)temp[i] == 0xFF) { + temp[i] = '\0'; + } + } + MobileCountryCode = (USHORT)atoi(temp); + + strncpy(temp, (CHAR *)pWcdmaSystemInfo->MNC, 3); + temp[3] = '\0'; + for (i = 0; i < 4; i++) { + if ((UCHAR)temp[i] == 0xFF) { + temp[i] = '\0'; + } + } + MobileNetworkCode = (USHORT)atoi(temp); + } + break; + case 0x19: // LTE_SYSTEM_INFO + // LTE_SYSTEM_INFO + pLteSystemInfo = (PLTE_SYSTEM_INFO)pServiceStatusInfo; + if (pLteSystemInfo->SrvDomainValid == 0x01) { + *pPSAttachedState = 0; + if (pLteSystemInfo->SrvDomain & 0x02) { + *pPSAttachedState = 1; + is_lte = 1; + s_is_cdma = 0; + } + } +#if 0 + if (pLteSystemInfo->SrvCapabilityValid == 0x01) { + *pPSAttachedState = 0; + if (pLteSystemInfo->SrvCapability & 0x02) { + *pPSAttachedState = 1; + is_lte = 1; + s_is_cdma = 0; + } + } +#endif + if (pLteSystemInfo->NetworkIdValid == 0x01) { + int i; + CHAR temp[10]; + strncpy(temp, (CHAR *)pLteSystemInfo->MCC, 3); + temp[3] = '\0'; + for (i = 0; i < 4; i++) { + if ((UCHAR)temp[i] == 0xFF) { + temp[i] = '\0'; + } + } + MobileCountryCode = (USHORT)atoi(temp); + + strncpy(temp, (CHAR *)pLteSystemInfo->MNC, 3); + temp[3] = '\0'; + for (i = 0; i < 4; i++) { + if ((UCHAR)temp[i] == 0xFF) { + temp[i] = '\0'; + } + } + MobileNetworkCode = (USHORT)atoi(temp); + } + break; + case 0x25: // TDSCDMA + // TDSCDMA_SYSTEM_INFO + pTdscdmaSystemInfo = (PTDSCDMA_SYSTEM_INFO)pServiceStatusInfo; + if (pTdscdmaSystemInfo->SrvDomainValid == 0x01) { + *pPSAttachedState = 0; + if (pTdscdmaSystemInfo->SrvDomain & 0x02) { + *pPSAttachedState = 1; + } + } +#if 0 + if (pTdscdmaSystemInfo->SrvCapabilityValid == 0x01) { + *pPSAttachedState = 0; + if (pTdscdmaSystemInfo->SrvCapability & 0x02) { + *pPSAttachedState = 1; + } + } +#endif + if (pTdscdmaSystemInfo->NetworkIdValid == 0x01) { + int i; + CHAR temp[10]; + strncpy(temp, (CHAR *)pTdscdmaSystemInfo->MCC, 3); + temp[3] = '\0'; + for (i = 0; i < 4; i++) { + if ((UCHAR)temp[i] == 0xFF) { + temp[i] = '\0'; + } + } + MobileCountryCode = (USHORT)atoi(temp); + + strncpy(temp, (CHAR *)pTdscdmaSystemInfo->MNC, 3); + temp[3] = '\0'; + for (i = 0; i < 4; i++) { + if ((UCHAR)temp[i] == 0xFF) { + temp[i] = '\0'; + } + } + MobileNetworkCode = (USHORT)atoi(temp); + } + break; + default: + break; + } /* switch (pServiceStatusInfo->TLYType) */ + remainingLen -= (le16_to_cpu(pServiceStatusInfo->TLVLength) + 3); + pServiceStatusInfo = (PSERVICE_STATUS_INFO)((PCHAR)&pServiceStatusInfo->TLVLength + le16_to_cpu(pServiceStatusInfo->TLVLength) + sizeof(USHORT)); + } /* while (remainingLen > 0) */ + + if (DeviceClass == DEVICE_CLASS_CDMA) { + if (s_hdr_personality == 2) { + pDataCapStr = s_hdr_personality == 2 ? "eHRPD" : "HRPD"; + } else if (DataCapList & WWAN_DATA_CLASS_1XEVDO_REVB) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_1XEVDO_REVB); + } else if (DataCapList & WWAN_DATA_CLASS_1XEVDO_REVA) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_1XEVDO_REVA); + } else if (DataCapList & WWAN_DATA_CLASS_1XEVDO) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_1XEVDO); + } else if (DataCapList & WWAN_DATA_CLASS_1XRTT) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_1XRTT); + } else if (DataCapList & WWAN_DATA_CLASS_3XRTT) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_3XRTT); + } else if (DataCapList & WWAN_DATA_CLASS_UMB) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_UMB); + } + } else { + if (DataCapList & WWAN_DATA_CLASS_LTE) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_LTE); + } else if ((DataCapList & WWAN_DATA_CLASS_HSDPA) && (DataCapList & WWAN_DATA_CLASS_HSUPA)) { + pDataCapStr = "HSDPA_HSUPA"; + } else if (DataCapList & WWAN_DATA_CLASS_HSDPA) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_HSDPA); + } else if (DataCapList & WWAN_DATA_CLASS_HSUPA) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_HSUPA); + } else if (DataCapList & WWAN_DATA_CLASS_UMTS) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_UMTS); + } else if (DataCapList & WWAN_DATA_CLASS_EDGE) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_EDGE); + } else if (DataCapList & WWAN_DATA_CLASS_GPRS) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_GPRS); + } + } + + dbg_time("%s MCC: %d, MNC: %d, PS: %s, DataCap: %s", __func__, + MobileCountryCode, MobileNetworkCode, (*pPSAttachedState == 1) ? "Attached" : "Detached" , pDataCapStr); + + free(pResponse); + + return 0; +} + +int requestRegistrationState(UCHAR *pPSAttachedState) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + PQMINAS_CURRENT_PLMN_MSG pCurrentPlmn; + PSERVING_SYSTEM pServingSystem; + PQMINAS_DATA_CAP pDataCap; + USHORT MobileCountryCode = 0; + USHORT MobileNetworkCode = 0; + const char *pDataCapStr = "UNKNOW"; + + if (s_9x07) { + return requestRegistrationState2(pPSAttachedState); + } + + pRequest = ComposeQMUXMsg(QMUX_TYPE_NAS, QMINAS_GET_SERVING_SYSTEM_REQ, NULL, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + pCurrentPlmn = (PQMINAS_CURRENT_PLMN_MSG)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x12); + if (pCurrentPlmn) { + MobileCountryCode = le16_to_cpu(pCurrentPlmn->MobileCountryCode); + MobileNetworkCode = le16_to_cpu(pCurrentPlmn->MobileNetworkCode); + } + + *pPSAttachedState = 0; + pServingSystem = (PSERVING_SYSTEM)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x01); + if (pServingSystem) { + //Packet-switched domain attach state of the mobile. + //0x00 PS_UNKNOWN ?Unknown or not applicable + //0x01 PS_ATTACHED ?Attached + //0x02 PS_DETACHED ?Detached + *pPSAttachedState = pServingSystem->RegistrationState; + if (pServingSystem->RegistrationState == 0x01) //0x01 ?C REGISTERED ?C Registered with a network + *pPSAttachedState = pServingSystem->PSAttachedState; + else { + //MobileCountryCode = MobileNetworkCode = 0; + *pPSAttachedState = 0x02; + } + } + + pDataCap = (PQMINAS_DATA_CAP)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x11); + if (pDataCap && pDataCap->DataCapListLen) { + UCHAR *DataCap = &pDataCap->DataCap; + if (pDataCap->DataCapListLen == 2) { + if ((DataCap[0] == 0x06) && ((DataCap[1] == 0x08) || (DataCap[1] == 0x0A))) + DataCap[0] = DataCap[1]; + } + switch (DataCap[0]) { + case 0x01: pDataCapStr = "GPRS"; break; + case 0x02: pDataCapStr = "EDGE"; break; + case 0x03: pDataCapStr = "HSDPA"; break; + case 0x04: pDataCapStr = "HSUPA"; break; + case 0x05: pDataCapStr = "UMTS"; break; + case 0x06: pDataCapStr = "1XRTT"; break; + case 0x07: pDataCapStr = "1XEVDO"; break; + case 0x08: pDataCapStr = "1XEVDO_REVA"; break; + case 0x09: pDataCapStr = "GPRS"; break; + case 0x0A: pDataCapStr = "1XEVDO_REVB"; break; + case 0x0B: pDataCapStr = "LTE"; break; + case 0x0C: pDataCapStr = "HSDPA"; break; + case 0x0D: pDataCapStr = "HSDPA"; break; + default: pDataCapStr = "UNKNOW"; break; + } + } + + if (pServingSystem && pServingSystem->RegistrationState == 0x01 && pServingSystem->InUseRadioIF && pServingSystem->RadioIF == 0x09) { + pDataCapStr = "TD-SCDMA"; + } + + s_is_cdma = 0; + if (pServingSystem && pServingSystem->RegistrationState == 0x01 && pServingSystem->InUseRadioIF && (pServingSystem->RadioIF == 0x01 || pServingSystem->RadioIF == 0x02)) { + USHORT cmda_mcc = 0, cdma_mnc = 0; + s_is_cdma = 1; + if(!requestGetHomeNetwork(&cmda_mcc, &cdma_mnc,NULL, NULL) && cmda_mcc) { + quectel_convert_cdma_mcc_2_ascii_mcc(&MobileCountryCode, cmda_mcc); + quectel_convert_cdma_mnc_2_ascii_mnc(&MobileNetworkCode, cdma_mnc); + } + if (1) { + PQCQMUX_TLV pTLV = (PQCQMUX_TLV)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x23); + if (pTLV) + s_hdr_personality = pTLV->Value; + else + s_hdr_personality = 0; + if (s_hdr_personality == 2) + pDataCapStr = "eHRPD"; + } + } + + dbg_time("%s MCC: %d, MNC: %d, PS: %s, DataCap: %s", __func__, + MobileCountryCode, MobileNetworkCode, (*pPSAttachedState == 1) ? "Attached" : "Detached" , pDataCapStr); + + free(pResponse); + + return 0; +} + +int requestQueryDataCall(UCHAR *pConnectionStatus, int curIpFamily) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + PQMIWDS_PKT_SRVC_TLV pPktSrvc; + UCHAR oldConnectionStatus = *pConnectionStatus; + UCHAR QMIType = (curIpFamily == IpFamilyV4) ? QMUX_TYPE_WDS : QMUX_TYPE_WDS_IPV6; + + pRequest = ComposeQMUXMsg(QMIType, QMIWDS_GET_PKT_SRVC_STATUS_REQ, NULL, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + *pConnectionStatus = QWDS_PKT_DATA_DISCONNECTED; + pPktSrvc = (PQMIWDS_PKT_SRVC_TLV)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x01); + if (pPktSrvc) { + *pConnectionStatus = pPktSrvc->ConnectionStatus; + if ((le16_to_cpu(pPktSrvc->TLVLength) == 2) && (pPktSrvc->ReconfigReqd == 0x01)) + *pConnectionStatus = QWDS_PKT_DATA_DISCONNECTED; + } + + if (*pConnectionStatus == QWDS_PKT_DATA_DISCONNECTED) { + if (curIpFamily == IpFamilyV4) + WdsConnectionIPv4Handle = 0; + else + WdsConnectionIPv6Handle = 0; + } + + if (oldConnectionStatus != *pConnectionStatus || debug_qmi) { + dbg_time("%s %sConnectionStatus: %s", __func__, (curIpFamily == IpFamilyV4) ? "IPv4" : "IPv6", + (*pConnectionStatus == QWDS_PKT_DATA_CONNECTED) ? "CONNECTED" : "DISCONNECTED"); + } + + free(pResponse); + return 0; +} + +#if 0 +BOOLEAN QCMAIN_IsDualIPSupported(PMP_ADAPTER pAdapter) +{ + return (pAdapter->QMUXVersion[QMUX_TYPE_WDS].Major >= 1 && pAdapter->QMUXVersion[QMUX_TYPE_WDS].Minor >= 9); +} // QCMAIN_IsDualIPSupported +#endif + +int requestSetupDataCall(PROFILE_T *profile, int curIpFamily) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err = 0; + int old_auth = profile->auth; + UCHAR QMIType = (curIpFamily == IpFamilyV4) ? QMUX_TYPE_WDS : QMUX_TYPE_WDS_IPV6; + +//DualIPSupported means can get ipv4 & ipv6 address at the same time, one wds for ipv4, the other wds for ipv6 +__requestSetupDataCall: + profile->curIpFamily = curIpFamily; + pRequest = ComposeQMUXMsg(QMIType, QMIWDS_START_NETWORK_INTERFACE_REQ, WdsStartNwInterfaceReq, profile); + err = QmiThreadSendQMITimeout(pRequest, &pResponse, 120 * 1000); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) + { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + + if (curIpFamily == IpFamilyV4 && old_auth == profile->auth + && profile->user && profile->user[0] && profile->password && profile->password[0]) { + profile->auth = (profile->auth == 1) ? 2 : 1; + free(pResponse); + goto __requestSetupDataCall; + } + + profile->auth = old_auth; + + err = le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + free(pResponse); + return err; + } + + if (curIpFamily == IpFamilyV4) { + WdsConnectionIPv4Handle = le32_to_cpu(pResponse->MUXMsg.StartNwInterfaceResp.Handle); + dbg_time("%s WdsConnectionIPv4Handle: 0x%08x", __func__, WdsConnectionIPv4Handle); + } else { + WdsConnectionIPv6Handle = le32_to_cpu(pResponse->MUXMsg.StartNwInterfaceResp.Handle); + dbg_time("%s WdsConnectionIPv6Handle: 0x%08x", __func__, WdsConnectionIPv6Handle); + } + + free(pResponse); + + return 0; +} + +int requestDeactivateDefaultPDP(PROFILE_T *profile, int curIpFamily) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + UCHAR QMIType = (curIpFamily == 0x04) ? QMUX_TYPE_WDS : QMUX_TYPE_WDS_IPV6; + + if (curIpFamily == IpFamilyV4 && WdsConnectionIPv4Handle == 0) + return 0; + if (curIpFamily == IpFamilyV6 && WdsConnectionIPv6Handle == 0) + return 0; + + pRequest = ComposeQMUXMsg(QMIType, QMIWDS_STOP_NETWORK_INTERFACE_REQ , WdsStopNwInterfaceReq, &curIpFamily); + err = QmiThreadSendQMI(pRequest, &pResponse); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + if (curIpFamily == IpFamilyV4) + WdsConnectionIPv4Handle = 0; + else + WdsConnectionIPv6Handle = 0; + free(pResponse); + return 0; +} + +int requestGetIPAddress(PROFILE_T *profile, int curIpFamily) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR pIpv4Addr; + PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV6_ADDR pIpv6Addr = NULL; + PQMIWDS_GET_RUNTIME_SETTINGS_TLV_MTU pMtu; + IPV4_T *pIpv4 = &profile->ipv4; + IPV6_T *pIpv6 = &profile->ipv6; + UCHAR QMIType = (curIpFamily == 0x04) ? QMUX_TYPE_WDS : QMUX_TYPE_WDS_IPV6; + + if (curIpFamily == IpFamilyV4) { + memset(pIpv4, 0x00, sizeof(IPV4_T)); + if (WdsConnectionIPv4Handle == 0) + return 0; + } else if (curIpFamily == IpFamilyV6) { + memset(pIpv6, 0x00, sizeof(IPV6_T)); + if (WdsConnectionIPv6Handle == 0) + return 0; + } + + pRequest = ComposeQMUXMsg(QMIType, QMIWDS_GET_RUNTIME_SETTINGS_REQ, WdsGetRuntimeSettingReq, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + pIpv4Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4PRIMARYDNS); + if (pIpv4Addr) { + pIpv4->DnsPrimary = pIpv4Addr->IPV4Address; + } + + pIpv4Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4SECONDARYDNS); + if (pIpv4Addr) { + pIpv4->DnsSecondary = pIpv4Addr->IPV4Address; + } + + pIpv4Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4GATEWAY); + if (pIpv4Addr) { + pIpv4->Gateway = pIpv4Addr->IPV4Address; + } + + pIpv4Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4SUBNET); + if (pIpv4Addr) { + pIpv4->SubnetMask = pIpv4Addr->IPV4Address; + } + + pIpv4Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4); + if (pIpv4Addr) { + pIpv4->Address = pIpv4Addr->IPV4Address; + } + + pIpv6Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV6_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6PRIMARYDNS); + if (pIpv6Addr) { + memcpy(pIpv6->DnsPrimary, pIpv6Addr->IPV6Address, 16); + } + + pIpv6Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV6_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6SECONDARYDNS); + if (pIpv6Addr) { + memcpy(pIpv6->DnsSecondary, pIpv6Addr->IPV6Address, 16); + } + + pIpv6Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV6_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6GATEWAY); + if (pIpv6Addr) { + memcpy(pIpv6->Gateway, pIpv6Addr->IPV6Address, 16); + pIpv6->PrefixLengthGateway = pIpv6Addr->PrefixLength; + } + + pIpv6Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV6_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6); + if (pIpv6Addr) { + memcpy(pIpv6->Address, pIpv6Addr->IPV6Address, 16); + pIpv6->PrefixLengthIPAddr = pIpv6Addr->PrefixLength; + } + + pMtu = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_MTU)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_MTU); + if (pMtu) { + pIpv4->Mtu = pIpv6->Mtu = le32_to_cpu(pMtu->Mtu); + } + + free(pResponse); + return 0; +} + +#ifdef CONFIG_APN +int requestSetProfile(PROFILE_T *profile) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + + if (!profile->pdp) + return 0; + + dbg_time("%s[%d] %s/%s/%s/%d", __func__, profile->pdp, profile->apn, profile->user, profile->password, profile->auth); + pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS, QMIWDS_MODIFY_PROFILE_SETTINGS_REQ, WdsModifyProfileSettingsReq, profile); + err = QmiThreadSendQMI(pRequest, &pResponse); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + free(pResponse); + return 0; +} + +int requestGetProfile(PROFILE_T *profile) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + char *apn = NULL; + char *user = NULL; + char *password = NULL; + int auth = 0; + PQMIWDS_APNNAME pApnName; + PQMIWDS_USERNAME pUserName; + PQMIWDS_PASSWD pPassWd; + PQMIWDS_AUTH_PREFERENCE pAuthPref; + + if (!profile->pdp) + return 0; + + pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS, QMIWDS_GET_PROFILE_SETTINGS_REQ, WdsGetProfileSettingsReqSend, profile); + err = QmiThreadSendQMI(pRequest, &pResponse); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + pApnName = (PQMIWDS_APNNAME)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x14); + pUserName = (PQMIWDS_USERNAME)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x1B); + pPassWd = (PQMIWDS_PASSWD)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x1C); + pAuthPref = (PQMIWDS_AUTH_PREFERENCE)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x1D); + + if (pApnName/* && le16_to_cpu(pApnName->TLVLength)*/) + apn = strndup((const char *)(&pApnName->ApnName), le16_to_cpu(pApnName->TLVLength)); + if (pUserName/* && pUserName->UserName*/) + user = strndup((const char *)(&pUserName->UserName), le16_to_cpu(pUserName->TLVLength)); + if (pPassWd/* && le16_to_cpu(pPassWd->TLVLength)*/) + password = strndup((const char *)(&pPassWd->Passwd), le16_to_cpu(pPassWd->TLVLength)); + if (pAuthPref/* && le16_to_cpu(pAuthPref->TLVLength)*/) { + auth = pAuthPref->AuthPreference; + } + +#if 0 + if (profile) { + profile->apn = apn; + profile->user = user; + profile->password = password; + profile->auth = auth; + } +#endif + + dbg_time("%s[%d] %s/%s/%s/%d", __func__, profile->pdp, apn, user, password, auth); + + free(pResponse); + return 0; +} +#endif + +#ifdef CONFIG_VERSION +int requestBaseBandVersion(const char **pp_reversion) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + PDEVICE_REV_ID revId; + int err; + + if (pp_reversion) *pp_reversion = NULL; + + pRequest = ComposeQMUXMsg(QMUX_TYPE_DMS, QMIDMS_GET_DEVICE_REV_ID_REQ, NULL, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + revId = (PDEVICE_REV_ID)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x01); + + if (revId && le16_to_cpu(revId->TLVLength)) + { + char *DeviceRevisionID = strndup((const char *)(&revId->RevisionID), le16_to_cpu(revId->TLVLength)); + dbg_time("%s %s", __func__, DeviceRevisionID); + if (s_9x07 == -1) { //fail to get QMUX_TYPE_WDS_ADMIN + if (strncmp(DeviceRevisionID, "EC20", strlen("EC20"))) + s_9x07 = 1; + else + s_9x07 = DeviceRevisionID[5] == 'F' || DeviceRevisionID[6] == 'F'; //EC20CF,EC20EF,EC20CEF + } + if (pp_reversion) *pp_reversion = DeviceRevisionID; + } + + free(pResponse); + return 0; +} +#endif + +#ifdef CONFIG_RESET_RADIO +static USHORT DmsSetOperatingModeReq(PQMUX_MSG pMUXMsg, void *arg) { + pMUXMsg->SetOperatingModeReq.TLVType = 0x01; + pMUXMsg->SetOperatingModeReq.TLVLength = 1; + pMUXMsg->SetOperatingModeReq.OperatingMode = *((UCHAR *)arg); + + return sizeof(QMIDMS_SET_OPERATING_MODE_REQ_MSG); +} + +int requestSetOperatingMode(UCHAR OperatingMode) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + + dbg_time("%s(%d)", __func__, OperatingMode); + + pRequest = ComposeQMUXMsg(QMUX_TYPE_DMS, QMIDMS_SET_OPERATING_MODE_REQ, DmsSetOperatingModeReq, &OperatingMode); + err = QmiThreadSendQMI(pRequest, &pResponse); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + free(pResponse); + return 0; +} +#endif diff --git a/root/package/link4all/quectel-CM/simcom-cm/QMIThread.h b/root/package/link4all/quectel-CM/simcom-cm/QMIThread.h new file mode 100644 index 00000000..0d85b0cc --- /dev/null +++ b/root/package/link4all/quectel-CM/simcom-cm/QMIThread.h @@ -0,0 +1,174 @@ +#ifndef __QMI_THREAD_H__ +#define __QMI_THREAD_H__ + +#define CONFIG_GOBINET +#define CONFIG_QMIWWAN +#define CONFIG_SIM +//#define CONFIG_APN +#define CONFIG_VERSION +#define CONFIG_DEFAULT_PDP 1 +//#define CONFIG_IMSI_ICCID +#ifndef ANDROID +#define CONFIG_RESET_RADIO (45) //Reset Radiao(AT+CFUN=4,AT+CFUN=1) when cann not register network or setup data call in 45 seconds +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "MPQMI.h" +#include "MPQCTL.h" +#include "MPQMUX.h" + +#define DEVICE_CLASS_UNKNOWN 0 +#define DEVICE_CLASS_CDMA 1 +#define DEVICE_CLASS_GSM 2 + +#define WWAN_DATA_CLASS_NONE 0x00000000 +#define WWAN_DATA_CLASS_GPRS 0x00000001 +#define WWAN_DATA_CLASS_EDGE 0x00000002 /* EGPRS */ +#define WWAN_DATA_CLASS_UMTS 0x00000004 +#define WWAN_DATA_CLASS_HSDPA 0x00000008 +#define WWAN_DATA_CLASS_HSUPA 0x00000010 +#define WWAN_DATA_CLASS_LTE 0x00000020 +#define WWAN_DATA_CLASS_1XRTT 0x00010000 +#define WWAN_DATA_CLASS_1XEVDO 0x00020000 +#define WWAN_DATA_CLASS_1XEVDO_REVA 0x00040000 +#define WWAN_DATA_CLASS_1XEVDV 0x00080000 +#define WWAN_DATA_CLASS_3XRTT 0x00100000 +#define WWAN_DATA_CLASS_1XEVDO_REVB 0x00200000 /* for future use */ +#define WWAN_DATA_CLASS_UMB 0x00400000 +#define WWAN_DATA_CLASS_CUSTOM 0x80000000 + +struct wwan_data_class_str { + ULONG class; + CHAR *str; +}; + +#pragma pack(push, 1) + +typedef struct _QCQMIMSG { + QCQMI_HDR QMIHdr; + union { + QMICTL_MSG CTLMsg; + QMUX_MSG MUXMsg; + }; +} __attribute__ ((packed)) QCQMIMSG, *PQCQMIMSG; + +#pragma pack(pop) + +typedef struct __IPV4 { + uint32_t Address; + uint32_t Gateway; + uint32_t SubnetMask; + uint32_t DnsPrimary; + uint32_t DnsSecondary; + uint32_t Mtu; +} IPV4_T; + +typedef struct __IPV6 { + UCHAR Address[16]; + UCHAR Gateway[16]; + UCHAR SubnetMask[16]; + UCHAR DnsPrimary[16]; + UCHAR DnsSecondary[16]; + UCHAR PrefixLengthIPAddr; + UCHAR PrefixLengthGateway; + ULONG Mtu; +} IPV6_T; + +#define IpFamilyV4 (0x04) +#define IpFamilyV6 (0x06) +typedef struct __PROFILE { + char * qmichannel; + char * usbnet_adapter; + const char *apn; + const char *user; + const char *password; + const char *pincode; + int auth; + int pdp; + int IsDualIPSupported; + int curIpFamily; + int rawIP; + IPV4_T ipv4; + IPV6_T ipv6; +} PROFILE_T; + +typedef enum { + SIM_ABSENT = 0, + SIM_NOT_READY = 1, + SIM_READY = 2, /* SIM_READY means the radio state is RADIO_STATE_SIM_READY */ + SIM_PIN = 3, + SIM_PUK = 4, + SIM_NETWORK_PERSONALIZATION = 5, + SIM_BAD = 6, +} SIM_Status; + +#define WDM_DEFAULT_BUFSIZE 256 +#define RIL_REQUEST_QUIT 0x1000 +#define RIL_INDICATE_DEVICE_CONNECTED 0x1002 +#define RIL_INDICATE_DEVICE_DISCONNECTED 0x1003 +#define RIL_UNSOL_RESPONSE_VOICE_NETWORK_STATE_CHANGED 0x1004 +#define RIL_UNSOL_DATA_CALL_LIST_CHANGED 0x1005 + +extern int pthread_cond_timeout_np(pthread_cond_t *cond, pthread_mutex_t * mutex, unsigned msecs); +extern int QmiThreadSendQMI(PQCQMIMSG pRequest, PQCQMIMSG *ppResponse); +extern int QmiThreadSendQMITimeout(PQCQMIMSG pRequest, PQCQMIMSG *ppResponse, unsigned msecs); +extern void QmiThreadRecvQMI(PQCQMIMSG pResponse); +extern int QmiWwanInit(PROFILE_T *profile); +extern int QmiWwanDeInit(void); +extern int QmiWwanSendQMI(PQCQMIMSG pRequest); +extern void * QmiWwanThread(void *pData); +extern int GobiNetSendQMI(PQCQMIMSG pRequest); +extern void * GobiNetThread(void *pData); +extern void udhcpc_start(PROFILE_T *profile); +extern void udhcpc_stop(PROFILE_T *profile); +extern void dump_qmi(void *dataBuffer, int dataLen); +extern void qmidevice_send_event_to_main(int triger_event); +extern int requestSetEthMode(PROFILE_T *profile); +extern int requestGetSIMStatus(SIM_Status *pSIMStatus); +extern int requestEnterSimPin(const CHAR *pPinCode); +extern int requestGetICCID(void); +extern int requestGetIMSI(void); +extern int requestRegistrationState(UCHAR *pPSAttachedState); +extern int requestQueryDataCall(UCHAR *pConnectionStatus, int curIpFamily); +extern int requestSetupDataCall(PROFILE_T *profile, int curIpFamily); +extern int requestDeactivateDefaultPDP(PROFILE_T *profile, int curIpFamily); +extern int requestSetProfile(PROFILE_T *profile); +extern int requestGetProfile(PROFILE_T *profile); +extern int requestBaseBandVersion(const char **pp_reversion); +extern int requestGetIPAddress(PROFILE_T *profile, int curIpFamily); +extern int requestSetOperatingMode(UCHAR OperatingMode); + +extern FILE *logfilefp; +extern int debug_qmi; +extern char * qmichannel; +extern int qmidevice_control_fd[2]; +extern int qmiclientId[QMUX_TYPE_WDS_ADMIN + 1]; +extern int cdc_wdm_fd; +extern void dbg_time (const char *fmt, ...); +extern USHORT le16_to_cpu(USHORT v16); +extern UINT le32_to_cpu (UINT v32); +extern UINT sc_swap32(UINT v32); +extern USHORT cpu_to_le16(USHORT v16); +extern UINT cpu_to_le32(UINT v32); +#endif diff --git a/root/package/link4all/quectel-CM/simcom-cm/QmiWwanCM.c b/root/package/link4all/quectel-CM/simcom-cm/QmiWwanCM.c new file mode 100644 index 00000000..f6be90dc --- /dev/null +++ b/root/package/link4all/quectel-CM/simcom-cm/QmiWwanCM.c @@ -0,0 +1,281 @@ +#include +#include +#include +#include +#include +#include "QMIThread.h" + +#ifdef CONFIG_QMIWWAN +int cdc_wdm_fd = -1; +static UCHAR GetQCTLTransactionId(void) { + static int TransactionId = 0; + if (++TransactionId > 0xFF) + TransactionId = 1; + return TransactionId; +} + +typedef USHORT (*CUSTOMQCTL)(PQMICTL_MSG pCTLMsg, void *arg); + +static PQCQMIMSG ComposeQCTLMsg(USHORT QMICTLType, CUSTOMQCTL customQctlMsgFunction, void *arg) { + UCHAR QMIBuf[WDM_DEFAULT_BUFSIZE]; + PQCQMIMSG pRequest = (PQCQMIMSG)QMIBuf; + int Length; + + pRequest->QMIHdr.IFType = USB_CTL_MSG_TYPE_QMI; + pRequest->QMIHdr.CtlFlags = 0x00; + pRequest->QMIHdr.QMIType = QMUX_TYPE_CTL; + pRequest->QMIHdr.ClientId= 0x00; + + pRequest->CTLMsg.QMICTLMsgHdr.CtlFlags = QMICTL_FLAG_REQUEST; + pRequest->CTLMsg.QMICTLMsgHdr.TransactionId = GetQCTLTransactionId(); + pRequest->CTLMsg.QMICTLMsgHdr.QMICTLType = cpu_to_le16(QMICTLType); + if (customQctlMsgFunction) + pRequest->CTLMsg.QMICTLMsgHdr.Length = cpu_to_le16(customQctlMsgFunction(&pRequest->CTLMsg, arg) - sizeof(QCQMICTL_MSG_HDR)); + else + pRequest->CTLMsg.QMICTLMsgHdr.Length = cpu_to_le16(0x0000); + + pRequest->QMIHdr.Length = cpu_to_le16(le16_to_cpu(pRequest->CTLMsg.QMICTLMsgHdr.Length) + sizeof(QCQMICTL_MSG_HDR) + sizeof(QCQMI_HDR) - 1); + Length = le16_to_cpu(pRequest->QMIHdr.Length) + 1; + + pRequest = (PQCQMIMSG)malloc(Length); + if (pRequest == NULL) { + dbg_time("%s fail to malloc", __func__); + } else { + memcpy(pRequest, QMIBuf, Length); + } + + return pRequest; +} + +static USHORT CtlGetVersionReq(PQMICTL_MSG QCTLMsg, void *arg) { + QCTLMsg->GetVersionReq.TLVType = QCTLV_TYPE_REQUIRED_PARAMETER; + QCTLMsg->GetVersionReq.TLVLength = cpu_to_le16(0x0001); + QCTLMsg->GetVersionReq.QMUXTypes = QMUX_TYPE_ALL; + return sizeof(QMICTL_GET_VERSION_REQ_MSG); +} + +static USHORT CtlGetClientIdReq(PQMICTL_MSG QCTLMsg, void *arg) { + QCTLMsg->GetClientIdReq.TLVType = QCTLV_TYPE_REQUIRED_PARAMETER; + QCTLMsg->GetClientIdReq.TLVLength = cpu_to_le16(0x0001); + QCTLMsg->GetClientIdReq.QMIType = ((UCHAR *)arg)[0]; + return sizeof(QMICTL_GET_CLIENT_ID_REQ_MSG); +} + +static USHORT CtlReleaseClientIdReq(PQMICTL_MSG QCTLMsg, void *arg) { + QCTLMsg->ReleaseClientIdReq.TLVType = QCTLV_TYPE_REQUIRED_PARAMETER; + QCTLMsg->ReleaseClientIdReq.TLVLength = cpu_to_le16(0x0002); + QCTLMsg->ReleaseClientIdReq.QMIType = ((UCHAR *)arg)[0]; + QCTLMsg->ReleaseClientIdReq.ClientId = ((UCHAR *)arg)[1] ; + return sizeof(QMICTL_RELEASE_CLIENT_ID_REQ_MSG); +} + +int QmiWwanSendQMI(PQCQMIMSG pRequest) { + struct pollfd pollfds[]= {{cdc_wdm_fd, POLLOUT, 0}}; + int ret; + + if (cdc_wdm_fd == -1) { + dbg_time("%s cdc_wdm_fd = -1", __func__); + return -ENODEV; + } + + if (pRequest->QMIHdr.QMIType == QMUX_TYPE_WDS_IPV6) + pRequest->QMIHdr.QMIType = QMUX_TYPE_WDS; + + do { + ret = poll(pollfds, sizeof(pollfds)/sizeof(pollfds[0]), 5000); + } while ((ret < 0) && (errno == EINTR)); + + if (pollfds[0].revents & POLLOUT) { + ssize_t nwrites = le16_to_cpu(pRequest->QMIHdr.Length) + 1; + ret = write(cdc_wdm_fd, pRequest, nwrites); + if (ret == nwrites) { + ret = 0; + } else { + dbg_time("%s write=%d, errno: %d (%s)", __func__, ret, errno, strerror(errno)); + } + } else { + dbg_time("%s poll=%d, revents = 0x%x, errno: %d (%s)", __func__, ret, pollfds[0].revents, errno, strerror(errno)); + } + + return ret; +} + +static int QmiWwanGetClientID(UCHAR QMIType) { + PQCQMIMSG pResponse; + + QmiThreadSendQMI(ComposeQCTLMsg(QMICTL_GET_CLIENT_ID_REQ, CtlGetClientIdReq, &QMIType), &pResponse); + + if (pResponse) { + USHORT QMUXResult = cpu_to_le16(pResponse->CTLMsg.QMICTLMsgHdrRsp.QMUXResult); // QMI_RESULT_SUCCESS + USHORT QMUXError = cpu_to_le16(pResponse->CTLMsg.QMICTLMsgHdrRsp.QMUXError); // QMI_ERR_INVALID_ARG + //UCHAR QMIType = pResponse->CTLMsg.GetClientIdRsp.QMIType; + UCHAR ClientId = pResponse->CTLMsg.GetClientIdRsp.ClientId; + + if (!QMUXResult && !QMUXError && (QMIType == pResponse->CTLMsg.GetClientIdRsp.QMIType)) { + switch (QMIType) { + case QMUX_TYPE_WDS: dbg_time("Get clientWDS = %d", ClientId); break; + case QMUX_TYPE_DMS: dbg_time("Get clientDMS = %d", ClientId); break; + case QMUX_TYPE_NAS: dbg_time("Get clientNAS = %d", ClientId); break; + case QMUX_TYPE_QOS: dbg_time("Get clientQOS = %d", ClientId); break; + case QMUX_TYPE_WMS: dbg_time("Get clientWMS = %d", ClientId); break; + case QMUX_TYPE_PDS: dbg_time("Get clientPDS = %d", ClientId); break; + case QMUX_TYPE_UIM: dbg_time("Get clientUIM = %d", ClientId); break; + case QMUX_TYPE_WDS_ADMIN: dbg_time("Get clientWDA = %d", ClientId); + break; + default: break; + } + return ClientId; + } + } + return 0; +} + +static int QmiWwanReleaseClientID(QMI_SERVICE_TYPE QMIType, UCHAR ClientId) { + UCHAR argv[] = {QMIType, ClientId}; + QmiThreadSendQMI(ComposeQCTLMsg(QMICTL_RELEASE_CLIENT_ID_REQ, CtlReleaseClientIdReq, argv), NULL); + return 0; +} + +int QmiWwanInit(PROFILE_T *profile) { + unsigned i; + int ret; + PQCQMIMSG pResponse; + + for (i = 0; i < 10; i++) { + ret = QmiThreadSendQMITimeout(ComposeQCTLMsg(QMICTL_SYNC_REQ, NULL, NULL), NULL, 1 * 1000); + if (!ret) + break; + sleep(1); + } + if (ret) + return ret; + + QmiThreadSendQMI(ComposeQCTLMsg(QMICTL_GET_VERSION_REQ, CtlGetVersionReq, NULL), &pResponse); + if (pResponse) free(pResponse); + qmiclientId[QMUX_TYPE_WDS] = QmiWwanGetClientID(QMUX_TYPE_WDS); + if (profile->IsDualIPSupported) + qmiclientId[QMUX_TYPE_WDS_IPV6] = QmiWwanGetClientID(QMUX_TYPE_WDS); + qmiclientId[QMUX_TYPE_DMS] = QmiWwanGetClientID(QMUX_TYPE_DMS); + qmiclientId[QMUX_TYPE_NAS] = QmiWwanGetClientID(QMUX_TYPE_NAS); + qmiclientId[QMUX_TYPE_UIM] = QmiWwanGetClientID(QMUX_TYPE_UIM); + qmiclientId[QMUX_TYPE_WDS_ADMIN] = QmiWwanGetClientID(QMUX_TYPE_WDS_ADMIN); + return 0; +} + +int QmiWwanDeInit(void) { + unsigned int i; + for (i = 0; i < sizeof(qmiclientId)/sizeof(qmiclientId[0]); i++) + { + if (qmiclientId[i] != 0) + { + QmiWwanReleaseClientID(i, qmiclientId[i]); + qmiclientId[i] = 0; + } + } + + return 0; +} + +void * QmiWwanThread(void *pData) { + const char *cdc_wdm = (const char *)pData; + cdc_wdm_fd = open(cdc_wdm, O_RDWR | O_NONBLOCK | O_NOCTTY); + if (cdc_wdm_fd == -1) { + dbg_time("%s Failed to open %s, errno: %d (%s)", __func__, cdc_wdm, errno, strerror(errno)); + qmidevice_send_event_to_main(RIL_INDICATE_DEVICE_DISCONNECTED); + pthread_exit(NULL); + return NULL; + } + fcntl(cdc_wdm_fd, F_SETFD, FD_CLOEXEC) ; + + dbg_time("cdc_wdm_fd = %d", cdc_wdm_fd); + + qmidevice_send_event_to_main(RIL_INDICATE_DEVICE_CONNECTED); + + while (1) { + struct pollfd pollfds[] = {{qmidevice_control_fd[1], POLLIN, 0}, {cdc_wdm_fd, POLLIN, 0}}; + int ne, ret, nevents = sizeof(pollfds)/sizeof(pollfds[0]); + + do { + ret = poll(pollfds, nevents, -1); + } while ((ret < 0) && (errno == EINTR)); + + if (ret <= 0) { + dbg_time("%s poll=%d, errno: %d (%s)", __func__, ret, errno, strerror(errno)); + break; + } + + for (ne = 0; ne < nevents; ne++) { + int fd = pollfds[ne].fd; + short revents = pollfds[ne].revents; + + //dbg_time("{%d, %x, %x}", pollfds[ne].fd, pollfds[ne].events, pollfds[ne].revents); + + if (revents & (POLLERR | POLLHUP | POLLNVAL)) { + dbg_time("%s poll err/hup/inval", __func__); + dbg_time("poll fd = %d, events = 0x%04x", fd, revents); + if (fd == cdc_wdm_fd) { + } else { + } + if (revents & (POLLHUP | POLLNVAL)) //EC20 bug, Can get POLLERR + goto __QmiWwanThread_quit; + } + + if ((revents & POLLIN) == 0) + continue; + + if (fd == qmidevice_control_fd[1]) { + int triger_event; + if (read(fd, &triger_event, sizeof(triger_event)) == sizeof(triger_event)) { + //DBG("triger_event = 0x%x", triger_event); + switch (triger_event) { + case RIL_REQUEST_QUIT: + goto __QmiWwanThread_quit; + break; + case SIGTERM: + case SIGHUP: + case SIGINT: + QmiThreadRecvQMI(NULL); + break; + default: + break; + } + } + } + + if (fd == cdc_wdm_fd) { + ssize_t nreads; + UCHAR QMIBuf[512]; + PQCQMIMSG pResponse = (PQCQMIMSG)QMIBuf; + + nreads = read(fd, QMIBuf, sizeof(QMIBuf)); + //dbg_time("%s read=%d errno: %d (%s)", __func__, (int)nreads, errno, strerror(errno)); + if (nreads <= 0) { + dbg_time("%s read=%d errno: %d (%s)", __func__, (int)nreads, errno, strerror(errno)); + break; + } + + if (nreads != (le16_to_cpu(pResponse->QMIHdr.Length) + 1)) { + dbg_time("%s nreads=%d, pQCQMI->QMIHdr.Length = %d", __func__, (int)nreads, le16_to_cpu(pResponse->QMIHdr.Length)); + continue; + } + + QmiThreadRecvQMI(pResponse); + } + } + } + +__QmiWwanThread_quit: + if (cdc_wdm_fd != -1) { close(cdc_wdm_fd); cdc_wdm_fd = -1; } + qmidevice_send_event_to_main(RIL_INDICATE_DEVICE_DISCONNECTED); + QmiThreadRecvQMI(NULL); //main thread may pending on QmiThreadSendQMI() + dbg_time("%s exit", __func__); + pthread_exit(NULL); + return NULL; +} + +#else +int QmiWwanSendQMI(PQCQMIMSG pRequest) {return -1;} +int QmiWwanInit(PROFILE_T *profile) {return -1;} +int QmiWwanDeInit(void) {return -1;} +void * QmiWwanThread(void *pData) {dbg_time("please set CONFIG_QMIWWAN"); return NULL;} +#endif diff --git a/root/package/link4all/quectel-CM/simcom-cm/default.script b/root/package/link4all/quectel-CM/simcom-cm/default.script new file mode 100644 index 00000000..ddce8d4c --- /dev/null +++ b/root/package/link4all/quectel-CM/simcom-cm/default.script @@ -0,0 +1,63 @@ +#!/bin/sh +# Busybox udhcpc dispatcher script. Copyright (C) 2009 by Axel Beckert. +# +# Based on the busybox example scripts and the old udhcp source +# package default.* scripts. + +RESOLV_CONF="/etc/resolv.conf" + +case $1 in + bound|renew) + [ -n "$broadcast" ] && BROADCAST="broadcast $broadcast" + [ -n "$subnet" ] && NETMASK="netmask $subnet" + + /sbin/ifconfig $interface $ip $BROADCAST $NETMASK + + if [ -n "$router" ]; then + echo "$0: Resetting default routes" + while /sbin/route del default gw 0.0.0.0 dev $interface; do :; done + + metric=0 + for i in $router; do + /sbin/route add default gw $i dev $interface metric $metric + metric=$(($metric + 1)) + done + fi + + # Update resolver configuration file + R="" + [ -n "$domain" ] && R="domain $domain +" + for i in $dns; do + echo "$0: Adding DNS $i" + R="${R}nameserver $i +" + done + + if [ ! -x /sbin/resolvconf ]; then + echo -n "$R" | resolvconf -a "${interface}.udhcpc" + else + echo -n "$R" > "$RESOLV_CONF" + fi + ;; + + deconfig) + if [ -x /sbin/resolvconf ]; then + resolvconf -d "${interface}.udhcpc" + fi + /sbin/ifconfig $interface 0.0.0.0 + ;; + + leasefail) + echo "$0: Lease failed: $message" + ;; + + nak) + echo "$0: Received a NAK: $message" + ;; + + *) + echo "$0: Unknown udhcpc command: $1"; + exit 1; + ;; +esac diff --git a/root/package/link4all/quectel-CM/simcom-cm/dhcpclient.c b/root/package/link4all/quectel-CM/simcom-cm/dhcpclient.c new file mode 100644 index 00000000..d7439822 --- /dev/null +++ b/root/package/link4all/quectel-CM/simcom-cm/dhcpclient.c @@ -0,0 +1,90 @@ +#ifdef ANDROID +/* + * Copyright 2008, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "QMIThread.h" +#ifdef USE_NDK +extern int (*ifc_init)(void); +extern void (*ifc_close)(void); +extern int (*do_dhcp)(const char *iname); +extern void (*get_dhcp_info)(uint32_t *ipaddr, uint32_t *gateway, uint32_t *prefixLength, + uint32_t *dns1, uint32_t *dns2, uint32_t *server, + uint32_t *lease); +extern int (*property_set)(const char *key, const char *value); +#else +#include +#include +extern int do_dhcp(const char *iname); +extern void get_dhcp_info(uint32_t *ipaddr, uint32_t *gateway, uint32_t *prefixLength, + uint32_t *dns1, uint32_t *dns2, uint32_t *server, + uint32_t *lease); +#endif + +static const char *ipaddr_to_string(in_addr_t addr) +{ + struct in_addr in_addr; + + in_addr.s_addr = addr; + return inet_ntoa(in_addr); +} + +void do_dhcp_request(PROFILE_T *profile) { +#ifdef USE_NDK + if (!ifc_init ||!ifc_close ||!do_dhcp || !get_dhcp_info || !property_set) { + return; + } +#endif + + char *ifname = profile->usbnet_adapter; + uint32_t ipaddr, gateway, prefixLength, dns1, dns2, server, lease; + char propKey[128]; + +#if 0 + if (profile->rawIP && ((profile->IPType==0x04 && profile->ipv4.Address))) + { + snprintf(propKey, sizeof(propKey), "net.%s.dns1", ifname); + property_set(propKey, profile->ipv4.DnsPrimary ? ipaddr_to_string(sc_swap32(profile->ipv4.DnsPrimary)) : "8.8.8.8"); + snprintf(propKey, sizeof(propKey), "net.%s.dns2", ifname); + property_set(propKey, profile->ipv4.DnsSecondary ? ipaddr_to_string(sc_swap32(profile->ipv4.DnsSecondary)) : "8.8.8.8"); + snprintf(propKey, sizeof(propKey), "net.%s.gw", ifname); + property_set(propKey, profile->ipv4.Gateway ? ipaddr_to_string(sc_swap32(profile->ipv4.Gateway)) : "0.0.0.0"); + return; + } +#endif + + if(ifc_init()) { + dbg_time("failed to ifc_init(%s): %s\n", ifname, strerror(errno)); + } + + if (do_dhcp(ifname) < 0) { + dbg_time("failed to do_dhcp(%s): %s\n", ifname, strerror(errno)); + } + + ifc_close(); + + get_dhcp_info(&ipaddr, &gateway, &prefixLength, &dns1, &dns2, &server, &lease); + snprintf(propKey, sizeof(propKey), "net.%s.gw", ifname); + property_set(propKey, gateway ? ipaddr_to_string(gateway) : "0.0.0.0"); +} +#endif diff --git a/root/package/link4all/quectel-CM/simcom-cm/main.c b/root/package/link4all/quectel-CM/simcom-cm/main.c new file mode 100644 index 00000000..4a6e2bc6 --- /dev/null +++ b/root/package/link4all/quectel-CM/simcom-cm/main.c @@ -0,0 +1,965 @@ +#include "QMIThread.h" +#include +#include +#include +#include + +//#define CONFIG_EXIT_WHEN_DIAL_FAILED +//#define CONFIG_BACKGROUND_WHEN_GET_IP +//#define CONFIG_PID_FILE_FORMAT "/var/run/simcom-CM-%s.pid" //for example /var/run/simcom-CM-wwan0.pid + +int debug_qmi = 0; +int main_loop = 0; +char * qmichannel; +int qmidevice_control_fd[2]; +static int signal_control_fd[2]; + +#ifdef CONFIG_BACKGROUND_WHEN_GET_IP +static int daemon_pipe_fd[2]; + +static void sc_prepare_daemon(void) { + pid_t daemon_child_pid; + + if (pipe(daemon_pipe_fd) < 0) { + dbg_time("%s Faild to create daemon_pipe_fd: %d (%s)", __func__, errno, strerror(errno)); + return; + } + + daemon_child_pid = fork(); + if (daemon_child_pid > 0) { + struct pollfd pollfds[] = {{daemon_pipe_fd[0], POLLIN, 0}, {0, POLLIN, 0}}; + int ne, ret, nevents = sizeof(pollfds)/sizeof(pollfds[0]); + int signo; + + //dbg_time("father"); + + close(daemon_pipe_fd[1]); + + if (socketpair( AF_LOCAL, SOCK_STREAM, 0, signal_control_fd) < 0 ) { + dbg_time("%s Faild to create main_control_fd: %d (%s)", __func__, errno, strerror(errno)); + return; + } + + pollfds[1].fd = signal_control_fd[1]; + + while (1) { + do { + ret = poll(pollfds, nevents, -1); + } while ((ret < 0) && (errno == EINTR)); + + if (ret < 0) { + dbg_time("%s poll=%d, errno: %d (%s)", __func__, ret, errno, strerror(errno)); + goto __daemon_quit; + } + + for (ne = 0; ne < nevents; ne++) { + int fd = pollfds[ne].fd; + short revents = pollfds[ne].revents; + + if (revents & (POLLERR | POLLHUP | POLLNVAL)) { + //dbg_time("%s poll err/hup", __func__); + //dbg_time("poll fd = %d, events = 0x%04x", fd, revents); + if (revents & POLLHUP) + goto __daemon_quit; + } + + if ((revents & POLLIN) && read(fd, &signo, sizeof(signo)) == sizeof(signo)) { + if (signal_control_fd[1] == fd) { + if (signo == SIGCHLD) { + int status; + int pid = waitpid(daemon_child_pid, &status, 0); + dbg_time("waitpid pid=%d, status=%x", pid, status); + goto __daemon_quit; + } else { + kill(daemon_child_pid, signo); + } + } else if (daemon_pipe_fd[0] == fd) { + //dbg_time("daemon_pipe_signo = %d", signo); + goto __daemon_quit; + } + } + } + } +__daemon_quit: + //dbg_time("father exit"); + _exit(0); + } else if (daemon_child_pid == 0) { + close(daemon_pipe_fd[0]); + //dbg_time("child", getpid()); + } else { + close(daemon_pipe_fd[0]); + close(daemon_pipe_fd[1]); + dbg_time("%s Faild to create daemon_child_pid: %d (%s)", __func__, errno, strerror(errno)); + } +} + +static void sc_enter_daemon(int signo) { + if (daemon_pipe_fd[1] > 0) + if (signo) { + write(daemon_pipe_fd[1], &signo, sizeof(signo)); + sleep(1); + } + close(daemon_pipe_fd[1]); + daemon_pipe_fd[1] = -1; + setsid(); + } +#endif + +//UINT ifc_get_addr(const char *ifname); + +static void usbnet_link_change(int link, PROFILE_T *profile) { + static int s_link = 0; + + if (s_link == link) + return; + + s_link = link; + + if (link) { + requestGetIPAddress(profile, IpFamilyV4); + if (profile->IsDualIPSupported) + requestGetIPAddress(profile, IpFamilyV6); + udhcpc_start(profile); + } else { + udhcpc_stop(profile); + } + +#ifdef LINUX_RIL_SHLIB + if (link) { + int timeout = 6; + while (timeout-- /*&& ifc_get_addr(profile->usbnet_adapter) == 0*/) { + sleep(1); + } + } + + if (link && requestGetIPAddress(profile, 0x04) == 0) { + unsigned char *r; + + dbg_time("Using interface %s", profile->usbnet_adapter); + r = (unsigned char *)&profile->ipv4.Address; + dbg_time("local IP address %d.%d.%d.%d", r[3], r[2], r[1], r[0]); + r = (unsigned char *)&profile->ipv4.Gateway; + dbg_time("remote IP address %d.%d.%d.%d", r[3], r[2], r[1], r[0]); + r = (unsigned char *)&profile->ipv4.DnsPrimary; + dbg_time("primary DNS address %d.%d.%d.%d", r[3], r[2], r[1], r[0]); + r = (unsigned char *)&profile->ipv4.DnsSecondary; + dbg_time("secondary DNS address %d.%d.%d.%d", r[3], r[2], r[1], r[0]); + } +#endif + +#ifdef CONFIG_BACKGROUND_WHEN_GET_IP + if (link && daemon_pipe_fd[1] > 0) { + int timeout = 6; + while (timeout-- /*&& ifc_get_addr(profile->usbnet_adapter) == 0*/) { + sleep(1); + } + sc_enter_daemon(SIGUSR1); + } +#endif +} + +static int check_ipv4_address(PROFILE_T *now_profile) { + PROFILE_T new_profile_v; + PROFILE_T *new_profile = &new_profile_v; + + memcpy(new_profile, now_profile, sizeof(PROFILE_T)); + if (requestGetIPAddress(new_profile, 0x04) == 0) { + if (new_profile->ipv4.Address != now_profile->ipv4.Address || debug_qmi) { + unsigned char *l = (unsigned char *)&now_profile->ipv4.Address; + unsigned char *r = (unsigned char *)&new_profile->ipv4.Address; + dbg_time("localIP: %d.%d.%d.%d VS remoteIP: %d.%d.%d.%d", + l[3], l[2], l[1], l[0], r[3], r[2], r[1], r[0]); + } + return (new_profile->ipv4.Address == now_profile->ipv4.Address); + } + return 0; +} + +static void main_send_event_to_qmidevice(int triger_event) { + write(qmidevice_control_fd[0], &triger_event, sizeof(triger_event)); +} + +static void send_signo_to_main(int signo) { + write(signal_control_fd[0], &signo, sizeof(signo)); +} + +void qmidevice_send_event_to_main(int triger_event) { + write(qmidevice_control_fd[1], &triger_event, sizeof(triger_event)); +} + +#define MAX_PATH 256 + +static int ls_dir(const char *dir, int (*match)(const char *dir, const char *file, void *argv[]), void *argv[]){ + DIR *pDir; + struct dirent* ent = NULL; + int match_times = 0; + + pDir = opendir(dir); + if (pDir == NULL) { + dbg_time("Cannot open directory: %s, errno: %d (%s)", dir, errno, strerror(errno)); + return 0; + } + + while ((ent = readdir(pDir)) != NULL) { + match_times += match(dir, ent->d_name, argv); + } + closedir(pDir); + + return match_times; +} + +static int is_same_linkfile(const char *dir, const char *file, void *argv[]){ + const char *qmichannel = (const char *)argv[1]; + char linkname[MAX_PATH]; + char filename[MAX_PATH]; + int linksize; + + snprintf(linkname, MAX_PATH, "%s/%s", dir, file); + linksize = readlink(linkname, filename, MAX_PATH); + if (linksize <= 0) + return 0; + + filename[linksize] = 0; + if (strcmp(filename, qmichannel)) + return 0; + + dbg_time("%s -> %s", linkname, filename); + return 1; +} + +static int is_brother_process(const char *dir, const char *file, void *argv[]){ + //const char *myself = (const char *)argv[0]; + char linkname[MAX_PATH]; + char filename[MAX_PATH]; + int linksize; + int i = 0, kill_timeout = 15; + pid_t pid; + + while (file[i]) { + if (!isdigit(file[i])) + break; + i++; + } + + if (file[i]) { + return 0; + } + + snprintf(linkname, MAX_PATH, "%s/%s/exe", dir, file); + linksize = readlink(linkname, filename, MAX_PATH); + if (linksize <= 0) + return 0; + + filename[linksize] = 0; + + pid = atoi(file); + if (pid == getpid()) + return 0; + + snprintf(linkname, MAX_PATH, "%s/%s/fd", dir, file); + if (!ls_dir(linkname, is_same_linkfile, argv)) + return 0; + + dbg_time("%s/%s/exe -> %s", dir, file, filename); + while (kill_timeout-- && !kill(pid, 0)) + { + kill(pid, SIGTERM); + sleep(1); + } + if (!kill(pid, 0)) + { + dbg_time("force kill %s/%s/exe -> %s", dir, file, filename); + kill(pid, SIGKILL); + sleep(1); + } + + return 1; +} + +static int kill_brothers(const char *qmichannel){ + char myself[MAX_PATH]; + int filenamesize; + void *argv[2] = {myself, (void *)qmichannel}; + + filenamesize = readlink("/proc/self/exe", myself, MAX_PATH); + if (filenamesize <= 0) + return 0; + myself[filenamesize] = 0; + + if (ls_dir("/proc", is_brother_process, argv)) + sleep(1); + + return 0; +} + +static void sc_sigaction(int signo) { + if (SIGCHLD == signo) + waitpid(-1, NULL, WNOHANG); + else if (SIGALRM == signo) + send_signo_to_main(SIGUSR1); + else + { + if (SIGTERM == signo || SIGHUP == signo || SIGINT == signo) + main_loop = 0; + send_signo_to_main(signo); + main_send_event_to_qmidevice(signo); //main may be wating qmi response + } +} + +pthread_t gQmiThreadID; + +static int usage(const char *progname) { + dbg_time("Usage: %s [-s [apn [user password auth]]] [-p pincode] [-f logfilename] ", progname); + dbg_time("Example: %s -s 3gnet test 1234 0 -p 123456 -f log.txt", progname); + + return 0; +} +static int charsplit(const char *src,char* desc,int n,const char* splitStr){ + char* p; + char*p1; + int len; + + len=strlen(splitStr); + p=strstr(src,splitStr); + if(p==NULL) + return -1; + p1=strstr(p,"\n"); + if(p1==NULL) + return -1; + memset(desc,0,n); + memcpy(desc,p+len,p1-p-len); + + return 0; +} + +static int get_dev_major_minor(char* path, int *major, int *minor){ + int fd = -1; + char desc[128] = {0}; + char devmajor[64],devminor[64]; + int n = 0; + if(access(path, R_OK | W_OK)){ + return 1; + } + if((fd = open(path, O_RDWR)) < 0){ + return 1; + } + n = read(fd , desc, sizeof(desc)); + if(n == sizeof(desc)) { + dbg_time("may be overflow"); + } + close(fd); + if(charsplit(desc,devmajor,64,"MAJOR=")==-1 + ||charsplit(desc,devminor,64,"MINOR=")==-1 ) { + return 2; + } + *major = atoi(devmajor); + *minor = atoi(devminor); + return 0; +} + +static int qmidevice_detect(char **pp_qmichannel, char **pp_usbnet_adapter) { + struct dirent* ent = NULL; + DIR *pDir; + + char dir[255] = "/sys/bus/usb/devices"; + int major = 0, minor = 0; + pDir = opendir(dir); + if (pDir) { + while ((ent = readdir(pDir)) != NULL) { + struct dirent* subent = NULL; + DIR *psubDir; + char subdir[255]; + char subdir2[255 * 2]; + + char idVendor[4+1] = {0}; + char idProduct[4+1] = {0}; + int fd = 0; + + char netcard[32] = "\0"; + char qmifile[32] = "\0"; + + snprintf(subdir, sizeof(subdir), "%s/%s/idVendor", dir, ent->d_name); + fd = open(subdir, O_RDONLY); + if (fd > 0) { + read(fd, idVendor, 4); + close(fd); + } + + snprintf(subdir, sizeof(subdir), "%s/%s/idProduct", dir, ent->d_name); + fd = open(subdir, O_RDONLY); + if (fd > 0) { + read(fd, idProduct, 4); + close(fd); + } + + if (!strncasecmp(idVendor, "05c6", 4) || !strncasecmp(idVendor, "2c7c", 4)) + ; + else + continue; + + dbg_time("Find %s/%s idVendor=%s idProduct=%s", dir, ent->d_name, idVendor, idProduct); + + snprintf(subdir, sizeof(subdir), "%s/%s:1.2/net", dir, ent->d_name); + psubDir = opendir(subdir); + if (psubDir == NULL) { + dbg_time("Cannot open directory: %s, errno: %d (%s)", subdir, errno, strerror(errno)); + continue; + } + + while ((subent = readdir(psubDir)) != NULL) { + if (subent->d_name[0] == '.') + continue; + dbg_time("Find %s/%s", subdir, subent->d_name); + dbg_time("Find usbnet_adapter = %s", subent->d_name); + strcpy(netcard, subent->d_name); + break; + } + + closedir(psubDir); + + if (netcard[0]) { + } else { + continue; + } + + if (*pp_usbnet_adapter && strcmp(*pp_usbnet_adapter, netcard)) + continue; + + snprintf(subdir, sizeof(subdir), "%s/%s:1.2/GobiQMI", dir, ent->d_name); + if (access(subdir, R_OK)) { + snprintf(subdir, sizeof(subdir), "%s/%s:1.2/usbmisc", dir, ent->d_name); + if (access(subdir, R_OK)) { + snprintf(subdir, sizeof(subdir), "%s/%s:1.2/usb", dir, ent->d_name); + if (access(subdir, R_OK)) { + dbg_time("no GobiQMI/usbmic/usb found in %s/%s:1.4", dir, ent->d_name); + continue; + } + } + } + + psubDir = opendir(subdir); + if (pDir == NULL) { + dbg_time("Cannot open directory: %s, errno: %d (%s)", dir, errno, strerror(errno)); + continue; + } + + while ((subent = readdir(psubDir)) != NULL) { + if (subent->d_name[0] == '.') + continue; + dbg_time("Find %s/%s", subdir, subent->d_name); + dbg_time("Find qmichannel = /dev/%s", subent->d_name); + snprintf(qmifile, sizeof(qmifile), "/dev/%s", subent->d_name); + + //get major minor + snprintf(subdir2, sizeof(subdir), "%s/%s/uevent",subdir, subent->d_name); + if(!get_dev_major_minor(subdir2, &major, &minor)) + { + //dbg_time("%s major = %d, minor = %d\n",qmifile, major, minor); + }else + { + dbg_time("get %s major and minor failed\n",qmifile); + } + //get major minor + + if((fd = open(qmifile, R_OK)) < 0) + { + dbg_time("%s open failed", qmifile); + dbg_time("please mknod %s c %d %d", qmifile, major, minor); + }else + { + close(fd); + } + break; + } + + closedir(psubDir); + + if (netcard[0] && qmifile[0]) { + *pp_qmichannel = strdup(qmifile); + *pp_usbnet_adapter = strdup(netcard); + closedir(pDir); + return 1; + } + + } + + closedir(pDir); + } + + if ((pDir = opendir("/dev")) == NULL) { + dbg_time("Cannot open directory: %s, errno:%d (%s)", "/dev", errno, strerror(errno)); + return -ENODEV; + } + + while ((ent = readdir(pDir)) != NULL) { + if ((strncmp(ent->d_name, "cdc-wdm", strlen("cdc-wdm")) == 0) || (strncmp(ent->d_name, "qcqmi", strlen("qcqmi")) == 0)) { + char net_path[64]; + + *pp_qmichannel = (char *)malloc(32); + sprintf(*pp_qmichannel, "/dev/%s", ent->d_name); + dbg_time("Find qmichannel = %s", *pp_qmichannel); + + if (strncmp(ent->d_name, "cdc-wdm", strlen("cdc-wdm")) == 0) + sprintf(net_path, "/sys/class/net/wwan%s", &ent->d_name[strlen("cdc-wdm")]); + else + { + sprintf(net_path, "/sys/class/net/usb%s", &ent->d_name[strlen("qcqmi")]); + #if 0//ndef ANDROID + if (kernel_version >= KVERSION( 2,6,39 )) + sprintf(net_path, "/sys/class/net/eth%s", &ent->d_name[strlen("qcqmi")]); + #else + if (access(net_path, R_OK) && errno == ENOENT) + sprintf(net_path, "/sys/class/net/eth%s", &ent->d_name[strlen("qcqmi")]); + #endif +#if 0 //openWRT like use ppp# or lte# + if (access(net_path, R_OK) && errno == ENOENT) + sprintf(net_path, "/sys/class/net/ppp%s", &ent->d_name[strlen("qcqmi")]); + if (access(net_path, R_OK) && errno == ENOENT) + sprintf(net_path, "/sys/class/net/lte%s", &ent->d_name[strlen("qcqmi")]); +#endif + } + + if (access(net_path, R_OK) == 0) + { + if (*pp_usbnet_adapter && strcmp(*pp_usbnet_adapter, (net_path + strlen("/sys/class/net/")))) + { + free(*pp_qmichannel); *pp_qmichannel = NULL; + continue; + } + *pp_usbnet_adapter = strdup(net_path + strlen("/sys/class/net/")); + dbg_time("Find usbnet_adapter = %s", *pp_usbnet_adapter); + break; + } + else + { + dbg_time("Failed to access %s, errno:%d (%s)", net_path, errno, strerror(errno)); + free(*pp_qmichannel); *pp_qmichannel = NULL; + } + } + } + closedir(pDir); + + return (*pp_qmichannel && *pp_usbnet_adapter); +} + +#if defined(ANDROID) || defined(LINUX_RIL_SHLIB) +int simcom_CM(int argc, char *argv[]) +#else +int main(int argc, char *argv[]) +#endif +{ + int triger_event = 0; + int opt = 1; + int signo; +#ifdef CONFIG_SIM + SIM_Status SIMStatus; +#endif + UCHAR PSAttachedState; + UCHAR IPv4ConnectionStatus = 0xff; //unknow state + UCHAR IPV6ConnectionStatus = 0xff; //unknow state + char * save_usbnet_adapter = NULL; + PROFILE_T profile; +#ifdef CONFIG_RESET_RADIO + struct timeval resetRadioTime = {0}; + struct timeval nowTime; + gettimeofday(&resetRadioTime, (struct timezone *) NULL); +#endif + + memset(&profile, 0x00, sizeof(profile)); + profile.pdp = CONFIG_DEFAULT_PDP; + + if (!strcmp(argv[argc-1], "&")) + argc--; + + opt = 1; + while (opt < argc) + { + if (argv[opt][0] != '-') + return usage(argv[0]); + + switch (argv[opt++][1]) + { +#define has_more_argv() ((opt < argc) && (argv[opt][0] != '-')) + case 's': + profile.apn = profile.user = profile.password = ""; + if (has_more_argv()) + profile.apn = argv[opt++]; + if (has_more_argv()) + profile.user = argv[opt++]; + if (has_more_argv()) + { + profile.password = argv[opt++]; + if (profile.password && profile.password[0]) + profile.auth = 2; //default chap, customers may miss auth + } + if (has_more_argv()) + profile.auth = argv[opt++][0] - '0'; + break; + + case 'p': + if (has_more_argv()) + profile.pincode = argv[opt++]; + break; + + case 'n': + if (has_more_argv()) + profile.pdp = argv[opt++][0] - '0'; + break; + + case 'f': + if (has_more_argv()) + { + const char * filename = argv[opt++]; + logfilefp = fopen(filename, "a+"); + if (!logfilefp) { + dbg_time("Fail to open %s, errno: %d(%s)", filename, errno, strerror(errno)); + } + } + break; + + case 'i': + if (has_more_argv()) + profile.usbnet_adapter = save_usbnet_adapter = argv[opt++]; + break; + + case 'v': + debug_qmi = 1; + break; + + case 'l': + main_loop = 1; + break; + + case '6': + profile.IsDualIPSupported |= (1 << IpFamilyV6); //support ipv4&ipv6 + break; + + default: + return usage(argv[0]); + break; + } + } + + dbg_time("SIMCOM_CM START..."); + dbg_time("%s profile[%d] = %s/%s/%s/%d, pincode = %s", argv[0], profile.pdp, profile.apn, profile.user, profile.password, profile.auth, profile.pincode); + + signal(SIGUSR1, sc_sigaction); + signal(SIGUSR2, sc_sigaction); + signal(SIGINT, sc_sigaction); + signal(SIGTERM, sc_sigaction); + signal(SIGHUP, sc_sigaction); + signal(SIGCHLD, sc_sigaction); + signal(SIGALRM, sc_sigaction); + +#ifdef CONFIG_BACKGROUND_WHEN_GET_IP + sc_prepare_daemon(); +#endif + + if (socketpair( AF_LOCAL, SOCK_STREAM, 0, signal_control_fd) < 0 ) { + dbg_time("%s Faild to create main_control_fd: %d (%s)", __func__, errno, strerror(errno)); + return -1; + } + + if ( socketpair( AF_LOCAL, SOCK_STREAM, 0, qmidevice_control_fd ) < 0 ) { + dbg_time("%s Failed to create thread control socket pair: %d (%s)", __func__, errno, strerror(errno)); + return 0; + } + +//sudo apt-get install udhcpc +//sudo apt-get remove ModemManager +__main_loop: + while (!profile.qmichannel) + { + if (qmidevice_detect(&profile.qmichannel, &profile.usbnet_adapter)) + break; + if (main_loop) + { + int wait_for_device = 3000; + dbg_time("Wait for simcom modules connect"); + while (wait_for_device && main_loop) { + wait_for_device -= 100; + usleep(100*1000); + } + continue; + } + dbg_time("Cannot find qmichannel(%s) usbnet_adapter(%s) for simcom modules", profile.qmichannel, profile.usbnet_adapter); + return -ENODEV; + } + + if (access(profile.qmichannel, R_OK | W_OK)) { + dbg_time("Fail to access %s, errno: %d (%s)", profile.qmichannel, errno, strerror(errno)); + return errno; + } + +#if 0 //for test only, make fd > 255 +{ + int max_dup = 255; + while (max_dup--) + dup(0); +} +#endif + + kill_brothers(profile.qmichannel); + + qmichannel = profile.qmichannel; + if (!strncmp(profile.qmichannel, "/dev/qcqmi", strlen("/dev/qcqmi"))) + { + if (pthread_create( &gQmiThreadID, 0, GobiNetThread, (void *)&profile) != 0) + { + dbg_time("%s Failed to create GobiNetThread: %d (%s)", __func__, errno, strerror(errno)); + return 0; + } + } + else + { + if (pthread_create( &gQmiThreadID, 0, QmiWwanThread, (void *)profile.qmichannel) != 0) + { + dbg_time("%s Failed to create QmiWwanThread: %d (%s)", __func__, errno, strerror(errno)); + return 0; + } + } + + if ((read(qmidevice_control_fd[0], &triger_event, sizeof(triger_event)) != sizeof(triger_event)) + || (triger_event != RIL_INDICATE_DEVICE_CONNECTED)) { + dbg_time("%s Failed to init QMIThread: %d (%s)", __func__, errno, strerror(errno)); + return 0; + } + + if (!strncmp(profile.qmichannel, "/dev/cdc-wdm", strlen("/dev/cdc-wdm"))) { + if (QmiWwanInit(&profile)) { + dbg_time("%s Failed to QmiWwanInit: %d (%s)", __func__, errno, strerror(errno)); + return 0; + } + } + +#ifdef CONFIG_VERSION + requestBaseBandVersion(NULL); +#endif + requestSetEthMode(&profile); + +#ifdef CONFIG_SIM + requestGetSIMStatus(&SIMStatus); + if ((SIMStatus == SIM_PIN) && profile.pincode) { + requestEnterSimPin(profile.pincode); + } +#ifdef CONFIG_IMSI_ICCID + if (SIMStatus == SIM_READY) { + requestGetICCID(); + requestGetIMSI(); + } +#endif +#endif +#ifdef CONFIG_APN + if (profile.apn || profile.user || profile.password) { + requestSetProfile(&profile); + } + requestGetProfile(&profile); +#endif + requestRegistrationState(&PSAttachedState); + + if (!requestQueryDataCall(&IPv4ConnectionStatus, IpFamilyV4) && (QWDS_PKT_DATA_CONNECTED == IPv4ConnectionStatus)) + usbnet_link_change(1, &profile); + else + usbnet_link_change(0, &profile); + + send_signo_to_main(SIGUSR1); + +#ifdef CONFIG_PID_FILE_FORMAT + { + char cmd[255]; + sprintf(cmd, "echo %d > " CONFIG_PID_FILE_FORMAT, getpid(), profile.usbnet_adapter); + system(cmd); + } +#endif + + while (1) + { + struct pollfd pollfds[] = {{signal_control_fd[1], POLLIN, 0}, {qmidevice_control_fd[0], POLLIN, 0}}; + int ne, ret, nevents = sizeof(pollfds)/sizeof(pollfds[0]); + + do { + ret = poll(pollfds, nevents, 15*1000); + } while ((ret < 0) && (errno == EINTR)); + + if (ret == 0) + { + send_signo_to_main(SIGUSR2); + continue; + } + + if (ret <= 0) { + dbg_time("%s poll=%d, errno: %d (%s)", __func__, ret, errno, strerror(errno)); + goto __main_quit; + } + + for (ne = 0; ne < nevents; ne++) { + int fd = pollfds[ne].fd; + short revents = pollfds[ne].revents; + + if (revents & (POLLERR | POLLHUP | POLLNVAL)) { + dbg_time("%s poll err/hup", __func__); + dbg_time("epoll fd = %d, events = 0x%04x", fd, revents); + main_send_event_to_qmidevice(RIL_REQUEST_QUIT); + if (revents & POLLHUP) + goto __main_quit; + } + + if ((revents & POLLIN) == 0) + continue; + + if (fd == signal_control_fd[1]) + { + if (read(fd, &signo, sizeof(signo)) == sizeof(signo)) + { + alarm(0); + switch (signo) + { + case SIGUSR1: + requestQueryDataCall(&IPv4ConnectionStatus, IpFamilyV4); + if (QWDS_PKT_DATA_CONNECTED != IPv4ConnectionStatus) + { + usbnet_link_change(0, &profile); + requestRegistrationState(&PSAttachedState); + if (PSAttachedState == 1 && requestSetupDataCall(&profile, IpFamilyV4) == 0) + { + //succssful setup data call + if (profile.IsDualIPSupported) { + requestSetupDataCall(&profile, IpFamilyV6); + } + } + else + { +#ifdef CONFIG_EXIT_WHEN_DIAL_FAILED + kill(getpid(), SIGTERM); +#endif +#ifdef CONFIG_RESET_RADIO + gettimeofday(&nowTime, (struct timezone *) NULL); + if (abs(nowTime.tv_sec - resetRadioTime.tv_sec) > CONFIG_RESET_RADIO) { + resetRadioTime = nowTime; + //requestSetOperatingMode(0x06); //same as AT+CFUN=0 + requestSetOperatingMode(0x01); //same as AT+CFUN=4 + requestSetOperatingMode(0x00); //same as AT+CFUN=1 + } +#endif + alarm(5); //try to setup data call 5 seconds later + } + } + break; + + case SIGUSR2: + if (QWDS_PKT_DATA_CONNECTED == IPv4ConnectionStatus) + requestQueryDataCall(&IPv4ConnectionStatus, IpFamilyV4); + + //local ip is different with remote ip + if (QWDS_PKT_DATA_CONNECTED == IPv4ConnectionStatus && check_ipv4_address(&profile) == 0) { + requestDeactivateDefaultPDP(&profile, IpFamilyV4); + IPv4ConnectionStatus = QWDS_PKT_DATA_DISCONNECTED; + } + + if (QWDS_PKT_DATA_CONNECTED != IPv4ConnectionStatus) { +#if defined(ANDROID) || defined(LINUX_RIL_SHLIB) + kill(getpid(), SIGTERM); //android will setup data call again +#else + send_signo_to_main(SIGUSR1); +#endif + } + break; + + case SIGTERM: + case SIGHUP: + case SIGINT: + if (QWDS_PKT_DATA_CONNECTED == IPv4ConnectionStatus) { + requestDeactivateDefaultPDP(&profile, IpFamilyV4); + if (profile.IsDualIPSupported) + requestDeactivateDefaultPDP(&profile, IpFamilyV6); + } + usbnet_link_change(0, &profile); + if (!strncmp(profile.qmichannel, "/dev/cdc-wdm", strlen("/dev/cdc-wdm"))) + QmiWwanDeInit(); + main_send_event_to_qmidevice(RIL_REQUEST_QUIT); + goto __main_quit; + break; + + default: + break; + } + } + } + + if (fd == qmidevice_control_fd[0]) { + if (read(fd, &triger_event, sizeof(triger_event)) == sizeof(triger_event)) { + switch (triger_event) { + case RIL_INDICATE_DEVICE_DISCONNECTED: + usbnet_link_change(0, &profile); + if (main_loop) + { + if (pthread_join(gQmiThreadID, NULL)) { + dbg_time("%s Error joining to listener thread (%s)", __func__, strerror(errno)); + } + profile.qmichannel = NULL; + profile.usbnet_adapter = save_usbnet_adapter; + goto __main_loop; + } + goto __main_quit; + break; + + case RIL_UNSOL_RESPONSE_VOICE_NETWORK_STATE_CHANGED: + requestRegistrationState(&PSAttachedState); + if (PSAttachedState == 1 && QWDS_PKT_DATA_DISCONNECTED == IPv4ConnectionStatus) + send_signo_to_main(SIGUSR1); + break; + + case RIL_UNSOL_DATA_CALL_LIST_CHANGED: + { +#if defined(ANDROID) || defined(LINUX_RIL_SHLIB) + UCHAR oldConnectionStatus = IPv4ConnectionStatus; +#endif + requestQueryDataCall(&IPv4ConnectionStatus, IpFamilyV4); + if (profile.IsDualIPSupported) + requestQueryDataCall(&IPV6ConnectionStatus, IpFamilyV6); + if (QWDS_PKT_DATA_CONNECTED != IPv4ConnectionStatus) + { + usbnet_link_change(0, &profile); +#if defined(ANDROID) || defined(LINUX_RIL_SHLIB) + if (oldConnectionStatus == QWDS_PKT_DATA_CONNECTED) //connected change to disconnect + kill(getpid(), SIGTERM); //android will setup data call again +#else + send_signo_to_main(SIGUSR1); +#endif + } else if (QWDS_PKT_DATA_CONNECTED == IPv4ConnectionStatus) { + usbnet_link_change(1, &profile); + } + } + break; + + default: + break; + } + } + } + } + } + +__main_quit: + usbnet_link_change(0, &profile); + if (pthread_join(gQmiThreadID, NULL)) { + dbg_time("%s Error joining to listener thread (%s)", __func__, strerror(errno)); + } + close(signal_control_fd[0]); + close(signal_control_fd[1]); + close(qmidevice_control_fd[0]); + close(qmidevice_control_fd[1]); + dbg_time("SIMCOM_CM exit..."); + if (logfilefp) + fclose(logfilefp); + +#ifdef CONFIG_PID_FILE_FORMAT + { + char cmd[255]; + sprintf(cmd, "rm " CONFIG_PID_FILE_FORMAT, profile.usbnet_adapter); + system(cmd); + } +#endif + + return 0; +} diff --git a/root/package/link4all/quectel-CM/simcom-cm/udhcpc.c b/root/package/link4all/quectel-CM/simcom-cm/udhcpc.c new file mode 100644 index 00000000..58548e4e --- /dev/null +++ b/root/package/link4all/quectel-CM/simcom-cm/udhcpc.c @@ -0,0 +1,222 @@ +#include +#include +#include +#include +#include "QMIThread.h" + +static int sc_system(const char *shell_cmd) { + int ret = 0; + dbg_time("%s", shell_cmd); + ret = system(shell_cmd); + if (ret) { + //dbg_time("Fail to system(\"%s\") = %d, errno: %d (%s)", shell_cmd, ret, errno, strerror(errno)); + } + return ret; +} + +static void sc_set_mtu(const char *usbnet_adapter, int ifru_mtu) { + int inet_sock; + struct ifreq ifr; + + inet_sock = socket(AF_INET, SOCK_DGRAM, 0); + + if (inet_sock > 0) { + strcpy(ifr.ifr_name, usbnet_adapter); + + if (!ioctl(inet_sock, SIOCGIFMTU, &ifr)) { + if (ifr.ifr_ifru.ifru_mtu != ifru_mtu) { + dbg_time("change mtu %d -> %d", ifr.ifr_ifru.ifru_mtu , ifru_mtu); + ifr.ifr_ifru.ifru_mtu = ifru_mtu; + ioctl(inet_sock, SIOCSIFMTU, &ifr); + } + } + + close(inet_sock); + } +} + +#ifdef ANDROID +static void android_property_set(char *ifname, char *type, uint32_t ipaddr) { + char shell_cmd[128]; + unsigned char *r = (unsigned char *)&ipaddr; + + snprintf(shell_cmd, sizeof(shell_cmd), "/system/bin/setprop net.%s.%s %d.%d.%d.%d", ifname, type, r[3], r[2], r[1], r[0]); + sc_system(shell_cmd); +} +#endif + +static void* udhcpc_thread_function(void* arg) { + FILE * udhcpc_fp; + char *udhcpc_cmd = (char *)arg; + + if (udhcpc_cmd == NULL) + return NULL; + + dbg_time("%s", udhcpc_cmd); + udhcpc_fp = popen(udhcpc_cmd, "r"); + free(udhcpc_cmd); + if (udhcpc_fp) { + char buf[0xff]; + + while((fgets(buf, sizeof(buf), udhcpc_fp)) != NULL) { + if ((strlen(buf) > 1) && (buf[strlen(buf) - 1] == '\n')) + buf[strlen(buf) - 1] = '\0'; + dbg_time("%s", buf); + } + + pclose(udhcpc_fp); + } + + return NULL; +} + +//#define USE_DHCLIENT +#ifdef USE_DHCLIENT +static int dhclient_alive = 0; +#endif +static int dibbler_client_alive = 0; + +void udhcpc_start(PROFILE_T *profile) { + char *ifname = profile->usbnet_adapter; + char shell_cmd[128]; + + if (profile->rawIP && profile->ipv4.Address && profile->ipv4.Mtu) { + sc_set_mtu(profile->usbnet_adapter, (profile->ipv4.Mtu)); + } + +#ifdef ANDROID + if(!access("/system/bin/netcfg", F_OK)) { + snprintf(shell_cmd, sizeof(shell_cmd), "/system/bin/netcfg %s up", ifname); + sc_system(shell_cmd); + snprintf(shell_cmd, sizeof(shell_cmd), "/system/bin/netcfg %s dhcp", ifname); + sc_system(shell_cmd); + } else { + snprintf(shell_cmd, sizeof(shell_cmd), "/system/bin/dhcptool %s", ifname); + sc_system(shell_cmd); + } + + android_property_set(ifname, "gw", profile->ipv4.Gateway); + return; +#endif + + snprintf(shell_cmd, sizeof(shell_cmd) - 1, "ifconfig %s up", ifname); + sc_system(shell_cmd); + +#if 1 //for bridge mode, only one public IP, so donot run udhcpc to obtain + { + const char *BRIDGE_MODE_FILE = "/sys/module/GobiNet/parameters/bridge_mode"; + const char *BRIDGE_IPV4_FILE = "/sys/module/GobiNet/parameters/bridge_ipv4"; + + if (strncmp(qmichannel, "/dev/qcqmi", strlen("/dev/qcqmi"))) { + BRIDGE_MODE_FILE = "/sys/module/qmi_wwan/parameters/bridge_mode"; + BRIDGE_IPV4_FILE = "/sys/module/qmi_wwan/parameters/bridge_ipv4"; + } + + if (profile->ipv4.Address && !access(BRIDGE_MODE_FILE, R_OK)) { + int bridge_fd = open(BRIDGE_MODE_FILE, O_RDONLY); + char bridge_mode[2] = {0, 0}; + + if (bridge_fd > 0) { + read(bridge_fd, &bridge_mode, sizeof(bridge_mode)); + close(bridge_fd); + if(bridge_mode[0] != '0') { + snprintf(shell_cmd, sizeof(shell_cmd), "echo 0x%08x > %s", profile->ipv4.Address, BRIDGE_IPV4_FILE); + sc_system(shell_cmd); + return; + } + } + } + } +#endif + +//because must use udhcpc to obtain IP when working on ETH mode, +//so it is better also use udhcpc to obtain IP when working on IP mode. +//use the same policy for all modules + + { + char udhcpc_cmd[128]; + pthread_attr_t udhcpc_thread_attr; + pthread_t udhcpc_thread_id; + + pthread_attr_init(&udhcpc_thread_attr); + pthread_attr_setdetachstate(&udhcpc_thread_attr, PTHREAD_CREATE_DETACHED); + + if (profile->ipv4.Address) { +#ifdef USE_DHCLIENT + snprintf(udhcpc_cmd, sizeof(udhcpc_cmd), "dhclient -4 -d --no-pid %s", ifname); + dhclient_alive++; +#else + if (access("/usr/share/udhcpc/default.script", X_OK)) { + dbg_time("Fail to access /usr/share/udhcpc/default.script, errno: %d (%s)", errno, strerror(errno)); + } + + //-f,--foreground Run in foreground + //-b,--background Background if lease is not obtained + //-n,--now Exit if lease is not obtained + //-q,--quit Exit after obtaining lease + //-t,--retries N Send up to N discover packets (default 3) + snprintf(udhcpc_cmd, sizeof(udhcpc_cmd), "busybox udhcpc -f -n -q -t 5 -i %s", ifname); +#endif + + pthread_create(&udhcpc_thread_id, &udhcpc_thread_attr, udhcpc_thread_function, (void*)strdup(udhcpc_cmd)); + sleep(1); + } + + if (profile->ipv6.Address[0] && profile->ipv6.PrefixLengthIPAddr) { +#ifdef USE_DHCLIENT + snprintf(udhcpc_cmd, sizeof(udhcpc_cmd), "dhclient -6 -d --no-pid %s", ifname); + dhclient_alive++; +#else + /* + DHCPv6: Dibbler - a portable DHCPv6 + 1. download from http://klub.com.pl/dhcpv6/ + 2. cross-compile + 2.1 ./configure --host=arm-linux-gnueabihf + 2.2 copy dibbler-client to your board + 3. mkdir -p /var/log/dibbler/ /var/lib/ on your board + 4. create /etc/dibbler/client.conf on your board, the content is + log-mode short + log-level 7 + iface wwan0 { + ia + option dns-server + } + 5. run "dibbler-client start" to get ipV6 address + 6. run "route -A inet6 add default dev wwan0" to add default route + */ + snprintf(shell_cmd, sizeof(shell_cmd), "route -A inet6 add default %s", ifname); + sc_system(shell_cmd); + snprintf(udhcpc_cmd, sizeof(udhcpc_cmd), "dibbler-client run"); + dibbler_client_alive++; +#endif + + pthread_create(&udhcpc_thread_id, &udhcpc_thread_attr, udhcpc_thread_function, (void*)strdup(udhcpc_cmd)); + } + } +} + +void udhcpc_stop(PROFILE_T *profile) { + char *ifname = profile->usbnet_adapter; + char shell_cmd[128]; + +#ifdef ANDROID + if(!access("/system/bin/netcfg", F_OK)) { + snprintf(shell_cmd, sizeof(shell_cmd) - 1, "/system/bin/netcfg %s down", ifname); + } else { + snprintf(shell_cmd, sizeof(shell_cmd) - 1, "ifconfig %s down", ifname); //for android 6.0 and above + } +#else +#ifdef USE_DHCLIENT + if (dhclient_alive) { + system("killall dhclient"); + dhclient_alive = 0; + } +#endif + if (dibbler_client_alive) { + system("killall dibbler-client"); + dibbler_client_alive = 0; + } + snprintf(shell_cmd, sizeof(shell_cmd) - 1, "ifconfig %s down", ifname); +#endif + sc_system(shell_cmd); +} diff --git a/root/package/link4all/quectel-CM/simcom-cm/util.c b/root/package/link4all/quectel-CM/simcom-cm/util.c new file mode 100644 index 00000000..c5d27b97 --- /dev/null +++ b/root/package/link4all/quectel-CM/simcom-cm/util.c @@ -0,0 +1,158 @@ +#include "QMIThread.h" +#include + +#if defined(__STDC__) +#include +#define __V(x) x +#else +#include +#define __V(x) (va_alist) va_dcl +#define const +#define volatile +#endif + +#ifdef ANDROID +#define LOG_TAG "NDIS" +#include "../sc-log.h" +#else +#include +#endif + +#ifndef ANDROID //defined in atchannel.c +static void setTimespecRelative(struct timespec *p_ts, long long msec) +{ + struct timeval tv; + + gettimeofday(&tv, (struct timezone *) NULL); + + /* what's really funny about this is that I know + pthread_cond_timedwait just turns around and makes this + a relative time again */ + p_ts->tv_sec = tv.tv_sec + (msec / 1000); + p_ts->tv_nsec = (tv.tv_usec + (msec % 1000) * 1000L ) * 1000L; +} + +int pthread_cond_timeout_np(pthread_cond_t *cond, + pthread_mutex_t * mutex, + unsigned msecs) { + if (msecs != 0) { + struct timespec ts; + setTimespecRelative(&ts, msecs); + return pthread_cond_timedwait(cond, mutex, &ts); + } else { + return pthread_cond_wait(cond, mutex); + } +} +#endif + +static const char * get_time(void) { + static char time_buf[50]; + struct timeval tv; + time_t time; + suseconds_t millitm; + struct tm *ti; + + gettimeofday (&tv, NULL); + + time= tv.tv_sec; + millitm = (tv.tv_usec + 500) / 1000; + + if (millitm == 1000) { + ++time; + millitm = 0; + } + + ti = localtime(&time); + sprintf(time_buf, "[%02d-%02d_%02d:%02d:%02d:%03d]", ti->tm_mon+1, ti->tm_mday, ti->tm_hour, ti->tm_min, ti->tm_sec, (int)millitm); + return time_buf; +} + +FILE *logfilefp = NULL; +static pthread_mutex_t printfMutex = PTHREAD_MUTEX_INITIALIZER; +static char line[1024]; +void dbg_time (const char *fmt, ...) { + va_list args; + va_start(args, fmt); + + pthread_mutex_lock(&printfMutex); +#ifdef ANDROID + vsnprintf(line, sizeof(line), fmt, args); + RLOGD("%s", line); +#else +#ifdef LINUX_RIL_SHLIB + line[0] = '\0'; +#else + snprintf(line, sizeof(line), "%s ", get_time()); +#endif + vsnprintf(line + strlen(line), sizeof(line) - strlen(line), fmt, args); + fprintf(stdout, "%s\n", line); +#endif + if (logfilefp) { + fprintf(logfilefp, "%s\n", line); + } + pthread_mutex_unlock(&printfMutex); +} + +const int i = 1; +#define is_bigendian() ( (*(char*)&i) == 0 ) + +USHORT le16_to_cpu(USHORT v16) { + USHORT tmp = v16; + if (is_bigendian()) { + unsigned char *s = (unsigned char *)(&v16); + unsigned char *d = (unsigned char *)(&tmp); + d[0] = s[1]; + d[1] = s[0]; + } + return tmp; +} + +UINT le32_to_cpu (UINT v32) { + UINT tmp = v32; + if (is_bigendian()) { + unsigned char *s = (unsigned char *)(&v32); + unsigned char *d = (unsigned char *)(&tmp); + d[0] = s[3]; + d[1] = s[2]; + d[2] = s[1]; + d[3] = s[0]; + } + return tmp; +} + +UINT sc_swap32(UINT v32) { + UINT tmp = v32; + { + unsigned char *s = (unsigned char *)(&v32); + unsigned char *d = (unsigned char *)(&tmp); + d[0] = s[3]; + d[1] = s[2]; + d[2] = s[1]; + d[3] = s[0]; + } + return tmp; +} + +USHORT cpu_to_le16(USHORT v16) { + USHORT tmp = v16; + if (is_bigendian()) { + unsigned char *s = (unsigned char *)(&v16); + unsigned char *d = (unsigned char *)(&tmp); + d[0] = s[1]; + d[1] = s[0]; + } + return tmp; +} + +UINT cpu_to_le32 (UINT v32) { + UINT tmp = v32; + if (is_bigendian()) { + unsigned char *s = (unsigned char *)(&v32); + unsigned char *d = (unsigned char *)(&tmp); + d[0] = s[3]; + d[1] = s[2]; + d[2] = s[1]; + d[3] = s[0]; + } + return tmp; +} diff --git a/root/package/link4all/quectel-CM/simcom-cm/util.h b/root/package/link4all/quectel-CM/simcom-cm/util.h new file mode 100644 index 00000000..6b94d08e --- /dev/null +++ b/root/package/link4all/quectel-CM/simcom-cm/util.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _UTILS_H_ +#define _UTILS_H_ + +#include + +struct listnode +{ + struct listnode *next; + struct listnode *prev; +}; + +#define node_to_item(node, container, member) \ + (container *) (((char*) (node)) - offsetof(container, member)) + +#define list_declare(name) \ + struct listnode name = { \ + .next = &name, \ + .prev = &name, \ + } + +#define list_for_each(node, list) \ + for (node = (list)->next; node != (list); node = node->next) + +#define list_for_each_reverse(node, list) \ + for (node = (list)->prev; node != (list); node = node->prev) + +void list_init(struct listnode *list); +void list_add_tail(struct listnode *list, struct listnode *item); +void list_add_head(struct listnode *head, struct listnode *item); +void list_remove(struct listnode *item); + +#define list_empty(list) ((list) == (list)->next) +#define list_head(list) ((list)->next) +#define list_tail(list) ((list)->prev) + +int epoll_register(int epoll_fd, int fd, unsigned int events); +int epoll_deregister(int epoll_fd, int fd); +#endif diff --git a/root/package/link4all/quectel-CM/src.all/GobiNetCM.c b/root/package/link4all/quectel-CM/src.all/GobiNetCM.c new file mode 100755 index 00000000..c81d8b90 --- /dev/null +++ b/root/package/link4all/quectel-CM/src.all/GobiNetCM.c @@ -0,0 +1,213 @@ +#include +#include +#include +#include +#include +#include "QMIThread.h" + +#ifdef CONFIG_GOBINET + +// 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 + +int GobiNetSendQMI(PQCQMIMSG pRequest) { + int ret, fd; + + fd = qmiclientId[pRequest->QMIHdr.QMIType]; + + if (fd <= 0) { + dbg_time("%s QMIType: %d has no clientID", __func__, pRequest->QMIHdr.QMIType); + return -ENODEV; + } + + // Always ready to write + if (1 == 1) { + ssize_t nwrites = le16_to_cpu(pRequest->QMIHdr.Length) + 1 - sizeof(QCQMI_HDR); + ret = write(fd, &pRequest->MUXMsg, nwrites); + if (ret == nwrites) { + ret = 0; + } else { + dbg_time("%s write=%d, errno: %d (%s)", __func__, ret, errno, strerror(errno)); + } + } else { + dbg_time("%s poll=%d, errno: %d (%s)", __func__, ret, errno, strerror(errno)); + } + + return ret; +} + +static int GobiNetGetClientID(const char *qcqmi, UCHAR QMIType) { + int ClientId; + ClientId = open(qcqmi, O_RDWR | O_NONBLOCK | O_NOCTTY); + if (ClientId == -1) { + dbg_time("failed to open %s, errno: %d (%s)", qcqmi, errno, strerror(errno)); + return -1; + } + if (ioctl(ClientId, IOCTL_QMI_GET_SERVICE_FILE, QMIType) != 0) { + dbg_time("failed to get ClientID for 0x%02x errno: %d (%s)", QMIType, errno, strerror(errno)); + close(ClientId); + ClientId = 0; + } + + qmiclientId[QMIType] = ClientId; + switch (QMIType) { + case QMUX_TYPE_WDS: dbg_time("Get clientWDS = %d", ClientId); break; + case QMUX_TYPE_DMS: dbg_time("Get clientDMS = %d", ClientId); break; + case QMUX_TYPE_NAS: dbg_time("Get clientNAS = %d", ClientId); break; + case QMUX_TYPE_QOS: dbg_time("Get clientQOS = %d", ClientId); break; + case QMUX_TYPE_WMS: dbg_time("Get clientWMS = %d", ClientId); break; + case QMUX_TYPE_PDS: dbg_time("Get clientPDS = %d", ClientId); break; + case QMUX_TYPE_UIM: dbg_time("Get clientUIM = %d", ClientId); break; + case QMUX_TYPE_WDS_ADMIN: dbg_time("Get clientWDA = %d", ClientId); + break; + default: break; + } + + return ClientId; +} + +int GobiNetDeInit(void) { + unsigned int i; + for (i = 0; i < sizeof(qmiclientId)/sizeof(qmiclientId[0]); i++) + { + if (qmiclientId[i] != 0) + { + close(qmiclientId[i]); + qmiclientId[i] = 0; + } + } + + return 0; +} + + +void * GobiNetThread(void *pData) { + const char *qcqmi = (const char *)pData; + GobiNetGetClientID(qcqmi, QMUX_TYPE_WDS); + GobiNetGetClientID(qcqmi, QMUX_TYPE_DMS); + GobiNetGetClientID(qcqmi, QMUX_TYPE_NAS); + GobiNetGetClientID(qcqmi, QMUX_TYPE_UIM); + GobiNetGetClientID(qcqmi, QMUX_TYPE_WDS_ADMIN); + + //donot check clientWDA, there is only one client for WDA, if quectel-CM is killed by SIGKILL, i cannot get client ID for WDA again! + if ((qmiclientId[QMUX_TYPE_WDS] == 0) /*|| (clientWDA == -1)*/) { + GobiNetDeInit(); + dbg_time("%s Failed to open %s, errno: %d (%s)", __func__, qcqmi, errno, strerror(errno)); + qmidevice_send_event_to_main(RIL_INDICATE_DEVICE_DISCONNECTED); + pthread_exit(NULL); + return NULL; + } + + qmidevice_send_event_to_main(RIL_INDICATE_DEVICE_CONNECTED); + + while (1) { + struct pollfd pollfds[16] = {{qmidevice_control_fd[1], POLLIN, 0}}; + int ne, ret, nevents = 1; + unsigned int i; + + for (i = 0; i < sizeof(qmiclientId)/sizeof(qmiclientId[0]); i++) + { + if (qmiclientId[i] != 0) + { + pollfds[nevents].fd = qmiclientId[i]; + pollfds[nevents].events = POLLIN; + pollfds[nevents].revents = 0; + nevents++; + } + } + + do { + ret = poll(pollfds, nevents, -1); + } while ((ret < 0) && (errno == EINTR)); + + if (ret <= 0) { + dbg_time("%s poll=%d, errno: %d (%s)", __func__, ret, errno, strerror(errno)); + break; + } + + for (ne = 0; ne < nevents; ne++) { + int fd = pollfds[ne].fd; + short revents = pollfds[ne].revents; + + if (revents & (POLLERR | POLLHUP | POLLNVAL)) { + dbg_time("%s poll err/hup/inval", __func__); + dbg_time("epoll fd = %d, events = 0x%04x", fd, revents); + if (fd == qmidevice_control_fd[1]) { + } else { + } + if (revents & (POLLERR | POLLHUP | POLLNVAL)) + goto __GobiNetThread_quit; + } + + if ((revents & POLLIN) == 0) + continue; + + if (fd == qmidevice_control_fd[1]) { + int triger_event; + if (read(fd, &triger_event, sizeof(triger_event)) == sizeof(triger_event)) { + //DBG("triger_event = 0x%x", triger_event); + switch (triger_event) { + case RIL_REQUEST_QUIT: + goto __GobiNetThread_quit; + break; + case SIGTERM: + case SIGHUP: + case SIGINT: + QmiThreadRecvQMI(NULL); + break; + default: + break; + } + } + continue; + } + + { + ssize_t nreads; + UCHAR QMIBuf[512]; + PQCQMIMSG pResponse = (PQCQMIMSG)QMIBuf; + + nreads = read(fd, &pResponse->MUXMsg, sizeof(QMIBuf) - sizeof(QCQMI_HDR)); + if (nreads <= 0) + { + dbg_time("%s read=%d errno: %d (%s)", __func__, (int)nreads, errno, strerror(errno)); + break; + } + + for (i = 0; i < sizeof(qmiclientId)/sizeof(qmiclientId[0]); i++) + { + if (qmiclientId[i] == fd) + { + pResponse->QMIHdr.QMIType = i; + } + } + + pResponse->QMIHdr.IFType = USB_CTL_MSG_TYPE_QMI; + pResponse->QMIHdr.Length = cpu_to_le16(nreads + sizeof(QCQMI_HDR) - 1); + pResponse->QMIHdr.CtlFlags = 0x00; + pResponse->QMIHdr.ClientId = fd & 0xFF; + + QmiThreadRecvQMI(pResponse); + } + } + } + +__GobiNetThread_quit: + GobiNetDeInit(); + qmidevice_send_event_to_main(RIL_INDICATE_DEVICE_DISCONNECTED); + QmiThreadRecvQMI(NULL); //main thread may pending on QmiThreadSendQMI() + dbg_time("%s exit", __func__); + pthread_exit(NULL); + return NULL; +} + +#else +int GobiNetSendQMI(PQCQMIMSG pRequest) {return -1;} +void * GobiNetThread(void *pData) {dbg_time("please set CONFIG_GOBINET"); return NULL;} +#endif diff --git a/root/package/link4all/quectel-CM/src.all/MPQCTL.h b/root/package/link4all/quectel-CM/src.all/MPQCTL.h new file mode 100755 index 00000000..77d8aee2 --- /dev/null +++ b/root/package/link4all/quectel-CM/src.all/MPQCTL.h @@ -0,0 +1,376 @@ +/*=========================================================================== + + M P Q C T L. H +DESCRIPTION: + + This module contains QMI QCTL module. + +INITIALIZATION AND SEQUENCING REQUIREMENTS: + +Copyright (C) 2011 by Qualcomm Technologies, Incorporated. All Rights Reserved. +===========================================================================*/ + +#ifndef MPQCTL_H +#define MPQCTL_H + +#include "MPQMI.h" + +#pragma pack(push, 1) + +// ================= QMICTL ================== + +// QMICTL Control Flags +#define QMICTL_CTL_FLAG_CMD 0x00 +#define QMICTL_CTL_FLAG_RSP 0x01 +#define QMICTL_CTL_FLAG_IND 0x02 + +#if 0 +typedef struct _QMICTL_TRANSACTION_ITEM +{ + LIST_ENTRY List; + UCHAR TransactionId; // QMICTL transaction id + PVOID Context; // Adapter or IocDev + PIRP Irp; +} QMICTL_TRANSACTION_ITEM, *PQMICTL_TRANSACTION_ITEM; +#endif + +typedef struct _QCQMICTL_MSG_HDR +{ + UCHAR CtlFlags; // 00-cmd, 01-rsp, 10-ind + UCHAR TransactionId; + USHORT QMICTLType; + USHORT Length; +} __attribute__ ((packed)) QCQMICTL_MSG_HDR, *PQCQMICTL_MSG_HDR; + +#define QCQMICTL_MSG_HDR_SIZE sizeof(QCQMICTL_MSG_HDR) + +typedef struct _QCQMICTL_MSG_HDR_RESP +{ + UCHAR CtlFlags; // 00-cmd, 01-rsp, 10-ind + UCHAR TransactionId; + USHORT QMICTLType; + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} __attribute__ ((packed)) QCQMICTL_MSG_HDR_RESP, *PQCQMICTL_MSG_HDR_RESP; + +typedef struct _QCQMICTL_MSG +{ + UCHAR CtlFlags; // 00-cmd, 01-rsp, 10-ind + UCHAR TransactionId; + USHORT QMICTLType; + USHORT Length; + UCHAR Payload; +} __attribute__ ((packed)) QCQMICTL_MSG, *PQCQMICTL_MSG; + +// TLV Header +typedef struct _QCQMICTL_TLV_HDR +{ + UCHAR TLVType; + USHORT TLVLength; +} __attribute__ ((packed)) QCQMICTL_TLV_HDR, *PQCQMICTL_TLV_HDR; + +#define QCQMICTL_TLV_HDR_SIZE sizeof(QCQMICTL_TLV_HDR) + +// QMICTL Type +#define QMICTL_SET_INSTANCE_ID_REQ 0x0020 +#define QMICTL_SET_INSTANCE_ID_RESP 0x0020 +#define QMICTL_GET_VERSION_REQ 0x0021 +#define QMICTL_GET_VERSION_RESP 0x0021 +#define QMICTL_GET_CLIENT_ID_REQ 0x0022 +#define QMICTL_GET_CLIENT_ID_RESP 0x0022 +#define QMICTL_RELEASE_CLIENT_ID_REQ 0x0023 +#define QMICTL_RELEASE_CLIENT_ID_RESP 0x0023 +#define QMICTL_REVOKE_CLIENT_ID_IND 0x0024 +#define QMICTL_INVALID_CLIENT_ID_IND 0x0025 +#define QMICTL_SET_DATA_FORMAT_REQ 0x0026 +#define QMICTL_SET_DATA_FORMAT_RESP 0x0026 +#define QMICTL_SYNC_REQ 0x0027 +#define QMICTL_SYNC_RESP 0x0027 +#define QMICTL_SYNC_IND 0x0027 + +#define QMICTL_FLAG_REQUEST 0x00 +#define QMICTL_FLAG_RESPONSE 0x01 +#define QMICTL_FLAG_INDICATION 0x02 + +// QMICTL Message Definitions + +typedef struct _QMICTL_SET_INSTANCE_ID_REQ_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_REQUEST + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_SET_INSTANCE_ID_REQ + USHORT Length; // 4 + UCHAR TLVType; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLVLength; // 1 + UCHAR Value; // Host-unique QMI instance for this device driver +} __attribute__ ((packed)) QMICTL_SET_INSTANCE_ID_REQ_MSG, *PQMICTL_SET_INSTANCE_ID_REQ_MSG; + +typedef struct _QMICTL_SET_INSTANCE_ID_RESP_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_RESPONSE + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_SET_INSTANCE_ID_RESP + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_RESULT_CODE + USHORT TLVLength; // 0x0004 + USHORT QMIResult; + USHORT QMIError; + UCHAR TLV2Type; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLV2Length; // 0x0002 + USHORT QMI_ID; // Upper byte is assigned by MSM, + // lower assigned by host +} __attribute__ ((packed)) QMICTL_SET_INSTANCE_ID_RESP_MSG, *PQMICTL_SET_INSTANCE_ID_RESP_MSG; + +typedef struct _QMICTL_GET_VERSION_REQ_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_REQUEST + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_GET_VERSION_REQ + USHORT Length; // 0 + UCHAR TLVType; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLVLength; // var + UCHAR QMUXTypes; // List of one byte QMUX_TYPE values + // 0xFF returns a list of versions for all + // QMUX_TYPEs implemented on the device +} __attribute__ ((packed)) QMICTL_GET_VERSION_REQ_MSG, *PQMICTL_GET_VERSION_REQ_MSG; + +typedef struct _QMUX_TYPE_VERSION_STRUCT +{ + UCHAR QMUXType; + USHORT MajorVersion; + USHORT MinorVersion; +} __attribute__ ((packed)) QMUX_TYPE_VERSION_STRUCT, *PQMUX_TYPE_VERSION_STRUCT; + +typedef struct _ADDENDUM_VERSION_PREAMBLE +{ + UCHAR LabelLength; + UCHAR Label; +} __attribute__ ((packed)) ADDENDUM_VERSION_PREAMBLE, *PADDENDUM_VERSION_PREAMBLE; + +#define QMICTL_GET_VERSION_RSP_TLV_TYPE_VERSION 0x01 +#define QMICTL_GET_VERSION_RSP_TLV_TYPE_ADD_VERSION 0x10 + +typedef struct _QMICTL_GET_VERSION_RESP_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_RESPONSE + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_GET_VERSION_RESP + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_RESULT_CODE + USHORT TLVLength; // 0x0004 + USHORT QMIResult; + USHORT QMIError; + UCHAR TLV2Type; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLV2Length; // var + UCHAR NumElements; // Num of QMUX_TYPE_VERSION_STRUCT + QMUX_TYPE_VERSION_STRUCT TypeVersion; +} __attribute__ ((packed)) QMICTL_GET_VERSION_RESP_MSG, *PQMICTL_GET_VERSION_RESP_MSG; + +typedef struct _QMICTL_GET_CLIENT_ID_REQ_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_REQUEST + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_GET_CLIENT_ID_REQ + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLVLength; // 1 + UCHAR QMIType; // QMUX type +} __attribute__ ((packed)) QMICTL_GET_CLIENT_ID_REQ_MSG, *PQMICTL_GET_CLIENT_ID_REQ_MSG; + +typedef struct _QMICTL_GET_CLIENT_ID_RESP_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_RESPONSE + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_GET_CLIENT_ID_RESP + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_RESULT_CODE + USHORT TLVLength; // 0x0004 + USHORT QMIResult; // result code + USHORT QMIError; // error code + UCHAR TLV2Type; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLV2Length; // 2 + UCHAR QMIType; + UCHAR ClientId; +} __attribute__ ((packed)) QMICTL_GET_CLIENT_ID_RESP_MSG, *PQMICTL_GET_CLIENT_ID_RESP_MSG; + +typedef struct _QMICTL_RELEASE_CLIENT_ID_REQ_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_REQUEST + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_RELEASE_CLIENT_ID_REQ + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLVLength; // 0x0002 + UCHAR QMIType; + UCHAR ClientId; +} __attribute__ ((packed)) QMICTL_RELEASE_CLIENT_ID_REQ_MSG, *PQMICTL_RELEASE_CLIENT_ID_REQ_MSG; + +typedef struct _QMICTL_RELEASE_CLIENT_ID_RESP_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_RESPONSE + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_RELEASE_CLIENT_ID_RESP + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_RESULT_CODE + USHORT TLVLength; // 0x0004 + USHORT QMIResult; // result code + USHORT QMIError; // error code + UCHAR TLV2Type; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLV2Length; // 2 + UCHAR QMIType; + UCHAR ClientId; +} __attribute__ ((packed)) QMICTL_RELEASE_CLIENT_ID_RESP_MSG, *PQMICTL_RELEASE_CLIENT_ID_RESP_MSG; + +typedef struct _QMICTL_REVOKE_CLIENT_ID_IND_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_INDICATION + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_REVOKE_CLIENT_ID_IND + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLVLength; // 0x0002 + UCHAR QMIType; + UCHAR ClientId; +} __attribute__ ((packed)) QMICTL_REVOKE_CLIENT_ID_IND_MSG, *PQMICTL_REVOKE_CLIENT_ID_IND_MSG; + +typedef struct _QMICTL_INVALID_CLIENT_ID_IND_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_INDICATION + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_REVOKE_CLIENT_ID_IND + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLVLength; // 0x0002 + UCHAR QMIType; + UCHAR ClientId; +} __attribute__ ((packed)) QMICTL_INVALID_CLIENT_ID_IND_MSG, *PQMICTL_INVALID_CLIENT_ID_IND_MSG; + +typedef struct _QMICTL_SET_DATA_FORMAT_REQ_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_REQUEST + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_SET_DATA_FORMAT_REQ + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLVLength; // 1 + UCHAR DataFormat; // 0-default; 1-QoS hdr present +} __attribute__ ((packed)) QMICTL_SET_DATA_FORMAT_REQ_MSG, *PQMICTL_SET_DATA_FORMAT_REQ_MSG; + +#ifdef QC_IP_MODE +#define SET_DATA_FORMAT_TLV_TYPE_LINK_PROTO 0x10 +#define SET_DATA_FORMAT_LINK_PROTO_ETH 0x0001 +#define SET_DATA_FORMAT_LINK_PROTO_IP 0x0002 +typedef struct _QMICTL_SET_DATA_FORMAT_TLV_LINK_PROT +{ + UCHAR TLVType; // Link-Layer Protocol + USHORT TLVLength; // 2 + USHORT LinkProt; // 0x1: ETH; 0x2: IP +} QMICTL_SET_DATA_FORMAT_TLV_LINK_PROT, *PQMICTL_SET_DATA_FORMAT_TLV_LINK_PROT; + +#ifdef QCMP_UL_TLP +#define SET_DATA_FORMAT_TLV_TYPE_UL_TLP 0x11 +typedef struct _QMICTL_SET_DATA_FORMAT_TLV_UL_TLP +{ + UCHAR TLVType; // 0x11, Uplink TLP Setting + USHORT TLVLength; // 1 + UCHAR UlTlpSetting; // 0x0: Disable; 0x01: Enable +} QMICTL_SET_DATA_FORMAT_TLV_UL_TLP, *PQMICTL_SET_DATA_FORMAT_TLV_UL_TLP; +#endif // QCMP_UL_TLP + +#ifdef QCMP_DL_TLP +#define SET_DATA_FORMAT_TLV_TYPE_DL_TLP 0x13 +typedef struct _QMICTL_SET_DATA_FORMAT_TLV_DL_TLP +{ + UCHAR TLVType; // 0x11, Uplink TLP Setting + USHORT TLVLength; // 1 + UCHAR DlTlpSetting; // 0x0: Disable; 0x01: Enable +} QMICTL_SET_DATA_FORMAT_TLV_DL_TLP, *PQMICTL_SET_DATA_FORMAT_TLV_DL_TLP; +#endif // QCMP_DL_TLP + +#endif // QC_IP_MODE + +#ifdef MP_QCQOS_ENABLED +#define SET_DATA_FORMAT_TLV_TYPE_QOS_SETTING 0x12 +typedef struct _QMICTL_SET_DATA_FORMAT_TLV_QOS_SETTING +{ + UCHAR TLVType; // 0x12, QoS setting + USHORT TLVLength; // 1 + UCHAR QosSetting; // 0x0: Disable; 0x01: Enable +} QMICTL_SET_DATA_FORMAT_TLV_QOS_SETTING, *PQMICTL_SET_DATA_FORMAT_TLV_QOS_SETTING; +#endif // MP_QCQOS_ENABLED + +typedef struct _QMICTL_SET_DATA_FORMAT_RESP_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_RESPONSE + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_SET_DATA_FORMAT_RESP + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_RESULT_CODE + USHORT TLVLength; // 0x0004 + USHORT QMIResult; // result code + USHORT QMIError; // error code +} __attribute__ ((packed)) QMICTL_SET_DATA_FORMAT_RESP_MSG, *PQMICTL_SET_DATA_FORMAT_RESP_MSG; + +typedef struct _QMICTL_SYNC_REQ_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_REQUEST + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_CTL_SYNC_REQ + USHORT Length; // 0 +} __attribute__ ((packed)) QMICTL_SYNC_REQ_MSG, *PQMICTL_SYNC_REQ_MSG; + +typedef struct _QMICTL_SYNC_RESP_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_RESPONSE + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_CTL_SYNC_RESP + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_RESULT_CODE + USHORT TLVLength; // 0x0004 + USHORT QMIResult; + USHORT QMIError; +} __attribute__ ((packed)) QMICTL_SYNC_RESP_MSG, *PQMICTL_SYNC_RESP_MSG; + +typedef struct _QMICTL_SYNC_IND_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_INDICATION + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_REVOKE_CLIENT_ID_IND + USHORT Length; +} __attribute__ ((packed)) QMICTL_SYNC_IND_MSG, *PQMICTL_SYNC_IND_MSG; + +typedef struct _QMICTL_MSG +{ + union + { + // Message Header + QCQMICTL_MSG_HDR QMICTLMsgHdr; + QCQMICTL_MSG_HDR_RESP QMICTLMsgHdrRsp; + + // QMICTL Message + QMICTL_SET_INSTANCE_ID_REQ_MSG SetInstanceIdReq; + QMICTL_SET_INSTANCE_ID_RESP_MSG SetInstanceIdRsp; + QMICTL_GET_VERSION_REQ_MSG GetVersionReq; + QMICTL_GET_VERSION_RESP_MSG GetVersionRsp; + QMICTL_GET_CLIENT_ID_REQ_MSG GetClientIdReq; + QMICTL_GET_CLIENT_ID_RESP_MSG GetClientIdRsp; + QMICTL_RELEASE_CLIENT_ID_REQ_MSG ReleaseClientIdReq; + QMICTL_RELEASE_CLIENT_ID_RESP_MSG ReleaseClientIdRsp; + QMICTL_REVOKE_CLIENT_ID_IND_MSG RevokeClientIdInd; + QMICTL_INVALID_CLIENT_ID_IND_MSG InvalidClientIdInd; + QMICTL_SET_DATA_FORMAT_REQ_MSG SetDataFormatReq; + QMICTL_SET_DATA_FORMAT_RESP_MSG SetDataFormatRsp; + QMICTL_SYNC_REQ_MSG SyncReq; + QMICTL_SYNC_RESP_MSG SyncRsp; + QMICTL_SYNC_IND_MSG SyncInd; + }; +} __attribute__ ((packed)) QMICTL_MSG, *PQMICTL_MSG; + +#endif // MPQCTL_H diff --git a/root/package/link4all/quectel-CM/src.all/MPQMI.h b/root/package/link4all/quectel-CM/src.all/MPQMI.h new file mode 100755 index 00000000..ca457c4a --- /dev/null +++ b/root/package/link4all/quectel-CM/src.all/MPQMI.h @@ -0,0 +1,220 @@ +/*=========================================================================== + + M P Q M I. H +DESCRIPTION: + + This module contains forward references to the QMI module. + +INITIALIZATION AND SEQUENCING REQUIREMENTS: + +Copyright (C) 2011 by Qualcomm Technologies, Incorporated. All Rights Reserved. +===========================================================================*/ +/*=========================================================================== + + EDIT HISTORY FOR FILE + $Header: //depot/QMI/win/qcdrivers/ndis/MPQMI.h#3 $ + +when who what, where, why +-------- --- ---------------------------------------------------------- +11/20/04 hg Initial version. +===========================================================================*/ + +#ifndef USBQMI_H +#define USBQMI_H + +typedef char CHAR; +typedef unsigned char UCHAR; +typedef unsigned short USHORT; +typedef int INT; +typedef unsigned int UINT; +typedef long LONG; +typedef unsigned int ULONG; +typedef unsigned long long ULONG64; +typedef char *PCHAR; +typedef unsigned char *PUCHAR; +typedef int *PINT; +typedef int BOOL; + +#define TRUE (1 == 1) +#define FALSE (1 != 1) + +#define QMICTL_SUPPORTED_MAJOR_VERSION 1 +#define QMICTL_SUPPORTED_MINOR_VERSION 0 + +#pragma pack(push, 1) + +// ========= USB Control Message ========== + +#define USB_CTL_MSG_TYPE_QMI 0x01 + +// USB Control Message +typedef struct _QCUSB_CTL_MSG_HDR +{ + UCHAR IFType; +} __attribute__ ((packed)) QCUSB_CTL_MSG_HDR, *PQCUSB_CTL_MSG_HDR; + +#define QCUSB_CTL_MSG_HDR_SIZE sizeof(QCUSB_CTL_MSG_HDR) + +typedef struct _QCUSB_CTL_MSG +{ + UCHAR IFType; + UCHAR Message; +} __attribute__ ((packed)) QCUSB_CTL_MSG, *PQCUSB_CTL_MSG; + +#define QCTLV_TYPE_REQUIRED_PARAMETER 0x01 +#define QCTLV_TYPE_RESULT_CODE 0x02 + +// ================= QMI ================== + +// Define QMI Type +typedef enum _QMI_SERVICE_TYPE +{ + QMUX_TYPE_CTL = 0x00, + QMUX_TYPE_WDS = 0x01, + QMUX_TYPE_DMS = 0x02, + QMUX_TYPE_NAS = 0x03, + QMUX_TYPE_QOS = 0x04, + QMUX_TYPE_WMS = 0x05, + QMUX_TYPE_PDS = 0x06, + QMUX_TYPE_UIM = 0x0B, + QMUX_TYPE_WDS_ADMIN = 0x1A, + QMUX_TYPE_MAX = 0xFF, + QMUX_TYPE_ALL = 0xFF +} QMI_SERVICE_TYPE; + +typedef enum _QMI_RESULT_CODE_TYPE +{ + QMI_RESULT_SUCCESS = 0x0000, + QMI_RESULT_FAILURE = 0x0001 +} QMI_RESULT_CODE_TYPE; + +typedef enum _QMI_ERROR_CODE_TYPE +{ + QMI_ERR_NONE = 0x0000, + QMI_ERR_INTERNAL = 0x0003, + QMI_ERR_CLIENT_IDS_EXHAUSTED = 0x0005, + QMI_ERR_DENIED = 0x0006, + QMI_ERR_INVALID_CLIENT_IDS = 0x0007, + QMI_ERR_NO_BATTERY = 0x0008, + QMI_ERR_INVALID_HANDLE = 0x0009, + QMI_ERR_INVALID_PROFILE = 0x000A, + QMI_ERR_STORAGE_EXCEEDED = 0x000B, + QMI_ERR_INCORRECT_PIN = 0x000C, + QMI_ERR_NO_NETWORK = 0x000D, + QMI_ERR_PIN_LOCKED = 0x000E, + QMI_ERR_OUT_OF_CALL = 0x000F, + QMI_ERR_NOT_PROVISIONED = 0x0010, + QMI_ERR_ARG_TOO_LONG = 0x0013, + QMI_ERR_DEVICE_IN_USE = 0x0017, + QMI_ERR_OP_DEVICE_UNSUPPORTED = 0x0019, + QMI_ERR_NO_EFFECT = 0x001A, + QMI_ERR_INVALID_ARG = 0x0020, + QMI_ERR_NO_MEMORY = 0x0021, + QMI_ERR_PIN_BLOCKED = 0x0023, + QMI_ERR_PIN_PERM_BLOCKED = 0x0024, + QMI_ERR_INVALID_INDEX = 0x0031, + QMI_ERR_NO_ENTRY = 0x0032, + QMI_ERR_EXTENDED_INTERNAL = 0x0051, + QMI_ERR_ACCESS_DENIED = 0x0052 +} QMI_ERROR_CODE_TYPE; + +#define QCQMI_CTL_FLAG_SERVICE 0x80 +#define QCQMI_CTL_FLAG_CTL_POINT 0x00 + +typedef struct _QCQMI_HDR +{ + UCHAR IFType; + USHORT Length; + UCHAR CtlFlags; // reserved + UCHAR QMIType; + UCHAR ClientId; +} __attribute__ ((packed)) QCQMI_HDR, *PQCQMI_HDR; + +#define QCQMI_HDR_SIZE (sizeof(QCQMI_HDR)-1) + +typedef struct _QCQMI +{ + UCHAR IFType; + USHORT Length; + UCHAR CtlFlags; // reserved + UCHAR QMIType; + UCHAR ClientId; + UCHAR SDU; +} __attribute__ ((packed)) QCQMI, *PQCQMI; + +typedef struct _QMI_SERVICE_VERSION +{ + USHORT Major; + USHORT Minor; + USHORT AddendumMajor; + USHORT AddendumMinor; +} __attribute__ ((packed)) QMI_SERVICE_VERSION, *PQMI_SERVICE_VERSION; + +// ================= QMUX ================== + +#define QMUX_MSG_OVERHEAD_BYTES 4 // Type(USHORT) Length(USHORT) -- header + +#define QMUX_BROADCAST_CID 0xFF + +typedef struct _QCQMUX_HDR +{ + UCHAR CtlFlags; // 0: single QMUX Msg; 1: + USHORT TransactionId; +} __attribute__ ((packed)) QCQMUX_HDR, *PQCQMUX_HDR; + +typedef struct _QCQMUX +{ + UCHAR CtlFlags; // 0: single QMUX Msg; 1: + USHORT TransactionId; + UCHAR Message; // Type(2), Length(2), Value +} __attribute__ ((packed)) QCQMUX, *PQCQMUX; + +#define QCQMUX_HDR_SIZE sizeof(QCQMUX_HDR) + +typedef struct _QCQMUX_MSG_HDR +{ + USHORT Type; + USHORT Length; +} __attribute__ ((packed)) QCQMUX_MSG_HDR, *PQCQMUX_MSG_HDR; + +#define QCQMUX_MSG_HDR_SIZE sizeof(QCQMUX_MSG_HDR) + +typedef struct _QCQMUX_MSG_HDR_RESP +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} __attribute__ ((packed)) QCQMUX_MSG_HDR_RESP, *PQCQMUX_MSG_HDR_RESP; + +typedef struct _QCQMUX_TLV +{ + UCHAR Type; + USHORT Length; + UCHAR Value; +} __attribute__ ((packed)) QCQMUX_TLV, *PQCQMUX_TLV; + +typedef struct _QMI_TLV_HDR +{ + UCHAR TLVType; + USHORT TLVLength; +} __attribute__ ((packed)) QMI_TLV_HDR, *PQMI_TLV_HDR; + +// QMUX Message Definitions -- QMI SDU +#define QMUX_CTL_FLAG_SINGLE_MSG 0x00 +#define QMUX_CTL_FLAG_COMPOUND_MSG 0x01 +#define QMUX_CTL_FLAG_TYPE_CMD 0x00 +#define QMUX_CTL_FLAG_TYPE_RSP 0x02 +#define QMUX_CTL_FLAG_TYPE_IND 0x04 +#define QMUX_CTL_FLAG_MASK_COMPOUND 0x01 +#define QMUX_CTL_FLAG_MASK_TYPE 0x06 // 00-cmd, 01-rsp, 10-ind + +#pragma pack(pop) + +#endif // USBQMI_H diff --git a/root/package/link4all/quectel-CM/src.all/MPQMUX.c b/root/package/link4all/quectel-CM/src.all/MPQMUX.c new file mode 100755 index 00000000..b03ed408 --- /dev/null +++ b/root/package/link4all/quectel-CM/src.all/MPQMUX.c @@ -0,0 +1,422 @@ +#include "QMIThread.h" +static char line[1024]; +static pthread_mutex_t dumpQMIMutex = PTHREAD_MUTEX_INITIALIZER; +#undef dbg +#define dbg( format, arg... ) do {if (strlen(line) < sizeof(line)) snprintf(&line[strlen(line)], sizeof(line) - strlen(line), format, ## arg);} while (0) + +PQMI_TLV_HDR GetTLV (PQCQMUX_MSG_HDR pQMUXMsgHdr, int TLVType); + +typedef struct { + UINT type; + const char *name; +} QMI_NAME_T; + +#define qmi_name_item(type) {type, #type} + +static const QMI_NAME_T qmi_IFType[] = { +{USB_CTL_MSG_TYPE_QMI, "USB_CTL_MSG_TYPE_QMI"}, +}; + +static const QMI_NAME_T qmi_CtlFlags[] = { +qmi_name_item(QMICTL_CTL_FLAG_CMD), +qmi_name_item(QCQMI_CTL_FLAG_SERVICE), +}; + +static const QMI_NAME_T qmi_QMIType[] = { +qmi_name_item(QMUX_TYPE_CTL), +qmi_name_item(QMUX_TYPE_WDS), +qmi_name_item(QMUX_TYPE_DMS), +qmi_name_item(QMUX_TYPE_NAS), +qmi_name_item(QMUX_TYPE_QOS), +qmi_name_item(QMUX_TYPE_WMS), +qmi_name_item(QMUX_TYPE_PDS), +qmi_name_item(QMUX_TYPE_WDS_ADMIN), +}; + +static const QMI_NAME_T qmi_ctl_CtlFlags[] = { +qmi_name_item(QMICTL_FLAG_REQUEST), +qmi_name_item(QMICTL_FLAG_RESPONSE), +qmi_name_item(QMICTL_FLAG_INDICATION), +}; + +static const QMI_NAME_T qmux_ctl_QMICTLType[] = { +// QMICTL Type +qmi_name_item(QMICTL_SET_INSTANCE_ID_REQ), // 0x0020 +qmi_name_item(QMICTL_SET_INSTANCE_ID_RESP), // 0x0020 +qmi_name_item(QMICTL_GET_VERSION_REQ), // 0x0021 +qmi_name_item(QMICTL_GET_VERSION_RESP), // 0x0021 +qmi_name_item(QMICTL_GET_CLIENT_ID_REQ), // 0x0022 +qmi_name_item(QMICTL_GET_CLIENT_ID_RESP), // 0x0022 +qmi_name_item(QMICTL_RELEASE_CLIENT_ID_REQ), // 0x0023 +qmi_name_item(QMICTL_RELEASE_CLIENT_ID_RESP), // 0x0023 +qmi_name_item(QMICTL_REVOKE_CLIENT_ID_IND), // 0x0024 +qmi_name_item(QMICTL_INVALID_CLIENT_ID_IND), // 0x0025 +qmi_name_item(QMICTL_SET_DATA_FORMAT_REQ), // 0x0026 +qmi_name_item(QMICTL_SET_DATA_FORMAT_RESP), // 0x0026 +qmi_name_item(QMICTL_SYNC_REQ), // 0x0027 +qmi_name_item(QMICTL_SYNC_RESP), // 0x0027 +qmi_name_item(QMICTL_SYNC_IND), // 0x0027 +}; + +static const QMI_NAME_T qmux_CtlFlags[] = { +qmi_name_item(QMUX_CTL_FLAG_TYPE_CMD), +qmi_name_item(QMUX_CTL_FLAG_TYPE_RSP), +qmi_name_item(QMUX_CTL_FLAG_TYPE_IND), +}; + + +static const QMI_NAME_T qmux_wds_Type[] = { +qmi_name_item(QMIWDS_SET_EVENT_REPORT_REQ), // 0x0001 +qmi_name_item(QMIWDS_SET_EVENT_REPORT_RESP), // 0x0001 +qmi_name_item(QMIWDS_EVENT_REPORT_IND), // 0x0001 +qmi_name_item(QMIWDS_START_NETWORK_INTERFACE_REQ), // 0x0020 +qmi_name_item(QMIWDS_START_NETWORK_INTERFACE_RESP), // 0x0020 +qmi_name_item(QMIWDS_STOP_NETWORK_INTERFACE_REQ), // 0x0021 +qmi_name_item(QMIWDS_STOP_NETWORK_INTERFACE_RESP), // 0x0021 +qmi_name_item(QMIWDS_GET_PKT_SRVC_STATUS_REQ), // 0x0022 +qmi_name_item(QMIWDS_GET_PKT_SRVC_STATUS_RESP), // 0x0022 +qmi_name_item(QMIWDS_GET_PKT_SRVC_STATUS_IND), // 0x0022 +qmi_name_item(QMIWDS_GET_CURRENT_CHANNEL_RATE_REQ), // 0x0023 +qmi_name_item(QMIWDS_GET_CURRENT_CHANNEL_RATE_RESP), // 0x0023 +qmi_name_item(QMIWDS_GET_PKT_STATISTICS_REQ), // 0x0024 +qmi_name_item(QMIWDS_GET_PKT_STATISTICS_RESP), // 0x0024 +qmi_name_item(QMIWDS_MODIFY_PROFILE_SETTINGS_REQ), // 0x0028 +qmi_name_item(QMIWDS_MODIFY_PROFILE_SETTINGS_RESP), // 0x0028 +qmi_name_item(QMIWDS_GET_PROFILE_SETTINGS_REQ), // 0x002B +qmi_name_item(QMIWDS_GET_PROFILE_SETTINGS_RESP), // 0x002BD +qmi_name_item(QMIWDS_GET_DEFAULT_SETTINGS_REQ), // 0x002C +qmi_name_item(QMIWDS_GET_DEFAULT_SETTINGS_RESP), // 0x002C +qmi_name_item(QMIWDS_GET_RUNTIME_SETTINGS_REQ), // 0x002D +qmi_name_item(QMIWDS_GET_RUNTIME_SETTINGS_RESP), // 0x002D +qmi_name_item(QMIWDS_GET_MIP_MODE_REQ), // 0x002F +qmi_name_item(QMIWDS_GET_MIP_MODE_RESP), // 0x002F +qmi_name_item(QMIWDS_GET_DATA_BEARER_REQ), // 0x0037 +qmi_name_item(QMIWDS_GET_DATA_BEARER_RESP), // 0x0037 +qmi_name_item(QMIWDS_DUN_CALL_INFO_REQ), // 0x0038 +qmi_name_item(QMIWDS_DUN_CALL_INFO_RESP), // 0x0038 +qmi_name_item(QMIWDS_DUN_CALL_INFO_IND), // 0x0038 +qmi_name_item(QMIWDS_SET_CLIENT_IP_FAMILY_PREF_REQ), // 0x004D +qmi_name_item(QMIWDS_SET_CLIENT_IP_FAMILY_PREF_RESP), // 0x004D +qmi_name_item(QMIWDS_BIND_MUX_DATA_PORT_REQ), // 0x00A2 +qmi_name_item(QMIWDS_BIND_MUX_DATA_PORT_RESP), // 0x00A2 +}; + +static const QMI_NAME_T qmux_dms_Type[] = { +// ======================= DMS ============================== +qmi_name_item(QMIDMS_SET_EVENT_REPORT_REQ), // 0x0001 +qmi_name_item(QMIDMS_SET_EVENT_REPORT_RESP), // 0x0001 +qmi_name_item(QMIDMS_EVENT_REPORT_IND), // 0x0001 +qmi_name_item(QMIDMS_GET_DEVICE_CAP_REQ), // 0x0020 +qmi_name_item(QMIDMS_GET_DEVICE_CAP_RESP), // 0x0020 +qmi_name_item(QMIDMS_GET_DEVICE_MFR_REQ), // 0x0021 +qmi_name_item(QMIDMS_GET_DEVICE_MFR_RESP), // 0x0021 +qmi_name_item(QMIDMS_GET_DEVICE_MODEL_ID_REQ), // 0x0022 +qmi_name_item(QMIDMS_GET_DEVICE_MODEL_ID_RESP), // 0x0022 +qmi_name_item(QMIDMS_GET_DEVICE_REV_ID_REQ), // 0x0023 +qmi_name_item(QMIDMS_GET_DEVICE_REV_ID_RESP), // 0x0023 +qmi_name_item(QMIDMS_GET_MSISDN_REQ), // 0x0024 +qmi_name_item(QMIDMS_GET_MSISDN_RESP), // 0x0024 +qmi_name_item(QMIDMS_GET_DEVICE_SERIAL_NUMBERS_REQ), // 0x0025 +qmi_name_item(QMIDMS_GET_DEVICE_SERIAL_NUMBERS_RESP), // 0x0025 +qmi_name_item(QMIDMS_UIM_SET_PIN_PROTECTION_REQ), // 0x0027 +qmi_name_item(QMIDMS_UIM_SET_PIN_PROTECTION_RESP), // 0x0027 +qmi_name_item(QMIDMS_UIM_VERIFY_PIN_REQ), // 0x0028 +qmi_name_item(QMIDMS_UIM_VERIFY_PIN_RESP), // 0x0028 +qmi_name_item(QMIDMS_UIM_UNBLOCK_PIN_REQ), // 0x0029 +qmi_name_item(QMIDMS_UIM_UNBLOCK_PIN_RESP), // 0x0029 +qmi_name_item(QMIDMS_UIM_CHANGE_PIN_REQ), // 0x002A +qmi_name_item(QMIDMS_UIM_CHANGE_PIN_RESP), // 0x002A +qmi_name_item(QMIDMS_UIM_GET_PIN_STATUS_REQ), // 0x002B +qmi_name_item(QMIDMS_UIM_GET_PIN_STATUS_RESP), // 0x002B +qmi_name_item(QMIDMS_GET_DEVICE_HARDWARE_REV_REQ), // 0x002C +qmi_name_item(QMIDMS_GET_DEVICE_HARDWARE_REV_RESP), // 0x002C +qmi_name_item(QMIDMS_GET_OPERATING_MODE_REQ), // 0x002D +qmi_name_item(QMIDMS_GET_OPERATING_MODE_RESP), // 0x002D +qmi_name_item(QMIDMS_SET_OPERATING_MODE_REQ), // 0x002E +qmi_name_item(QMIDMS_SET_OPERATING_MODE_RESP), // 0x002E +qmi_name_item(QMIDMS_GET_ACTIVATED_STATUS_REQ), // 0x0031 +qmi_name_item(QMIDMS_GET_ACTIVATED_STATUS_RESP), // 0x0031 +qmi_name_item(QMIDMS_ACTIVATE_AUTOMATIC_REQ), // 0x0032 +qmi_name_item(QMIDMS_ACTIVATE_AUTOMATIC_RESP), // 0x0032 +qmi_name_item(QMIDMS_ACTIVATE_MANUAL_REQ), // 0x0033 +qmi_name_item(QMIDMS_ACTIVATE_MANUAL_RESP), // 0x0033 +qmi_name_item(QMIDMS_UIM_GET_ICCID_REQ), // 0x003C +qmi_name_item(QMIDMS_UIM_GET_ICCID_RESP), // 0x003C +qmi_name_item(QMIDMS_UIM_GET_CK_STATUS_REQ), // 0x0040 +qmi_name_item(QMIDMS_UIM_GET_CK_STATUS_RESP), // 0x0040 +qmi_name_item(QMIDMS_UIM_SET_CK_PROTECTION_REQ), // 0x0041 +qmi_name_item(QMIDMS_UIM_SET_CK_PROTECTION_RESP), // 0x0041 +qmi_name_item(QMIDMS_UIM_UNBLOCK_CK_REQ), // 0x0042 +qmi_name_item(QMIDMS_UIM_UNBLOCK_CK_RESP), // 0x0042 +qmi_name_item(QMIDMS_UIM_GET_IMSI_REQ), // 0x0043 +qmi_name_item(QMIDMS_UIM_GET_IMSI_RESP), // 0x0043 +qmi_name_item(QMIDMS_UIM_GET_STATE_REQ), // 0x0044 +qmi_name_item(QMIDMS_UIM_GET_STATE_RESP), // 0x0044 +qmi_name_item(QMIDMS_GET_BAND_CAP_REQ), // 0x0045 +qmi_name_item(QMIDMS_GET_BAND_CAP_RESP), // 0x0045 +}; + +static const QMI_NAME_T qmux_nas_Type[] = { +// ======================= NAS ============================== +qmi_name_item(QMINAS_SET_EVENT_REPORT_REQ), // 0x0002 +qmi_name_item(QMINAS_SET_EVENT_REPORT_RESP), // 0x0002 +qmi_name_item(QMINAS_EVENT_REPORT_IND), // 0x0002 +qmi_name_item(QMINAS_GET_SIGNAL_STRENGTH_REQ), // 0x0020 +qmi_name_item(QMINAS_GET_SIGNAL_STRENGTH_RESP), // 0x0020 +qmi_name_item(QMINAS_PERFORM_NETWORK_SCAN_REQ), // 0x0021 +qmi_name_item(QMINAS_PERFORM_NETWORK_SCAN_RESP), // 0x0021 +qmi_name_item(QMINAS_INITIATE_NW_REGISTER_REQ), // 0x0022 +qmi_name_item(QMINAS_INITIATE_NW_REGISTER_RESP), // 0x0022 +qmi_name_item(QMINAS_INITIATE_ATTACH_REQ), // 0x0023 +qmi_name_item(QMINAS_INITIATE_ATTACH_RESP), // 0x0023 +qmi_name_item(QMINAS_GET_SERVING_SYSTEM_REQ), // 0x0024 +qmi_name_item(QMINAS_GET_SERVING_SYSTEM_RESP), // 0x0024 +qmi_name_item(QMINAS_SERVING_SYSTEM_IND), // 0x0024 +qmi_name_item(QMINAS_GET_HOME_NETWORK_REQ), // 0x0025 +qmi_name_item(QMINAS_GET_HOME_NETWORK_RESP), // 0x0025 +qmi_name_item(QMINAS_GET_PREFERRED_NETWORK_REQ), // 0x0026 +qmi_name_item(QMINAS_GET_PREFERRED_NETWORK_RESP), // 0x0026 +qmi_name_item(QMINAS_SET_PREFERRED_NETWORK_REQ), // 0x0027 +qmi_name_item(QMINAS_SET_PREFERRED_NETWORK_RESP), // 0x0027 +qmi_name_item(QMINAS_GET_FORBIDDEN_NETWORK_REQ), // 0x0028 +qmi_name_item(QMINAS_GET_FORBIDDEN_NETWORK_RESP), // 0x0028 +qmi_name_item(QMINAS_SET_FORBIDDEN_NETWORK_REQ), // 0x0029 +qmi_name_item(QMINAS_SET_FORBIDDEN_NETWORK_RESP), // 0x0029 +qmi_name_item(QMINAS_SET_TECHNOLOGY_PREF_REQ), // 0x002A +qmi_name_item(QMINAS_SET_TECHNOLOGY_PREF_RESP), // 0x002A +qmi_name_item(QMINAS_GET_RF_BAND_INFO_REQ), // 0x0031 +qmi_name_item(QMINAS_GET_RF_BAND_INFO_RESP), // 0x0031 +qmi_name_item(QMINAS_GET_PLMN_NAME_REQ), // 0x0044 +qmi_name_item(QMINAS_GET_PLMN_NAME_RESP), // 0x0044 +qmi_name_item(QUECTEL_PACKET_TRANSFER_START_IND), // 0X100 +qmi_name_item(QUECTEL_PACKET_TRANSFER_END_IND), // 0X101 +qmi_name_item(QMINAS_GET_SYS_INFO_REQ), // 0x004D +qmi_name_item(QMINAS_GET_SYS_INFO_RESP), // 0x004D +qmi_name_item(QMINAS_SYS_INFO_IND), // 0x004D +}; + +static const QMI_NAME_T qmux_wms_Type[] = { +// ======================= WMS ============================== +qmi_name_item(QMIWMS_SET_EVENT_REPORT_REQ), // 0x0001 +qmi_name_item(QMIWMS_SET_EVENT_REPORT_RESP), // 0x0001 +qmi_name_item(QMIWMS_EVENT_REPORT_IND), // 0x0001 +qmi_name_item(QMIWMS_RAW_SEND_REQ), // 0x0020 +qmi_name_item(QMIWMS_RAW_SEND_RESP), // 0x0020 +qmi_name_item(QMIWMS_RAW_WRITE_REQ), // 0x0021 +qmi_name_item(QMIWMS_RAW_WRITE_RESP), // 0x0021 +qmi_name_item(QMIWMS_RAW_READ_REQ), // 0x0022 +qmi_name_item(QMIWMS_RAW_READ_RESP), // 0x0022 +qmi_name_item(QMIWMS_MODIFY_TAG_REQ), // 0x0023 +qmi_name_item(QMIWMS_MODIFY_TAG_RESP), // 0x0023 +qmi_name_item(QMIWMS_DELETE_REQ), // 0x0024 +qmi_name_item(QMIWMS_DELETE_RESP), // 0x0024 +qmi_name_item(QMIWMS_GET_MESSAGE_PROTOCOL_REQ), // 0x0030 +qmi_name_item(QMIWMS_GET_MESSAGE_PROTOCOL_RESP), // 0x0030 +qmi_name_item(QMIWMS_LIST_MESSAGES_REQ), // 0x0031 +qmi_name_item(QMIWMS_LIST_MESSAGES_RESP), // 0x0031 +qmi_name_item(QMIWMS_GET_SMSC_ADDRESS_REQ), // 0x0034 +qmi_name_item(QMIWMS_GET_SMSC_ADDRESS_RESP), // 0x0034 +qmi_name_item(QMIWMS_SET_SMSC_ADDRESS_REQ), // 0x0035 +qmi_name_item(QMIWMS_SET_SMSC_ADDRESS_RESP), // 0x0035 +qmi_name_item(QMIWMS_GET_STORE_MAX_SIZE_REQ), // 0x0036 +qmi_name_item(QMIWMS_GET_STORE_MAX_SIZE_RESP), // 0x0036 +}; + +static const QMI_NAME_T qmux_wds_admin_Type[] = { +qmi_name_item(QMIWDS_ADMIN_SET_DATA_FORMAT_REQ), // 0x0020 +qmi_name_item(QMIWDS_ADMIN_SET_DATA_FORMAT_RESP), // 0x0020 +qmi_name_item(QMIWDS_ADMIN_GET_DATA_FORMAT_REQ), // 0x0021 +qmi_name_item(QMIWDS_ADMIN_GET_DATA_FORMAT_RESP), // 0x0021 +qmi_name_item(QMIWDS_ADMIN_SET_QMAP_SETTINGS_REQ), // 0x002B +qmi_name_item(QMIWDS_ADMIN_SET_QMAP_SETTINGS_RESP), // 0x002B +qmi_name_item(QMIWDS_ADMIN_GET_QMAP_SETTINGS_REQ), // 0x002C +qmi_name_item(QMIWDS_ADMIN_GET_QMAP_SETTINGS_RESP), // 0x002C +}; + +static const QMI_NAME_T qmux_uim_Type[] = { +qmi_name_item( QMIUIM_READ_TRANSPARENT_REQ), // 0x0020 +qmi_name_item( QMIUIM_READ_TRANSPARENT_RESP), // 0x0020 +qmi_name_item( QMIUIM_READ_TRANSPARENT_IND), // 0x0020 +qmi_name_item( QMIUIM_READ_RECORD_REQ), // 0x0021 +qmi_name_item( QMIUIM_READ_RECORD_RESP), // 0x0021 +qmi_name_item( QMIUIM_READ_RECORD_IND), // 0x0021 +qmi_name_item( QMIUIM_WRITE_TRANSPARENT_REQ), // 0x0022 +qmi_name_item( QMIUIM_WRITE_TRANSPARENT_RESP), // 0x0022 +qmi_name_item( QMIUIM_WRITE_TRANSPARENT_IND), // 0x0022 +qmi_name_item( QMIUIM_WRITE_RECORD_REQ), // 0x0023 +qmi_name_item( QMIUIM_WRITE_RECORD_RESP), // 0x0023 +qmi_name_item( QMIUIM_WRITE_RECORD_IND), // 0x0023 +qmi_name_item( QMIUIM_SET_PIN_PROTECTION_REQ), // 0x0025 +qmi_name_item( QMIUIM_SET_PIN_PROTECTION_RESP), // 0x0025 +qmi_name_item( QMIUIM_SET_PIN_PROTECTION_IND), // 0x0025 +qmi_name_item( QMIUIM_VERIFY_PIN_REQ), // 0x0026 +qmi_name_item( QMIUIM_VERIFY_PIN_RESP), // 0x0026 +qmi_name_item( QMIUIM_VERIFY_PIN_IND), // 0x0026 +qmi_name_item( QMIUIM_UNBLOCK_PIN_REQ), // 0x0027 +qmi_name_item( QMIUIM_UNBLOCK_PIN_RESP), // 0x0027 +qmi_name_item( QMIUIM_UNBLOCK_PIN_IND), // 0x0027 +qmi_name_item( QMIUIM_CHANGE_PIN_REQ), // 0x0028 +qmi_name_item( QMIUIM_CHANGE_PIN_RESP), // 0x0028 +qmi_name_item( QMIUIM_CHANGE_PIN_IND), // 0x0028 +qmi_name_item( QMIUIM_DEPERSONALIZATION_REQ), // 0x0029 +qmi_name_item( QMIUIM_DEPERSONALIZATION_RESP), // 0x0029 +qmi_name_item( QMIUIM_EVENT_REG_REQ), // 0x002E +qmi_name_item( QMIUIM_EVENT_REG_RESP), // 0x002E +qmi_name_item( QMIUIM_GET_CARD_STATUS_REQ), // 0x002F +qmi_name_item( QMIUIM_GET_CARD_STATUS_RESP), // 0x002F +qmi_name_item( QMIUIM_STATUS_CHANGE_IND), // 0x0032 +}; + +static const char * qmi_name_get(const QMI_NAME_T *table, size_t size, int type, const char *tag) { + static char unknow[40]; + size_t i; + + if (qmux_CtlFlags == table) { + if (!strcmp(tag, "_REQ")) + tag = "_CMD"; + else if (!strcmp(tag, "_RESP")) + tag = "_RSP"; + } + + for (i = 0; i < size; i++) { + if (table[i].type == (UINT)type) { + if (!tag || (strstr(table[i].name, tag))) + return table[i].name; + } + } + sprintf(unknow, "unknow_%x", type); + return unknow; +} + +#define QMI_NAME(table, type) qmi_name_get(table, sizeof(table) / sizeof(table[0]), type, 0) +#define QMUX_NAME(table, type, tag) qmi_name_get(table, sizeof(table) / sizeof(table[0]), type, tag) + +void dump_tlv(PQCQMUX_MSG_HDR pQMUXMsgHdr) { + int TLVFind = 0; + int i; + //dbg("QCQMUX_TLV-----------------------------------\n"); + //dbg("{Type,\tLength,\tValue}\n"); + + while (1) { + PQMI_TLV_HDR TLVHdr = GetTLV(pQMUXMsgHdr, 0x1000 + (++TLVFind)); + if (TLVHdr == NULL) + break; + + //if ((TLVHdr->TLVType == 0x02) && ((USHORT *)(TLVHdr+1))[0]) + { + dbg("{%02x,\t%04x,\t", TLVHdr->TLVType, le16_to_cpu(TLVHdr->TLVLength)); + for (i = 0; i < le16_to_cpu(TLVHdr->TLVLength); i++) { + dbg("%02x ", ((UCHAR *)(TLVHdr+1))[i]); + } + dbg("}\n"); + } + } // while +} + +void dump_ctl(PQCQMICTL_MSG_HDR CTLHdr) { + const char *tag; + + //dbg("QCQMICTL_MSG--------------------------------------------\n"); + //dbg("CtlFlags: %02x\t\t%s\n", CTLHdr->CtlFlags, QMI_NAME(qmi_ctl_CtlFlags, CTLHdr->CtlFlags)); + dbg("TransactionId: %02x\n", CTLHdr->TransactionId); + switch (CTLHdr->CtlFlags) { + case QMICTL_FLAG_REQUEST: tag = "_REQ"; break; + case QMICTL_FLAG_RESPONSE: tag = "_RESP"; break; + case QMICTL_FLAG_INDICATION: tag = "_IND"; break; + default: tag = 0; break; + } + dbg("QMICTLType: %04x\t%s\n", le16_to_cpu(CTLHdr->QMICTLType), + QMUX_NAME(qmux_ctl_QMICTLType, le16_to_cpu(CTLHdr->QMICTLType), tag)); + dbg("Length: %04x\n", le16_to_cpu(CTLHdr->Length)); + + dump_tlv((PQCQMUX_MSG_HDR)(&CTLHdr->QMICTLType)); +} + +int dump_qmux(QMI_SERVICE_TYPE serviceType, PQCQMUX_HDR QMUXHdr) { + PQCQMUX_MSG_HDR QMUXMsgHdr = (PQCQMUX_MSG_HDR) (QMUXHdr + 1); + CHAR *tag; + + //dbg("QCQMUX--------------------------------------------\n"); + switch (QMUXHdr->CtlFlags&QMUX_CTL_FLAG_MASK_TYPE) { + case QMUX_CTL_FLAG_TYPE_CMD: tag = "_REQ"; break; + case QMUX_CTL_FLAG_TYPE_RSP: tag = "_RESP"; break; + case QMUX_CTL_FLAG_TYPE_IND: tag = "_IND"; break; + default: tag = 0; break; + } + //dbg("CtlFlags: %02x\t\t%s\n", QMUXHdr->CtlFlags, QMUX_NAME(qmux_CtlFlags, QMUXHdr->CtlFlags, tag)); + dbg("TransactionId: %04x\n", le16_to_cpu(QMUXHdr->TransactionId)); + + //dbg("QCQMUX_MSG_HDR-----------------------------------\n"); + switch (serviceType) { + case QMUX_TYPE_DMS: + dbg("Type: %04x\t%s\n", le16_to_cpu(QMUXMsgHdr->Type), + QMUX_NAME(qmux_dms_Type, le16_to_cpu(QMUXMsgHdr->Type), tag)); + break; + case QMUX_TYPE_NAS: + dbg("Type: %04x\t%s\n", le16_to_cpu(QMUXMsgHdr->Type), + QMUX_NAME(qmux_nas_Type, le16_to_cpu(QMUXMsgHdr->Type), tag)); + break; + case QMUX_TYPE_WDS: + dbg("Type: %04x\t%s\n", le16_to_cpu(QMUXMsgHdr->Type), + QMUX_NAME(qmux_wds_Type, le16_to_cpu(QMUXMsgHdr->Type), tag)); + break; + case QMUX_TYPE_WMS: + dbg("Type: %04x\t%s\n", le16_to_cpu(QMUXMsgHdr->Type), + QMUX_NAME(qmux_wms_Type, le16_to_cpu(QMUXMsgHdr->Type), tag)); + break; + case QMUX_TYPE_WDS_ADMIN: + dbg("Type: %04x\t%s\n", le16_to_cpu(QMUXMsgHdr->Type), + QMUX_NAME(qmux_wds_admin_Type, le16_to_cpu(QMUXMsgHdr->Type), tag)); + break; + case QMUX_TYPE_UIM: + dbg("Type: %04x\t%s\n", le16_to_cpu(QMUXMsgHdr->Type), + QMUX_NAME(qmux_uim_Type, le16_to_cpu(QMUXMsgHdr->Type), tag)); + break; + case QMUX_TYPE_PDS: + case QMUX_TYPE_QOS: + case QMUX_TYPE_CTL: + default: + dbg("Type: %04x\t%s\n", le16_to_cpu(QMUXMsgHdr->Type), "PDS/QOS/CTL/unknown!"); + break; + } + dbg("Length: %04x\n", le16_to_cpu(QMUXMsgHdr->Length)); + + dump_tlv(QMUXMsgHdr); + + return 0; +} + +void dump_qmi(void *dataBuffer, int dataLen) +{ + PQCQMI_HDR QMIHdr = (PQCQMI_HDR)dataBuffer; + PQCQMUX_HDR QMUXHdr = (PQCQMUX_HDR) (QMIHdr + 1); + PQCQMICTL_MSG_HDR CTLHdr = (PQCQMICTL_MSG_HDR) (QMIHdr + 1); + + int i; + + if (!debug_qmi) + return; + + pthread_mutex_lock(&dumpQMIMutex); + line[0] = 0; + for (i = 0; i < dataLen; i++) { + dbg("%02x ", ((unsigned char *)dataBuffer)[i]); + } + dbg_time("%s", line); + line[0] = 0; + + //dbg("QCQMI_HDR-----------------------------------------"); + //dbg("IFType: %02x\t\t%s", QMIHdr->IFType, QMI_NAME(qmi_IFType, QMIHdr->IFType)); + //dbg("Length: %04x", le16_to_cpu(QMIHdr->Length)); + //dbg("CtlFlags: %02x\t\t%s", QMIHdr->CtlFlags, QMI_NAME(qmi_CtlFlags, QMIHdr->CtlFlags)); + //dbg("QMIType: %02x\t\t%s", QMIHdr->QMIType, QMI_NAME(qmi_QMIType, QMIHdr->QMIType)); + //dbg("ClientId: %02x", QMIHdr->ClientId); + + if ((QMIHdr->QMIType == QMUX_TYPE_CTL) ) { + dump_ctl(CTLHdr); + } else { + dump_qmux(QMIHdr->QMIType, QMUXHdr); + } + dbg_time("%s", line); + pthread_mutex_unlock(&dumpQMIMutex); +} diff --git a/root/package/link4all/quectel-CM/src.all/MPQMUX.h b/root/package/link4all/quectel-CM/src.all/MPQMUX.h new file mode 100755 index 00000000..a15cad53 --- /dev/null +++ b/root/package/link4all/quectel-CM/src.all/MPQMUX.h @@ -0,0 +1,3244 @@ +/*=========================================================================== + + M P Q M U X. H +DESCRIPTION: + + This file provides support for QMUX. + +INITIALIZATION AND SEQUENCING REQUIREMENTS: + +Copyright (C) 2011 by Qualcomm Technologies, Incorporated. All Rights Reserved. +===========================================================================*/ + +#ifndef MPQMUX_H +#define MPQMUX_H + +#include "MPQMI.h" + +#pragma pack(push, 1) + +#define QMIWDS_SET_EVENT_REPORT_REQ 0x0001 +#define QMIWDS_SET_EVENT_REPORT_RESP 0x0001 +#define QMIWDS_EVENT_REPORT_IND 0x0001 +#define QMIWDS_START_NETWORK_INTERFACE_REQ 0x0020 +#define QMIWDS_START_NETWORK_INTERFACE_RESP 0x0020 +#define QMIWDS_STOP_NETWORK_INTERFACE_REQ 0x0021 +#define QMIWDS_STOP_NETWORK_INTERFACE_RESP 0x0021 +#define QMIWDS_GET_PKT_SRVC_STATUS_REQ 0x0022 +#define QMIWDS_GET_PKT_SRVC_STATUS_RESP 0x0022 +#define QMIWDS_GET_PKT_SRVC_STATUS_IND 0x0022 +#define QMIWDS_GET_CURRENT_CHANNEL_RATE_REQ 0x0023 +#define QMIWDS_GET_CURRENT_CHANNEL_RATE_RESP 0x0023 +#define QMIWDS_GET_PKT_STATISTICS_REQ 0x0024 +#define QMIWDS_GET_PKT_STATISTICS_RESP 0x0024 +#define QMIWDS_MODIFY_PROFILE_SETTINGS_REQ 0x0028 +#define QMIWDS_MODIFY_PROFILE_SETTINGS_RESP 0x0028 +#define QMIWDS_GET_PROFILE_SETTINGS_REQ 0x002B +#define QMIWDS_GET_PROFILE_SETTINGS_RESP 0x002B +#define QMIWDS_GET_DEFAULT_SETTINGS_REQ 0x002C +#define QMIWDS_GET_DEFAULT_SETTINGS_RESP 0x002C +#define QMIWDS_GET_RUNTIME_SETTINGS_REQ 0x002D +#define QMIWDS_GET_RUNTIME_SETTINGS_RESP 0x002D +#define QMIWDS_GET_MIP_MODE_REQ 0x002F +#define QMIWDS_GET_MIP_MODE_RESP 0x002F +#define QMIWDS_GET_DATA_BEARER_REQ 0x0037 +#define QMIWDS_GET_DATA_BEARER_RESP 0x0037 +#define QMIWDS_DUN_CALL_INFO_REQ 0x0038 +#define QMIWDS_DUN_CALL_INFO_RESP 0x0038 +#define QMIWDS_DUN_CALL_INFO_IND 0x0038 +#define QMIWDS_SET_CLIENT_IP_FAMILY_PREF_REQ 0x004D +#define QMIWDS_SET_CLIENT_IP_FAMILY_PREF_RESP 0x004D +#define QMIWDS_BIND_MUX_DATA_PORT_REQ 0x00A2 +#define QMIWDS_BIND_MUX_DATA_PORT_RESP 0x00A2 + + +// Stats masks +#define QWDS_STAT_MASK_TX_PKT_OK 0x00000001 +#define QWDS_STAT_MASK_RX_PKT_OK 0x00000002 +#define QWDS_STAT_MASK_TX_PKT_ER 0x00000004 +#define QWDS_STAT_MASK_RX_PKT_ER 0x00000008 +#define QWDS_STAT_MASK_TX_PKT_OF 0x00000010 +#define QWDS_STAT_MASK_RX_PKT_OF 0x00000020 + +// TLV Types for xfer statistics +#define TLV_WDS_TX_GOOD_PKTS 0x10 +#define TLV_WDS_RX_GOOD_PKTS 0x11 +#define TLV_WDS_TX_ERROR 0x12 +#define TLV_WDS_RX_ERROR 0x13 +#define TLV_WDS_TX_OVERFLOW 0x14 +#define TLV_WDS_RX_OVERFLOW 0x15 +#define TLV_WDS_CHANNEL_RATE 0x16 +#define TLV_WDS_DATA_BEARER 0x17 +#define TLV_WDS_DORMANCY_STATUS 0x18 + +#define QWDS_PKT_DATA_DISCONNECTED 0x01 +#define QWDS_PKT_DATA_CONNECTED 0x02 +#define QWDS_PKT_DATA_SUSPENDED 0x03 +#define QWDS_PKT_DATA_AUTHENTICATING 0x04 + +#define QMIWDS_ADMIN_SET_DATA_FORMAT_REQ 0x0020 +#define QMIWDS_ADMIN_SET_DATA_FORMAT_RESP 0x0020 +#define QMIWDS_ADMIN_GET_DATA_FORMAT_REQ 0x0021 +#define QMIWDS_ADMIN_GET_DATA_FORMAT_RESP 0x0021 +#define QMIWDS_ADMIN_SET_QMAP_SETTINGS_REQ 0x002B +#define QMIWDS_ADMIN_SET_QMAP_SETTINGS_RESP 0x002B +#define QMIWDS_ADMIN_GET_QMAP_SETTINGS_REQ 0x002C +#define QMIWDS_ADMIN_GET_QMAP_SETTINGS_RESP 0x002C + +#define NETWORK_DESC_ENCODING_OCTET 0x00 +#define NETWORK_DESC_ENCODING_EXTPROTOCOL 0x01 +#define NETWORK_DESC_ENCODING_7BITASCII 0x02 +#define NETWORK_DESC_ENCODING_IA5 0x03 +#define NETWORK_DESC_ENCODING_UNICODE 0x04 +#define NETWORK_DESC_ENCODING_SHIFTJIS 0x05 +#define NETWORK_DESC_ENCODING_KOREAN 0x06 +#define NETWORK_DESC_ENCODING_LATINH 0x07 +#define NETWORK_DESC_ENCODING_LATIN 0x08 +#define NETWORK_DESC_ENCODING_GSM7BIT 0x09 +#define NETWORK_DESC_ENCODING_GSMDATA 0x0A +#define NETWORK_DESC_ENCODING_UNKNOWN 0xFF + +typedef struct _QMIWDS_ADMIN_SET_DATA_FORMAT +{ + USHORT Type; // QMUX type 0x0000 + USHORT Length; +} __attribute__ ((packed)) QMIWDS_ADMIN_SET_DATA_FORMAT, *PQMIWDS_ADMIN_SET_DATA_FORMAT; + +typedef struct _QMIWDS_ADMIN_SET_DATA_FORMAT_TLV_QOS +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR QOSSetting; +} __attribute__ ((packed)) QMIWDS_ADMIN_SET_DATA_FORMAT_TLV_QOS, *PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV_QOS; + +typedef struct _QMIWDS_ADMIN_SET_DATA_FORMAT_TLV +{ + UCHAR TLVType; + USHORT TLVLength; + ULONG Value; +} __attribute__ ((packed)) QMIWDS_ADMIN_SET_DATA_FORMAT_TLV, *PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV; + +#if 0 +typedef struct _QMIWDS_ENDPOINT_TLV +{ + UCHAR TLVType; + USHORT TLVLength; + ULONG ep_type; + ULONG iface_id; +} QMIWDS_ENDPOINT_TLV, *PQMIWDS_ENDPOINT_TLV; + +typedef enum _QMI_RETURN_CODES { + QMI_SUCCESS = 0, + QMI_SUCCESS_NOT_COMPLETE, + QMI_FAILURE +}QMI_RETURN_CODES; + +typedef struct _QMIWDS_GET_PKT_SRVC_STATUS_REQ_MSG +{ + USHORT Type; // 0x0022 + USHORT Length; // 0x0000 +} QMIWDS_GET_PKT_SRVC_STATUS_REQ_MSG, *PQMIWDS_GET_PKT_SRVC_STATUS_REQ_MSG; + +typedef struct _QMIWDS_GET_PKT_SRVC_STATUS_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLVType2; + USHORT TLVLength2; + UCHAR ConnectionStatus; // 0x01: QWDS_PKT_DATAC_DISCONNECTED + // 0x02: QWDS_PKT_DATA_CONNECTED + // 0x03: QWDS_PKT_DATA_SUSPENDED + // 0x04: QWDS_PKT_DATA_AUTHENTICATING +} QMIWDS_GET_PKT_SRVC_STATUS_RESP_MSG, *PQMIWDS_GET_PKT_SRVC_STATUS_RESP_MSG; + +typedef struct _QMIWDS_GET_PKT_SRVC_STATUS_IND_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR ConnectionStatus; // 0x01: QWDS_PKT_DATAC_DISCONNECTED + // 0x02: QWDS_PKT_DATA_CONNECTED + // 0x03: QWDS_PKT_DATA_SUSPENDED + UCHAR ReconfigRequired; // 0x00: No need to reconfigure + // 0x01: Reconfiguration required +} QMIWDS_GET_PKT_SRVC_STATUS_IND_MSG, *PQMIWDS_GET_PKT_SRVC_STATUS_IND_MSG; + +typedef struct _WDS_PKT_SRVC_IP_FAMILY_TLV +{ + UCHAR TLVType; // 0x12 + USHORT TLVLength; // 1 + UCHAR IpFamily; // IPV4-0x04, IPV6-0x06 +} WDS_PKT_SRVC_IP_FAMILY_TLV, *PWDS_PKT_SRVC_IP_FAMILY_TLV; + +typedef struct _QMIWDS_DUN_CALL_INFO_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + ULONG Mask; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR ReportConnectionStatus; +} QMIWDS_DUN_CALL_INFO_REQ_MSG, *PQMIWDS_DUN_CALL_INFO_REQ_MSG; + +typedef struct _QMIWDS_DUN_CALL_INFO_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} QMIWDS_DUN_CALL_INFO_RESP_MSG, *PQMIWDS_DUN_CALL_INFO_RESP_MSG; + +typedef struct _QMIWDS_DUN_CALL_INFO_IND_MSG +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR ConnectionStatus; +} QMIWDS_DUN_CALL_INFO_IND_MSG, *PQMIWDS_DUN_CALL_INFO_IND_MSG; + +typedef struct _QMIWDS_GET_CURRENT_CHANNEL_RATE_REQ_MSG +{ + USHORT Type; // QMUX type 0x0040 + USHORT Length; +} QMIWDS_GET_CURRENT_CHANNEL_RATE_REQ_MSG, *PQMIWDS_GET_CURRENT_CHANNEL_RATE_REQ_MSG; + +typedef struct _QMIWDS_GET_CURRENT_CHANNEL_RATE_RESP_MSG +{ + USHORT Type; // QMUX type 0x0040 + USHORT Length; + UCHAR TLVType; // 0x02 + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + + UCHAR TLV2Type; // 0x01 + USHORT TLV2Length; // 16 + //ULONG CallHandle; // Context corresponding to reported channel + ULONG CurrentTxRate; // bps + ULONG CurrentRxRate; // bps + ULONG ServingSystemTxRate; // bps + ULONG ServingSystemRxRate; // bps + +} QMIWDS_GET_CURRENT_CHANNEL_RATE_RESP_MSG, *PQMIWDS_GET_CURRENT_CHANNEL_RATE_RESP; + +#define QWDS_EVENT_REPORT_MASK_RATES 0x01 +#define QWDS_EVENT_REPORT_MASK_STATS 0x02 + +#ifdef QCUSB_MUX_PROTOCOL +#error code not present +#endif // QCUSB_MUX_PROTOCOL + +typedef struct _QMIWDS_SET_EVENT_REPORT_REQ_MSG +{ + USHORT Type; // QMUX type 0x0042 + USHORT Length; + + UCHAR TLVType; // 0x10 -- current channel rate indicator + USHORT TLVLength; // 1 + UCHAR Mode; // 0-do not report; 1-report when rate changes + + UCHAR TLV2Type; // 0x11 + USHORT TLV2Length; // 5 + UCHAR StatsPeriod; // seconds between reports; 0-do not report + ULONG StatsMask; // + + UCHAR TLV3Type; // 0x12 -- current data bearer indicator + USHORT TLV3Length; // 1 + UCHAR Mode3; // 0-do not report; 1-report when changes + + UCHAR TLV4Type; // 0x13 -- dormancy status indicator + USHORT TLV4Length; // 1 + UCHAR DormancyStatus; // 0-do not report; 1-report when changes +} QMIWDS_SET_EVENT_REPORT_REQ_MSG, *PQMIWDS_SET_EVENT_REPORT_REQ_MSG; + +typedef struct _QMIWDS_SET_EVENT_REPORT_RESP_MSG +{ + USHORT Type; // QMUX type 0x0042 + USHORT Length; + + UCHAR TLVType; // 0x02 result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_NO_BATTERY + // QMI_ERR_FAULT +} QMIWDS_SET_EVENT_REPORT_RESP_MSG, *PQMIWDS_SET_EVENT_REPORT_RESP_MSG; + +typedef struct _QMIWDS_EVENT_REPORT_IND_MSG +{ + USHORT Type; // QMUX type 0x0001 + USHORT Length; +} QMIWDS_EVENT_REPORT_IND_MSG, *PQMIWDS_EVENT_REPORT_IND_MSG; + +// PQCTLV_PKT_STATISTICS + +typedef struct _QMIWDS_EVENT_REPORT_IND_CHAN_RATE_TLV +{ + UCHAR Type; + USHORT Length; // 8 + ULONG TxRate; + ULONG RxRate; +} QMIWDS_EVENT_REPORT_IND_CHAN_RATE_TLV, *PQMIWDS_EVENT_REPORT_IND_CHAN_RATE_TLV; + +#ifdef QCUSB_MUX_PROTOCOL +#error code not present +#endif // QCUSB_MUX_PROTOCOL + +typedef struct _QMIWDS_GET_PKT_STATISTICS_REQ_MSG +{ + USHORT Type; // QMUX type 0x0041 + USHORT Length; + UCHAR TLVType; // 0x01 + USHORT TLVLength; // 4 + ULONG StateMask; // 0x00000001 tx success packets + // 0x00000002 rx success packets + // 0x00000004 rx packet errors (checksum) + // 0x00000008 rx packets dropped (memory) + +} QMIWDS_GET_PKT_STATISTICS_REQ_MSG, *PQMIWDS_GET_PKT_STATISTICS_REQ_MSG; + +typedef struct _QMIWDS_GET_PKT_STATISTICS_RESP_MSG +{ + USHORT Type; // QMUX type 0x0041 + USHORT Length; + UCHAR TLVType; // 0x02 + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} QMIWDS_GET_PKT_STATISTICS_RESP_MSG, *PQMIWDS_GET_PKT_STATISTICS_RESP_MSG; + +// optional TLV for stats +typedef struct _QCTLV_PKT_STATISTICS +{ + UCHAR TLVType; // see above definitions for TLV types + USHORT TLVLength; // 4 + ULONG Count; +} QCTLV_PKT_STATISTICS, *PQCTLV_PKT_STATISTICS; +#endif + +//#ifdef QC_IP_MODE + +#define QMIWDS_GET_RUNTIME_SETTINGS_MASK_IPV4DNS_ADDR 0x0010 +#define QMIWDS_GET_RUNTIME_SETTINGS_MASK_IPV4_ADDR 0x0100 +#define QMIWDS_GET_RUNTIME_SETTINGS_MASK_IPV4GATEWAY_ADDR 0x0200 +#define QMIWDS_GET_RUNTIME_SETTINGS_MASK_MTU 0x2000 + +typedef struct _QMIWDS_GET_RUNTIME_SETTINGS_REQ_MSG +{ + USHORT Type; // QMIWDS_GET_RUNTIME_SETTINGS_REQ + USHORT Length; + UCHAR TLVType; // 0x10 + USHORT TLVLength; // 0x0004 + ULONG Mask; // mask, bit 8: IP addr -- 0x0100 +} __attribute__ ((packed)) QMIWDS_GET_RUNTIME_SETTINGS_REQ_MSG, *PQMIWDS_GET_RUNTIME_SETTINGS_REQ_MSG; + +typedef struct _QMIWDS_BIND_MUX_DATA_PORT_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + ULONG ep_type; + ULONG iface_id; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR MuxId; + UCHAR TLV3Type; + USHORT TLV3Length; + ULONG client_type; +} __attribute__ ((packed)) QMIWDS_BIND_MUX_DATA_PORT_REQ_MSG, *PQMIWDS_BIND_MUX_DATA_PORT_REQ_MSG; + +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4PRIMARYDNS 0x15 +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4SECONDARYDNS 0x16 +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4 0x1E +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4GATEWAY 0x20 +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4SUBNET 0x21 + +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6 0x25 +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6GATEWAY 0x26 +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6PRIMARYDNS 0x27 +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6SECONDARYDNS 0x28 +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_MTU 0x29 + +typedef struct _QMIWDS_GET_RUNTIME_SETTINGS_TLV_MTU +{ + UCHAR TLVType; // QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_MTU + USHORT TLVLength; // 4 + ULONG Mtu; // MTU +} __attribute__ ((packed)) QMIWDS_GET_RUNTIME_SETTINGS_TLV_MTU, *PQMIWDS_GET_RUNTIME_SETTINGS_TLV_MTU; + +typedef struct _QMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR +{ + UCHAR TLVType; // QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4 + USHORT TLVLength; // 4 + ULONG IPV4Address; // address +} __attribute__ ((packed)) QMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR, *PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR; + +typedef struct _QMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV6_ADDR +{ + UCHAR TLVType; // QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6 + USHORT TLVLength; // 16 + UCHAR IPV6Address[16]; // address + UCHAR PrefixLength; // prefix length +} __attribute__ ((packed)) QMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV6_ADDR, *PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV6_ADDR; + +typedef struct _QMIWDS_GET_RUNTIME_SETTINGS_RESP_MSG +{ + USHORT Type; // QMIWDS_GET_RUNTIME_SETTINGS_RESP + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_RESULT_CODE + USHORT TLVLength; // 0x0004 + USHORT QMUXResult; // result code + USHORT QMUXError; // error code +} __attribute__ ((packed)) QMIWDS_GET_RUNTIME_SETTINGS_RESP_MSG, *PQMIWDS_GET_RUNTIME_SETTINGS_RESP_MSG; + +//#endif // QC_IP_MODE + +typedef struct _QMIWDS_IP_FAMILY_TLV +{ + UCHAR TLVType; // 0x12 + USHORT TLVLength; // 1 + UCHAR IpFamily; // IPV4-0x04, IPV6-0x06 +} __attribute__ ((packed)) QMIWDS_IP_FAMILY_TLV, *PQMIWDS_IP_FAMILY_TLV; + +typedef struct _QMIWDS_PKT_SRVC_TLV +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR ConnectionStatus; + UCHAR ReconfigReqd; +} __attribute__ ((packed)) QMIWDS_PKT_SRVC_TLV, *PQMIWDS_PKT_SRVC_TLV; + +#if 0 +typedef struct _QMIWDS_CALL_END_REASON_TLV +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT CallEndReason; +} QMIWDS_CALL_END_REASON_TLV, *PQMIWDS_CALL_END_REASON_TLV; + +typedef struct _QMIWDS_CALL_END_REASON_V_TLV +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT CallEndReasonType; + USHORT CallEndReason; +} QMIWDS_CALL_END_REASON_V_TLV, *PQMIWDS_CALL_END_REASON_V_TLV; + +typedef struct _QMIWDS_SET_CLIENT_IP_FAMILY_PREF_REQ_MSG +{ + USHORT Type; // QMUX type 0x004D + USHORT Length; + UCHAR TLVType; // 0x01 + USHORT TLVLength; // 1 + UCHAR IpPreference; // IPV4-0x04, IPV6-0x06 +} QMIWDS_SET_CLIENT_IP_FAMILY_PREF_REQ_MSG, *PQMIWDS_SET_CLIENT_IP_FAMILY_PREF_REQ_MSG; + +typedef struct _QMIWDS_SET_CLIENT_IP_FAMILY_PREF_RESP_MSG +{ + USHORT Type; // QMUX type 0x0037 + USHORT Length; + UCHAR TLVType; // 0x02 + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS, QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INTERNAL, QMI_ERR_MALFORMED_MSG, QMI_ERR_INVALID_ARG +} QMIWDS_SET_CLIENT_IP_FAMILY_PREF_RESP_MSG, *PQMIWDS_SET_CLIENT_IP_FAMILY_PREF_RESP_MSG; + +typedef struct _QMIWDS_GET_MIP_MODE_REQ_MSG +{ + USHORT Type; // QMUX type 0x0040 + USHORT Length; +} QMIWDS_GET_MIP_MODE_REQ_MSG, *PQMIWDS_GET_MIP_MODE_REQ_MSG; + +typedef struct _QMIWDS_GET_MIP_MODE_RESP_MSG +{ + USHORT Type; // QMUX type 0x0040 + USHORT Length; + UCHAR TLVType; // 0x02 + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + + UCHAR TLV2Type; // 0x01 + USHORT TLV2Length; // 20 + UCHAR MipMode; // +} QMIWDS_GET_MIP_MODE_RESP_MSG, *PQMIWDS_GET_MIP_MODE_RESP_MSG; +#endif + +typedef struct _QMIWDS_TECHNOLOGY_PREFERECE +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR TechPreference; +} __attribute__ ((packed)) QMIWDS_TECHNOLOGY_PREFERECE, *PQMIWDS_TECHNOLOGY_PREFERECE; + +typedef struct _QMIWDS_PROFILE_IDENTIFIER +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR ProfileIndex; +} __attribute__ ((packed)) QMIWDS_PROFILE_IDENTIFIER, *PQMIWDS_PROFILE_IDENTIFIER; + +#if 0 +typedef struct _QMIWDS_IPADDRESS +{ + UCHAR TLVType; + USHORT TLVLength; + ULONG IPv4Address; +}QMIWDS_IPADDRESS, *PQMIWDS_IPADDRESS; + +/* +typedef struct _QMIWDS_UMTS_QOS +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR TrafficClass; + ULONG MaxUplinkBitRate; + ULONG MaxDownlinkBitRate; + ULONG GuarUplinkBitRate; + ULONG GuarDownlinkBitRate; + UCHAR QOSDevOrder; + ULONG MAXSDUSize; + UCHAR SDUErrorRatio; + UCHAR ResidualBerRatio; + UCHAR DeliveryErrorSDUs; + ULONG TransferDelay; + ULONG TrafficHndPri; +}QMIWDS_UMTS_QOS, *PQMIWDS_UMTS_QOS; + +typedef struct _QMIWDS_GPRS_QOS +{ + UCHAR TLVType; + USHORT TLVLength; + ULONG PrecedenceClass; + ULONG DelayClass; + ULONG ReliabilityClass; + ULONG PeekThroClass; + ULONG MeanThroClass; +}QMIWDS_GPRS_QOS, *PQMIWDS_GPRS_QOS; +*/ +#endif + +typedef struct _QMIWDS_PROFILENAME +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR ProfileName; +} __attribute__ ((packed)) QMIWDS_PROFILENAME, *PQMIWDS_PROFILENAME; + +typedef struct _QMIWDS_PDPTYPE +{ + UCHAR TLVType; + USHORT TLVLength; +// 0 ¨C PDP-IP (IPv4) +// 1 ¨C PDP-PPP +// 2 ¨C PDP-IPv6 +// 3 ¨C PDP-IPv4v6 + UCHAR PdpType; +} __attribute__ ((packed)) QMIWDS_PDPTYPE, *PQMIWDS_PDPTYPE; + +typedef struct _QMIWDS_USERNAME +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR UserName; +} __attribute__ ((packed)) QMIWDS_USERNAME, *PQMIWDS_USERNAME; + +typedef struct _QMIWDS_PASSWD +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR Passwd; +} __attribute__ ((packed)) QMIWDS_PASSWD, *PQMIWDS_PASSWD; + +typedef struct _QMIWDS_AUTH_PREFERENCE +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR AuthPreference; +} __attribute__ ((packed)) QMIWDS_AUTH_PREFERENCE, *PQMIWDS_AUTH_PREFERENCE; + +typedef struct _QMIWDS_APNNAME +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR ApnName; +} __attribute__ ((packed)) QMIWDS_APNNAME, *PQMIWDS_APNNAME; + +typedef struct _QMIWDS_AUTOCONNECT +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR AutoConnect; +} __attribute__ ((packed)) QMIWDS_AUTOCONNECT, *PQMIWDS_AUTOCONNECT; + +typedef struct _QMIWDS_START_NETWORK_INTERFACE_REQ_MSG +{ + USHORT Type; + USHORT Length; +} __attribute__ ((packed)) QMIWDS_START_NETWORK_INTERFACE_REQ_MSG, *PQMIWDS_START_NETWORK_INTERFACE_REQ_MSG; + +typedef struct _QMIWDS_CALLENDREASON +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT Reason; +}__attribute__ ((packed)) QMIWDS_CALLENDREASON, *PQMIWDS_CALLENDREASON; + +typedef struct _QMIWDS_START_NETWORK_INTERFACE_RESP_MSG +{ + USHORT Type; // QMUX type 0x0040 + USHORT Length; + UCHAR TLVType; // 0x02 + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + + UCHAR TLV2Type; // 0x01 + USHORT TLV2Length; // 20 + ULONG Handle; // +} __attribute__ ((packed)) QMIWDS_START_NETWORK_INTERFACE_RESP_MSG, *PQMIWDS_START_NETWORK_INTERFACE_RESP_MSG; + +typedef struct _QMIWDS_STOP_NETWORK_INTERFACE_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + ULONG Handle; +} __attribute__ ((packed)) QMIWDS_STOP_NETWORK_INTERFACE_REQ_MSG, *PQMIWDS_STOP_NETWORK_INTERFACE_REQ_MSG; + +typedef struct _QMIWDS_STOP_NETWORK_INTERFACE_RESP_MSG +{ + USHORT Type; // QMUX type 0x0040 + USHORT Length; + UCHAR TLVType; // 0x02 + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + +} __attribute__ ((packed)) QMIWDS_STOP_NETWORK_INTERFACE_RESP_MSG, *PQMIWDS_STOP_NETWORK_INTERFACE_RESP_MSG; + +typedef struct _QMIWDS_GET_DEFAULT_SETTINGS_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR ProfileType; +} __attribute__ ((packed)) QMIWDS_GET_DEFAULT_SETTINGS_REQ_MSG, *PQMIWDS_GET_DEFAULT_SETTINGS_REQ_MSG; + +typedef struct _QMIWDS_GET_DEFAULT_SETTINGS_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} __attribute__ ((packed)) QMIWDS_GET_DEFAULT_SETTINGS_RESP_MSG, *PQMIWDS_GET_DEFAULT_SETTINGS_RESP_MSG; + +typedef struct _QMIWDS_MODIFY_PROFILE_SETTINGS_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR ProfileType; + UCHAR ProfileIndex; +} __attribute__ ((packed)) QMIWDS_MODIFY_PROFILE_SETTINGS_REQ_MSG, *PQMIWDS_MODIFY_PROFILE_SETTINGS_REQ_MSG; + +typedef struct _QMIWDS_MODIFY_PROFILE_SETTINGS_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} __attribute__ ((packed)) QMIWDS_MODIFY_PROFILE_SETTINGS_RESP_MSG, *PQMIWDS_MODIFY_PROFILE_SETTINGS_RESP_MSG; + +typedef struct _QMIWDS_GET_PROFILE_SETTINGS_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR ProfileType; + UCHAR ProfileIndex; +} __attribute__ ((packed)) QMIWDS_GET_PROFILE_SETTINGS_REQ_MSG, *PQMIWDS_GET_PROFILE_SETTINGS_REQ_MSG; + +#if 0 +typedef struct _QMIWDS_EVENT_REPORT_IND_DATA_BEARER_TLV +{ + UCHAR Type; + USHORT Length; + UCHAR DataBearer; +} QMIWDS_EVENT_REPORT_IND_DATA_BEARER_TLV, *PQMIWDS_EVENT_REPORT_IND_DATA_BEARER_TLV; + +typedef struct _QMIWDS_EVENT_REPORT_IND_DORMANCY_STATUS_TLV +{ + UCHAR Type; + USHORT Length; + UCHAR DormancyStatus; +} QMIWDS_EVENT_REPORT_IND_DORMANCY_STATUS_TLV, *PQMIWDS_EVENT_REPORT_IND_DORMANCY_STATUS_TLV; + + +typedef struct _QMIWDS_GET_DATA_BEARER_REQ_MSG +{ + USHORT Type; // QMUX type 0x0037 + USHORT Length; +} QMIWDS_GET_DATA_BEARER_REQ_MSG, *PQMIWDS_GET_DATA_BEARER_REQ_MSG; + +typedef struct _QMIWDS_GET_DATA_BEARER_RESP_MSG +{ + USHORT Type; // QMUX type 0x0037 + USHORT Length; + UCHAR TLVType; // 0x02 + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INTERNAL + // QMI_ERR_MALFORMED_MSG + // QMI_ERR_NO_MEMORY + // QMI_ERR_OUT_OF_CALL + // QMI_ERR_INFO_UNAVAILABLE + UCHAR TLV2Type; // 0x01 + USHORT TLV2Length; // + UCHAR Technology; // +} QMIWDS_GET_DATA_BEARER_RESP_MSG, *PQMIWDS_GET_DATA_BEARER_RESP_MSG; +#endif + +// ======================= DMS ============================== +#define QMIDMS_SET_EVENT_REPORT_REQ 0x0001 +#define QMIDMS_SET_EVENT_REPORT_RESP 0x0001 +#define QMIDMS_EVENT_REPORT_IND 0x0001 +#define QMIDMS_GET_DEVICE_CAP_REQ 0x0020 +#define QMIDMS_GET_DEVICE_CAP_RESP 0x0020 +#define QMIDMS_GET_DEVICE_MFR_REQ 0x0021 +#define QMIDMS_GET_DEVICE_MFR_RESP 0x0021 +#define QMIDMS_GET_DEVICE_MODEL_ID_REQ 0x0022 +#define QMIDMS_GET_DEVICE_MODEL_ID_RESP 0x0022 +#define QMIDMS_GET_DEVICE_REV_ID_REQ 0x0023 +#define QMIDMS_GET_DEVICE_REV_ID_RESP 0x0023 +#define QMIDMS_GET_MSISDN_REQ 0x0024 +#define QMIDMS_GET_MSISDN_RESP 0x0024 +#define QMIDMS_GET_DEVICE_SERIAL_NUMBERS_REQ 0x0025 +#define QMIDMS_GET_DEVICE_SERIAL_NUMBERS_RESP 0x0025 +#define QMIDMS_UIM_SET_PIN_PROTECTION_REQ 0x0027 +#define QMIDMS_UIM_SET_PIN_PROTECTION_RESP 0x0027 +#define QMIDMS_UIM_VERIFY_PIN_REQ 0x0028 +#define QMIDMS_UIM_VERIFY_PIN_RESP 0x0028 +#define QMIDMS_UIM_UNBLOCK_PIN_REQ 0x0029 +#define QMIDMS_UIM_UNBLOCK_PIN_RESP 0x0029 +#define QMIDMS_UIM_CHANGE_PIN_REQ 0x002A +#define QMIDMS_UIM_CHANGE_PIN_RESP 0x002A +#define QMIDMS_UIM_GET_PIN_STATUS_REQ 0x002B +#define QMIDMS_UIM_GET_PIN_STATUS_RESP 0x002B +#define QMIDMS_GET_DEVICE_HARDWARE_REV_REQ 0x002C +#define QMIDMS_GET_DEVICE_HARDWARE_REV_RESP 0x002C +#define QMIDMS_GET_OPERATING_MODE_REQ 0x002D +#define QMIDMS_GET_OPERATING_MODE_RESP 0x002D +#define QMIDMS_SET_OPERATING_MODE_REQ 0x002E +#define QMIDMS_SET_OPERATING_MODE_RESP 0x002E +#define QMIDMS_GET_ACTIVATED_STATUS_REQ 0x0031 +#define QMIDMS_GET_ACTIVATED_STATUS_RESP 0x0031 +#define QMIDMS_ACTIVATE_AUTOMATIC_REQ 0x0032 +#define QMIDMS_ACTIVATE_AUTOMATIC_RESP 0x0032 +#define QMIDMS_ACTIVATE_MANUAL_REQ 0x0033 +#define QMIDMS_ACTIVATE_MANUAL_RESP 0x0033 +#define QMIDMS_UIM_GET_ICCID_REQ 0x003C +#define QMIDMS_UIM_GET_ICCID_RESP 0x003C +#define QMIDMS_UIM_GET_CK_STATUS_REQ 0x0040 +#define QMIDMS_UIM_GET_CK_STATUS_RESP 0x0040 +#define QMIDMS_UIM_SET_CK_PROTECTION_REQ 0x0041 +#define QMIDMS_UIM_SET_CK_PROTECTION_RESP 0x0041 +#define QMIDMS_UIM_UNBLOCK_CK_REQ 0x0042 +#define QMIDMS_UIM_UNBLOCK_CK_RESP 0x0042 +#define QMIDMS_UIM_GET_IMSI_REQ 0x0043 +#define QMIDMS_UIM_GET_IMSI_RESP 0x0043 +#define QMIDMS_UIM_GET_STATE_REQ 0x0044 +#define QMIDMS_UIM_GET_STATE_RESP 0x0044 +#define QMIDMS_GET_BAND_CAP_REQ 0x0045 +#define QMIDMS_GET_BAND_CAP_RESP 0x0045 + +#if 0 +typedef struct _QMIDMS_GET_DEVICE_MFR_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; +} QMIDMS_GET_DEVICE_MFR_REQ_MSG, *PQMIDMS_GET_DEVICE_MFR_REQ_MSG; + +typedef struct _QMIDMS_GET_DEVICE_MFR_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR TLV2Type; // 0x01 - required parameter + USHORT TLV2Length; // length of the mfr string + UCHAR DeviceManufacturer; // first byte of string +} QMIDMS_GET_DEVICE_MFR_RESP_MSG, *PQMIDMS_GET_DEVICE_MFR_RESP_MSG; + +typedef struct _QMIDMS_GET_DEVICE_MODEL_ID_REQ_MSG +{ + USHORT Type; // QMUX type 0x0004 + USHORT Length; +} QMIDMS_GET_DEVICE_MODEL_ID_REQ_MSG, *PQMIDMS_GET_DEVICE_MODEL_ID_REQ_MSG; + +typedef struct _QMIDMS_GET_DEVICE_MODEL_ID_RESP_MSG +{ + USHORT Type; // QMUX type 0x0004 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR TLV2Type; // 0x01 - required parameter + USHORT TLV2Length; // length of the modem id string + UCHAR DeviceModelID; // device model id +} QMIDMS_GET_DEVICE_MODEL_ID_RESP_MSG, *PQMIDMS_GET_DEVICE_MODEL_ID_RESP_MSG; +#endif + +typedef struct _QMIDMS_GET_DEVICE_REV_ID_REQ_MSG +{ + USHORT Type; // QMUX type 0x0005 + USHORT Length; +} __attribute__ ((packed)) QMIDMS_GET_DEVICE_REV_ID_REQ_MSG, *PQMIDMS_GET_DEVICE_REV_ID_REQ_MSG; + +typedef struct _DEVICE_REV_ID +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR RevisionID; +} __attribute__ ((packed)) DEVICE_REV_ID, *PDEVICE_REV_ID; + +#if 0 +typedef struct _QMIDMS_GET_DEVICE_REV_ID_RESP_MSG +{ + USHORT Type; // QMUX type 0x0023 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} QMIDMS_GET_DEVICE_REV_ID_RESP_MSG, *PQMIDMS_GET_DEVICE_REV_ID_RESP_MSG; + +typedef struct _QMIDMS_GET_MSISDN_REQ_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; +} QMIDMS_GET_MSISDN_REQ_MSG, *PQMIDMS_GET_MSISDN_REQ_MSG; + +typedef struct _QCTLV_DEVICE_VOICE_NUMBERS +{ + UCHAR TLVType; // as defined above + USHORT TLVLength; // 4/7/7 + UCHAR VoideNumberString; // ESN, IMEI, or MEID + +} QCTLV_DEVICE_VOICE_NUMBERS, *PQCTLV_DEVICE_VOICE_NUMBERS; + + +typedef struct _QMIDMS_GET_MSISDN_RESP_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG +} QMIDMS_GET_MSISDN_RESP_MSG, *PQMIDMS_GET_MSISDN_RESP_MSG; +#endif + +typedef struct _QMIDMS_UIM_GET_IMSI_REQ_MSG +{ + USHORT Type; + USHORT Length; +} __attribute__ ((packed)) QMIDMS_UIM_GET_IMSI_REQ_MSG, *PQMIDMS_UIM_GET_IMSI_REQ_MSG; + +typedef struct _QMIDMS_UIM_GET_IMSI_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR IMSI; +} __attribute__ ((packed)) QMIDMS_UIM_GET_IMSI_RESP_MSG, *PQMIDMS_UIM_GET_IMSI_RESP_MSG; + +#if 0 +typedef struct _QMIDMS_GET_DEVICE_SERIAL_NUMBERS_REQ_MSG +{ + USHORT Type; // QMUX type 0x0007 + USHORT Length; +} QMIDMS_GET_DEVICE_SERIAL_NUMBERS_REQ_MSG, *PQMIDMS_GET_DEVICE_SERIAL_NUMBERS_REQ_MSG; + +#define QCTLV_TYPE_SER_NUM_ESN 0x10 +#define QCTLV_TYPE_SER_NUM_IMEI 0x11 +#define QCTLV_TYPE_SER_NUM_MEID 0x12 + +typedef struct _QCTLV_DEVICE_SERIAL_NUMBER +{ + UCHAR TLVType; // as defined above + USHORT TLVLength; // 4/7/7 + UCHAR SerialNumberString; // ESN, IMEI, or MEID + +} QCTLV_DEVICE_SERIAL_NUMBER, *PQCTLV_DEVICE_SERIAL_NUMBER; + +typedef struct _QMIDMS_GET_DEVICE_SERIAL_NUMBERS_RESP_MSG +{ + USHORT Type; // QMUX type 0x0007 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + // followed by optional TLV +} QMIDMS_GET_DEVICE_SERIAL_NUMBERS_RESP_MSG, *PQMIDMS_GET_DEVICE_SERIAL_NUMBERS_RESP; + +typedef struct _QMIDMS_GET_DMS_BAND_CAP +{ + USHORT Type; + USHORT Length; +} QMIDMS_GET_BAND_CAP_REQ_MSG, *PQMIDMS_GET_BAND_CAP_REQ_MSG; + +typedef struct _QMIDMS_GET_BAND_CAP_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_NONE + // QMI_ERR_INTERNAL + // QMI_ERR_MALFORMED_MSG + // QMI_ERR_NO_MEMORY + + UCHAR TLV2Type; // 0x01 + USHORT TLV2Length; // 2 + ULONG64 BandCap; +} QMIDMS_GET_BAND_CAP_RESP_MSG, *PQMIDMS_GET_BAND_CAP_RESP; + +typedef struct _QMIDMS_GET_DEVICE_CAP_REQ_MSG +{ + USHORT Type; // QMUX type 0x0002 + USHORT Length; +} QMIDMS_GET_DEVICE_CAP_REQ_MSG, *PQMIDMS_GET_DEVICE_CAP_REQ_MSG; + +typedef struct _QMIDMS_GET_DEVICE_CAP_RESP_MSG +{ + USHORT Type; // QMUX type 0x0002 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMUX_RESULT_SUCCESS + // QMUX_RESULT_FAILURE + USHORT QMUXError; // QMUX_ERR_INVALID_ARG + // QMUX_ERR_NO_MEMORY + // QMUX_ERR_INTERNAL + // QMUX_ERR_FAULT + UCHAR TLV2Type; // 0x01 + USHORT TLV2Length; // 2 + + ULONG MaxTxChannelRate; + ULONG MaxRxChannelRate; + UCHAR VoiceCap; + UCHAR SimCap; + + UCHAR RadioIfListCnt; // #elements in radio interface list + UCHAR RadioIfList; // N 1-byte elements +} QMIDMS_GET_DEVICE_CAP_RESP_MSG, *PQMIDMS_GET_DEVICE_CAP_RESP_MSG; + +typedef struct _QMIDMS_GET_ACTIVATED_STATUS_REQ_MSG +{ + USHORT Type; // QMUX type 0x0002 + USHORT Length; +} QMIDMS_GET_ACTIVATED_STATUS_REQ_MSG, *PQMIDMS_GET_ACTIVATES_STATUD_REQ_MSG; + +typedef struct _QMIDMS_GET_ACTIVATED_STATUS_RESP_MSG +{ + USHORT Type; // QMUX type 0x0002 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMUX_RESULT_SUCCESS + // QMUX_RESULT_FAILURE + USHORT QMUXError; // QMUX_ERR_INVALID_ARG + // QMUX_ERR_NO_MEMORY + // QMUX_ERR_INTERNAL + // QMUX_ERR_FAULT + UCHAR TLV2Type; // 0x01 + USHORT TLV2Length; // 2 + + USHORT ActivatedStatus; +} QMIDMS_GET_ACTIVATED_STATUS_RESP_MSG, *PQMIDMS_GET_ACTIVATED_STATUS_RESP_MSG; + +typedef struct _QMIDMS_GET_OPERATING_MODE_REQ_MSG +{ + USHORT Type; // QMUX type 0x0002 + USHORT Length; +} QMIDMS_GET_OPERATING_MODE_REQ_MSG, *PQMIDMS_GET_OPERATING_MODE_REQ_MSG; + +typedef struct _OFFLINE_REASON +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT OfflineReason; +} OFFLINE_REASON, *POFFLINE_REASON; + +typedef struct _HARDWARE_RESTRICTED_MODE +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR HardwareControlledMode; +} HARDWARE_RESTRICTED_MODE, *PHARDWARE_RESTRICTED_MODE; + +typedef struct _QMIDMS_GET_OPERATING_MODE_RESP_MSG +{ + USHORT Type; // QMUX type 0x0002 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMUX_RESULT_SUCCESS + // QMUX_RESULT_FAILURE + USHORT QMUXError; // QMUX_ERR_INVALID_ARG + // QMUX_ERR_NO_MEMORY + // QMUX_ERR_INTERNAL + // QMUX_ERR_FAULT + UCHAR TLV2Type; // 0x01 + USHORT TLV2Length; // 2 + + UCHAR OperatingMode; +} QMIDMS_GET_OPERATING_MODE_RESP_MSG, *PQMIDMS_GET_OPERATING_MODE_RESP_MSG; + +typedef struct _QMIDMS_UIM_GET_ICCID_REQ_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; +} QMIDMS_UIM_GET_ICCID_REQ_MSG, *PQMIDMS_UIM_GET_ICCID_REQ_MSG; + +typedef struct _QMIDMS_UIM_GET_ICCID_RESP_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR TLV2Type; // 0x01 - required parameter + USHORT TLV2Length; // var + UCHAR ICCID; // String of voice number +} QMIDMS_UIM_GET_ICCID_RESP_MSG, *PQMIDMS_UIM_GET_ICCID_RESP_MSG; + +typedef struct _QMIDMS_SET_OPERATING_MODE_REQ_MSG +{ + USHORT Type; // QMUX type 0x0002 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + UCHAR OperatingMode; +} QMIDMS_SET_OPERATING_MODE_REQ_MSG, *PQMIDMS_SET_OPERATING_MODE_REQ_MSG; + +typedef struct _QMIDMS_SET_OPERATING_MODE_RESP_MSG +{ + USHORT Type; // QMUX type 0x0002 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMUX_RESULT_SUCCESS + // QMUX_RESULT_FAILURE + USHORT QMUXError; // QMUX_ERR_INVALID_ARG + // QMUX_ERR_NO_MEMORY + // QMUX_ERR_INTERNAL + // QMUX_ERR_FAULT +} QMIDMS_SET_OPERATING_MODE_RESP_MSG, *PQMIDMS_SET_OPERATING_MODE_RESP_MSG; + +typedef struct _QMIDMS_ACTIVATE_AUTOMATIC_REQ_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // + UCHAR ActivateCodelen; + UCHAR ActivateCode; +} QMIDMS_ACTIVATE_AUTOMATIC_REQ_MSG, *PQMIDMS_ACTIVATE_AUTOMATIC_REQ_MSG; + +typedef struct _QMIDMS_ACTIVATE_AUTOMATIC_RESP_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} QMIDMS_ACTIVATE_AUTOMATIC_RESP_MSG, *PQMIDMS_ACTIVATE_AUTOMATIC_RESP_MSG; + + +typedef struct _SPC_MSG +{ + UCHAR SPC[6]; + USHORT SID; +} SPC_MSG, *PSPC_MSG; + +typedef struct _MDN_MSG +{ + UCHAR MDNLEN; + UCHAR MDN; +} MDN_MSG, *PMDN_MSG; + +typedef struct _MIN_MSG +{ + UCHAR MINLEN; + UCHAR MIN; +} MIN_MSG, *PMIN_MSG; + +typedef struct _PRL_MSG +{ + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // + USHORT PRLLEN; + UCHAR PRL; +} PRL_MSG, *PPRL_MSG; + +typedef struct _MN_HA_KEY_MSG +{ + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // + UCHAR MN_HA_KEY_LEN; + UCHAR MN_HA_KEY; +} MN_HA_KEY_MSG, *PMN_HA_KEY_MSG; + +typedef struct _MN_AAA_KEY_MSG +{ + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // + UCHAR MN_AAA_KEY_LEN; + UCHAR MN_AAA_KEY; +} MN_AAA_KEY_MSG, *PMN_AAA_KEY_MSG; + +typedef struct _QMIDMS_ACTIVATE_MANUAL_REQ_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // + UCHAR Value; +} QMIDMS_ACTIVATE_MANUAL_REQ_MSG, *PQMIDMS_ACTIVATE_MANUAL_REQ_MSG; + +typedef struct _QMIDMS_ACTIVATE_MANUAL_RESP_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} QMIDMS_ACTIVATE_MANUAL_RESP_MSG, *PQMIDMS_ACTIVATE_MANUAL_RESP_MSG; +#endif + +typedef struct _QMIDMS_UIM_GET_STATE_REQ_MSG +{ + USHORT Type; + USHORT Length; +} __attribute__ ((packed)) QMIDMS_UIM_GET_STATE_REQ_MSG, *PQMIDMS_UIM_GET_STATE_REQ_MSG; + +typedef struct _QMIDMS_UIM_GET_STATE_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR UIMState; +} __attribute__ ((packed)) QMIDMS_UIM_GET_STATE_RESP_MSG, *PQMIDMS_UIM_GET_STATE_RESP_MSG; + +typedef struct _QMIDMS_UIM_GET_PIN_STATUS_REQ_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; +} __attribute__ ((packed)) QMIDMS_UIM_GET_PIN_STATUS_REQ_MSG, *PQMIDMS_UIM_GET_PIN_STATUS_REQ_MSG; + +typedef struct _QMIDMS_UIM_PIN_STATUS +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR PINStatus; + UCHAR PINVerifyRetriesLeft; + UCHAR PINUnblockRetriesLeft; +} __attribute__ ((packed)) QMIDMS_UIM_PIN_STATUS, *PQMIDMS_UIM_PIN_STATUS; + +#define QMI_PIN_STATUS_NOT_INIT 0 +#define QMI_PIN_STATUS_NOT_VERIF 1 +#define QMI_PIN_STATUS_VERIFIED 2 +#define QMI_PIN_STATUS_DISABLED 3 +#define QMI_PIN_STATUS_BLOCKED 4 +#define QMI_PIN_STATUS_PERM_BLOCKED 5 +#define QMI_PIN_STATUS_UNBLOCKED 6 +#define QMI_PIN_STATUS_CHANGED 7 + + +typedef struct _QMIDMS_UIM_GET_PIN_STATUS_RESP_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR PinStatus; +} __attribute__ ((packed)) QMIDMS_UIM_GET_PIN_STATUS_RESP_MSG, *PQMIDMS_UIM_GET_PIN_STATUS_RESP_MSG; + +#if 0 +typedef struct _QMIDMS_UIM_GET_CK_STATUS_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR Facility; +} QMIDMS_UIM_GET_CK_STATUS_REQ_MSG, *PQMIDMS_UIM_GET_CK_STATUS_REQ_MSG; + + +typedef struct _QMIDMS_UIM_CK_STATUS +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR FacilityStatus; + UCHAR FacilityVerifyRetriesLeft; + UCHAR FacilityUnblockRetriesLeft; +} QMIDMS_UIM_CK_STATUS, *PQMIDMS_UIM_CK_STATUS; + +typedef struct _QMIDMS_UIM_CK_OPERATION_STATUS +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR OperationBlocking; +} QMIDMS_UIM_CK_OPERATION_STATUS, *PQMIDMS_UIM_CK_OPERATION_STATUS; + +typedef struct _QMIDMS_UIM_GET_CK_STATUS_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR CkStatus; +} QMIDMS_UIM_GET_CK_STATUS_RESP_MSG, *PQMIDMS_UIM_GET_CK_STATUS_RESP_MSG; +#endif + +typedef struct _QMIDMS_UIM_VERIFY_PIN_REQ_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + UCHAR PINID; + UCHAR PINLen; + UCHAR PINValue; +} __attribute__ ((packed)) QMIDMS_UIM_VERIFY_PIN_REQ_MSG, *PQMIDMS_UIM_VERIFY_PIN_REQ_MSG; + +typedef struct _QMIDMS_UIM_VERIFY_PIN_RESP_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR PINVerifyRetriesLeft; + UCHAR PINUnblockRetriesLeft; +} __attribute__ ((packed)) QMIDMS_UIM_VERIFY_PIN_RESP_MSG, *PQMIDMS_UIM_VERIFY_PIN_RESP_MSG; + +#if 0 +typedef struct _QMIDMS_UIM_SET_PIN_PROTECTION_REQ_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + UCHAR PINID; + UCHAR ProtectionSetting; + UCHAR PINLen; + UCHAR PINValue; +} QMIDMS_UIM_SET_PIN_PROTECTION_REQ_MSG, *PQMIDMS_UIM_SET_PIN_PROTECTION_REQ_MSG; + +typedef struct _QMIDMS_UIM_SET_PIN_PROTECTION_RESP_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR PINVerifyRetriesLeft; + UCHAR PINUnblockRetriesLeft; +} QMIDMS_UIM_SET_PIN_PROTECTION_RESP_MSG, *PQMIDMS_UIM_SET_PIN_PROTECTION_RESP_MSG; + +typedef struct _QMIDMS_UIM_SET_CK_PROTECTION_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR Facility; + UCHAR FacilityState; + UCHAR FacliltyLen; + UCHAR FacliltyValue; +} QMIDMS_UIM_SET_CK_PROTECTION_REQ_MSG, *PQMIDMS_UIM_SET_CK_PROTECTION_REQ_MSG; + +typedef struct _QMIDMS_UIM_SET_CK_PROTECTION_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR FacilityRetriesLeft; +} QMIDMS_UIM_SET_CK_PROTECTION_RESP_MSG, *PQMIDMS_UIM_SET_CK_PROTECTION_RESP_MSG; + + +typedef struct _UIM_PIN +{ + UCHAR PinLength; + UCHAR PinValue; +} UIM_PIN, *PUIM_PIN; + +typedef struct _QMIDMS_UIM_CHANGE_PIN_REQ_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + UCHAR PINID; + UCHAR PinDetails; +} QMIDMS_UIM_CHANGE_PIN_REQ_MSG, *PQMIDMS_UIM_CHANGE_PIN_REQ_MSG; + +typedef struct QMIDMS_UIM_CHANGE_PIN_RESP_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR PINVerifyRetriesLeft; + UCHAR PINUnblockRetriesLeft; +} QMIDMS_UIM_CHANGE_PIN_RESP_MSG, *PQMIDMS_UIM_CHANGE_PIN_RESP_MSG; + +typedef struct _UIM_PUK +{ + UCHAR PukLength; + UCHAR PukValue; +} UIM_PUK, *PUIM_PUK; + +typedef struct _QMIDMS_UIM_UNBLOCK_PIN_REQ_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + UCHAR PINID; + UCHAR PinDetails; +} QMIDMS_UIM_UNBLOCK_PIN_REQ_MSG, *PQMIDMS_UIM_BLOCK_PIN_REQ_MSG; + +typedef struct QMIDMS_UIM_UNBLOCK_PIN_RESP_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR PINVerifyRetriesLeft; + UCHAR PINUnblockRetriesLeft; +} QMIDMS_UIM_UNBLOCK_PIN_RESP_MSG, *PQMIDMS_UIM_UNBLOCK_PIN_RESP_MSG; + +typedef struct _QMIDMS_UIM_UNBLOCK_CK_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR Facility; + UCHAR FacliltyUnblockLen; + UCHAR FacliltyUnblockValue; +} QMIDMS_UIM_UNBLOCK_CK_REQ_MSG, *PQMIDMS_UIM_BLOCK_CK_REQ_MSG; + +typedef struct QMIDMS_UIM_UNBLOCK_CK_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR FacilityUnblockRetriesLeft; +} QMIDMS_UIM_UNBLOCK_CK_RESP_MSG, *PQMIDMS_UIM_UNBLOCK_CK_RESP_MSG; + +typedef struct _QMIDMS_SET_EVENT_REPORT_REQ_MSG +{ + USHORT Type; + USHORT Length; +} QMIDMS_SET_EVENT_REPORT_REQ_MSG, *PQMIDMS_SET_EVENT_REPORT_REQ_MSG; + +typedef struct _QMIDMS_SET_EVENT_REPORT_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG +} QMIDMS_SET_EVENT_REPORT_RESP_MSG, *PQMIDMS_SET_EVENT_REPORT_RESP_MSG; + +typedef struct _PIN_STATUS +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR ReportPinState; +} PIN_STATUS, *PPIN_STATUS; + +typedef struct _POWER_STATUS +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR PowerStatus; + UCHAR BatteryLvl; +} POWER_STATUS, *PPOWER_STATUS; + +typedef struct _ACTIVATION_STATE +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT ActivationState; +} ACTIVATION_STATE, *PACTIVATION_STATE; + +typedef struct _ACTIVATION_STATE_REQ +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR ActivationState; +} ACTIVATION_STATE_REQ, *PACTIVATION_STATE_REQ; + +typedef struct _OPERATING_MODE +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR OperatingMode; +} OPERATING_MODE, *POPERATING_MODE; + +typedef struct _UIM_STATE +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR UIMState; +} UIM_STATE, *PUIM_STATE; + +typedef struct _WIRELESS_DISABLE_STATE +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR WirelessDisableState; +} WIRELESS_DISABLE_STATE, *PWIRELESS_DISABLE_STATE; + +typedef struct _QMIDMS_EVENT_REPORT_IND_MSG +{ + USHORT Type; + USHORT Length; +} QMIDMS_EVENT_REPORT_IND_MSG, *PQMIDMS_EVENT_REPORT_IND_MSG; +#endif + +// ============================ END OF DMS =============================== + +// ======================= QOS ============================== +typedef struct _MPIOC_DEV_INFO MPIOC_DEV_INFO, *PMPIOC_DEV_INFO; + +#define QMI_QOS_SET_EVENT_REPORT_REQ 0x0001 +#define QMI_QOS_SET_EVENT_REPORT_RESP 0x0001 +#define QMI_QOS_EVENT_REPORT_IND 0x0001 + +#if 0 +typedef struct _QMI_QOS_SET_EVENT_REPORT_REQ_MSG +{ + USHORT Type; // QMUX type 0x0001 + USHORT Length; + // UCHAR TLVType; // 0x01 - physical link state + // USHORT TLVLength; // 1 + // UCHAR PhyLinkStatusRpt; // 0-enable; 1-disable + UCHAR TLVType2; // 0x02 = global flow reporting + USHORT TLVLength2; // 1 + UCHAR GlobalFlowRpt; // 1-enable; 0-disable +} QMI_QOS_SET_EVENT_REPORT_REQ_MSG, *PQMI_QOS_SET_EVENT_REPORT_REQ_MSG; + +typedef struct _QMI_QOS_SET_EVENT_REPORT_RESP_MSG +{ + USHORT Type; // QMUX type 0x0010 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMUX_RESULT_SUCCESS + // QMUX_RESULT_FAILURE + USHORT QMUXError; // QMUX_ERR_INVALID_ARG + // QMUX_ERR_NO_MEMORY + // QMUX_ERR_INTERNAL + // QMUX_ERR_FAULT +} QMI_QOS_SET_EVENT_REPORT_RESP_MSG, *PQMI_QOS_SET_EVENT_REPORT_RESP_MSG; + +typedef struct _QMI_QOS_EVENT_REPORT_IND_MSG +{ + USHORT Type; // QMUX type 0x0001 + USHORT Length; + UCHAR TLVs; +} QMI_QOS_EVENT_REPORT_IND_MSG, *PQMI_QOS_EVENT_REPORT_IND_MSG; + +#define QOS_EVENT_RPT_IND_FLOW_ACTIVATED 0x01 +#define QOS_EVENT_RPT_IND_FLOW_MODIFIED 0x02 +#define QOS_EVENT_RPT_IND_FLOW_DELETED 0x03 +#define QOS_EVENT_RPT_IND_FLOW_SUSPENDED 0x04 +#define QOS_EVENT_RPT_IND_FLOW_ENABLED 0x05 +#define QOS_EVENT_RPT_IND_FLOW_DISABLED 0x06 + +#define QOS_EVENT_RPT_IND_TLV_PHY_LINK_STATE_TYPE 0x01 +#define QOS_EVENT_RPT_IND_TLV_GLOBAL_FL_RPT_STATE 0x10 +#define QOS_EVENT_RPT_IND_TLV_GLOBAL_FL_RPT_TYPE 0x10 +#define QOS_EVENT_RPT_IND_TLV_TX_FLOW_TYPE 0x11 +#define QOS_EVENT_RPT_IND_TLV_RX_FLOW_TYPE 0x12 +#define QOS_EVENT_RPT_IND_TLV_TX_FILTER_TYPE 0x13 +#define QOS_EVENT_RPT_IND_TLV_RX_FILTER_TYPE 0x14 +#define QOS_EVENT_RPT_IND_TLV_FLOW_SPEC 0x10 +#define QOS_EVENT_RPT_IND_TLV_FILTER_SPEC 0x10 + +typedef struct _QOS_EVENT_RPT_IND_TLV_PHY_LINK_STATE +{ + UCHAR TLVType; // 0x01 + USHORT TLVLength; // 1 + UCHAR PhyLinkState; // 0-dormant, 1-active +} QOS_EVENT_RPT_IND_TLV_PHY_LINK_STATE, *PQOS_EVENT_RPT_IND_TLV_PHY_LINK_STATE; + +typedef struct _QOS_EVENT_RPT_IND_TLV_GLOBAL_FL_RPT +{ + UCHAR TLVType; // 0x10 + USHORT TLVLength; // 6 + ULONG QosId; + UCHAR NewFlow; // 1: newly added flow; 0: existing flow + UCHAR StateChange; // 1: activated; 2: modified; 3: deleted; + // 4: suspended(delete); 5: enabled; 6: disabled +} QOS_EVENT_RPT_IND_TLV_GLOBAL_FL_RPT, *PQOS_EVENT_RPT_IND_TLV_GLOBAL_FL_RPT; + +// QOS Flow + +typedef struct _QOS_EVENT_RPT_IND_TLV_FLOW +{ + UCHAR TLVType; // 0x10-TX flow; 0x11-RX flow + USHORT TLVLength; // var + // embedded TLV's +} QOS_EVENT_RPT_IND_TLV_TX_FLOW, *PQOS_EVENT_RPT_IND_TLV_TX_FLOW; + +#define QOS_FLOW_TLV_IP_FLOW_IDX_TYPE 0x10 +#define QOS_FLOW_TLV_IP_FLOW_TRAFFIC_CLASS_TYPE 0x11 +#define QOS_FLOW_TLV_IP_FLOW_DATA_RATE_MIN_MAX_TYPE 0x12 +#define QOS_FLOW_TLV_IP_FLOW_DATA_RATE_TOKEN_BUCKET_TYPE 0x13 +#define QOS_FLOW_TLV_IP_FLOW_LATENCY_TYPE 0x14 +#define QOS_FLOW_TLV_IP_FLOW_JITTER_TYPE 0x15 +#define QOS_FLOW_TLV_IP_FLOW_PKT_ERR_RATE_TYPE 0x16 +#define QOS_FLOW_TLV_IP_FLOW_MIN_PKT_SIZE_TYPE 0x17 +#define QOS_FLOW_TLV_IP_FLOW_MAX_PKT_SIZE_TYPE 0x18 +#define QOS_FLOW_TLV_IP_FLOW_3GPP_BIT_ERR_RATE_TYPE 0x19 +#define QOS_FLOW_TLV_IP_FLOW_3GPP_TRAF_PRIORITY_TYPE 0x1A +#define QOS_FLOW_TLV_IP_FLOW_3GPP2_PROFILE_ID_TYPE 0x1B + +typedef struct _QOS_FLOW_TLV_IP_FLOW_IDX +{ + UCHAR TLVType; // 0x10 + USHORT TLVLength; // 1 + UCHAR IpFlowIndex; +} QOS_FLOW_TLV_IP_FLOW_IDX, *PQOS_FLOW_TLV_IP_FLOW_IDX; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_TRAFFIC_CLASS +{ + UCHAR TLVType; // 0x11 + USHORT TLVLength; // 1 + UCHAR TrafficClass; +} QOS_FLOW_TLV_IP_FLOW_TRAFFIC_CLASS, *PQOS_FLOW_TLV_IP_FLOW_TRAFFIC_CLASS; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_DATA_RATE_MIN_MAX +{ + UCHAR TLVType; // 0x12 + USHORT TLVLength; // 8 + ULONG DataRateMax; + ULONG GuaranteedRate; +} QOS_FLOW_TLV_IP_FLOW_DATA_RATE_MIN_MAX, *PQOS_FLOW_TLV_IP_FLOW_DATA_RATE_MIN_MAX; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_DATA_RATE_TOKEN_BUCKET +{ + UCHAR TLVType; // 0x13 + USHORT TLVLength; // 12 + ULONG PeakRate; + ULONG TokenRate; + ULONG BucketSize; +} QOS_FLOW_TLV_IP_FLOW_DATA_RATE_TOKEN_BUCKET, *PQOS_FLOW_TLV_IP_FLOW_DATA_RATE_TOKEN_BUCKET; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_LATENCY +{ + UCHAR TLVType; // 0x14 + USHORT TLVLength; // 4 + ULONG IpFlowLatency; +} QOS_FLOW_TLV_IP_FLOW_LATENCY, *PQOS_FLOW_TLV_IP_FLOW_LATENCY; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_JITTER +{ + UCHAR TLVType; // 0x15 + USHORT TLVLength; // 4 + ULONG IpFlowJitter; +} QOS_FLOW_TLV_IP_FLOW_JITTER, *PQOS_FLOW_TLV_IP_FLOW_JITTER; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_PKT_ERR_RATE +{ + UCHAR TLVType; // 0x16 + USHORT TLVLength; // 4 + USHORT ErrRateMultiplier; + USHORT ErrRateExponent; +} QOS_FLOW_TLV_IP_FLOW_PKT_ERR_RATE, *PQOS_FLOW_TLV_IP_FLOW_PKT_ERR_RATE; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_MIN_PKT_SIZE +{ + UCHAR TLVType; // 0x17 + USHORT TLVLength; // 4 + ULONG MinPolicedPktSize; +} QOS_FLOW_TLV_IP_FLOW_MIN_PKT_SIZE, *PQOS_FLOW_TLV_IP_FLOW_MIN_PKT_SIZE; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_MAX_PKT_SIZE +{ + UCHAR TLVType; // 0x18 + USHORT TLVLength; // 4 + ULONG MaxAllowedPktSize; +} QOS_FLOW_TLV_IP_FLOW_MAX_PKT_SIZE, *PQOS_FLOW_TLV_IP_FLOW_MAX_PKT_SIZE; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_3GPP_BIT_ERR_RATE +{ + UCHAR TLVType; // 0x19 + USHORT TLVLength; // 1 + UCHAR ResidualBitErrorRate; +} QOS_FLOW_TLV_IP_FLOW_3GPP_BIT_ERR_RATE, *PQOS_FLOW_TLV_IP_FLOW_3GPP_BIT_ERR_RATE; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_3GPP_TRAF_PRIORITY +{ + UCHAR TLVType; // 0x1A + USHORT TLVLength; // 1 + UCHAR TrafficHandlingPriority; +} QOS_FLOW_TLV_IP_FLOW_3GPP_TRAF_PRIORITY, *PQOS_FLOW_TLV_IP_FLOW_3GPP_TRAF_PRIORITY; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_3GPP2_PROFILE_ID +{ + UCHAR TLVType; // 0x1B + USHORT TLVLength; // 2 + USHORT ProfileId; +} QOS_FLOW_TLV_IP_FLOW_3GPP2_PROFILE_ID, *PQOS_FLOW_TLV_IP_FLOW_3GPP2_PROFILE_ID; + +// QOS Filter + +#define QOS_FILTER_TLV_IP_FILTER_IDX_TYPE 0x10 +#define QOS_FILTER_TLV_IP_VERSION_TYPE 0x11 +#define QOS_FILTER_TLV_IPV4_SRC_ADDR_TYPE 0x12 +#define QOS_FILTER_TLV_IPV4_DEST_ADDR_TYPE 0x13 +#define QOS_FILTER_TLV_NEXT_HDR_PROTOCOL_TYPE 0x14 +#define QOS_FILTER_TLV_IPV4_TYPE_OF_SERVICE_TYPE 0x15 +#define QOS_FILTER_TLV_TCP_UDP_PORT_SRC_TCP_TYPE 0x1B +#define QOS_FILTER_TLV_TCP_UDP_PORT_DEST_TCP_TYPE 0x1C +#define QOS_FILTER_TLV_TCP_UDP_PORT_SRC_UDP_TYPE 0x1D +#define QOS_FILTER_TLV_TCP_UDP_PORT_DEST_UDP_TYPE 0x1E +#define QOS_FILTER_TLV_ICMP_FILTER_MSG_TYPE_TYPE 0x1F +#define QOS_FILTER_TLV_ICMP_FILTER_MSG_CODE_TYPE 0x20 +#define QOS_FILTER_TLV_TCP_UDP_PORT_SRC_TYPE 0x24 +#define QOS_FILTER_TLV_TCP_UDP_PORT_DEST_TYPE 0x25 + +typedef struct _QOS_EVENT_RPT_IND_TLV_FILTER +{ + UCHAR TLVType; // 0x12-TX filter; 0x13-RX filter + USHORT TLVLength; // var + // embedded TLV's +} QOS_EVENT_RPT_IND_TLV_RX_FILTER, *PQOS_EVENT_RPT_IND_TLV_RX_FILTER; + +typedef struct _QOS_FILTER_TLV_IP_FILTER_IDX +{ + UCHAR TLVType; // 0x10 + USHORT TLVLength; // 1 + UCHAR IpFilterIndex; +} QOS_FILTER_TLV_IP_FILTER_IDX, *PQOS_FILTER_TLV_IP_FILTER_IDX; + +typedef struct _QOS_FILTER_TLV_IP_VERSION +{ + UCHAR TLVType; // 0x11 + USHORT TLVLength; // 1 + UCHAR IpVersion; +} QOS_FILTER_TLV_IP_VERSION, *PQOS_FILTER_TLV_IP_VERSION; + +typedef struct _QOS_FILTER_TLV_IPV4_SRC_ADDR +{ + UCHAR TLVType; // 0x12 + USHORT TLVLength; // 8 + ULONG IpSrcAddr; + ULONG IpSrcSubnetMask; +} QOS_FILTER_TLV_IPV4_SRC_ADDR, *PQOS_FILTER_TLV_IPV4_SRC_ADDR; + +typedef struct _QOS_FILTER_TLV_IPV4_DEST_ADDR +{ + UCHAR TLVType; // 0x13 + USHORT TLVLength; // 8 + ULONG IpDestAddr; + ULONG IpDestSubnetMask; +} QOS_FILTER_TLV_IPV4_DEST_ADDR, *PQOS_FILTER_TLV_IPV4_DEST_ADDR; + +typedef struct _QOS_FILTER_TLV_NEXT_HDR_PROTOCOL +{ + UCHAR TLVType; // 0x14 + USHORT TLVLength; // 1 + UCHAR NextHdrProtocol; +} QOS_FILTER_TLV_NEXT_HDR_PROTOCOL, *PQOS_FILTER_TLV_NEXT_HDR_PROTOCOL; + +typedef struct _QOS_FILTER_TLV_IPV4_TYPE_OF_SERVICE +{ + UCHAR TLVType; // 0x15 + USHORT TLVLength; // 2 + UCHAR Ipv4TypeOfService; + UCHAR Ipv4TypeOfServiceMask; +} QOS_FILTER_TLV_IPV4_TYPE_OF_SERVICE, *PQOS_FILTER_TLV_IPV4_TYPE_OF_SERVICE; + +typedef struct _QOS_FILTER_TLV_TCP_UDP_PORT +{ + UCHAR TLVType; // source port: 0x1B-TCP; 0x1D-UDP + // dest port: 0x1C-TCP; 0x1E-UDP + USHORT TLVLength; // 4 + USHORT FilterPort; + USHORT FilterPortRange; +} QOS_FILTER_TLV_TCP_UDP_PORT, *PQOS_FILTER_TLV_TCP_UDP_PORT; + +typedef struct _QOS_FILTER_TLV_ICMP_FILTER_MSG_TYPE +{ + UCHAR TLVType; // 0x1F + USHORT TLVLength; // 1 + UCHAR IcmpFilterMsgType; +} QOS_FILTER_TLV_ICMP_FILTER_MSG_TYPE, *PQOS_FILTER_TLV_ICMP_FILTER_MSG_TYPE; + +typedef struct _QOS_FILTER_TLV_ICMP_FILTER_MSG_CODE +{ + UCHAR TLVType; // 0x20 + USHORT TLVLength; // 1 + UCHAR IcmpFilterMsgCode; +} QOS_FILTER_TLV_ICMP_FILTER_MSG_CODE, *PQOS_FILTER_TLV_ICMP_FILTER_MSG_CODE; + +#define QOS_FILTER_PRECEDENCE_INVALID 256 +#define QOS_FILTER_TLV_PRECEDENCE_TYPE 0x22 +#define QOS_FILTER_TLV_ID_TYPE 0x23 + +typedef struct _QOS_FILTER_TLV_PRECEDENCE +{ + UCHAR TLVType; // 0x22 + USHORT TLVLength; // 2 + USHORT Precedence; // precedence of the filter +} QOS_FILTER_TLV_PRECEDENCE, *PQOS_FILTER_TLV_PRECEDENCE; + +typedef struct _QOS_FILTER_TLV_ID +{ + UCHAR TLVType; // 0x23 + USHORT TLVLength; // 2 + USHORT FilterId; // filter ID +} QOS_FILTER_TLV_ID, *PQOS_FILTER_TLV_ID; + +#ifdef QCQOS_IPV6 + +#define QOS_FILTER_TLV_IPV6_SRC_ADDR_TYPE 0x16 +#define QOS_FILTER_TLV_IPV6_DEST_ADDR_TYPE 0x17 +#define QOS_FILTER_TLV_IPV6_NEXT_HDR_PROTOCOL_TYPE 0x14 // same as IPV4 +#define QOS_FILTER_TLV_IPV6_TRAFFIC_CLASS_TYPE 0x19 +#define QOS_FILTER_TLV_IPV6_FLOW_LABEL_TYPE 0x1A + +typedef struct _QOS_FILTER_TLV_IPV6_SRC_ADDR +{ + UCHAR TLVType; // 0x16 + USHORT TLVLength; // 17 + UCHAR IpSrcAddr[16]; + UCHAR IpSrcAddrPrefixLen; // [0..128] +} QOS_FILTER_TLV_IPV6_SRC_ADDR, *PQOS_FILTER_TLV_IPV6_SRC_ADDR; + +typedef struct _QOS_FILTER_TLV_IPV6_DEST_ADDR +{ + UCHAR TLVType; // 0x17 + USHORT TLVLength; // 17 + UCHAR IpDestAddr[16]; + UCHAR IpDestAddrPrefixLen; // [0..128] +} QOS_FILTER_TLV_IPV6_DEST_ADDR, *PQOS_FILTER_TLV_IPV6_DEST_ADDR; + +#define QOS_FILTER_IPV6_NEXT_HDR_PROTOCOL_TCP 0x06 +#define QOS_FILTER_IPV6_NEXT_HDR_PROTOCOL_UDP 0x11 + +typedef struct _QOS_FILTER_TLV_IPV6_TRAFFIC_CLASS +{ + UCHAR TLVType; // 0x19 + USHORT TLVLength; // 2 + UCHAR TrafficClass; + UCHAR TrafficClassMask; // compare the first 6 bits only +} QOS_FILTER_TLV_IPV6_TRAFFIC_CLASS, *PQOS_FILTER_TLV_IPV6_TRAFFIC_CLASS; + +typedef struct _QOS_FILTER_TLV_IPV6_FLOW_LABEL +{ + UCHAR TLVType; // 0x1A + USHORT TLVLength; // 4 + ULONG FlowLabel; +} QOS_FILTER_TLV_IPV6_FLOW_LABEL, *PQOS_FILTER_TLV_IPV6_FLOW_LABEL; + +#endif // QCQOS_IPV6 +#endif + +// ======================= WMS ============================== +#define QMIWMS_SET_EVENT_REPORT_REQ 0x0001 +#define QMIWMS_SET_EVENT_REPORT_RESP 0x0001 +#define QMIWMS_EVENT_REPORT_IND 0x0001 +#define QMIWMS_RAW_SEND_REQ 0x0020 +#define QMIWMS_RAW_SEND_RESP 0x0020 +#define QMIWMS_RAW_WRITE_REQ 0x0021 +#define QMIWMS_RAW_WRITE_RESP 0x0021 +#define QMIWMS_RAW_READ_REQ 0x0022 +#define QMIWMS_RAW_READ_RESP 0x0022 +#define QMIWMS_MODIFY_TAG_REQ 0x0023 +#define QMIWMS_MODIFY_TAG_RESP 0x0023 +#define QMIWMS_DELETE_REQ 0x0024 +#define QMIWMS_DELETE_RESP 0x0024 +#define QMIWMS_GET_MESSAGE_PROTOCOL_REQ 0x0030 +#define QMIWMS_GET_MESSAGE_PROTOCOL_RESP 0x0030 +#define QMIWMS_LIST_MESSAGES_REQ 0x0031 +#define QMIWMS_LIST_MESSAGES_RESP 0x0031 +#define QMIWMS_GET_SMSC_ADDRESS_REQ 0x0034 +#define QMIWMS_GET_SMSC_ADDRESS_RESP 0x0034 +#define QMIWMS_SET_SMSC_ADDRESS_REQ 0x0035 +#define QMIWMS_SET_SMSC_ADDRESS_RESP 0x0035 +#define QMIWMS_GET_STORE_MAX_SIZE_REQ 0x0036 +#define QMIWMS_GET_STORE_MAX_SIZE_RESP 0x0036 + + +#define WMS_MESSAGE_PROTOCOL_CDMA 0x00 +#define WMS_MESSAGE_PROTOCOL_WCDMA 0x01 + +#if 0 +typedef struct _QMIWMS_GET_MESSAGE_PROTOCOL_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; +} QMIWMS_GET_MESSAGE_PROTOCOL_REQ_MSG, *PQMIWMS_GET_MESSAGE_PROTOCOL_REQ_MSG; + +typedef struct _QMIWMS_GET_MESSAGE_PROTOCOL_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR MessageProtocol; +} QMIWMS_GET_MESSAGE_PROTOCOL_RESP_MSG, *PQMIWMS_GET_MESSAGE_PROTOCOL_RESP_MSG; + +typedef struct _QMIWMS_GET_STORE_MAX_SIZE_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR StorageType; +} QMIWMS_GET_STORE_MAX_SIZE_REQ_MSG, *PQMIWMS_GET_STORE_MAX_SIZE_REQ_MSG; + +typedef struct _QMIWMS_GET_STORE_MAX_SIZE_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLV2Type; + USHORT TLV2Length; + ULONG MemStoreMaxSize; +} QMIWMS_GET_STORE_MAX_SIZE_RESP_MSG, *PQMIWMS_GET_STORE_MAX_SIZE_RESP_MSG; + +typedef struct _REQUEST_TAG +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR TagType; +} REQUEST_TAG, *PREQUEST_TAG; + +typedef struct _QMIWMS_LIST_MESSAGES_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR StorageType; +} QMIWMS_LIST_MESSAGES_REQ_MSG, *PQMIWMS_LIST_MESSAGES_REQ_MSG; + +typedef struct _QMIWMS_MESSAGE +{ + ULONG MessageIndex; + UCHAR TagType; +} QMIWMS_MESSAGE, *PQMIWMS_MESSAGE; + +typedef struct _QMIWMS_LIST_MESSAGES_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLV2Type; + USHORT TLV2Length; + ULONG NumMessages; +} QMIWMS_LIST_MESSAGES_RESP_MSG, *PQMIWMS_LIST_MESSAGES_RESP_MSG; + +typedef struct _QMIWMS_RAW_READ_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR StorageType; + ULONG MemoryIndex; +} QMIWMS_RAW_READ_REQ_MSG, *PQMIWMS_RAW_READ_REQ_MSG; + +typedef struct _QMIWMS_RAW_READ_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR TagType; + UCHAR Format; + USHORT MessageLength; + UCHAR Message; +} QMIWMS_RAW_READ_RESP_MSG, *PQMIWMS_RAW_READ_RESP_MSG; + +typedef struct _QMIWMS_MODIFY_TAG_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR StorageType; + ULONG MemoryIndex; + UCHAR TagType; +} QMIWMS_MODIFY_TAG_REQ_MSG, *PQMIWMS_MODIFY_TAG_REQ_MSG; + +typedef struct _QMIWMS_MODIFY_TAG_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} QMIWMS_MODIFY_TAG_RESP_MSG, *PQMIWMS_MODIFY_TAG_RESP_MSG; + +typedef struct _QMIWMS_RAW_SEND_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR SmsFormat; + USHORT SmsLength; + UCHAR SmsMessage; +} QMIWMS_RAW_SEND_REQ_MSG, *PQMIWMS_RAW_SEND_REQ_MSG; + +typedef struct _RAW_SEND_CAUSE_CODE +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT CauseCode; +} RAW_SEND_CAUSE_CODE, *PRAW_SEND_CAUSE_CODE; + + +typedef struct _QMIWMS_RAW_SEND_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} QMIWMS_RAW_SEND_RESP_MSG, *PQMIWMS_RAW_SEND_RESP_MSG; + + +typedef struct _WMS_DELETE_MESSAGE_INDEX +{ + UCHAR TLVType; + USHORT TLVLength; + ULONG MemoryIndex; +} WMS_DELETE_MESSAGE_INDEX, *PWMS_DELETE_MESSAGE_INDEX; + +typedef struct _WMS_DELETE_MESSAGE_TAG +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR MessageTag; +} WMS_DELETE_MESSAGE_TAG, *PWMS_DELETE_MESSAGE_TAG; + +typedef struct _QMIWMS_DELETE_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR StorageType; +} QMIWMS_DELETE_REQ_MSG, *PQMIWMS_DELETE_REQ_MSG; + +typedef struct _QMIWMS_DELETE_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} QMIWMS_DELETE_RESP_MSG, *PQMIWMS_DELETE_RESP_MSG; + + +typedef struct _QMIWMS_GET_SMSC_ADDRESS_REQ_MSG +{ + USHORT Type; + USHORT Length; +} QMIWMS_GET_SMSC_ADDRESS_REQ_MSG, *PQMIWMS_GET_SMSC_ADDRESS_REQ_MSG; + +typedef struct _QMIWMS_SMSC_ADDRESS +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR SMSCAddressType[3]; + UCHAR SMSCAddressLength; + UCHAR SMSCAddressDigits; +} QMIWMS_SMSC_ADDRESS, *PQMIWMS_SMSC_ADDRESS; + + +typedef struct _QMIWMS_GET_SMSC_ADDRESS_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR SMSCAddress; +} QMIWMS_GET_SMSC_ADDRESS_RESP_MSG, *PQMIWMS_GET_SMSC_ADDRESS_RESP_MSG; + +typedef struct _QMIWMS_SET_SMSC_ADDRESS_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR SMSCAddress; +} QMIWMS_SET_SMSC_ADDRESS_REQ_MSG, *PQMIWMS_SET_SMSC_ADDRESS_REQ_MSG; + +typedef struct _QMIWMS_SET_SMSC_ADDRESS_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} QMIWMS_SET_SMSC_ADDRESS_RESP_MSG, *PQMIWMS_SET_SMSC_ADDRESS_RESP_MSG; + +typedef struct _QMIWMS_SET_EVENT_REPORT_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR ReportNewMessage; +} QMIWMS_SET_EVENT_REPORT_REQ_MSG, *PQMIWMS_SET_EVENT_REPORT_REQ_MSG; + +typedef struct _QMIWMS_SET_EVENT_REPORT_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} QMIWMS_SET_EVENT_REPORT_RESP_MSG, *PQMIWMS_SET_EVENT_REPORT_RESP_MSG; + +typedef struct _QMIWMS_EVENT_REPORT_IND_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR StorageType; + ULONG StorageIndex; +} QMIWMS_EVENT_REPORT_IND_MSG, *PQMIWMS_EVENT_REPORT_IND_MSG; +#endif + +// ======================= End of WMS ============================== + + +// ======================= NAS ============================== +#define QMINAS_SET_EVENT_REPORT_REQ 0x0002 +#define QMINAS_SET_EVENT_REPORT_RESP 0x0002 +#define QMINAS_EVENT_REPORT_IND 0x0002 +#define QMINAS_GET_SIGNAL_STRENGTH_REQ 0x0020 +#define QMINAS_GET_SIGNAL_STRENGTH_RESP 0x0020 +#define QMINAS_PERFORM_NETWORK_SCAN_REQ 0x0021 +#define QMINAS_PERFORM_NETWORK_SCAN_RESP 0x0021 +#define QMINAS_INITIATE_NW_REGISTER_REQ 0x0022 +#define QMINAS_INITIATE_NW_REGISTER_RESP 0x0022 +#define QMINAS_INITIATE_ATTACH_REQ 0x0023 +#define QMINAS_INITIATE_ATTACH_RESP 0x0023 +#define QMINAS_GET_SERVING_SYSTEM_REQ 0x0024 +#define QMINAS_GET_SERVING_SYSTEM_RESP 0x0024 +#define QMINAS_SERVING_SYSTEM_IND 0x0024 +#define QMINAS_GET_HOME_NETWORK_REQ 0x0025 +#define QMINAS_GET_HOME_NETWORK_RESP 0x0025 +#define QMINAS_GET_PREFERRED_NETWORK_REQ 0x0026 +#define QMINAS_GET_PREFERRED_NETWORK_RESP 0x0026 +#define QMINAS_SET_PREFERRED_NETWORK_REQ 0x0027 +#define QMINAS_SET_PREFERRED_NETWORK_RESP 0x0027 +#define QMINAS_GET_FORBIDDEN_NETWORK_REQ 0x0028 +#define QMINAS_GET_FORBIDDEN_NETWORK_RESP 0x0028 +#define QMINAS_SET_FORBIDDEN_NETWORK_REQ 0x0029 +#define QMINAS_SET_FORBIDDEN_NETWORK_RESP 0x0029 +#define QMINAS_SET_TECHNOLOGY_PREF_REQ 0x002A +#define QMINAS_SET_TECHNOLOGY_PREF_RESP 0x002A +#define QMINAS_GET_RF_BAND_INFO_REQ 0x0031 +#define QMINAS_GET_RF_BAND_INFO_RESP 0x0031 +#define QMINAS_GET_PLMN_NAME_REQ 0x0044 +#define QMINAS_GET_PLMN_NAME_RESP 0x0044 +#define QUECTEL_PACKET_TRANSFER_START_IND 0X100 +#define QUECTEL_PACKET_TRANSFER_END_IND 0X101 +#define QMINAS_GET_SYS_INFO_REQ 0x004D +#define QMINAS_GET_SYS_INFO_RESP 0x004D +#define QMINAS_SYS_INFO_IND 0x004D + +typedef struct _QMINAS_GET_HOME_NETWORK_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; +} __attribute__ ((packed)) QMINAS_GET_HOME_NETWORK_REQ_MSG, *PQMINAS_GET_HOME_NETWORK_REQ_MSG; + +typedef struct _HOME_NETWORK_SYSTEMID +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT SystemID; + USHORT NetworkID; +} __attribute__ ((packed)) HOME_NETWORK_SYSTEMID, *PHOME_NETWORK_SYSTEMID; + +typedef struct _HOME_NETWORK +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT MobileCountryCode; + USHORT MobileNetworkCode; + UCHAR NetworkDesclen; + UCHAR NetworkDesc; +} __attribute__ ((packed)) HOME_NETWORK, *PHOME_NETWORK; + +#if 0 +typedef struct _HOME_NETWORK_EXT +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT MobileCountryCode; + USHORT MobileNetworkCode; + UCHAR NetworkDescDisp; + UCHAR NetworkDescEncoding; + UCHAR NetworkDesclen; + UCHAR NetworkDesc; +} HOME_NETWORK_EXT, *PHOME_NETWORK_EXT; + +typedef struct _QMINAS_GET_HOME_NETWORK_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} QMINAS_GET_HOME_NETWORK_RESP_MSG, *PQMINAS_GET_HOME_NETWORK_RESP_MSG; + +typedef struct _QMINAS_GET_PREFERRED_NETWORK_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; +} QMINAS_GET_PREFERRED_NETWORK_REQ_MSG, *PQMINAS_GET_PREFERRED_NETWORK_REQ_MSG; + + +typedef struct _PREFERRED_NETWORK +{ + USHORT MobileCountryCode; + USHORT MobileNetworkCode; + USHORT RadioAccess; +} PREFERRED_NETWORK, *PPREFERRED_NETWORK; + +typedef struct _QMINAS_GET_PREFERRED_NETWORK_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR TLV2Type; // 0x01 - required parameter + USHORT TLV2Length; // length of the mfr string + USHORT NumPreferredNetwork; +} QMINAS_GET_PREFERRED_NETWORK_RESP_MSG, *PQMINAS_GET_PREFERRED_NETWORK_RESP_MSG; + +typedef struct _QMINAS_GET_FORBIDDEN_NETWORK_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; +} QMINAS_GET_FORBIDDEN_NETWORK_REQ_MSG, *PQMINAS_GET_FORBIDDEN_NETWORK_REQ_MSG; + +typedef struct _FORBIDDEN_NETWORK +{ + USHORT MobileCountryCode; + USHORT MobileNetworkCode; +} FORBIDDEN_NETWORK, *PFORBIDDEN_NETWORK; + +typedef struct _QMINAS_GET_FORBIDDEN_NETWORK_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR TLV2Type; // 0x01 - required parameter + USHORT TLV2Length; // length of the mfr string + USHORT NumForbiddenNetwork; +} QMINAS_GET_FORBIDDEN_NETWORK_RESP_MSG, *PQMINAS_GET_FORBIDDEN_NETWORK_RESP_MSG; + +typedef struct _QMINAS_GET_SERVING_SYSTEM_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; +} QMINAS_GET_SERVING_SYSTEM_REQ_MSG, *PQMINAS_GET_SERVING_SYSTEM_REQ_MSG; + +typedef struct _QMINAS_ROAMING_INDICATOR_MSG +{ + UCHAR TLVType; // 0x01 - required parameter + USHORT TLVLength; // length of the mfr string + UCHAR RoamingIndicator; +} QMINAS_ROAMING_INDICATOR_MSG, *PQMINAS_ROAMING_INDICATOR_MSG; +#endif + +typedef struct _QMINAS_DATA_CAP +{ + UCHAR TLVType; // 0x01 - required parameter + USHORT TLVLength; // length of the mfr string + UCHAR DataCapListLen; + UCHAR DataCap; +} __attribute__ ((packed)) QMINAS_DATA_CAP, *PQMINAS_DATA_CAP; + +typedef struct _QMINAS_CURRENT_PLMN_MSG +{ + UCHAR TLVType; // 0x01 - required parameter + USHORT TLVLength; // length of the mfr string + USHORT MobileCountryCode; + USHORT MobileNetworkCode; + UCHAR NetworkDesclen; + UCHAR NetworkDesc; +} __attribute__ ((packed)) QMINAS_CURRENT_PLMN_MSG, *PQMINAS_CURRENT_PLMN_MSG; + +typedef struct _QMINAS_GET_SERVING_SYSTEM_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} __attribute__ ((packed)) QMINAS_GET_SERVING_SYSTEM_RESP_MSG, *PQMINAS_GET_SERVING_SYSTEM_RESP_MSG; + +typedef struct _SERVING_SYSTEM +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR RegistrationState; + UCHAR CSAttachedState; + UCHAR PSAttachedState; + UCHAR RegistredNetwork; + UCHAR InUseRadioIF; + UCHAR RadioIF; +} __attribute__ ((packed)) SERVING_SYSTEM, *PSERVING_SYSTEM; + +typedef struct _QMINAS_GET_SYS_INFO_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} __attribute__ ((packed)) QMINAS_GET_SYS_INFO_RESP_MSG, *PQMINAS_GET_SYS_INFO_RESP_MSG; + +typedef struct _QMINAS_SYS_INFO_IND_MSG +{ + USHORT Type; + USHORT Length; +} __attribute__ ((packed)) QMINAS_SYS_INFO_IND_MSG, *PQMINAS_SYS_INFO_IND_MSG; + +typedef struct _SERVICE_STATUS_INFO +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR SrvStatus; + UCHAR IsPrefDataPath; +} __attribute__ ((packed)) SERVICE_STATUS_INFO, *PSERVICE_STATUS_INFO; + +typedef struct _CDMA_SYSTEM_INFO +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR SrvDomainValid; + UCHAR SrvDomain; + UCHAR SrvCapabilityValid; + UCHAR SrvCapability; + UCHAR RoamStatusValid; + UCHAR RoamStatus; + UCHAR IsSysForbiddenValid; + UCHAR IsSysForbidden; + UCHAR IsSysPrlMatchValid; + UCHAR IsSysPrlMatch; + UCHAR PRevInUseValid; + UCHAR PRevInUse; + UCHAR BSPRevValid; + UCHAR BSPRev; + UCHAR CCSSupportedValid; + UCHAR CCSSupported; + UCHAR CDMASysIdValid; + USHORT SID; + USHORT NID; + UCHAR BSInfoValid; + USHORT BaseID; + ULONG BaseLAT; + ULONG BaseLONG; + UCHAR PacketZoneValid; + USHORT PacketZone; + UCHAR NetworkIdValid; + UCHAR MCC[3]; + UCHAR MNC[3]; +} __attribute__ ((packed)) CDMA_SYSTEM_INFO, *PCDMA_SYSTEM_INFO; + +typedef struct _HDR_SYSTEM_INFO +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR SrvDomainValid; + UCHAR SrvDomain; + UCHAR SrvCapabilityValid; + UCHAR SrvCapability; + UCHAR RoamStatusValid; + UCHAR RoamStatus; + UCHAR IsSysForbiddenValid; + UCHAR IsSysForbidden; + UCHAR IsSysPrlMatchValid; + UCHAR IsSysPrlMatch; + UCHAR HdrPersonalityValid; + UCHAR HdrPersonality; + UCHAR HdrActiveProtValid; + UCHAR HdrActiveProt; + UCHAR is856SysIdValid; + UCHAR is856SysId[16]; +} __attribute__ ((packed)) HDR_SYSTEM_INFO, *PHDR_SYSTEM_INFO; + +typedef struct _GSM_SYSTEM_INFO +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR SrvDomainValid; + UCHAR SrvDomain; + UCHAR SrvCapabilityValid; + UCHAR SrvCapability; + UCHAR RoamStatusValid; + UCHAR RoamStatus; + UCHAR IsSysForbiddenValid; + UCHAR IsSysForbidden; + UCHAR LacValid; + USHORT Lac; + UCHAR CellIdValid; + ULONG CellId; + UCHAR RegRejectInfoValid; + UCHAR RejectSrvDomain; + UCHAR RejCause; + UCHAR NetworkIdValid; + UCHAR MCC[3]; + UCHAR MNC[3]; + UCHAR EgprsSuppValid; + UCHAR EgprsSupp; + UCHAR DtmSuppValid; + UCHAR DtmSupp; +} __attribute__ ((packed)) GSM_SYSTEM_INFO, *PGSM_SYSTEM_INFO; + +typedef struct _WCDMA_SYSTEM_INFO +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR SrvDomainValid; + UCHAR SrvDomain; + UCHAR SrvCapabilityValid; + UCHAR SrvCapability; + UCHAR RoamStatusValid; + UCHAR RoamStatus; + UCHAR IsSysForbiddenValid; + UCHAR IsSysForbidden; + UCHAR LacValid; + USHORT Lac; + UCHAR CellIdValid; + ULONG CellId; + UCHAR RegRejectInfoValid; + UCHAR RejectSrvDomain; + UCHAR RejCause; + UCHAR NetworkIdValid; + UCHAR MCC[3]; + UCHAR MNC[3]; + UCHAR HsCallStatusValid; + UCHAR HsCallStatus; + UCHAR HsIndValid; + UCHAR HsInd; + UCHAR PscValid; + UCHAR Psc; +} __attribute__ ((packed)) WCDMA_SYSTEM_INFO, *PWCDMA_SYSTEM_INFO; + +typedef struct _LTE_SYSTEM_INFO +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR SrvDomainValid; + UCHAR SrvDomain; + UCHAR SrvCapabilityValid; + UCHAR SrvCapability; + UCHAR RoamStatusValid; + UCHAR RoamStatus; + UCHAR IsSysForbiddenValid; + UCHAR IsSysForbidden; + UCHAR LacValid; + USHORT Lac; + UCHAR CellIdValid; + ULONG CellId; + UCHAR RegRejectInfoValid; + UCHAR RejectSrvDomain; + UCHAR RejCause; + UCHAR NetworkIdValid; + UCHAR MCC[3]; + UCHAR MNC[3]; + UCHAR TacValid; + USHORT Tac; +} __attribute__ ((packed)) LTE_SYSTEM_INFO, *PLTE_SYSTEM_INFO; + +typedef struct _TDSCDMA_SYSTEM_INFO +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR SrvDomainValid; + UCHAR SrvDomain; + UCHAR SrvCapabilityValid; + UCHAR SrvCapability; + UCHAR RoamStatusValid; + UCHAR RoamStatus; + UCHAR IsSysForbiddenValid; + UCHAR IsSysForbidden; + UCHAR LacValid; + USHORT Lac; + UCHAR CellIdValid; + ULONG CellId; + UCHAR RegRejectInfoValid; + UCHAR RejectSrvDomain; + UCHAR RejCause; + UCHAR NetworkIdValid; + UCHAR MCC[3]; + UCHAR MNC[3]; + UCHAR HsCallStatusValid; + UCHAR HsCallStatus; + UCHAR HsIndValid; + UCHAR HsInd; + UCHAR CellParameterIdValid; + USHORT CellParameterId; + UCHAR CellBroadcastCapValid; + ULONG CellBroadcastCap; + UCHAR CsBarStatusValid; + ULONG CsBarStatus; + UCHAR PsBarStatusValid; + ULONG PsBarStatus; + UCHAR CipherDomainValid; + UCHAR CipherDomain; +} __attribute__ ((packed)) TDSCDMA_SYSTEM_INFO, *PTDSCDMA_SYSTEM_INFO; + +#if 0 +typedef struct _QMINAS_SERVING_SYSTEM_IND_MSG +{ + USHORT Type; + USHORT Length; +} QMINAS_SERVING_SYSTEM_IND_MSG, *PQMINAS_SERVING_SYSTEM_IND_MSG; + +typedef struct _QMINAS_SET_PREFERRED_NETWORK_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT NumPreferredNetwork; + USHORT MobileCountryCode; + USHORT MobileNetworkCode; + USHORT RadioAccess; +} QMINAS_SET_PREFERRED_NETWORK_REQ_MSG, *PQMINAS_SET_PREFERRED_NETWORK_REQ_MSG; + +typedef struct _QMINAS_SET_PREFERRED_NETWORK_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} QMINAS_SET_PREFERRED_NETWORK_RESP_MSG, *PQMINAS_SET_PREFERRED_NETWORK_RESP_MSG; + +typedef struct _QMINAS_SET_FORBIDDEN_NETWORK_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT NumForbiddenNetwork; + USHORT MobileCountryCode; + USHORT MobileNetworkCode; +} QMINAS_SET_FORBIDDEN_NETWORK_REQ_MSG, *PQMINAS_SET_FORBIDDEN_NETWORK_REQ_MSG; + +typedef struct _QMINAS_SET_FORBIDDEN_NETWORK_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} QMINAS_SET_FORBIDDEN_NETWORK_RESP_MSG, *PQMINAS_SET_FORBIDDEN_NETWORK_RESP_MSG; + +typedef struct _QMINAS_PERFORM_NETWORK_SCAN_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; +} QMINAS_PERFORM_NETWORK_SCAN_REQ_MSG, *PQMINAS_PERFORM_NETWORK_SCAN_REQ_MSG; + +typedef struct _VISIBLE_NETWORK +{ + USHORT MobileCountryCode; + USHORT MobileNetworkCode; + UCHAR NetworkStatus; + UCHAR NetworkDesclen; +} VISIBLE_NETWORK, *PVISIBLE_NETWORK; + +typedef struct _QMINAS_PERFORM_NETWORK_SCAN_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} QMINAS_PERFORM_NETWORK_SCAN_RESP_MSG, *PQMINAS_PERFORM_NETWORK_SCAN_RESP_MSG; + +typedef struct _QMINAS_PERFORM_NETWORK_SCAN_NETWORK_INFO +{ + UCHAR TLVType; // 0x010 - required parameter + USHORT TLVLength; // length + USHORT NumNetworkInstances; +} QMINAS_PERFORM_NETWORK_SCAN_NETWORK_INFO, *PQMINAS_PERFORM_NETWORK_SCAN_NETWORK_INFO; + +typedef struct _QMINAS_PERFORM_NETWORK_SCAN_RAT_INFO +{ + UCHAR TLVType; // 0x011 - required parameter + USHORT TLVLength; // length + USHORT NumInst; +} QMINAS_PERFORM_NETWORK_SCAN_RAT_INFO, *PQMINAS_PERFORM_NETWORK_SCAN_RAT_INFO; + +typedef struct _QMINAS_PERFORM_NETWORK_SCAN_RAT +{ + USHORT MCC; + USHORT MNC; + UCHAR RAT; +} QMINAS_PERFORM_NETWORK_SCAN_RAT, *PQMINAS_PERFORM_NETWORK_SCAN_RAT; + + +typedef struct _QMINAS_MANUAL_NW_REGISTER +{ + UCHAR TLV2Type; // 0x02 - result code + USHORT TLV2Length; // 4 + USHORT MobileCountryCode; + USHORT MobileNetworkCode; + UCHAR RadioAccess; +} QMINAS_MANUAL_NW_REGISTER, *PQMINAS_MANUAL_NW_REGISTER; + +typedef struct _QMINAS_INITIATE_NW_REGISTER_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + UCHAR RegisterAction; +} QMINAS_INITIATE_NW_REGISTER_REQ_MSG, *PQMINAS_INITIATE_NW_REGISTER_REQ_MSG; + +typedef struct _QMINAS_INITIATE_NW_REGISTER_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} QMINAS_INITIATE_NW_REGISTER_RESP_MSG, *PQMINAS_INITIATE_NW_REGISTER_RESP_MSG; + +typedef struct _QMINAS_SET_TECHNOLOGY_PREF_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT TechPref; + UCHAR Duration; +} QMINAS_SET_TECHNOLOGY_PREF_REQ_MSG, *PQMINAS_SET_TECHNOLOGY_PREF_REQ_MSG; + +typedef struct _QMINAS_SET_TECHNOLOGY_PREF_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} QMINAS_SET_TECHNOLOGY_PREF_RESP_MSG, *PQMINAS_SET_TECHNOLOGY_PREF_RESP_MSG; + +typedef struct _QMINAS_GET_SIGNAL_STRENGTH_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; +} QMINAS_GET_SIGNAL_STRENGTH_REQ_MSG, *PQMINAS_GET_SIGNAL_STRENGTH_REQ_MSG; + +typedef struct _QMINAS_SIGNAL_STRENGTH +{ + CHAR SigStrength; + UCHAR RadioIf; +} QMINAS_SIGNAL_STRENGTH, *PQMINAS_SIGNAL_STRENGTH; + +typedef struct _QMINAS_SIGNAL_STRENGTH_LIST +{ + UCHAR TLV3Type; + USHORT TLV3Length; + USHORT NumInstance; +} QMINAS_SIGNAL_STRENGTH_LIST, *PQMINAS_SIGNAL_STRENGTH_LIST; + + +typedef struct _QMINAS_GET_SIGNAL_STRENGTH_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR TLV2Type; + USHORT TLV2Length; + CHAR SignalStrength; + UCHAR RadioIf; +} QMINAS_GET_SIGNAL_STRENGTH_RESP_MSG, *PQMINAS_GET_SIGNAL_STRENGTH_RESP_MSG; + + +typedef struct _QMINAS_SET_EVENT_REPORT_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR ReportSigStrength; + UCHAR NumTresholds; + CHAR TresholdList[2]; +} QMINAS_SET_EVENT_REPORT_REQ_MSG, *PQMINAS_SET_EVENT_REPORT_REQ_MSG; + +typedef struct _QMINAS_SET_EVENT_REPORT_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} QMINAS_SET_EVENT_REPORT_RESP_MSG, *PQMINAS_SET_EVENT_REPORT_RESP_MSG; + +typedef struct _QMINAS_SIGNAL_STRENGTH_TLV +{ + UCHAR TLVType; + USHORT TLVLength; + CHAR SigStrength; + UCHAR RadioIf; +} QMINAS_SIGNAL_STRENGTH_TLV, *PQMINAS_SIGNAL_STRENGTH_TLV; + +typedef struct _QMINAS_REJECT_CAUSE_TLV +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR ServiceDomain; + USHORT RejectCause; +} QMINAS_REJECT_CAUSE_TLV, *PQMINAS_REJECT_CAUSE_TLV; + +typedef struct _QMINAS_EVENT_REPORT_IND_MSG +{ + USHORT Type; + USHORT Length; +} QMINAS_EVENT_REPORT_IND_MSG, *PQMINAS_EVENT_REPORT_IND_MSG; + +typedef struct _QMINAS_GET_RF_BAND_INFO_REQ_MSG +{ + USHORT Type; + USHORT Length; +} QMINAS_GET_RF_BAND_INFO_REQ_MSG, *PQMINAS_GET_RF_BAND_INFO_REQ_MSG; + +typedef struct _QMINASRF_BAND_INFO +{ + UCHAR RadioIf; + USHORT ActiveBand; + USHORT ActiveChannel; +} QMINASRF_BAND_INFO, *PQMINASRF_BAND_INFO; + +typedef struct _QMINAS_GET_RF_BAND_INFO_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR NumInstances; +} QMINAS_GET_RF_BAND_INFO_RESP_MSG, *PQMINAS_GET_RF_BAND_INFO_RESP_MSG; + + +typedef struct _QMINAS_GET_PLMN_NAME_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT MCC; + USHORT MNC; +} QMINAS_GET_PLMN_NAME_REQ_MSG, *PQMINAS_GET_PLMN_NAME_REQ_MSG; + +typedef struct _QMINAS_GET_PLMN_NAME_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} QMINAS_GET_PLMN_NAME_RESP_MSG, *PQMINAS_GET_PLMN_NAME_RESP_MSG; + +typedef struct _QMINAS_GET_PLMN_NAME_SPN +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR SPN_Enc; + UCHAR SPN_Len; +} QMINAS_GET_PLMN_NAME_SPN, *PQMINAS_GET_PLMN_NAME_SPN; + +typedef struct _QMINAS_GET_PLMN_NAME_PLMN +{ + UCHAR PLMN_Enc; + UCHAR PLMN_Ci; + UCHAR PLMN_SpareBits; + UCHAR PLMN_Len; +} QMINAS_GET_PLMN_NAME_PLMN, *PQMINAS_GET_PLMN_NAME_PLMN; + +typedef struct _QMINAS_INITIATE_ATTACH_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR PsAttachAction; +} QMINAS_INITIATE_ATTACH_REQ_MSG, *PQMINAS_INITIATE_ATTACH_REQ_MSG; + +typedef struct _QMINAS_INITIATE_ATTACH_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} QMINAS_INITIATE_ATTACH_RESP_MSG, *PQMINAS_INITIATE_ATTACH_RESP_MSG; +#endif +// ======================= End of NAS ============================== + +// ======================= UIM ============================== +#define QMIUIM_READ_TRANSPARENT_REQ 0x0020 +#define QMIUIM_READ_TRANSPARENT_RESP 0x0020 +#define QMIUIM_READ_TRANSPARENT_IND 0x0020 +#define QMIUIM_READ_RECORD_REQ 0x0021 +#define QMIUIM_READ_RECORD_RESP 0x0021 +#define QMIUIM_READ_RECORD_IND 0x0021 +#define QMIUIM_WRITE_TRANSPARENT_REQ 0x0022 +#define QMIUIM_WRITE_TRANSPARENT_RESP 0x0022 +#define QMIUIM_WRITE_TRANSPARENT_IND 0x0022 +#define QMIUIM_WRITE_RECORD_REQ 0x0023 +#define QMIUIM_WRITE_RECORD_RESP 0x0023 +#define QMIUIM_WRITE_RECORD_IND 0x0023 +#define QMIUIM_SET_PIN_PROTECTION_REQ 0x0025 +#define QMIUIM_SET_PIN_PROTECTION_RESP 0x0025 +#define QMIUIM_SET_PIN_PROTECTION_IND 0x0025 +#define QMIUIM_VERIFY_PIN_REQ 0x0026 +#define QMIUIM_VERIFY_PIN_RESP 0x0026 +#define QMIUIM_VERIFY_PIN_IND 0x0026 +#define QMIUIM_UNBLOCK_PIN_REQ 0x0027 +#define QMIUIM_UNBLOCK_PIN_RESP 0x0027 +#define QMIUIM_UNBLOCK_PIN_IND 0x0027 +#define QMIUIM_CHANGE_PIN_REQ 0x0028 +#define QMIUIM_CHANGE_PIN_RESP 0x0028 +#define QMIUIM_CHANGE_PIN_IND 0x0028 +#define QMIUIM_DEPERSONALIZATION_REQ 0x0029 +#define QMIUIM_DEPERSONALIZATION_RESP 0x0029 +#define QMIUIM_EVENT_REG_REQ 0x002E +#define QMIUIM_EVENT_REG_RESP 0x002E +#define QMIUIM_GET_CARD_STATUS_REQ 0x002F +#define QMIUIM_GET_CARD_STATUS_RESP 0x002F +#define QMIUIM_STATUS_CHANGE_IND 0x0032 + + +typedef struct _QMIUIM_GET_CARD_STATUS_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} __attribute__ ((packed)) QMIUIM_GET_CARD_STATUS_RESP_MSG, *PQMIUIM_GET_CARD_STATUS_RESP_MSG; + +typedef struct _QMIUIM_CARD_STATUS +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT IndexGWPri; + USHORT Index1XPri; + USHORT IndexGWSec; + USHORT Index1XSec; + UCHAR NumSlot; + UCHAR CardState; + UCHAR UPINState; + UCHAR UPINRetries; + UCHAR UPUKRetries; + UCHAR ErrorCode; + UCHAR NumApp; + UCHAR AppType; + UCHAR AppState; + UCHAR PersoState; + UCHAR PersoFeature; + UCHAR PersoRetries; + UCHAR PersoUnblockRetries; + UCHAR AIDLength; +} __attribute__ ((packed)) QMIUIM_CARD_STATUS, *PQMIUIM_CARD_STATUS; + +typedef struct _QMIUIM_PIN_STATE +{ + UCHAR UnivPIN; + UCHAR PIN1State; + UCHAR PIN1Retries; + UCHAR PUK1Retries; + UCHAR PIN2State; + UCHAR PIN2Retries; + UCHAR PUK2Retries; +} __attribute__ ((packed)) QMIUIM_PIN_STATE, *PQMIUIM_PIN_STATE; + +typedef struct _QMIUIM_VERIFY_PIN_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR Session_Type; + UCHAR Aid_Len; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR PINID; + UCHAR PINLen; + UCHAR PINValue; +} __attribute__ ((packed)) QMIUIM_VERIFY_PIN_REQ_MSG, *PQMIUIM_VERIFY_PIN_REQ_MSG; + +typedef struct _QMIUIM_VERIFY_PIN_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR PINVerifyRetriesLeft; + UCHAR PINUnblockRetriesLeft; +} __attribute__ ((packed)) QMIUIM_VERIFY_PIN_RESP_MSG, *PQMIUIM_VERIFY_PIN_RESP_MSG; + + +typedef struct _QMUX_MSG +{ + QCQMUX_HDR QMUXHdr; + union + { + // Message Header + QCQMUX_MSG_HDR QMUXMsgHdr; + QCQMUX_MSG_HDR_RESP QMUXMsgHdrResp; + + // QMIWDS Message +#if 0 + QMIWDS_GET_PKT_SRVC_STATUS_REQ_MSG PacketServiceStatusReq; + QMIWDS_GET_PKT_SRVC_STATUS_RESP_MSG PacketServiceStatusRsp; + QMIWDS_GET_PKT_SRVC_STATUS_IND_MSG PacketServiceStatusInd; + QMIWDS_EVENT_REPORT_IND_MSG EventReportInd; + QMIWDS_GET_CURRENT_CHANNEL_RATE_REQ_MSG GetCurrChannelRateReq; + QMIWDS_GET_CURRENT_CHANNEL_RATE_RESP_MSG GetCurrChannelRateRsp; + QMIWDS_GET_PKT_STATISTICS_REQ_MSG GetPktStatsReq; + QMIWDS_GET_PKT_STATISTICS_RESP_MSG GetPktStatsRsp; + QMIWDS_SET_EVENT_REPORT_REQ_MSG EventReportReq; + QMIWDS_SET_EVENT_REPORT_RESP_MSG EventReportRsp; +#endif + //#ifdef QC_IP_MODE + QMIWDS_GET_RUNTIME_SETTINGS_REQ_MSG GetRuntimeSettingsReq; + QMIWDS_GET_RUNTIME_SETTINGS_RESP_MSG GetRuntimeSettingsRsp; + //#endif // QC_IP_MODE +#if 0 + QMIWDS_SET_CLIENT_IP_FAMILY_PREF_REQ_MSG SetClientIpFamilyPrefReq; + QMIWDS_SET_CLIENT_IP_FAMILY_PREF_RESP_MSG SetClientIpFamilyPrefResp; + QMIWDS_GET_MIP_MODE_REQ_MSG GetMipModeReq; + QMIWDS_GET_MIP_MODE_RESP_MSG GetMipModeResp; +#endif + QMIWDS_START_NETWORK_INTERFACE_REQ_MSG StartNwInterfaceReq; + QMIWDS_START_NETWORK_INTERFACE_RESP_MSG StartNwInterfaceResp; + QMIWDS_STOP_NETWORK_INTERFACE_REQ_MSG StopNwInterfaceReq; + QMIWDS_STOP_NETWORK_INTERFACE_RESP_MSG StopNwInterfaceResp; + QMIWDS_GET_DEFAULT_SETTINGS_REQ_MSG GetDefaultSettingsReq; + QMIWDS_GET_DEFAULT_SETTINGS_RESP_MSG GetDefaultSettingsResp; + QMIWDS_MODIFY_PROFILE_SETTINGS_REQ_MSG ModifyProfileSettingsReq; + QMIWDS_MODIFY_PROFILE_SETTINGS_RESP_MSG ModifyProfileSettingsResp; + QMIWDS_GET_PROFILE_SETTINGS_REQ_MSG GetProfileSettingsReq; +#if 0 + QMIWDS_GET_DATA_BEARER_REQ_MSG GetDataBearerReq; + QMIWDS_GET_DATA_BEARER_RESP_MSG GetDataBearerResp; + QMIWDS_DUN_CALL_INFO_REQ_MSG DunCallInfoReq; + QMIWDS_DUN_CALL_INFO_RESP_MSG DunCallInfoResp; + QMIWDS_BIND_MUX_DATA_PORT_REQ_MSG BindMuxDataPortReq; +#endif + + // QMIDMS Messages +#if 0 + QMIDMS_GET_DEVICE_MFR_REQ_MSG GetDeviceMfrReq; + QMIDMS_GET_DEVICE_MFR_RESP_MSG GetDeviceMfrRsp; + QMIDMS_GET_DEVICE_MODEL_ID_REQ_MSG GetDeviceModeIdReq; + QMIDMS_GET_DEVICE_MODEL_ID_RESP_MSG GetDeviceModeIdRsp; + QMIDMS_GET_DEVICE_REV_ID_REQ_MSG GetDeviceRevIdReq; + QMIDMS_GET_DEVICE_REV_ID_RESP_MSG GetDeviceRevIdRsp; + QMIDMS_GET_MSISDN_REQ_MSG GetMsisdnReq; + QMIDMS_GET_MSISDN_RESP_MSG GetMsisdnRsp; + QMIDMS_GET_DEVICE_SERIAL_NUMBERS_REQ_MSG GetDeviceSerialNumReq; + QMIDMS_GET_DEVICE_SERIAL_NUMBERS_RESP_MSG GetDeviceSerialNumRsp; + QMIDMS_GET_DEVICE_CAP_REQ_MSG GetDeviceCapReq; + QMIDMS_GET_DEVICE_CAP_RESP_MSG GetDeviceCapResp; + QMIDMS_GET_BAND_CAP_REQ_MSG GetBandCapReq; + QMIDMS_GET_BAND_CAP_RESP_MSG GetBandCapRsp; + QMIDMS_GET_ACTIVATED_STATUS_REQ_MSG GetActivatedStatusReq; + QMIDMS_GET_ACTIVATED_STATUS_RESP_MSG GetActivatedStatusResp; + QMIDMS_GET_OPERATING_MODE_REQ_MSG GetOperatingModeReq; + QMIDMS_GET_OPERATING_MODE_RESP_MSG GetOperatingModeResp; + QMIDMS_SET_OPERATING_MODE_REQ_MSG SetOperatingModeReq; + QMIDMS_SET_OPERATING_MODE_RESP_MSG SetOperatingModeResp; + QMIDMS_UIM_GET_ICCID_REQ_MSG GetICCIDReq; + QMIDMS_UIM_GET_ICCID_RESP_MSG GetICCIDResp; + QMIDMS_ACTIVATE_AUTOMATIC_REQ_MSG ActivateAutomaticReq; + QMIDMS_ACTIVATE_AUTOMATIC_RESP_MSG ActivateAutomaticResp; + QMIDMS_ACTIVATE_MANUAL_REQ_MSG ActivateManualReq; + QMIDMS_ACTIVATE_MANUAL_RESP_MSG ActivateManualResp; +#endif + QMIDMS_UIM_GET_PIN_STATUS_REQ_MSG UIMGetPinStatusReq; + QMIDMS_UIM_GET_PIN_STATUS_RESP_MSG UIMGetPinStatusResp; + QMIDMS_UIM_VERIFY_PIN_REQ_MSG UIMVerifyPinReq; + QMIDMS_UIM_VERIFY_PIN_RESP_MSG UIMVerifyPinResp; +#if 0 + QMIDMS_UIM_SET_PIN_PROTECTION_REQ_MSG UIMSetPinProtectionReq; + QMIDMS_UIM_SET_PIN_PROTECTION_RESP_MSG UIMSetPinProtectionResp; + QMIDMS_UIM_CHANGE_PIN_REQ_MSG UIMChangePinReq; + QMIDMS_UIM_CHANGE_PIN_RESP_MSG UIMChangePinResp; + QMIDMS_UIM_UNBLOCK_PIN_REQ_MSG UIMUnblockPinReq; + QMIDMS_UIM_UNBLOCK_PIN_RESP_MSG UIMUnblockPinResp; + QMIDMS_SET_EVENT_REPORT_REQ_MSG DmsSetEventReportReq; + QMIDMS_SET_EVENT_REPORT_RESP_MSG DmsSetEventReportResp; + QMIDMS_EVENT_REPORT_IND_MSG DmsEventReportInd; +#endif + QMIDMS_UIM_GET_STATE_REQ_MSG UIMGetStateReq; + QMIDMS_UIM_GET_STATE_RESP_MSG UIMGetStateResp; + QMIDMS_UIM_GET_IMSI_REQ_MSG UIMGetIMSIReq; + QMIDMS_UIM_GET_IMSI_RESP_MSG UIMGetIMSIResp; +#if 0 + QMIDMS_UIM_GET_CK_STATUS_REQ_MSG UIMGetCkStatusReq; + QMIDMS_UIM_GET_CK_STATUS_RESP_MSG UIMGetCkStatusResp; + QMIDMS_UIM_SET_CK_PROTECTION_REQ_MSG UIMSetCkProtectionReq; + QMIDMS_UIM_SET_CK_PROTECTION_RESP_MSG UIMSetCkProtectionResp; + QMIDMS_UIM_UNBLOCK_CK_REQ_MSG UIMUnblockCkReq; + QMIDMS_UIM_UNBLOCK_CK_RESP_MSG UIMUnblockCkResp; +#endif + + // QMIQOS Messages +#if 0 + QMI_QOS_SET_EVENT_REPORT_REQ_MSG QosSetEventReportReq; + QMI_QOS_SET_EVENT_REPORT_RESP_MSG QosSetEventReportRsp; + QMI_QOS_EVENT_REPORT_IND_MSG QosEventReportInd; +#endif + + // QMIWMS Messages +#if 0 + QMIWMS_GET_MESSAGE_PROTOCOL_REQ_MSG GetMessageProtocolReq; + QMIWMS_GET_MESSAGE_PROTOCOL_RESP_MSG GetMessageProtocolResp; + QMIWMS_GET_SMSC_ADDRESS_REQ_MSG GetSMSCAddressReq; + QMIWMS_GET_SMSC_ADDRESS_RESP_MSG GetSMSCAddressResp; + QMIWMS_SET_SMSC_ADDRESS_REQ_MSG SetSMSCAddressReq; + QMIWMS_SET_SMSC_ADDRESS_RESP_MSG SetSMSCAddressResp; + QMIWMS_GET_STORE_MAX_SIZE_REQ_MSG GetStoreMaxSizeReq; + QMIWMS_GET_STORE_MAX_SIZE_RESP_MSG GetStoreMaxSizeResp; + QMIWMS_LIST_MESSAGES_REQ_MSG ListMessagesReq; + QMIWMS_LIST_MESSAGES_RESP_MSG ListMessagesResp; + QMIWMS_RAW_READ_REQ_MSG RawReadMessagesReq; + QMIWMS_RAW_READ_RESP_MSG RawReadMessagesResp; + QMIWMS_SET_EVENT_REPORT_REQ_MSG WmsSetEventReportReq; + QMIWMS_SET_EVENT_REPORT_RESP_MSG WmsSetEventReportResp; + QMIWMS_EVENT_REPORT_IND_MSG WmsEventReportInd; + QMIWMS_DELETE_REQ_MSG WmsDeleteReq; + QMIWMS_DELETE_RESP_MSG WmsDeleteResp; + QMIWMS_RAW_SEND_REQ_MSG RawSendMessagesReq; + QMIWMS_RAW_SEND_RESP_MSG RawSendMessagesResp; + QMIWMS_MODIFY_TAG_REQ_MSG WmsModifyTagReq; + QMIWMS_MODIFY_TAG_RESP_MSG WmsModifyTagResp; +#endif + + // QMINAS Messages +#if 0 + QMINAS_GET_HOME_NETWORK_REQ_MSG GetHomeNetworkReq; + QMINAS_GET_HOME_NETWORK_RESP_MSG GetHomeNetworkResp; + QMINAS_GET_PREFERRED_NETWORK_REQ_MSG GetPreferredNetworkReq; + QMINAS_GET_PREFERRED_NETWORK_RESP_MSG GetPreferredNetworkResp; + QMINAS_GET_FORBIDDEN_NETWORK_REQ_MSG GetForbiddenNetworkReq; + QMINAS_GET_FORBIDDEN_NETWORK_RESP_MSG GetForbiddenNetworkResp; + QMINAS_GET_SERVING_SYSTEM_REQ_MSG GetServingSystemReq; +#endif + QMINAS_GET_SERVING_SYSTEM_RESP_MSG GetServingSystemResp; + QMINAS_GET_SYS_INFO_RESP_MSG GetSysInfoResp; + QMINAS_SYS_INFO_IND_MSG NasSysInfoInd; +#if 0 + QMINAS_SERVING_SYSTEM_IND_MSG NasServingSystemInd; + QMINAS_SET_PREFERRED_NETWORK_REQ_MSG SetPreferredNetworkReq; + QMINAS_SET_PREFERRED_NETWORK_RESP_MSG SetPreferredNetworkResp; + QMINAS_SET_FORBIDDEN_NETWORK_REQ_MSG SetForbiddenNetworkReq; + QMINAS_SET_FORBIDDEN_NETWORK_RESP_MSG SetForbiddenNetworkResp; + QMINAS_PERFORM_NETWORK_SCAN_REQ_MSG PerformNetworkScanReq; + QMINAS_PERFORM_NETWORK_SCAN_RESP_MSG PerformNetworkScanResp; + QMINAS_INITIATE_NW_REGISTER_REQ_MSG InitiateNwRegisterReq; + QMINAS_INITIATE_NW_REGISTER_RESP_MSG InitiateNwRegisterResp; + QMINAS_SET_TECHNOLOGY_PREF_REQ_MSG SetTechnologyPrefReq; + QMINAS_SET_TECHNOLOGY_PREF_RESP_MSG SetTechnologyPrefResp; + QMINAS_GET_SIGNAL_STRENGTH_REQ_MSG GetSignalStrengthReq; + QMINAS_GET_SIGNAL_STRENGTH_RESP_MSG GetSignalStrengthResp; + QMINAS_SET_EVENT_REPORT_REQ_MSG SetEventReportReq; + QMINAS_SET_EVENT_REPORT_RESP_MSG SetEventReportResp; + QMINAS_EVENT_REPORT_IND_MSG NasEventReportInd; + QMINAS_GET_RF_BAND_INFO_REQ_MSG GetRFBandInfoReq; + QMINAS_GET_RF_BAND_INFO_RESP_MSG GetRFBandInfoResp; + QMINAS_INITIATE_ATTACH_REQ_MSG InitiateAttachReq; + QMINAS_INITIATE_ATTACH_RESP_MSG InitiateAttachResp; + QMINAS_GET_PLMN_NAME_REQ_MSG GetPLMNNameReq; + QMINAS_GET_PLMN_NAME_RESP_MSG GetPLMNNameResp; +#endif + + // QMIUIM Messages + QMIUIM_GET_CARD_STATUS_RESP_MSG UIMGetCardStatus; + QMIUIM_VERIFY_PIN_REQ_MSG UIMUIMVerifyPinReq; + QMIUIM_VERIFY_PIN_RESP_MSG UIMUIMVerifyPinResp; +#if 0 + QMIUIM_SET_PIN_PROTECTION_REQ_MSG UIMUIMSetPinProtectionReq; + QMIUIM_SET_PIN_PROTECTION_RESP_MSG UIMUIMSetPinProtectionResp; + QMIUIM_CHANGE_PIN_REQ_MSG UIMUIMChangePinReq; + QMIUIM_CHANGE_PIN_RESP_MSG UIMUIMChangePinResp; + QMIUIM_UNBLOCK_PIN_REQ_MSG UIMUIMUnblockPinReq; + QMIUIM_UNBLOCK_PIN_RESP_MSG UIMUIMUnblockPinResp; + QMIUIM_READ_TRANSPARENT_REQ_MSG UIMUIMReadTransparentReq; + QMIUIM_READ_TRANSPARENT_RESP_MSG UIMUIMReadTransparentResp; +#endif + + }; +} __attribute__ ((packed)) QMUX_MSG, *PQMUX_MSG; + +#pragma pack(pop) + +#endif // MPQMUX_H diff --git a/root/package/link4all/quectel-CM/src.all/Makefile b/root/package/link4all/quectel-CM/src.all/Makefile new file mode 100755 index 00000000..aaf8e4ed --- /dev/null +++ b/root/package/link4all/quectel-CM/src.all/Makefile @@ -0,0 +1,6 @@ +quectel-CM:clean + $(CC) -Wall -s QmiWwanCM.c GobiNetCM.c main.c MPQMUX.c QMIThread.c util.c udhcpc.c -o quectel-CM -lpthread + +clean: + rm -rf quectel-CM *~ + diff --git a/root/package/link4all/quectel-CM/src.all/QMIThread.c b/root/package/link4all/quectel-CM/src.all/QMIThread.c new file mode 100755 index 00000000..1ed3e59b --- /dev/null +++ b/root/package/link4all/quectel-CM/src.all/QMIThread.c @@ -0,0 +1,1739 @@ +#include "QMIThread.h" +extern char *strndup (const char *__string, size_t __n); + +int qmiclientId[QMUX_TYPE_WDS_ADMIN + 1]; //GobiNet use fd to indicate client ID, so type of qmiclientId must be int +static UINT WdsConnectionIPv4Handle = 0; +static int s_is_cdma = 0; +static int s_hdr_personality = 0; // 0x01-HRPD, 0x02-eHRPD +static char *qstrcpy(char *to, const char *from) { //no __strcpy_chk + char *save = to; + for (; (*to = *from) != '\0'; ++from, ++to); + return(save); +} + +static int s_9x07 = 0; + +typedef USHORT (*CUSTOMQMUX)(PQMUX_MSG pMUXMsg, void *arg); + +// To retrieve the ith (Index) TLV +PQMI_TLV_HDR GetTLV (PQCQMUX_MSG_HDR pQMUXMsgHdr, int TLVType) { + int TLVFind = 0; + USHORT Length = le16_to_cpu(pQMUXMsgHdr->Length); + PQMI_TLV_HDR pTLVHdr = (PQMI_TLV_HDR)(pQMUXMsgHdr + 1); + + while (Length >= sizeof(QMI_TLV_HDR)) { + TLVFind++; + if (TLVType > 0x1000) { + if ((TLVFind + 0x1000) == TLVType) + return pTLVHdr; + } else if (pTLVHdr->TLVType == TLVType) { + return pTLVHdr; + } + + Length -= (le16_to_cpu((pTLVHdr->TLVLength)) + sizeof(QMI_TLV_HDR)); + pTLVHdr = (PQMI_TLV_HDR)(((UCHAR *)pTLVHdr) + le16_to_cpu(pTLVHdr->TLVLength) + sizeof(QMI_TLV_HDR)); + } + + return NULL; +} + +static USHORT GetQMUXTransactionId(void) { + static int TransactionId = 0; + if (++TransactionId > 0xFFFF) + TransactionId = 1; + return TransactionId; +} + +static PQCQMIMSG ComposeQMUXMsg(UCHAR QMIType, USHORT Type, CUSTOMQMUX customQmuxMsgFunction, void *arg) { + UCHAR QMIBuf[WDM_DEFAULT_BUFSIZE]; + PQCQMIMSG pRequest = (PQCQMIMSG)QMIBuf; + int Length; + + memset(QMIBuf, 0x00, sizeof(QMIBuf)); + pRequest->QMIHdr.IFType = USB_CTL_MSG_TYPE_QMI; + pRequest->QMIHdr.CtlFlags = 0x00; + pRequest->QMIHdr.QMIType = QMIType; + pRequest->QMIHdr.ClientId = qmiclientId[pRequest->QMIHdr.QMIType] & 0xFF; + + if (pRequest->QMIHdr.ClientId == 0) { + dbg_time("QMIType %d has no clientID", pRequest->QMIHdr.QMIType); + return NULL; + } + + pRequest->MUXMsg.QMUXHdr.CtlFlags = QMUX_CTL_FLAG_SINGLE_MSG | QMUX_CTL_FLAG_TYPE_CMD; + pRequest->MUXMsg.QMUXHdr.TransactionId = cpu_to_le16(GetQMUXTransactionId()); + pRequest->MUXMsg.QMUXMsgHdr.Type = cpu_to_le16(Type); + if (customQmuxMsgFunction) + pRequest->MUXMsg.QMUXMsgHdr.Length = cpu_to_le16(customQmuxMsgFunction(&pRequest->MUXMsg, arg) - sizeof(QCQMUX_MSG_HDR)); + else + pRequest->MUXMsg.QMUXMsgHdr.Length = cpu_to_le16(0x0000); + + pRequest->QMIHdr.Length = cpu_to_le16(le16_to_cpu(pRequest->MUXMsg.QMUXMsgHdr.Length) + sizeof(QCQMUX_MSG_HDR) + sizeof(QCQMUX_HDR) + + sizeof(QCQMI_HDR) - 1); + Length = le16_to_cpu(pRequest->QMIHdr.Length) + 1; + + pRequest = (PQCQMIMSG)malloc(Length); + if (pRequest == NULL) { + dbg_time("%s fail to malloc", __func__); + } else { + memcpy(pRequest, QMIBuf, Length); + } + + return pRequest; +} + +#if 0 +static USHORT NasSetEventReportReq(PQMUX_MSG pMUXMsg, void *arg) { + pMUXMsg->SetEventReportReq.TLVType = 0x10; + pMUXMsg->SetEventReportReq.TLVLength = 0x04; + pMUXMsg->SetEventReportReq.ReportSigStrength = 0x00; + pMUXMsg->SetEventReportReq.NumTresholds = 2; + pMUXMsg->SetEventReportReq.TresholdList[0] = -113; + pMUXMsg->SetEventReportReq.TresholdList[1] = -50; + return sizeof(QMINAS_SET_EVENT_REPORT_REQ_MSG); +} + +static USHORT WdsSetEventReportReq(PQMUX_MSG pMUXMsg, void *arg) { + pMUXMsg->EventReportReq.TLVType = 0x10; // 0x10 -- current channel rate indicator + pMUXMsg->EventReportReq.TLVLength = 0x0001; // 1 + pMUXMsg->EventReportReq.Mode = 0x00; // 0-do not report; 1-report when rate changes + + pMUXMsg->EventReportReq.TLV2Type = 0x11; // 0x11 + pMUXMsg->EventReportReq.TLV2Length = 0x0005; // 5 + pMUXMsg->EventReportReq.StatsPeriod = 0x00; // seconds between reports; 0-do not report + pMUXMsg->EventReportReq.StatsMask = 0x000000ff; // + + pMUXMsg->EventReportReq.TLV3Type = 0x12; // 0x12 -- current data bearer indicator + pMUXMsg->EventReportReq.TLV3Length = 0x0001; // 1 + pMUXMsg->EventReportReq.Mode3 = 0x01; // 0-do not report; 1-report when changes + + pMUXMsg->EventReportReq.TLV4Type = 0x13; // 0x13 -- dormancy status indicator + pMUXMsg->EventReportReq.TLV4Length = 0x0001; // 1 + pMUXMsg->EventReportReq.DormancyStatus = 0x00; // 0-do not report; 1-report when changes + return sizeof(QMIWDS_SET_EVENT_REPORT_REQ_MSG); +} + +static USHORT DmsSetEventReportReq(PQMUX_MSG pMUXMsg) { + PPIN_STATUS pPinState = (PPIN_STATUS)(&pMUXMsg->DmsSetEventReportReq + 1); + PUIM_STATE pUimState = (PUIM_STATE)(pPinState + 1); + // Pin State + pPinState->TLVType = 0x12; + pPinState->TLVLength = 0x01; + pPinState->ReportPinState = 0x01; + // UIM State + pUimState->TLVType = 0x15; + pUimState->TLVLength = 0x01; + pUimState->UIMState = 0x01; + return sizeof(QMIDMS_SET_EVENT_REPORT_REQ_MSG) + sizeof(PIN_STATUS) + sizeof(UIM_STATE); +} +#endif + +static USHORT WdsStartNwInterfaceReq(PQMUX_MSG pMUXMsg, void *arg) { + PQMIWDS_TECHNOLOGY_PREFERECE pTechPref; + PQMIWDS_AUTH_PREFERENCE pAuthPref; + PQMIWDS_USERNAME pUserName; + PQMIWDS_PASSWD pPasswd; + PQMIWDS_APNNAME pApnName; + PQMIWDS_IP_FAMILY_TLV pIpFamily; + USHORT TLVLength = 0; + UCHAR *pTLV; + PROFILE_T *profile = (PROFILE_T *)arg; + + pTLV = (UCHAR *)(&pMUXMsg->StartNwInterfaceReq + 1); + pMUXMsg->StartNwInterfaceReq.Length = 0; + + // Set technology Preferece + pTechPref = (PQMIWDS_TECHNOLOGY_PREFERECE)(pTLV + TLVLength); + pTechPref->TLVType = 0x30; + pTechPref->TLVLength = cpu_to_le16(0x01); + if (s_is_cdma == 0) + pTechPref->TechPreference = 0x01; + else + pTechPref->TechPreference = 0x02; + TLVLength +=(le16_to_cpu(pTechPref->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + + // Set APN Name + if (profile->apn) { + pApnName = (PQMIWDS_APNNAME)(pTLV + TLVLength); + pApnName->TLVType = 0x14; + pApnName->TLVLength = cpu_to_le16(strlen(profile->apn)); + qstrcpy((char *)&pApnName->ApnName, profile->apn); + TLVLength +=(le16_to_cpu(pApnName->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + // Set User Name + if (profile->user) { + pUserName = (PQMIWDS_USERNAME)(pTLV + TLVLength); + pUserName->TLVType = 0x17; + pUserName->TLVLength = cpu_to_le16(strlen(profile->user)); + qstrcpy((char *)&pUserName->UserName, profile->user); + TLVLength += (le16_to_cpu(pUserName->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + // Set Password + if (profile->password) { + pPasswd = (PQMIWDS_PASSWD)(pTLV + TLVLength); + pPasswd->TLVType = 0x18; + pPasswd->TLVLength = cpu_to_le16(strlen(profile->password)); + qstrcpy((char *)&pPasswd->Passwd, profile->password); + TLVLength +=(le16_to_cpu(pPasswd->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + // Set Auth Protocol + if (profile->user && profile->password) { + pAuthPref = (PQMIWDS_AUTH_PREFERENCE)(pTLV + TLVLength); + pAuthPref->TLVType = 0x16; + pAuthPref->TLVLength = cpu_to_le16(0x01); + pAuthPref->AuthPreference = profile->auth; // 0 ~ None, 1 ~ Pap, 2 ~ Chap, 3 ~ MsChapV2 + TLVLength +=(le16_to_cpu(pAuthPref->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + // Add IP Family Preference + pIpFamily = (PQMIWDS_IP_FAMILY_TLV)(pTLV + TLVLength); + pIpFamily->TLVType = 0x19; + pIpFamily->TLVLength = cpu_to_le16(0x01); + pIpFamily->IpFamily = profile->IPType; + TLVLength += (le16_to_cpu(pIpFamily->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + + //Set Profile Index + if (profile->pdp) { + PQMIWDS_PROFILE_IDENTIFIER pProfileIndex = (PQMIWDS_PROFILE_IDENTIFIER)(pTLV + TLVLength); + pProfileIndex->TLVLength = cpu_to_le16(0x01); + pProfileIndex->TLVType = 0x31; + pProfileIndex->ProfileIndex = profile->pdp; + if (s_is_cdma && s_hdr_personality == 0x02) { + pProfileIndex->TLVType = 0x32; //profile_index_3gpp2 + pProfileIndex->ProfileIndex = 101; + } + TLVLength += (le16_to_cpu(pProfileIndex->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + return sizeof(QMIWDS_START_NETWORK_INTERFACE_REQ_MSG) + TLVLength; +} + +static USHORT WdsStopNwInterfaceReq(PQMUX_MSG pMUXMsg, void *arg) { + pMUXMsg->StopNwInterfaceReq.TLVType = 0x01; + pMUXMsg->StopNwInterfaceReq.TLVLength = cpu_to_le16(0x04); + pMUXMsg->StopNwInterfaceReq.Handle = cpu_to_le32(WdsConnectionIPv4Handle); + return sizeof(QMIWDS_STOP_NETWORK_INTERFACE_REQ_MSG); +} + +static USHORT WdaSetDataFormat(PQMUX_MSG pMUXMsg, void *arg) { + PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV_QOS pWdsAdminQosTlv; + PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV linkProto; + PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV dlTlp; + + pWdsAdminQosTlv = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV_QOS)(&pMUXMsg->QMUXMsgHdr + 1); + pWdsAdminQosTlv->TLVType = 0x10; + pWdsAdminQosTlv->TLVLength = cpu_to_le16(0x0001); + pWdsAdminQosTlv->QOSSetting = 0; /* no-QOS header */ + + linkProto = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV)(pWdsAdminQosTlv + 1); + linkProto->TLVType = 0x11; + linkProto->TLVLength = cpu_to_le16(4); + linkProto->Value = cpu_to_le32(0x01); /* Set Ethernet mode */ + + dlTlp = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV)(linkProto + 1);; + dlTlp->TLVType = 0x13; + dlTlp->TLVLength = cpu_to_le16(4); + dlTlp->Value = cpu_to_le32(0x00); + + if (sizeof(*linkProto) != 7 ) + dbg_time("%s sizeof(*linkProto) = %d, is not 7!", __func__, sizeof(*linkProto) ); + + return sizeof(QCQMUX_MSG_HDR) + sizeof(*pWdsAdminQosTlv) + sizeof(*linkProto) + sizeof(*dlTlp); +} + +#ifdef CONFIG_SIM +static USHORT DmsUIMVerifyPinReqSend(PQMUX_MSG pMUXMsg, void *arg) { + pMUXMsg->UIMVerifyPinReq.TLVType = 0x01; + pMUXMsg->UIMVerifyPinReq.PINID = 0x01; //Pin1, not Puk + pMUXMsg->UIMVerifyPinReq.PINLen = strlen((const char *)arg); + qstrcpy((PCHAR)&pMUXMsg->UIMVerifyPinReq.PINValue, ((const char *)arg)); + pMUXMsg->UIMVerifyPinReq.TLVLength = cpu_to_le16(2 + strlen((const char *)arg)); + return sizeof(QMIDMS_UIM_VERIFY_PIN_REQ_MSG) + (strlen((const char *)arg) - 1); +} + +static USHORT UimVerifyPinReqSend(PQMUX_MSG pMUXMsg, void *arg) +{ + pMUXMsg->UIMUIMVerifyPinReq.TLVType = 0x01; + pMUXMsg->UIMUIMVerifyPinReq.TLVLength = cpu_to_le16(0x02); + pMUXMsg->UIMUIMVerifyPinReq.Session_Type = 0x00; + pMUXMsg->UIMUIMVerifyPinReq.Aid_Len = 0x00; + pMUXMsg->UIMUIMVerifyPinReq.TLV2Type = 0x02; + pMUXMsg->UIMUIMVerifyPinReq.TLV2Length = cpu_to_le16(2 + strlen((const char *)arg)); + pMUXMsg->UIMUIMVerifyPinReq.PINID = 0x01; //Pin1, not Puk + pMUXMsg->UIMUIMVerifyPinReq.PINLen= strlen((const char *)arg); + qstrcpy((PCHAR)&pMUXMsg->UIMUIMVerifyPinReq.PINValue, ((const char *)arg)); + return sizeof(QMIUIM_VERIFY_PIN_REQ_MSG) + (strlen((const char *)arg) - 1); +} +#endif + +#ifdef CONFIG_APN +static USHORT WdsGetProfileSettingsReqSend(PQMUX_MSG pMUXMsg, void *arg) { + PROFILE_T *profile = (PROFILE_T *)arg; + pMUXMsg->GetProfileSettingsReq.Length = cpu_to_le16(sizeof(QMIWDS_GET_PROFILE_SETTINGS_REQ_MSG) - 4); + pMUXMsg->GetProfileSettingsReq.TLVType = 0x01; + pMUXMsg->GetProfileSettingsReq.TLVLength = cpu_to_le16(0x02); + pMUXMsg->GetProfileSettingsReq.ProfileType = 0x00; // 0 ~ 3GPP, 1 ~ 3GPP2 + pMUXMsg->GetProfileSettingsReq.ProfileIndex = profile->pdp; + return sizeof(QMIWDS_GET_PROFILE_SETTINGS_REQ_MSG); +} + +static USHORT WdsModifyProfileSettingsReq(PQMUX_MSG pMUXMsg, void *arg) { + USHORT TLVLength = 0; + UCHAR *pTLV; + PROFILE_T *profile = (PROFILE_T *)arg; + PQMIWDS_PDPTYPE pPdpType; + + pMUXMsg->ModifyProfileSettingsReq.Length = cpu_to_le16(sizeof(QMIWDS_MODIFY_PROFILE_SETTINGS_REQ_MSG) - 4); + pMUXMsg->ModifyProfileSettingsReq.TLVType = 0x01; + pMUXMsg->ModifyProfileSettingsReq.TLVLength = cpu_to_le16(0x02); + pMUXMsg->ModifyProfileSettingsReq.ProfileType = 0x00; // 0 ~ 3GPP, 1 ~ 3GPP2 + pMUXMsg->ModifyProfileSettingsReq.ProfileIndex = profile->pdp; + + pTLV = (UCHAR *)(&pMUXMsg->ModifyProfileSettingsReq + 1); + + pPdpType = (PQMIWDS_PDPTYPE)(pTLV + TLVLength); + pPdpType->TLVType = 0x11; + pPdpType->TLVLength = cpu_to_le16(0x01); +// 0 ¨C PDP-IP (IPv4) +// 1 ¨C PDP-PPP +// 2 ¨C PDP-IPv6 +// 3 ¨C PDP-IPv4v6 + pPdpType->PdpType = 3; + TLVLength +=(le16_to_cpu(pPdpType->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + + // Set APN Name + if (profile->apn) { + PQMIWDS_APNNAME pApnName = (PQMIWDS_APNNAME)(pTLV + TLVLength); + pApnName->TLVType = 0x14; + pApnName->TLVLength = cpu_to_le16(strlen(profile->apn)); + qstrcpy((char *)&pApnName->ApnName, profile->apn); + TLVLength +=(le16_to_cpu(pApnName->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + // Set User Name + if (profile->user) { + PQMIWDS_USERNAME pUserName = (PQMIWDS_USERNAME)(pTLV + TLVLength); + pUserName->TLVType = 0x1B; + pUserName->TLVLength = cpu_to_le16(strlen(profile->user)); + qstrcpy((char *)&pUserName->UserName, profile->user); + TLVLength += (le16_to_cpu(pUserName->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + // Set Password + if (profile->password) { + PQMIWDS_PASSWD pPasswd = (PQMIWDS_PASSWD)(pTLV + TLVLength); + pPasswd->TLVType = 0x1C; + pPasswd->TLVLength = cpu_to_le16(strlen(profile->password)); + qstrcpy((char *)&pPasswd->Passwd, profile->password); + TLVLength +=(le16_to_cpu(pPasswd->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + // Set Auth Protocol + if (profile->user && profile->password) { + PQMIWDS_AUTH_PREFERENCE pAuthPref = (PQMIWDS_AUTH_PREFERENCE)(pTLV + TLVLength); + pAuthPref->TLVType = 0x1D; + pAuthPref->TLVLength = cpu_to_le16(0x01); + pAuthPref->AuthPreference = profile->auth; // 0 ~ None, 1 ~ Pap, 2 ~ Chap, 3 ~ MsChapV2 + TLVLength += (le16_to_cpu(pAuthPref->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + return sizeof(QMIWDS_MODIFY_PROFILE_SETTINGS_REQ_MSG) + TLVLength; +} +#endif + +static USHORT WdsGetRuntimeSettingReq(PQMUX_MSG pMUXMsg, void *arg) { + pMUXMsg->GetRuntimeSettingsReq.TLVType = 0x10; + pMUXMsg->GetRuntimeSettingsReq.TLVLength = cpu_to_le16(0x04); + // the following mask also applies to IPV6 + pMUXMsg->GetRuntimeSettingsReq.Mask = cpu_to_le32(QMIWDS_GET_RUNTIME_SETTINGS_MASK_IPV4DNS_ADDR | + QMIWDS_GET_RUNTIME_SETTINGS_MASK_IPV4_ADDR | + QMIWDS_GET_RUNTIME_SETTINGS_MASK_MTU | + QMIWDS_GET_RUNTIME_SETTINGS_MASK_IPV4GATEWAY_ADDR); // | + // QMIWDS_GET_RUNTIME_SETTINGS_MASK_PCSCF_SV_ADDR | + // QMIWDS_GET_RUNTIME_SETTINGS_MASK_PCSCF_DOM_NAME; + + return sizeof(QMIWDS_GET_RUNTIME_SETTINGS_REQ_MSG); +} + +static PQCQMIMSG s_pRequest; +static PQCQMIMSG s_pResponse; +static pthread_mutex_t s_commandmutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t s_commandcond = PTHREAD_COND_INITIALIZER; + +static int is_response(const PQCQMIMSG pRequest, const PQCQMIMSG pResponse) { + if ((pRequest->QMIHdr.QMIType == pResponse->QMIHdr.QMIType) + && (pRequest->QMIHdr.ClientId == pResponse->QMIHdr.ClientId)) { + USHORT requestTID, responseTID; + if (pRequest->QMIHdr.QMIType == QMUX_TYPE_CTL) { + requestTID = pRequest->CTLMsg.QMICTLMsgHdr.TransactionId; + responseTID = pResponse->CTLMsg.QMICTLMsgHdr.TransactionId; + } else { + requestTID = le16_to_cpu(pRequest->MUXMsg.QMUXHdr.TransactionId); + responseTID = le16_to_cpu(pResponse->MUXMsg.QMUXHdr.TransactionId); + } + return (requestTID == responseTID); + } + return 0; +} + +int QmiThreadSendQMITimeout(PQCQMIMSG pRequest, PQCQMIMSG *ppResponse, unsigned msecs) { + int ret; + + if (!pRequest) + { + return -EINVAL; + } + + pthread_mutex_lock(&s_commandmutex); + + if (ppResponse) + *ppResponse = NULL; + + dump_qmi(pRequest, le16_to_cpu(pRequest->QMIHdr.Length) + 1); + + s_pRequest = pRequest; + s_pResponse = NULL; + + if (!strncmp(qmichannel, "/dev/qcqmi", strlen("/dev/qcqmi"))) + ret = GobiNetSendQMI(pRequest); + else + ret = QmiWwanSendQMI(pRequest); + + if (ret == 0) { + ret = pthread_cond_timeout_np(&s_commandcond, &s_commandmutex, msecs); + if (!ret) { + if (s_pResponse && ppResponse) { + *ppResponse = s_pResponse; + } else { + if (s_pResponse) { + free(s_pResponse); + s_pResponse = NULL; + } + } + } else { + dbg_time("%s pthread_cond_timeout_np=%d, errno: %d (%s)", __func__, ret, errno, strerror(errno)); + } + } + + pthread_mutex_unlock(&s_commandmutex); + + return ret; +} + +int QmiThreadSendQMI(PQCQMIMSG pRequest, PQCQMIMSG *ppResponse) { + return QmiThreadSendQMITimeout(pRequest, ppResponse, 30 * 1000); +} + +void QmiThreadRecvQMI(PQCQMIMSG pResponse) { + pthread_mutex_lock(&s_commandmutex); + if (pResponse == NULL) { + if (s_pRequest) { + free(s_pRequest); + s_pRequest = NULL; + s_pResponse = NULL; + pthread_cond_signal(&s_commandcond); + } + pthread_mutex_unlock(&s_commandmutex); + return; + } + dump_qmi(pResponse, le16_to_cpu(pResponse->QMIHdr.Length) + 1); + if (s_pRequest && is_response(s_pRequest, pResponse)) { + free(s_pRequest); + s_pRequest = NULL; + s_pResponse = malloc(le16_to_cpu(pResponse->QMIHdr.Length) + 1); + if (s_pResponse != NULL) { + memcpy(s_pResponse, pResponse, le16_to_cpu(pResponse->QMIHdr.Length) + 1); + } + pthread_cond_signal(&s_commandcond); + } else if ((pResponse->QMIHdr.QMIType == QMUX_TYPE_NAS) + && (le16_to_cpu(pResponse->MUXMsg.QMUXMsgHdrResp.Type) == QMINAS_SERVING_SYSTEM_IND)) { + qmidevice_send_event_to_main(RIL_UNSOL_RESPONSE_VOICE_NETWORK_STATE_CHANGED); + } else if ((pResponse->QMIHdr.QMIType == QMUX_TYPE_WDS) + && (le16_to_cpu(pResponse->MUXMsg.QMUXMsgHdrResp.Type) == QMIWDS_GET_PKT_SRVC_STATUS_IND)) { + qmidevice_send_event_to_main(RIL_UNSOL_DATA_CALL_LIST_CHANGED); + } else if ((pResponse->QMIHdr.QMIType == QMUX_TYPE_NAS) + && (le16_to_cpu(pResponse->MUXMsg.QMUXMsgHdrResp.Type) == QMINAS_SYS_INFO_IND)) { + qmidevice_send_event_to_main(RIL_UNSOL_RESPONSE_VOICE_NETWORK_STATE_CHANGED); + } else { + if (debug_qmi) + dbg_time("nobody care this qmi msg!!"); + } + pthread_mutex_unlock(&s_commandmutex); +} + +int requestSetEthMode(PROFILE_T *profile) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV linkProto; + + pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS_ADMIN, QMIWDS_ADMIN_SET_DATA_FORMAT_REQ, WdaSetDataFormat, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + linkProto = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x11); + if (linkProto != NULL) { + profile->rawIP = (le32_to_cpu(linkProto->Value) == 2); + s_9x07 = profile->rawIP; + } + + + free(pResponse); + return 0; +} + +#ifdef CONFIG_SIM +int requestGetPINStatus(SIM_Status *pSIMStatus) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + PQMIDMS_UIM_PIN_STATUS pPin1Status = NULL; + //PQMIDMS_UIM_PIN_STATUS pPin2Status = NULL; + + if (s_9x07 && qmiclientId[QMUX_TYPE_UIM]) + pRequest = ComposeQMUXMsg(QMUX_TYPE_UIM, QMIUIM_GET_CARD_STATUS_REQ, NULL, NULL); + else + pRequest = ComposeQMUXMsg(QMUX_TYPE_DMS, QMIDMS_UIM_GET_PIN_STATUS_REQ, NULL, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + pPin1Status = (PQMIDMS_UIM_PIN_STATUS)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x11); + //pPin2Status = (PQMIDMS_UIM_PIN_STATUS)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x12); + + if (pPin1Status != NULL) { + if (pPin1Status->PINStatus == QMI_PIN_STATUS_NOT_VERIF) { + *pSIMStatus = SIM_PIN; + } else if (pPin1Status->PINStatus == QMI_PIN_STATUS_BLOCKED) { + *pSIMStatus = SIM_PUK; + } else if (pPin1Status->PINStatus == QMI_PIN_STATUS_PERM_BLOCKED) { + *pSIMStatus = SIM_BAD; + } + } + + free(pResponse); + return 0; +} + +int requestGetSIMStatus(SIM_Status *pSIMStatus) { //RIL_REQUEST_GET_SIM_STATUS + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + const char * SIM_Status_String[] = { + "SIM_ABSENT", + "SIM_NOT_READY", + "SIM_READY", /* SIM_READY means the radio state is RADIO_STATE_SIM_READY */ + "SIM_PIN", + "SIM_PUK", + "SIM_NETWORK_PERSONALIZATION" + }; + + +__requestGetSIMStatus: + if (s_9x07 && qmiclientId[QMUX_TYPE_UIM]) + pRequest = ComposeQMUXMsg(QMUX_TYPE_UIM, QMIUIM_GET_CARD_STATUS_REQ, NULL, NULL); + else + pRequest = ComposeQMUXMsg(QMUX_TYPE_DMS, QMIDMS_UIM_GET_STATE_REQ, NULL, NULL); + + err = QmiThreadSendQMI(pRequest, &pResponse); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + if (QMI_ERR_OP_DEVICE_UNSUPPORTED == le16_to_cpu(pResponse->MUXMsg.QMUXMsgHdrResp.QMUXError)) { + sleep(1); + goto __requestGetSIMStatus; + } + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + *pSIMStatus = SIM_ABSENT; + if (s_9x07 && qmiclientId[QMUX_TYPE_UIM]) + { + PQMIUIM_CARD_STATUS pCardStatus = NULL; + PQMIUIM_PIN_STATE pPINState = NULL; + UCHAR CardState; + UCHAR PIN1State; + //UCHAR PIN1Retries; + //UCHAR PUK1Retries; + //UCHAR PIN2State; + //UCHAR PIN2Retries; + //UCHAR PUK2Retries; + + pCardStatus = (PQMIUIM_CARD_STATUS)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x10); + if (pCardStatus != NULL) + { + pPINState = (PQMIUIM_PIN_STATE)((PUCHAR)pCardStatus + sizeof(QMIUIM_CARD_STATUS) + pCardStatus->AIDLength); + CardState = pCardStatus->CardState; + if (pPINState->UnivPIN == 1) + { + PIN1State = pCardStatus->UPINState; + //PIN1Retries = pCardStatus->UPINRetries; + //PUK1Retries = pCardStatus->UPUKRetries; + } + else + { + PIN1State = pPINState->PIN1State; + //PIN1Retries = pPINState->PIN1Retries; + //PUK1Retries = pPINState->PUK1Retries; + } + //PIN2State = pPINState->PIN2State; + //PIN2Retries = pPINState->PIN2Retries; + //PUK2Retries = pPINState->PUK2Retries; + } + + *pSIMStatus = SIM_ABSENT; + if ((CardState == 0x01) && ((PIN1State == QMI_PIN_STATUS_VERIFIED)|| (PIN1State == QMI_PIN_STATUS_DISABLED))) + { + *pSIMStatus = SIM_READY; + } + else if (CardState == 0x01) + { + if (PIN1State == QMI_PIN_STATUS_NOT_VERIF) + { + *pSIMStatus = SIM_PIN; + } + if ( PIN1State == QMI_PIN_STATUS_BLOCKED) + { + *pSIMStatus = SIM_PUK; + } + else if (PIN1State == QMI_PIN_STATUS_PERM_BLOCKED) + { + *pSIMStatus = SIM_BAD; + } + else if (PIN1State == QMI_PIN_STATUS_NOT_INIT || PIN1State == QMI_PIN_STATUS_VERIFIED || PIN1State == QMI_PIN_STATUS_DISABLED) + { + *pSIMStatus = SIM_READY; + } + } + else if (CardState == 0x00 || CardState == 0x02) + { + } + else + { + } + } + else + { + //UIM state. Values: + // 0x00 UIM initialization completed + // 0x01 UIM is locked or the UIM failed + // 0x02 UIM is not present + // 0x03 Reserved + // 0xFF UIM state is currently + //unavailable + if (pResponse->MUXMsg.UIMGetStateResp.UIMState == 0x00) { + *pSIMStatus = SIM_READY; + } else if (pResponse->MUXMsg.UIMGetStateResp.UIMState == 0x01) { + *pSIMStatus = SIM_ABSENT; + err = requestGetPINStatus(pSIMStatus); + } else if ((pResponse->MUXMsg.UIMGetStateResp.UIMState == 0x02) || (pResponse->MUXMsg.UIMGetStateResp.UIMState == 0xFF)) { + *pSIMStatus = SIM_ABSENT; + } else { + *pSIMStatus = SIM_ABSENT; + } + } + dbg_time("%s SIMStatus: %s", __func__, SIM_Status_String[*pSIMStatus]); + + free(pResponse); + return 0; +} + +int requestEnterSimPin(const CHAR *pPinCode) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + + if (s_9x07 && qmiclientId[QMUX_TYPE_UIM]) + pRequest = ComposeQMUXMsg(QMUX_TYPE_UIM, QMIUIM_VERIFY_PIN_REQ, UimVerifyPinReqSend, (void *)pPinCode); + else + pRequest = ComposeQMUXMsg(QMUX_TYPE_DMS, QMIDMS_UIM_VERIFY_PIN_REQ, DmsUIMVerifyPinReqSend, (void *)pPinCode); + err = QmiThreadSendQMI(pRequest, &pResponse); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + free(pResponse); + return 0; +} +#endif + +#if 1 +static void quectel_convert_cdma_mcc_2_ascii_mcc( USHORT *p_mcc, USHORT mcc ) +{ + unsigned int d1, d2, d3, buf = mcc + 111; + + if ( mcc == 0x3FF ) // wildcard + { + *p_mcc = 3; + } + else + { + d3 = buf % 10; + buf = ( d3 == 0 ) ? (buf-10)/10 : buf/10; + + d2 = buf % 10; + buf = ( d2 == 0 ) ? (buf-10)/10 : buf/10; + + d1 = ( buf == 10 ) ? 0 : buf; + +//dbg_time("d1:%d, d2:%d,d3:%d",d1,d2,d3); + if ( d1<10 && d2<10 && d3<10 ) + { + *p_mcc = d1*100+d2*10+d3; +#if 0 + *(p_mcc+0) = '0' + d1; + *(p_mcc+1) = '0' + d2; + *(p_mcc+2) = '0' + d3; +#endif + } + else + { + //dbg_time( "invalid digits %d %d %d", d1, d2, d3 ); + *p_mcc = 0; + } + } +} + +static void quectel_convert_cdma_mnc_2_ascii_mnc( USHORT *p_mnc, USHORT imsi_11_12) +{ + unsigned int d1, d2, buf = imsi_11_12 + 11; + + if ( imsi_11_12 == 0x7F ) // wildcard + { + *p_mnc = 7; + } + else + { + d2 = buf % 10; + buf = ( d2 == 0 ) ? (buf-10)/10 : buf/10; + + d1 = ( buf == 10 ) ? 0 : buf; + + if ( d1<10 && d2<10 ) + { + *p_mnc = d1*10 + d2; + } + else + { + //dbg_time( "invalid digits %d %d", d1, d2, 0 ); + *p_mnc = 0; + } + } +} + +int requestGetHomeNetwork(USHORT *p_mcc, USHORT *p_mnc, USHORT *p_sid, USHORT *p_nid) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + PHOME_NETWORK pHomeNetwork; + PHOME_NETWORK_SYSTEMID pHomeNetworkSystemID; + + pRequest = ComposeQMUXMsg(QMUX_TYPE_NAS, QMINAS_GET_HOME_NETWORK_REQ, NULL, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + pHomeNetwork = (PHOME_NETWORK)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x01); + if (pHomeNetwork && p_mcc && p_mnc ) { + *p_mcc = le16_to_cpu(pHomeNetwork->MobileCountryCode); + *p_mnc = le16_to_cpu(pHomeNetwork->MobileNetworkCode); + //dbg_time("%s MobileCountryCode: %d, MobileNetworkCode: %d", __func__, *pMobileCountryCode, *pMobileNetworkCode); + } + + pHomeNetworkSystemID = (PHOME_NETWORK_SYSTEMID)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x10); + if (pHomeNetworkSystemID && p_sid && p_nid) { + *p_sid = le16_to_cpu(pHomeNetworkSystemID->SystemID); //china-hefei: sid 14451 + *p_nid = le16_to_cpu(pHomeNetworkSystemID->NetworkID); + //dbg_time("%s SystemID: %d, NetworkID: %d", __func__, *pSystemID, *pNetworkID); + } + + free(pResponse); + + return 0; +} +#endif + +#if 0 +// Lookup table for carriers known to produce SIMs which incorrectly indicate MNC length. +static const char * MCCMNC_CODES_HAVING_3DIGITS_MNC[] = { + "302370", "302720", "310260", + "405025", "405026", "405027", "405028", "405029", "405030", "405031", "405032", + "405033", "405034", "405035", "405036", "405037", "405038", "405039", "405040", + "405041", "405042", "405043", "405044", "405045", "405046", "405047", "405750", + "405751", "405752", "405753", "405754", "405755", "405756", "405799", "405800", + "405801", "405802", "405803", "405804", "405805", "405806", "405807", "405808", + "405809", "405810", "405811", "405812", "405813", "405814", "405815", "405816", + "405817", "405818", "405819", "405820", "405821", "405822", "405823", "405824", + "405825", "405826", "405827", "405828", "405829", "405830", "405831", "405832", + "405833", "405834", "405835", "405836", "405837", "405838", "405839", "405840", + "405841", "405842", "405843", "405844", "405845", "405846", "405847", "405848", + "405849", "405850", "405851", "405852", "405853", "405875", "405876", "405877", + "405878", "405879", "405880", "405881", "405882", "405883", "405884", "405885", + "405886", "405908", "405909", "405910", "405911", "405912", "405913", "405914", + "405915", "405916", "405917", "405918", "405919", "405920", "405921", "405922", + "405923", "405924", "405925", "405926", "405927", "405928", "405929", "405930", + "405931", "405932", "502142", "502143", "502145", "502146", "502147", "502148" +}; + +static const char * MCC_CODES_HAVING_3DIGITS_MNC[] = { + "302", //Canada + "310", //United States of America + "311", //United States of America + "312", //United States of America + "313", //United States of America + "314", //United States of America + "315", //United States of America + "316", //United States of America + "334", //Mexico + "338", //Jamaica + "342", //Barbados + "344", //Antigua and Barbuda + "346", //Cayman Islands + "348", //British Virgin Islands + "365", //Anguilla + "708", //Honduras (Republic of) + "722", //Argentine Republic + "732" //Colombia (Republic of) +}; + +int requestGetIMSI(const char **pp_imsi, USHORT *pMobileCountryCode, USHORT *pMobileNetworkCode) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + + if (pp_imsi) *pp_imsi = NULL; + if (pMobileCountryCode) *pMobileCountryCode = 0; + if (pMobileNetworkCode) *pMobileNetworkCode = 0; + + pRequest = ComposeQMUXMsg(QMUX_TYPE_DMS, QMIDMS_UIM_GET_IMSI_REQ, NULL, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + if (pMUXMsg->UIMGetIMSIResp.TLV2Type == 0x01 && le16_to_cpu(pMUXMsg->UIMGetIMSIResp.TLV2Length) >= 5) { + int mnc_len = 2; + unsigned i; + char tmp[4]; + + if (pp_imsi) *pp_imsi = strndup((const char *)(&pMUXMsg->UIMGetIMSIResp.IMSI), le16_to_cpu(pMUXMsg->UIMGetIMSIResp.TLV2Length)); + + for (i = 0; i < sizeof(MCCMNC_CODES_HAVING_3DIGITS_MNC)/sizeof(MCCMNC_CODES_HAVING_3DIGITS_MNC[0]); i++) { + if (!strncmp((const char *)(&pMUXMsg->UIMGetIMSIResp.IMSI), MCCMNC_CODES_HAVING_3DIGITS_MNC[i], 6)) { + mnc_len = 3; + break; + } + } + if (mnc_len == 2) { + for (i = 0; i < sizeof(MCC_CODES_HAVING_3DIGITS_MNC)/sizeof(MCC_CODES_HAVING_3DIGITS_MNC[0]); i++) { + if (!strncmp((const char *)(&pMUXMsg->UIMGetIMSIResp.IMSI), MCC_CODES_HAVING_3DIGITS_MNC[i], 3)) { + mnc_len = 3; + break; + } + } + } + + tmp[0] = (&pMUXMsg->UIMGetIMSIResp.IMSI)[0]; + tmp[1] = (&pMUXMsg->UIMGetIMSIResp.IMSI)[1]; + tmp[2] = (&pMUXMsg->UIMGetIMSIResp.IMSI)[2]; + tmp[3] = 0; + if (pMobileCountryCode) *pMobileCountryCode = atoi(tmp); + tmp[0] = (&pMUXMsg->UIMGetIMSIResp.IMSI)[3]; + tmp[1] = (&pMUXMsg->UIMGetIMSIResp.IMSI)[4]; + tmp[2] = 0; + if (mnc_len == 3) { + tmp[2] = (&pMUXMsg->UIMGetIMSIResp.IMSI)[6]; + } + if (pMobileNetworkCode) *pMobileNetworkCode = atoi(tmp); + } + + free(pResponse); + + return 0; +} +#endif + +struct wwan_data_class_str class2str[] = { + {WWAN_DATA_CLASS_NONE, "UNKNOWN"}, + {WWAN_DATA_CLASS_GPRS, "GPRS"}, + {WWAN_DATA_CLASS_EDGE, "EDGE"}, + {WWAN_DATA_CLASS_UMTS, "UMTS"}, + {WWAN_DATA_CLASS_HSDPA, "HSDPA"}, + {WWAN_DATA_CLASS_HSUPA, "HSUPA"}, + {WWAN_DATA_CLASS_LTE, "LTE"}, + {WWAN_DATA_CLASS_1XRTT, "1XRTT"}, + {WWAN_DATA_CLASS_1XEVDO, "1XEVDO"}, + {WWAN_DATA_CLASS_1XEVDO_REVA, "1XEVDO_REVA"}, + {WWAN_DATA_CLASS_1XEVDV, "1XEVDV"}, + {WWAN_DATA_CLASS_3XRTT, "3XRTT"}, + {WWAN_DATA_CLASS_1XEVDO_REVB, "1XEVDO_REVB"}, + {WWAN_DATA_CLASS_UMB, "UMB"}, + {WWAN_DATA_CLASS_CUSTOM, "CUSTOM"}, +}; + +CHAR *wwan_data_class2str(ULONG class) +{ + int i = 0; + for (i = 0; i < sizeof(class2str)/sizeof(class2str[0]); i++) { + if (class2str[i].class == class) { + return class2str[i].str; + } + } + return "UNKNOWN"; +} + +int requestRegistrationState2(UCHAR *pPSAttachedState) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + USHORT MobileCountryCode = 0; + USHORT MobileNetworkCode = 0; + const char *pDataCapStr = "UNKNOW"; + LONG remainingLen; + PSERVICE_STATUS_INFO pServiceStatusInfo; + int is_lte = 0; + PCDMA_SYSTEM_INFO pCdmaSystemInfo; + PHDR_SYSTEM_INFO pHdrSystemInfo; + PGSM_SYSTEM_INFO pGsmSystemInfo; + PWCDMA_SYSTEM_INFO pWcdmaSystemInfo; + PLTE_SYSTEM_INFO pLteSystemInfo; + PTDSCDMA_SYSTEM_INFO pTdscdmaSystemInfo; + UCHAR DeviceClass = 0; + ULONG DataCapList = 0; + + pRequest = ComposeQMUXMsg(QMUX_TYPE_NAS, QMINAS_GET_SYS_INFO_REQ, NULL, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + pServiceStatusInfo = (PSERVICE_STATUS_INFO)(((PCHAR)&pMUXMsg->GetSysInfoResp) + QCQMUX_MSG_HDR_SIZE); + remainingLen = le16_to_cpu(pMUXMsg->GetSysInfoResp.Length); + + s_is_cdma = 0; + while (remainingLen > 0) { + switch (pServiceStatusInfo->TLVType) { + case 0x10: // CDMA + if (pServiceStatusInfo->SrvStatus == 0x02) { + DataCapList = WWAN_DATA_CLASS_1XRTT| + WWAN_DATA_CLASS_1XEVDO| + WWAN_DATA_CLASS_1XEVDO_REVA| + WWAN_DATA_CLASS_1XEVDV| + WWAN_DATA_CLASS_1XEVDO_REVB; + DeviceClass = DEVICE_CLASS_CDMA; + s_is_cdma = (0 == is_lte); + } + break; + case 0x11: // HDR + if (pServiceStatusInfo->SrvStatus == 0x02) { + DataCapList = WWAN_DATA_CLASS_3XRTT| + WWAN_DATA_CLASS_UMB; + DeviceClass = DEVICE_CLASS_CDMA; + s_is_cdma = (0 == is_lte); + } + break; + case 0x12: // GSM + if (pServiceStatusInfo->SrvStatus == 0x02) { + DataCapList = WWAN_DATA_CLASS_GPRS| + WWAN_DATA_CLASS_EDGE; + DeviceClass = DEVICE_CLASS_GSM; + } + break; + case 0x13: // WCDMA + if (pServiceStatusInfo->SrvStatus == 0x02) { + DataCapList = WWAN_DATA_CLASS_UMTS; + DeviceClass = DEVICE_CLASS_GSM; + } + break; + case 0x14: // LTE + if (pServiceStatusInfo->SrvStatus == 0x02) { + DataCapList = WWAN_DATA_CLASS_LTE; + DeviceClass = DEVICE_CLASS_GSM; + is_lte = 1; + s_is_cdma = 0; + } + break; + case 0x24: // TDSCDMA + if (pServiceStatusInfo->SrvStatus == 0x02) { + pDataCapStr = "TD-SCDMA"; + } + break; + case 0x15: // CDMA + // CDMA_SYSTEM_INFO + pCdmaSystemInfo = (PCDMA_SYSTEM_INFO)pServiceStatusInfo; + if (pCdmaSystemInfo->SrvDomainValid == 0x01) { + *pPSAttachedState = 0; + if (pCdmaSystemInfo->SrvDomain & 0x02) { + *pPSAttachedState = 1; + s_is_cdma = (0 == is_lte); + } + } + if (pCdmaSystemInfo->SrvCapabilityValid == 0x01) { + *pPSAttachedState = 0; + if (pCdmaSystemInfo->SrvCapability & 0x02) { + *pPSAttachedState = 1; + s_is_cdma = (0 == is_lte); + } + } + if (pCdmaSystemInfo->NetworkIdValid == 0x01) { + int i; + CHAR temp[10]; + strncpy(temp, (CHAR *)pCdmaSystemInfo->MCC, 3); + temp[3] = '\0'; + for (i = 0; i < 4; i++) { + if ((UCHAR)temp[i] == 0xFF) { + temp[i] = '\0'; + } + } + MobileCountryCode = (USHORT)atoi(temp); + + strncpy(temp, (CHAR *)pCdmaSystemInfo->MNC, 3); + temp[3] = '\0'; + for (i = 0; i < 4; i++) { + if ((UCHAR)temp[i] == 0xFF) { + temp[i] = '\0'; + } + } + MobileNetworkCode = (USHORT)atoi(temp); + } + break; + case 0x16: // HDR + // HDR_SYSTEM_INFO + pHdrSystemInfo = (PHDR_SYSTEM_INFO)pServiceStatusInfo; + if (pHdrSystemInfo->SrvDomainValid == 0x01) { + *pPSAttachedState = 0; + if (pHdrSystemInfo->SrvDomain & 0x02) { + *pPSAttachedState = 1; + s_is_cdma = (0 == is_lte); + } + } + if (pHdrSystemInfo->SrvCapabilityValid == 0x01) { + *pPSAttachedState = 0; + if (pHdrSystemInfo->SrvCapability & 0x02) { + *pPSAttachedState = 1; + s_is_cdma = (0 == is_lte); + } + } + USHORT cmda_mcc = 0, cdma_mnc = 0; + if(!requestGetHomeNetwork(&cmda_mcc, &cdma_mnc,NULL, NULL) && cmda_mcc) { + quectel_convert_cdma_mcc_2_ascii_mcc(&MobileCountryCode, cmda_mcc); + quectel_convert_cdma_mnc_2_ascii_mnc(&MobileNetworkCode, cdma_mnc); + } + break; + case 0x17: // GSM + // GSM_SYSTEM_INFO + pGsmSystemInfo = (PGSM_SYSTEM_INFO)pServiceStatusInfo; + if (pGsmSystemInfo->SrvDomainValid == 0x01) { + *pPSAttachedState = 0; + if (pGsmSystemInfo->SrvDomain & 0x02) { + *pPSAttachedState = 1; + } + } + if (pGsmSystemInfo->SrvCapabilityValid == 0x01) { + *pPSAttachedState = 0; + if (pGsmSystemInfo->SrvCapability & 0x02) { + *pPSAttachedState = 1; + } + } + if (pGsmSystemInfo->NetworkIdValid == 0x01) { + int i; + CHAR temp[10]; + strncpy(temp, (CHAR *)pGsmSystemInfo->MCC, 3); + temp[3] = '\0'; + for (i = 0; i < 4; i++) { + if ((UCHAR)temp[i] == 0xFF) { + temp[i] = '\0'; + } + } + MobileCountryCode = (USHORT)atoi(temp); + + strncpy(temp, (CHAR *)pGsmSystemInfo->MNC, 3); + temp[3] = '\0'; + for (i = 0; i < 4; i++) { + if ((UCHAR)temp[i] == 0xFF) { + temp[i] = '\0'; + } + } + MobileNetworkCode = (USHORT)atoi(temp); + } + break; + case 0x18: // WCDMA + // WCDMA_SYSTEM_INFO + pWcdmaSystemInfo = (PWCDMA_SYSTEM_INFO)pServiceStatusInfo; + if (pWcdmaSystemInfo->SrvDomainValid == 0x01) { + *pPSAttachedState = 0; + if (pWcdmaSystemInfo->SrvDomain & 0x02) { + *pPSAttachedState = 1; + } + } + if (pWcdmaSystemInfo->SrvCapabilityValid == 0x01) { + *pPSAttachedState = 0; + if (pWcdmaSystemInfo->SrvCapability & 0x02) { + *pPSAttachedState = 1; + } + } + if (pWcdmaSystemInfo->NetworkIdValid == 0x01) { + int i; + CHAR temp[10]; + strncpy(temp, (CHAR *)pWcdmaSystemInfo->MCC, 3); + temp[3] = '\0'; + for (i = 0; i < 4; i++) { + if ((UCHAR)temp[i] == 0xFF) { + temp[i] = '\0'; + } + } + MobileCountryCode = (USHORT)atoi(temp); + + strncpy(temp, (CHAR *)pWcdmaSystemInfo->MNC, 3); + temp[3] = '\0'; + for (i = 0; i < 4; i++) { + if ((UCHAR)temp[i] == 0xFF) { + temp[i] = '\0'; + } + } + MobileNetworkCode = (USHORT)atoi(temp); + } + break; + case 0x19: // LTE_SYSTEM_INFO + // LTE_SYSTEM_INFO + pLteSystemInfo = (PLTE_SYSTEM_INFO)pServiceStatusInfo; + if (pLteSystemInfo->SrvDomainValid == 0x01) { + *pPSAttachedState = 0; + if (pLteSystemInfo->SrvDomain & 0x02) { + *pPSAttachedState = 1; + is_lte = 1; + s_is_cdma = 0; + } + } + if (pLteSystemInfo->SrvCapabilityValid == 0x01) { + *pPSAttachedState = 0; + if (pLteSystemInfo->SrvCapability & 0x02) { + *pPSAttachedState = 1; + is_lte = 1; + s_is_cdma = 0; + } + } + if (pLteSystemInfo->NetworkIdValid == 0x01) { + int i; + CHAR temp[10]; + strncpy(temp, (CHAR *)pLteSystemInfo->MCC, 3); + temp[3] = '\0'; + for (i = 0; i < 4; i++) { + if ((UCHAR)temp[i] == 0xFF) { + temp[i] = '\0'; + } + } + MobileCountryCode = (USHORT)atoi(temp); + + strncpy(temp, (CHAR *)pLteSystemInfo->MNC, 3); + temp[3] = '\0'; + for (i = 0; i < 4; i++) { + if ((UCHAR)temp[i] == 0xFF) { + temp[i] = '\0'; + } + } + MobileNetworkCode = (USHORT)atoi(temp); + } + break; + case 0x25: // TDSCDMA + // TDSCDMA_SYSTEM_INFO + pTdscdmaSystemInfo = (PTDSCDMA_SYSTEM_INFO)pServiceStatusInfo; + if (pTdscdmaSystemInfo->SrvDomainValid == 0x01) { + *pPSAttachedState = 0; + if (pTdscdmaSystemInfo->SrvDomain & 0x02) { + *pPSAttachedState = 1; + } + } + if (pTdscdmaSystemInfo->SrvCapabilityValid == 0x01) { + *pPSAttachedState = 0; + if (pTdscdmaSystemInfo->SrvCapability & 0x02) { + *pPSAttachedState = 1; + } + } + if (pTdscdmaSystemInfo->NetworkIdValid == 0x01) { + int i; + CHAR temp[10]; + strncpy(temp, (CHAR *)pTdscdmaSystemInfo->MCC, 3); + temp[3] = '\0'; + for (i = 0; i < 4; i++) { + if ((UCHAR)temp[i] == 0xFF) { + temp[i] = '\0'; + } + } + MobileCountryCode = (USHORT)atoi(temp); + + strncpy(temp, (CHAR *)pTdscdmaSystemInfo->MNC, 3); + temp[3] = '\0'; + for (i = 0; i < 4; i++) { + if ((UCHAR)temp[i] == 0xFF) { + temp[i] = '\0'; + } + } + MobileNetworkCode = (USHORT)atoi(temp); + } + break; + default: + break; + } /* switch (pServiceStatusInfo->TLYType) */ + remainingLen -= (le16_to_cpu(pServiceStatusInfo->TLVLength) + 3); + pServiceStatusInfo = (PSERVICE_STATUS_INFO)((PCHAR)&pServiceStatusInfo->TLVLength + le16_to_cpu(pServiceStatusInfo->TLVLength) + sizeof(USHORT)); + } /* while (remainingLen > 0) */ + + if (DeviceClass == DEVICE_CLASS_CDMA) { + if (DataCapList & WWAN_DATA_CLASS_1XEVDO_REVB) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_1XEVDO_REVB); + } else if (DataCapList & WWAN_DATA_CLASS_1XEVDO_REVA) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_1XEVDO_REVA); + } else if (DataCapList & WWAN_DATA_CLASS_1XEVDO) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_1XEVDO); + } else if (DataCapList & WWAN_DATA_CLASS_1XRTT) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_1XRTT); + } else if (DataCapList & WWAN_DATA_CLASS_3XRTT) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_3XRTT); + } else if (DataCapList & WWAN_DATA_CLASS_UMB) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_UMB); + } + } else { + if (DataCapList & WWAN_DATA_CLASS_LTE) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_LTE); + } else if ((DataCapList & WWAN_DATA_CLASS_HSDPA) && (DataCapList & WWAN_DATA_CLASS_HSUPA)) { + pDataCapStr = "HSDPA_HSUPA"; + } else if (DataCapList & WWAN_DATA_CLASS_HSDPA) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_HSDPA); + } else if (DataCapList & WWAN_DATA_CLASS_HSUPA) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_HSUPA); + } else if (DataCapList & WWAN_DATA_CLASS_UMTS) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_UMTS); + } else if (DataCapList & WWAN_DATA_CLASS_EDGE) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_EDGE); + } else if (DataCapList & WWAN_DATA_CLASS_GPRS) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_GPRS); + } + } + + dbg_time("%s MCC: %d, MNC: %d, PS: %s, DataCap: %s", __func__, + MobileCountryCode, MobileNetworkCode, (*pPSAttachedState == 1) ? "Attached" : "Detached" , pDataCapStr); + + free(pResponse); + + return 0; +} + +int requestRegistrationState(UCHAR *pPSAttachedState) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + PQMINAS_CURRENT_PLMN_MSG pCurrentPlmn; + PSERVING_SYSTEM pServingSystem; + PQMINAS_DATA_CAP pDataCap; + USHORT MobileCountryCode = 0; + USHORT MobileNetworkCode = 0; + const char *pDataCapStr = "UNKNOW"; + + if (s_9x07) { + return requestRegistrationState2(pPSAttachedState); + } + + pRequest = ComposeQMUXMsg(QMUX_TYPE_NAS, QMINAS_GET_SERVING_SYSTEM_REQ, NULL, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + pCurrentPlmn = (PQMINAS_CURRENT_PLMN_MSG)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x12); + if (pCurrentPlmn) { + MobileCountryCode = le16_to_cpu(pCurrentPlmn->MobileCountryCode); + MobileNetworkCode = le16_to_cpu(pCurrentPlmn->MobileNetworkCode); + } + + *pPSAttachedState = 0; + pServingSystem = (PSERVING_SYSTEM)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x01); + if (pServingSystem) { + //Packet-switched domain attach state of the mobile. + //0x00 PS_UNKNOWN ?Unknown or not applicable + //0x01 PS_ATTACHED ?Attached + //0x02 PS_DETACHED ?Detached + *pPSAttachedState = pServingSystem->RegistrationState; + if (pServingSystem->RegistrationState == 0x01) //0x01 ¨C REGISTERED ¨C Registered with a network + *pPSAttachedState = pServingSystem->PSAttachedState; + else { + //MobileCountryCode = MobileNetworkCode = 0; + *pPSAttachedState = 0x02; + } + } + + pDataCap = (PQMINAS_DATA_CAP)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x11); + if (pDataCap && pDataCap->DataCapListLen) { + UCHAR *DataCap = &pDataCap->DataCap; + if (pDataCap->DataCapListLen == 2) { + if ((DataCap[0] == 0x06) && ((DataCap[1] == 0x08) || (DataCap[1] == 0x0A))) + DataCap[0] = DataCap[1]; + } + switch (DataCap[0]) { + case 0x01: pDataCapStr = "GPRS"; break; + case 0x02: pDataCapStr = "EDGE"; break; + case 0x03: pDataCapStr = "HSDPA"; break; + case 0x04: pDataCapStr = "HSUPA"; break; + case 0x05: pDataCapStr = "UMTS"; break; + case 0x06: pDataCapStr = "1XRTT"; break; + case 0x07: pDataCapStr = "1XEVDO"; break; + case 0x08: pDataCapStr = "1XEVDO_REVA"; break; + case 0x09: pDataCapStr = "GPRS"; break; + case 0x0A: pDataCapStr = "1XEVDO_REVB"; break; + case 0x0B: pDataCapStr = "LTE"; break; + case 0x0C: pDataCapStr = "HSDPA"; break; + case 0x0D: pDataCapStr = "HSDPA"; break; + default: pDataCapStr = "UNKNOW"; break; + } + } + + if (pServingSystem && pServingSystem->RegistrationState == 0x01 && pServingSystem->InUseRadioIF && pServingSystem->RadioIF == 0x09) { + pDataCapStr = "TD-SCDMA"; + } + + s_is_cdma = 0; + if (pServingSystem && pServingSystem->RegistrationState == 0x01 && pServingSystem->InUseRadioIF && (pServingSystem->RadioIF == 0x01 || pServingSystem->RadioIF == 0x02)) { + USHORT cmda_mcc = 0, cdma_mnc = 0; + s_is_cdma = 1; + if(!requestGetHomeNetwork(&cmda_mcc, &cdma_mnc,NULL, NULL) && cmda_mcc) { + quectel_convert_cdma_mcc_2_ascii_mcc(&MobileCountryCode, cmda_mcc); + quectel_convert_cdma_mnc_2_ascii_mnc(&MobileNetworkCode, cdma_mnc); + } + if (1) { + PQCQMUX_TLV pTLV = (PQCQMUX_TLV)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x23); + if (pTLV) + s_hdr_personality = pTLV->Value; + else + s_hdr_personality = 0; + if (s_hdr_personality == 2) + pDataCapStr = "eHRPD"; + } + } + + dbg_time("%s MCC: %d, MNC: %d, PS: %s, DataCap: %s", __func__, + MobileCountryCode, MobileNetworkCode, (*pPSAttachedState == 1) ? "Attached" : "Detached" , pDataCapStr); + + free(pResponse); + + return 0; +} + +int requestQueryDataCall(UCHAR *pConnectionStatus) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + PQMIWDS_PKT_SRVC_TLV pPktSrvc; + UCHAR oldConnectionStatus = *pConnectionStatus; + + pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS, QMIWDS_GET_PKT_SRVC_STATUS_REQ, NULL, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + *pConnectionStatus = QWDS_PKT_DATA_DISCONNECTED; + pPktSrvc = (PQMIWDS_PKT_SRVC_TLV)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x01); + if (pPktSrvc) { + *pConnectionStatus = pPktSrvc->ConnectionStatus; + if ((le16_to_cpu(pPktSrvc->TLVLength) == 2) && (pPktSrvc->ReconfigReqd == 0x01)) + *pConnectionStatus = QWDS_PKT_DATA_DISCONNECTED; + } + + if (*pConnectionStatus == QWDS_PKT_DATA_DISCONNECTED) + WdsConnectionIPv4Handle = 0; + + if (oldConnectionStatus != *pConnectionStatus || debug_qmi) + dbg_time("%s ConnectionStatus: %s", __func__, (*pConnectionStatus == QWDS_PKT_DATA_CONNECTED) ? "CONNECTED" : "DISCONNECTED"); + + free(pResponse); + return 0; +} + +#if 0 +BOOLEAN QCMAIN_IsDualIPSupported(PMP_ADAPTER pAdapter) +{ + return (pAdapter->QMUXVersion[QMUX_TYPE_WDS].Major >= 1 && pAdapter->QMUXVersion[QMUX_TYPE_WDS].Minor >= 9); +} // QCMAIN_IsDualIPSupported +#endif + +int requestSetupDataCall(PROFILE_T *profile) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + + +//DualIPSupported means can get ipv4 & ipv6 address at the same time, one wds for ipv4, the other wds for ipv6 + profile->IPType = 0x04; //ipv4 first +__requestSetupDataCall: + pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS, QMIWDS_START_NETWORK_INTERFACE_REQ, WdsStartNwInterfaceReq, profile); + err = QmiThreadSendQMITimeout(pRequest, &pResponse, 120 * 1000); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) + { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + + if (!access("/proc/net/if_inet6", R_OK) && QMI_ERR_PIN_LOCKED == le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError) && profile->IPType == 0x04) + { + free(pResponse); + profile->IPType = 0x06; //ipv6 + goto __requestSetupDataCall; + } + + profile->IPType = 0x04; //reset to ipv4 first + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + WdsConnectionIPv4Handle = le32_to_cpu(pResponse->MUXMsg.StartNwInterfaceResp.Handle); + dbg_time("%s %s: 0x%08x", __func__, + (profile->IPType == 0x04) ? "WdsConnectionIPv4Handle" : "WdsConnectionIPv6Handle", WdsConnectionIPv4Handle); + + free(pResponse); + return 0; +} + +int requestDeactivateDefaultPDP(void) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + + pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS, QMIWDS_STOP_NETWORK_INTERFACE_REQ , WdsStopNwInterfaceReq, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + WdsConnectionIPv4Handle = 0; + free(pResponse); + return 0; +} + +int requestGetIPAddress(PROFILE_T *profile) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR pIpv4Addr; + PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV6_ADDR pIpv6Addr = NULL; + PQMIWDS_GET_RUNTIME_SETTINGS_TLV_MTU pMtu; + IPV4_T *pIpv4 = &profile->ipv4; + IPV6_T *pIpv6 = &profile->ipv6; + + if (profile->IPType == 0x04) + memset(pIpv4, 0x00, sizeof(IPV4_T)); + else + memset(pIpv6, 0x00, sizeof(IPV6_T)); + + pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS, QMIWDS_GET_RUNTIME_SETTINGS_REQ, WdsGetRuntimeSettingReq, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + pIpv4Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4PRIMARYDNS); + if (pIpv4Addr) { + pIpv4->DnsPrimary = pIpv4Addr->IPV4Address; + } + + pIpv4Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4SECONDARYDNS); + if (pIpv4Addr) { + pIpv4->DnsSecondary = pIpv4Addr->IPV4Address; + } + + pIpv4Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4GATEWAY); + if (pIpv4Addr) { + pIpv4->Gateway = pIpv4Addr->IPV4Address; + } + + pIpv4Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4SUBNET); + if (pIpv4Addr) { + pIpv4->SubnetMask = pIpv4Addr->IPV4Address; + } + + pIpv4Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4); + if (pIpv4Addr) { + pIpv4->Address = pIpv4Addr->IPV4Address; + } + + pIpv6Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV6_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6PRIMARYDNS); + if (pIpv6Addr) { + memcpy(pIpv6->DnsPrimary, pIpv6Addr->IPV6Address, 16); + } + + pIpv6Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV6_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6SECONDARYDNS); + if (pIpv6Addr) { + memcpy(pIpv6->DnsSecondary, pIpv6Addr->IPV6Address, 16); + } + + pIpv6Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV6_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6GATEWAY); + if (pIpv6Addr) { + memcpy(pIpv6->Gateway, pIpv6Addr->IPV6Address, 16); + pIpv6->PrefixLengthGateway = pIpv6Addr->PrefixLength; + } + + pIpv6Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV6_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6); + if (pIpv6Addr) { + memcpy(pIpv6->Address, pIpv6Addr->IPV6Address, 16); + pIpv6->PrefixLengthIPAddr = pIpv6Addr->PrefixLength; + } + + pMtu = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_MTU)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_MTU); + if (pMtu) { + if (profile->IPType == 0x04) + pIpv4->Mtu = le32_to_cpu(pMtu->Mtu); + else + pIpv6->Mtu = le32_to_cpu(pMtu->Mtu); + } + + free(pResponse); + return 0; +} + +#ifdef CONFIG_APN +int requestSetProfile(PROFILE_T *profile) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + + dbg_time("%s[%d] %s/%s/%s/%d", __func__, profile->pdp, profile->apn, profile->user, profile->password, profile->auth); + pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS, QMIWDS_MODIFY_PROFILE_SETTINGS_REQ, WdsModifyProfileSettingsReq, profile); + err = QmiThreadSendQMI(pRequest, &pResponse); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + free(pResponse); + return 0; +} + +int requestGetProfile(PROFILE_T *profile) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + char *apn = NULL; + char *user = NULL; + char *password = NULL; + int auth = 0; + PQMIWDS_APNNAME pApnName; + PQMIWDS_USERNAME pUserName; + PQMIWDS_PASSWD pPassWd; + PQMIWDS_AUTH_PREFERENCE pAuthPref; + + pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS, QMIWDS_GET_PROFILE_SETTINGS_REQ, WdsGetProfileSettingsReqSend, profile); + err = QmiThreadSendQMI(pRequest, &pResponse); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + pApnName = (PQMIWDS_APNNAME)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x14); + pUserName = (PQMIWDS_USERNAME)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x1B); + pPassWd = (PQMIWDS_PASSWD)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x1C); + pAuthPref = (PQMIWDS_AUTH_PREFERENCE)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x1D); + + if (pApnName/* && le16_to_cpu(pApnName->TLVLength)*/) + apn = strndup((const char *)(&pApnName->ApnName), le16_to_cpu(pApnName->TLVLength)); + if (pUserName/* && pUserName->UserName*/) + user = strndup((const char *)(&pUserName->UserName), le16_to_cpu(pUserName->TLVLength)); + if (pPassWd/* && le16_to_cpu(pPassWd->TLVLength)*/) + password = strndup((const char *)(&pPassWd->Passwd), le16_to_cpu(pPassWd->TLVLength)); + if (pAuthPref/* && le16_to_cpu(pAuthPref->TLVLength)*/) { + auth = pAuthPref->AuthPreference; + } + +#if 0 + if (profile) { + profile->apn = apn; + profile->user = user; + profile->password = password; + profile->auth = auth; + } +#endif + + dbg_time("%s[%d] %s/%s/%s/%d", __func__, profile->pdp, apn, user, password, auth); + + free(pResponse); + return 0; +} +#endif + +#ifdef CONFIG_VERSION +int requestBaseBandVersion(const char **pp_reversion) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + PDEVICE_REV_ID revId; + int err; + + if (pp_reversion) *pp_reversion = NULL; + + pRequest = ComposeQMUXMsg(QMUX_TYPE_DMS, QMIDMS_GET_DEVICE_REV_ID_REQ, NULL, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + revId = (PDEVICE_REV_ID)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x01); + + if (revId && le16_to_cpu(revId->TLVLength)) + { + char *DeviceRevisionID = strndup((const char *)(&revId->RevisionID), le16_to_cpu(revId->TLVLength)); + dbg_time("%s %s", __func__, DeviceRevisionID); + s_9x07 = (!strncmp(DeviceRevisionID, "EC21", 4) || !strncmp(DeviceRevisionID, "EC25", 4) + || !strncmp(DeviceRevisionID, "EC20CEF", 7)); //may fail to get QMUX_TYPE_WDS_ADMIN + if (pp_reversion) *pp_reversion = DeviceRevisionID; + } + + free(pResponse); + return 0; +} +#endif diff --git a/root/package/link4all/quectel-CM/src.all/QMIThread.h b/root/package/link4all/quectel-CM/src.all/QMIThread.h new file mode 100755 index 00000000..7685d14f --- /dev/null +++ b/root/package/link4all/quectel-CM/src.all/QMIThread.h @@ -0,0 +1,164 @@ +#ifndef __QMI_THREAD_H__ +#define __QMI_THREAD_H__ + +#define CONFIG_GOBINET +#define CONFIG_QMIWWAN +#define CONFIG_SIM +#define CONFIG_APN +#define CONFIG_VERSION +#define CONFIG_DEFAULT_PDP 1 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "MPQMI.h" +#include "MPQCTL.h" +#include "MPQMUX.h" + +#define DEVICE_CLASS_UNKNOWN 0 +#define DEVICE_CLASS_CDMA 1 +#define DEVICE_CLASS_GSM 2 + +#define WWAN_DATA_CLASS_NONE 0x00000000 +#define WWAN_DATA_CLASS_GPRS 0x00000001 +#define WWAN_DATA_CLASS_EDGE 0x00000002 /* EGPRS */ +#define WWAN_DATA_CLASS_UMTS 0x00000004 +#define WWAN_DATA_CLASS_HSDPA 0x00000008 +#define WWAN_DATA_CLASS_HSUPA 0x00000010 +#define WWAN_DATA_CLASS_LTE 0x00000020 +#define WWAN_DATA_CLASS_1XRTT 0x00010000 +#define WWAN_DATA_CLASS_1XEVDO 0x00020000 +#define WWAN_DATA_CLASS_1XEVDO_REVA 0x00040000 +#define WWAN_DATA_CLASS_1XEVDV 0x00080000 +#define WWAN_DATA_CLASS_3XRTT 0x00100000 +#define WWAN_DATA_CLASS_1XEVDO_REVB 0x00200000 /* for future use */ +#define WWAN_DATA_CLASS_UMB 0x00400000 +#define WWAN_DATA_CLASS_CUSTOM 0x80000000 + +struct wwan_data_class_str { + ULONG class; + CHAR *str; +}; + +#pragma pack(push, 1) + +typedef struct _QCQMIMSG { + QCQMI_HDR QMIHdr; + union { + QMICTL_MSG CTLMsg; + QMUX_MSG MUXMsg; + }; +} __attribute__ ((packed)) QCQMIMSG, *PQCQMIMSG; + +#pragma pack(pop) + +typedef struct __IPV4 { + ULONG Address; + ULONG Gateway; + ULONG SubnetMask; + ULONG DnsPrimary; + ULONG DnsSecondary; + ULONG Mtu; +} IPV4_T; + +typedef struct __IPV6 { + UCHAR Address[16]; + UCHAR Gateway[16]; + UCHAR SubnetMask[16]; + UCHAR DnsPrimary[16]; + UCHAR DnsSecondary[16]; + UCHAR PrefixLengthIPAddr; + UCHAR PrefixLengthGateway; + ULONG Mtu; +} IPV6_T; + +typedef struct __PROFILE { + char * qmichannel; + char * usbnet_adapter; + const char *apn; + const char *user; + const char *password; + const char *pincode; + int auth; + int pdp; + int IPType; + int rawIP; + IPV4_T ipv4; + IPV6_T ipv6; +} PROFILE_T; + +typedef enum { + SIM_ABSENT = 0, + SIM_NOT_READY = 1, + SIM_READY = 2, /* SIM_READY means the radio state is RADIO_STATE_SIM_READY */ + SIM_PIN = 3, + SIM_PUK = 4, + SIM_NETWORK_PERSONALIZATION = 5, + SIM_BAD = 6, +} SIM_Status; + +#define WDM_DEFAULT_BUFSIZE 256 +#define RIL_REQUEST_QUIT 0x1000 +#define RIL_INDICATE_DEVICE_CONNECTED 0x1002 +#define RIL_INDICATE_DEVICE_DISCONNECTED 0x1003 +#define RIL_UNSOL_RESPONSE_VOICE_NETWORK_STATE_CHANGED 0x1004 +#define RIL_UNSOL_DATA_CALL_LIST_CHANGED 0x1005 + +extern int pthread_cond_timeout_np(pthread_cond_t *cond, pthread_mutex_t * mutex, unsigned msecs); +extern int QmiThreadSendQMI(PQCQMIMSG pRequest, PQCQMIMSG *ppResponse); +extern int QmiThreadSendQMITimeout(PQCQMIMSG pRequest, PQCQMIMSG *ppResponse, unsigned msecs); +extern void QmiThreadRecvQMI(PQCQMIMSG pResponse); +extern int QmiWwanInit(void); +extern int QmiWwanDeInit(void); +extern int QmiWwanSendQMI(PQCQMIMSG pRequest); +extern void * QmiWwanThread(void *pData); +extern int GobiNetSendQMI(PQCQMIMSG pRequest); +extern void * GobiNetThread(void *pData); +extern void udhcpc_start(PROFILE_T *profile); +extern void udhcpc_stop(PROFILE_T *profile); +extern void dump_qmi(void *dataBuffer, int dataLen); +extern void qmidevice_send_event_to_main(int triger_event); +extern int requestSetEthMode(PROFILE_T *profile); +extern int requestGetSIMStatus(SIM_Status *pSIMStatus); +extern int requestEnterSimPin(const CHAR *pPinCode); +extern int requestRegistrationState(UCHAR *pPSAttachedState); +extern int requestQueryDataCall(UCHAR *pConnectionStatus); +extern int requestSetupDataCall(PROFILE_T *profile); +extern int requestDeactivateDefaultPDP(void); +extern int requestSetProfile(PROFILE_T *profile); +extern int requestGetProfile(PROFILE_T *profile); +extern int requestBaseBandVersion(const char **pp_reversion); +extern int requestGetIPAddress(PROFILE_T *profile); + +extern FILE *logfilefp; +extern int debug_qmi; +extern char * qmichannel; +extern int qmidevice_control_fd[2]; +extern int qmiclientId[QMUX_TYPE_WDS_ADMIN + 1]; +extern int cdc_wdm_fd; +extern void dbg_time (const char *fmt, ...); +extern USHORT le16_to_cpu(USHORT v16); +extern UINT le32_to_cpu (UINT v32); +extern UINT ql_swap32(UINT v32); +extern USHORT cpu_to_le16(USHORT v16); +extern UINT cpu_to_le32(UINT v32); +#endif diff --git a/root/package/link4all/quectel-CM/src.all/QmiWwanCM.c b/root/package/link4all/quectel-CM/src.all/QmiWwanCM.c new file mode 100755 index 00000000..694aca3b --- /dev/null +++ b/root/package/link4all/quectel-CM/src.all/QmiWwanCM.c @@ -0,0 +1,274 @@ +#include +#include +#include +#include +#include +#include "QMIThread.h" + +#ifdef CONFIG_QMIWWAN +int cdc_wdm_fd = -1; +static UCHAR GetQCTLTransactionId(void) { + static int TransactionId = 0; + if (++TransactionId > 0xFF) + TransactionId = 1; + return TransactionId; +} + +typedef USHORT (*CUSTOMQCTL)(PQMICTL_MSG pCTLMsg, void *arg); + +static PQCQMIMSG ComposeQCTLMsg(USHORT QMICTLType, CUSTOMQCTL customQctlMsgFunction, void *arg) { + UCHAR QMIBuf[WDM_DEFAULT_BUFSIZE]; + PQCQMIMSG pRequest = (PQCQMIMSG)QMIBuf; + int Length; + + pRequest->QMIHdr.IFType = USB_CTL_MSG_TYPE_QMI; + pRequest->QMIHdr.CtlFlags = 0x00; + pRequest->QMIHdr.QMIType = QMUX_TYPE_CTL; + pRequest->QMIHdr.ClientId= 0x00; + + pRequest->CTLMsg.QMICTLMsgHdr.CtlFlags = QMICTL_FLAG_REQUEST; + pRequest->CTLMsg.QMICTLMsgHdr.TransactionId = GetQCTLTransactionId(); + pRequest->CTLMsg.QMICTLMsgHdr.QMICTLType = cpu_to_le16(QMICTLType); + if (customQctlMsgFunction) + pRequest->CTLMsg.QMICTLMsgHdr.Length = cpu_to_le16(customQctlMsgFunction(&pRequest->CTLMsg, arg) - sizeof(QCQMICTL_MSG_HDR)); + else + pRequest->CTLMsg.QMICTLMsgHdr.Length = cpu_to_le16(0x0000); + + pRequest->QMIHdr.Length = cpu_to_le16(le16_to_cpu(pRequest->CTLMsg.QMICTLMsgHdr.Length) + sizeof(QCQMICTL_MSG_HDR) + sizeof(QCQMI_HDR) - 1); + Length = le16_to_cpu(pRequest->QMIHdr.Length) + 1; + + pRequest = (PQCQMIMSG)malloc(Length); + if (pRequest == NULL) { + dbg_time("%s fail to malloc", __func__); + } else { + memcpy(pRequest, QMIBuf, Length); + } + + return pRequest; +} + +static USHORT CtlGetVersionReq(PQMICTL_MSG QCTLMsg, void *arg) { + QCTLMsg->GetVersionReq.TLVType = QCTLV_TYPE_REQUIRED_PARAMETER; + QCTLMsg->GetVersionReq.TLVLength = cpu_to_le16(0x0001); + QCTLMsg->GetVersionReq.QMUXTypes = QMUX_TYPE_ALL; + return sizeof(QMICTL_GET_VERSION_REQ_MSG); +} + +static USHORT CtlGetClientIdReq(PQMICTL_MSG QCTLMsg, void *arg) { + QCTLMsg->GetClientIdReq.TLVType = QCTLV_TYPE_REQUIRED_PARAMETER; + QCTLMsg->GetClientIdReq.TLVLength = cpu_to_le16(0x0001); + QCTLMsg->GetClientIdReq.QMIType = ((UCHAR *)arg)[0]; + return sizeof(QMICTL_GET_CLIENT_ID_REQ_MSG); +} + +static USHORT CtlReleaseClientIdReq(PQMICTL_MSG QCTLMsg, void *arg) { + QCTLMsg->ReleaseClientIdReq.TLVType = QCTLV_TYPE_REQUIRED_PARAMETER; + QCTLMsg->ReleaseClientIdReq.TLVLength = cpu_to_le16(0x0002); + QCTLMsg->ReleaseClientIdReq.QMIType = ((UCHAR *)arg)[0]; + QCTLMsg->ReleaseClientIdReq.ClientId = ((UCHAR *)arg)[1] ; + return sizeof(QMICTL_RELEASE_CLIENT_ID_REQ_MSG); +} + +int QmiWwanSendQMI(PQCQMIMSG pRequest) { + struct pollfd pollfds[]= {{cdc_wdm_fd, POLLOUT, 0}}; + int ret; + + if (cdc_wdm_fd == -1) { + dbg_time("%s cdc_wdm_fd = -1", __func__); + return -ENODEV; + } + + do { + ret = poll(pollfds, sizeof(pollfds)/sizeof(pollfds[0]), 5000); + } while ((ret < 0) && (errno == EINTR)); + + if (pollfds[0].revents & POLLOUT) { + ssize_t nwrites = le16_to_cpu(pRequest->QMIHdr.Length) + 1; + ret = write(cdc_wdm_fd, pRequest, nwrites); + if (ret == nwrites) { + ret = 0; + } else { + dbg_time("%s write=%d, errno: %d (%s)", __func__, ret, errno, strerror(errno)); + } + } else { + dbg_time("%s poll=%d, revents = 0x%x, errno: %d (%s)", __func__, ret, pollfds[0].revents, errno, strerror(errno)); + } + + return ret; +} + +static int QmiWwanGetClientID(UCHAR QMIType) { + PQCQMIMSG pResponse; + + QmiThreadSendQMI(ComposeQCTLMsg(QMICTL_GET_CLIENT_ID_REQ, CtlGetClientIdReq, &QMIType), &pResponse); + + if (pResponse) { + USHORT QMUXResult = cpu_to_le16(pResponse->CTLMsg.QMICTLMsgHdrRsp.QMUXResult); // QMI_RESULT_SUCCESS + USHORT QMUXError = cpu_to_le16(pResponse->CTLMsg.QMICTLMsgHdrRsp.QMUXError); // QMI_ERR_INVALID_ARG + //UCHAR QMIType = pResponse->CTLMsg.GetClientIdRsp.QMIType; + UCHAR ClientId = pResponse->CTLMsg.GetClientIdRsp.ClientId; + + if (!QMUXResult && !QMUXError && (QMIType == pResponse->CTLMsg.GetClientIdRsp.QMIType)) { + qmiclientId[QMIType] = ClientId; + switch (QMIType) { + case QMUX_TYPE_WDS: dbg_time("Get clientWDS = %d", ClientId); break; + case QMUX_TYPE_DMS: dbg_time("Get clientDMS = %d", ClientId); break; + case QMUX_TYPE_NAS: dbg_time("Get clientNAS = %d", ClientId); break; + case QMUX_TYPE_QOS: dbg_time("Get clientQOS = %d", ClientId); break; + case QMUX_TYPE_WMS: dbg_time("Get clientWMS = %d", ClientId); break; + case QMUX_TYPE_PDS: dbg_time("Get clientPDS = %d", ClientId); break; + case QMUX_TYPE_UIM: dbg_time("Get clientUIM = %d", ClientId); break; + case QMUX_TYPE_WDS_ADMIN: dbg_time("Get clientWDA = %d", ClientId); + break; + default: break; + } + } + } + return 0; +} + +static int QmiWwanReleaseClientID(QMI_SERVICE_TYPE QMIType, UCHAR ClientId) { + UCHAR argv[] = {QMIType, ClientId}; + QmiThreadSendQMI(ComposeQCTLMsg(QMICTL_RELEASE_CLIENT_ID_REQ, CtlReleaseClientIdReq, argv), NULL); + return 0; +} + +int QmiWwanInit(void) { + unsigned i; + int ret; + PQCQMIMSG pResponse; + + for (i = 0; i < 10; i++) { + ret = QmiThreadSendQMITimeout(ComposeQCTLMsg(QMICTL_SYNC_REQ, NULL, NULL), NULL, 1 * 1000); + if (!ret) + break; + sleep(1); + } + if (ret) + return ret; + + QmiThreadSendQMI(ComposeQCTLMsg(QMICTL_GET_VERSION_REQ, CtlGetVersionReq, NULL), &pResponse); + if (pResponse) free(pResponse); + QmiWwanGetClientID(QMUX_TYPE_WDS); + QmiWwanGetClientID(QMUX_TYPE_DMS); + QmiWwanGetClientID(QMUX_TYPE_NAS); + QmiWwanGetClientID(QMUX_TYPE_UIM); + QmiWwanGetClientID(QMUX_TYPE_WDS_ADMIN); + return 0; +} + +int QmiWwanDeInit(void) { + unsigned int i; + for (i = 0; i < sizeof(qmiclientId)/sizeof(qmiclientId[0]); i++) + { + if (qmiclientId[i] != 0) + { + QmiWwanReleaseClientID(i, qmiclientId[i]); + qmiclientId[i] = 0; + } + } + + return 0; +} + +void * QmiWwanThread(void *pData) { + const char *cdc_wdm = (const char *)pData; + cdc_wdm_fd = open(cdc_wdm, O_RDWR | O_NONBLOCK | O_NOCTTY); + if (cdc_wdm_fd == -1) { + dbg_time("%s Failed to open %s, errno: %d (%s)", __func__, cdc_wdm, errno, strerror(errno)); + qmidevice_send_event_to_main(RIL_INDICATE_DEVICE_DISCONNECTED); + pthread_exit(NULL); + return NULL; + } + dbg_time("cdc_wdm_fd = %d", cdc_wdm_fd); + + qmidevice_send_event_to_main(RIL_INDICATE_DEVICE_CONNECTED); + + while (1) { + struct pollfd pollfds[] = {{qmidevice_control_fd[1], POLLIN, 0}, {cdc_wdm_fd, POLLIN, 0}}; + int ne, ret, nevents = sizeof(pollfds)/sizeof(pollfds[0]); + + do { + ret = poll(pollfds, nevents, -1); + } while ((ret < 0) && (errno == EINTR)); + + if (ret <= 0) { + dbg_time("%s poll=%d, errno: %d (%s)", __func__, ret, errno, strerror(errno)); + break; + } + + for (ne = 0; ne < nevents; ne++) { + int fd = pollfds[ne].fd; + short revents = pollfds[ne].revents; + + //dbg_time("{%d, %x, %x}", pollfds[ne].fd, pollfds[ne].events, pollfds[ne].revents); + + if (revents & (POLLERR | POLLHUP | POLLNVAL)) { + dbg_time("%s poll err/hup/inval", __func__); + dbg_time("poll fd = %d, events = 0x%04x", fd, revents); + if (fd == cdc_wdm_fd) { + } else { + } + if (revents & (POLLHUP | POLLNVAL)) //EC20 bug, Can get POLLERR + goto __QmiWwanThread_quit; + } + + if ((revents & POLLIN) == 0) + continue; + + if (fd == qmidevice_control_fd[1]) { + int triger_event; + if (read(fd, &triger_event, sizeof(triger_event)) == sizeof(triger_event)) { + //DBG("triger_event = 0x%x", triger_event); + switch (triger_event) { + case RIL_REQUEST_QUIT: + goto __QmiWwanThread_quit; + break; + case SIGTERM: + case SIGHUP: + case SIGINT: + QmiThreadRecvQMI(NULL); + break; + default: + break; + } + } + } + + if (fd == cdc_wdm_fd) { + ssize_t nreads; + UCHAR QMIBuf[512]; + PQCQMIMSG pResponse = (PQCQMIMSG)QMIBuf; + + nreads = read(fd, QMIBuf, sizeof(QMIBuf)); + //dbg_time("%s read=%d errno: %d (%s)", __func__, (int)nreads, errno, strerror(errno)); + if (nreads <= 0) { + dbg_time("%s read=%d errno: %d (%s)", __func__, (int)nreads, errno, strerror(errno)); + break; + } + + if (nreads != (le16_to_cpu(pResponse->QMIHdr.Length) + 1)) { + dbg_time("%s nreads=%d, pQCQMI->QMIHdr.Length = %d", __func__, (int)nreads, le16_to_cpu(pResponse->QMIHdr.Length)); + continue; + } + + QmiThreadRecvQMI(pResponse); + } + } + } + +__QmiWwanThread_quit: + if (cdc_wdm_fd != -1) { close(cdc_wdm_fd); cdc_wdm_fd = -1; } + qmidevice_send_event_to_main(RIL_INDICATE_DEVICE_DISCONNECTED); + QmiThreadRecvQMI(NULL); //main thread may pending on QmiThreadSendQMI() + dbg_time("%s exit", __func__); + pthread_exit(NULL); + return NULL; +} + +#else +int QmiWwanSendQMI(PQCQMIMSG pRequest) {return -1;} +int QmiWwanInit(void) {return -1;} +int QmiWwanDeInit(void) {return -1;} +void * QmiWwanThread(void *pData) {dbg_time("please set CONFIG_QMIWWAN"); return NULL;} +#endif diff --git a/root/package/link4all/quectel-CM/src.all/dhcpclient.c b/root/package/link4all/quectel-CM/src.all/dhcpclient.c new file mode 100755 index 00000000..e5aafd22 --- /dev/null +++ b/root/package/link4all/quectel-CM/src.all/dhcpclient.c @@ -0,0 +1,90 @@ +#ifdef ANDROID +/* + * Copyright 2008, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "QMIThread.h" +#ifdef USE_NDK +extern int (*ifc_init)(void); +extern void (*ifc_close)(void); +extern int (*do_dhcp)(const char *iname); +extern void (*get_dhcp_info)(uint32_t *ipaddr, uint32_t *gateway, uint32_t *prefixLength, + uint32_t *dns1, uint32_t *dns2, uint32_t *server, + uint32_t *lease); +extern int (*property_set)(const char *key, const char *value); +#else +#include +#include +extern int do_dhcp(const char *iname); +extern void get_dhcp_info(uint32_t *ipaddr, uint32_t *gateway, uint32_t *prefixLength, + uint32_t *dns1, uint32_t *dns2, uint32_t *server, + uint32_t *lease); +#endif + +static const char *ipaddr_to_string(in_addr_t addr) +{ + struct in_addr in_addr; + + in_addr.s_addr = addr; + return inet_ntoa(in_addr); +} + +void do_dhcp_request(PROFILE_T *profile) { +#ifdef USE_NDK + if (!ifc_init ||!ifc_close ||!do_dhcp || !get_dhcp_info || !property_set) { + return; + } +#endif + + char *ifname = profile->usbnet_adapter; + uint32_t ipaddr, gateway, prefixLength, dns1, dns2, server, lease; + char propKey[128]; + +#if 0 + if (profile->rawIP && ((profile->IPType==0x04 && profile->ipv4.Address))) + { + snprintf(propKey, sizeof(propKey), "net.%s.dns1", ifname); + property_set(propKey, profile->ipv4.DnsPrimary ? ipaddr_to_string(ql_swap32(profile->ipv4.DnsPrimary)) : "8.8.8.8"); + snprintf(propKey, sizeof(propKey), "net.%s.dns2", ifname); + property_set(propKey, profile->ipv4.DnsSecondary ? ipaddr_to_string(ql_swap32(profile->ipv4.DnsSecondary)) : "8.8.8.8"); + snprintf(propKey, sizeof(propKey), "net.%s.gw", ifname); + property_set(propKey, profile->ipv4.Gateway ? ipaddr_to_string(ql_swap32(profile->ipv4.Gateway)) : "0.0.0.0"); + return; + } +#endif + + if(ifc_init()) { + dbg_time("failed to ifc_init(%s): %s\n", ifname, strerror(errno)); + } + + if (do_dhcp(ifname) < 0) { + dbg_time("failed to do_dhcp(%s): %s\n", ifname, strerror(errno)); + } + + ifc_close(); + + get_dhcp_info(&ipaddr, &gateway, &prefixLength, &dns1, &dns2, &server, &lease); + snprintf(propKey, sizeof(propKey), "net.%s.gw", ifname); + property_set(propKey, gateway ? ipaddr_to_string(gateway) : "0.0.0.0"); +} +#endif diff --git a/root/package/link4all/quectel-CM/src.all/main.c b/root/package/link4all/quectel-CM/src.all/main.c new file mode 100755 index 00000000..4e6c2b39 --- /dev/null +++ b/root/package/link4all/quectel-CM/src.all/main.c @@ -0,0 +1,681 @@ +#include "QMIThread.h" +#include +#include +#include + +#define POLL_DATA_CALL_STATE_SECONDS 15 //poll data call state, for qmi ind maybe not work well + +int debug_qmi = 0; +int main_loop = 0; +char * qmichannel; +int qmidevice_control_fd[2]; +static int signal_control_fd[2]; + +#ifdef LINUX_RIL_SHLIB +UINT ifc_get_addr(const char *ifname); +static int check_ipv4_address(PROFILE_T *profile); +#endif + +static void usbnet_link_change(int link, PROFILE_T *profile) { + static int s_link = -1; + + if (s_link == link) + return; + + s_link = link; + + if (link){ + printf("Connected!,now getting IP!\n"); + udhcpc_start(profile); + } + else{ + printf("Not connected!,try later\n"); + udhcpc_stop(profile); + } + +#ifdef LINUX_RIL_SHLIB + if (link) { + while (ifc_get_addr(profile->usbnet_adapter) == 0) { + sleep(1); + } + } + + if (link && requestGetIPAddress(profile) == 0) { + unsigned char *r; + + dbg_time("Using interface %s", profile->usbnet_adapter); + r = (unsigned char *)&profile->ipv4.Address; + dbg_time("local IP address %d.%d.%d.%d", r[3], r[2], r[1], r[0]); + r = (unsigned char *)&profile->ipv4.Gateway; + dbg_time("remote IP address %d.%d.%d.%d", r[3], r[2], r[1], r[0]); + r = (unsigned char *)&profile->ipv4.DnsPrimary; + dbg_time("primary DNS address %d.%d.%d.%d", r[3], r[2], r[1], r[0]); + r = (unsigned char *)&profile->ipv4.DnsSecondary; + dbg_time("secondary DNS address %d.%d.%d.%d", r[3], r[2], r[1], r[0]); + } +#endif +} + +UINT ifc_get_addr(const char *ifname); +static int check_ipv4_address(PROFILE_T *profile) { + if (requestGetIPAddress(profile) == 0) { + UINT localIP = ifc_get_addr(profile->usbnet_adapter); + UINT remoteIP = ql_swap32(profile->ipv4.Address); + unsigned char *l = (unsigned char *)&localIP; + unsigned char *r = (unsigned char *)&remoteIP; + if (remoteIP != remoteIP || debug_qmi) + dbg_time("localIP: %d.%d.%d.%d VS remoteIP: %d.%d.%d.%d", + l[0], l[1], l[2], l[3], r[0], r[1], r[2], r[3]); + if (profile->IPType == 0x04) + return (localIP == remoteIP); + } + return 0; +} + +static void main_send_event_to_qmidevice(int triger_event) { + write(qmidevice_control_fd[0], &triger_event, sizeof(triger_event)); +} + +static void send_signo_to_main(int signo) { + write(signal_control_fd[0], &signo, sizeof(signo)); +} + +void qmidevice_send_event_to_main(int triger_event) { + write(qmidevice_control_fd[1], &triger_event, sizeof(triger_event)); +} + +#define MAX_PATH 256 + +static int ls_dir(const char *dir, int (*match)(const char *dir, const char *file, void *argv[]), void *argv[]) +{ + DIR *pDir; + struct dirent* ent = NULL; + int match_times = 0; + + pDir = opendir(dir); + if (pDir == NULL) { + dbg_time("Cannot open directory: %s, errno: %d (%s)", dir, errno, strerror(errno)); + return 0; + } + + while ((ent = readdir(pDir)) != NULL) { + match_times += match(dir, ent->d_name, argv); + } + closedir(pDir); + + return match_times; +} + +static int is_same_linkfile(const char *dir, const char *file, void *argv[]) +{ + const char *qmichannel = (const char *)argv[1]; + char linkname[MAX_PATH]; + char filename[MAX_PATH]; + int linksize; + + snprintf(linkname, MAX_PATH, "%s/%s", dir, file); + linksize = readlink(linkname, filename, MAX_PATH); + if (linksize <= 0) + return 0; + + filename[linksize] = 0; + if (strcmp(filename, qmichannel)) + return 0; + + dbg_time("%s -> %s", linkname, filename); + return 1; +} + +static int is_brother_process(const char *dir, const char *file, void *argv[]) +{ + //const char *myself = (const char *)argv[0]; + char linkname[MAX_PATH]; + char filename[MAX_PATH]; + int linksize; + int i = 0, kill_timeout = 15; + pid_t pid; + + //dbg_time("%s", file); + while (file[i]) { + if (!isdigit(file[i])) + break; + i++; + } + + if (file[i]) { + //dbg_time("%s not digit", file); + return 0; + } + + snprintf(linkname, MAX_PATH, "%s/%s/exe", dir, file); + linksize = readlink(linkname, filename, MAX_PATH); + if (linksize <= 0) + return 0; + + filename[linksize] = 0; +#if 0 //check all process + if (strcmp(filename, myself)) + return 0; +#endif + + pid = atoi(file); + if (pid == getpid()) + return 0; + + snprintf(linkname, MAX_PATH, "%s/%s/fd", dir, file); + if (!ls_dir(linkname, is_same_linkfile, argv)) + return 0; + + dbg_time("%s/%s/exe -> %s", dir, file, filename); + while (kill_timeout-- && !kill(pid, 0)) + { + kill(pid, SIGTERM); + sleep(1); + } + if (!kill(pid, 0)) + { + dbg_time("force kill %s/%s/exe -> %s", dir, file, filename); + kill(pid, SIGKILL); + sleep(1); + } + + return 1; +} + +static int kill_brothers(const char *qmichannel) +{ + char myself[MAX_PATH]; + int filenamesize; + void *argv[2] = {myself, (void *)qmichannel}; + + filenamesize = readlink("/proc/self/exe", myself, MAX_PATH); + if (filenamesize <= 0) + return 0; + myself[filenamesize] = 0; + + if (ls_dir("/proc", is_brother_process, argv)) + sleep(1); + + return 0; +} + +static void ql_sigaction(int signo) { + if (SIGCHLD == signo) + waitpid(-1, NULL, WNOHANG); + else if (SIGALRM == signo) + send_signo_to_main(SIGUSR1); + else + { + if (SIGTERM == signo || SIGHUP == signo || SIGINT == signo) + main_loop = 0; + send_signo_to_main(signo); + main_send_event_to_qmidevice(signo); //main may be wating qmi response + } +} + +pthread_t gQmiThreadID; + +static int usage(const char *progname) { + dbg_time("Usage: %s [-s [apn [user password auth]]] [-p pincode] [-f logfilename] ", progname); + dbg_time("-s [apn [user password auth]] Set apn/user/password/auth get from your network provider"); + dbg_time("-p pincode Verify sim card pin if sim card is locked"); + dbg_time("-f logfilename Save log message of this program to file"); + dbg_time("Example 1: %s ", progname); + dbg_time("Example 2: %s -s 3gnet ", progname); + dbg_time("Example 3: %s -s 3gnet carl 1234 0 -p 1234 -f gobinet_log.txt", progname); + return 0; +} + +static int qmidevice_detect(char **pp_qmichannel, char **pp_usbnet_adapter) { + struct dirent* ent = NULL; + DIR *pDir; +#if 0 //ndef ANDROID + int osmaj, osmin, ospatch; + static struct utsname utsname; /* for the kernel version */ + static int kernel_version; +#define KVERSION(j,n,p) ((j)*1000000 + (n)*1000 + (p)) + + /* get the kernel version now, since we are called before sys_init */ + uname(&utsname); + osmaj = osmin = ospatch = 0; + sscanf(utsname.release, "%d.%d.%d", &osmaj, &osmin, &ospatch); + kernel_version = KVERSION(osmaj, osmin, ospatch); +#endif + + if ((pDir = opendir("/dev")) == NULL) { + dbg_time("Cannot open directory: %s, errno:%d (%s)", "/dev", errno, strerror(errno)); + return -ENODEV; + } + + while ((ent = readdir(pDir)) != NULL) { + if ((strncmp(ent->d_name, "cdc-wdm", strlen("cdc-wdm")) == 0) || (strncmp(ent->d_name, "qcqmi", strlen("qcqmi")) == 0)) { + char net_path[64]; + + *pp_qmichannel = (char *)malloc(32); + sprintf(*pp_qmichannel, "/dev/%s", ent->d_name); + dbg_time("Find qmichannel = %s", *pp_qmichannel); + + if (strncmp(ent->d_name, "cdc-wdm", strlen("cdc-wdm")) == 0) + sprintf(net_path, "/sys/class/net/wwan%s", &ent->d_name[strlen("cdc-wdm")]); + else + { + sprintf(net_path, "/sys/class/net/usb%s", &ent->d_name[strlen("qcqmi")]); + #if 0//ndef ANDROID + if (kernel_version >= KVERSION( 2,6,39 )) + sprintf(net_path, "/sys/class/net/eth%s", &ent->d_name[strlen("qcqmi")]); + #else + if (access(net_path, R_OK) && errno == ENOENT) + sprintf(net_path, "/sys/class/net/eth%s", &ent->d_name[strlen("qcqmi")]); + #endif + } + + if (access(net_path, R_OK) == 0) + { + if (*pp_usbnet_adapter && strcmp(*pp_usbnet_adapter, (net_path + strlen("/sys/class/net/")))) + { + free(*pp_qmichannel); *pp_qmichannel = NULL; + continue; + } + *pp_usbnet_adapter = strdup(net_path + strlen("/sys/class/net/")); + dbg_time("Find usbnet_adapter = %s", *pp_usbnet_adapter); + break; + } + else + { + dbg_time("Failed to access %s, errno:%d (%s)", net_path, errno, strerror(errno)); + free(*pp_qmichannel); *pp_qmichannel = NULL; + } + } + } + closedir(pDir); + + return (*pp_qmichannel && *pp_usbnet_adapter); +} + +#if defined(ANDROID) || defined(LINUX_RIL_SHLIB) +int quectel_CM(int argc, char *argv[]) +#else +int main(int argc, char *argv[]) +#endif +{ + int triger_event = 0; + int opt = 1; + int signo; +#ifdef CONFIG_SIM + SIM_Status SIMStatus; +#endif + UCHAR PSAttachedState; + UCHAR ConnectionStatus = 0xff; //unknow state + char * save_usbnet_adapter = NULL; + PROFILE_T profile; + int slient_seconds = 0; + + memset(&profile, 0x00, sizeof(profile)); +#if CONFIG_DEFAULT_PDP + profile.pdp = CONFIG_DEFAULT_PDP; +#else + profile.pdp = 1; +#endif + profile.IPType = 0x04; //ipv4 first + + if (!strcmp(argv[argc-1], "&")) + argc--; + + opt = 1; + while (opt < argc) + { + if (argv[opt][0] != '-') + return usage(argv[0]); + + switch (argv[opt++][1]) + { +#define has_more_argv() ((opt < argc) && (argv[opt][0] != '-')) + case 's': + profile.apn = profile.user = profile.password = ""; + if (has_more_argv()) + profile.apn = argv[opt++]; + if (has_more_argv()) + profile.user = argv[opt++]; + if (has_more_argv()) + { + profile.password = argv[opt++]; + if (profile.password && profile.password[0]) + profile.auth = 2; //default chap, customers may miss auth + } + if (has_more_argv()) + profile.auth = argv[opt++][0] - '0'; + break; + + case 'p': + if (has_more_argv()) + profile.pincode = argv[opt++]; + break; + + case 'n': + if (has_more_argv()) + profile.pdp = argv[opt++][0] - '0'; + break; + + case 'f': + if (has_more_argv()) + { + const char * filename = argv[opt++]; + logfilefp = fopen(filename, "a+"); + if (!logfilefp) { + dbg_time("Fail to open %s, errno: %d(%s)", filename, errno, strerror(errno)); + } + } + break; + + case 'i': + if (has_more_argv()) + profile.usbnet_adapter = save_usbnet_adapter = argv[opt++]; + break; + + case 'v': + debug_qmi = 1; + break; + + case 'l': + main_loop = 1; + break; + + default: + return usage(argv[0]); + break; + } + } + + dbg_time("Quectel_Linux_ConnectManager_SR01A01V21"); + dbg_time("%s profile[%d] = %s/%s/%s/%d, pincode = %s", argv[0], profile.pdp, profile.apn, profile.user, profile.password, profile.auth, profile.pincode); + + signal(SIGUSR1, ql_sigaction); + signal(SIGUSR2, ql_sigaction); + signal(SIGINT, ql_sigaction); + signal(SIGTERM, ql_sigaction); + signal(SIGHUP, ql_sigaction); + signal(SIGCHLD, ql_sigaction); + signal(SIGALRM, ql_sigaction); + + if (socketpair( AF_LOCAL, SOCK_STREAM, 0, signal_control_fd) < 0 ) { + dbg_time("%s Faild to create main_control_fd: %d (%s)", __func__, errno, strerror(errno)); + return -1; + } + + if ( socketpair( AF_LOCAL, SOCK_STREAM, 0, qmidevice_control_fd ) < 0 ) { + dbg_time("%s Failed to create thread control socket pair: %d (%s)", __func__, errno, strerror(errno)); + return 0; + } + +//sudo apt-get install udhcpc +//sudo apt-get remove ModemManager +__main_loop: + while (!profile.qmichannel) + { + if (qmidevice_detect(&profile.qmichannel, &profile.usbnet_adapter)) + break; + if (main_loop) + { + int wait_for_device = 3000; + dbg_time("Wait for Quectel modules connect"); + while (wait_for_device && main_loop) { + wait_for_device -= 100; + usleep(100*1000); + } + continue; + } + dbg_time("Cannot find qmichannel(%s) usbnet_adapter(%s) for Quectel modules", profile.qmichannel, profile.usbnet_adapter); + return -ENODEV; + } + + if (access(profile.qmichannel, R_OK | W_OK)) { + dbg_time("Fail to access %s, errno: %d (%s)", profile.qmichannel, errno, strerror(errno)); + return errno; + } + +#if 0 //for test only, make fd > 255 +{ + int max_dup = 255; + while (max_dup--) + dup(0); +} +#endif + + kill_brothers(profile.qmichannel); + + qmichannel = profile.qmichannel; + if (!strncmp(profile.qmichannel, "/dev/qcqmi", strlen("/dev/qcqmi"))) + { + if (pthread_create( &gQmiThreadID, 0, GobiNetThread, (void *)profile.qmichannel) != 0) + { + dbg_time("%s Failed to create GobiNetThread: %d (%s)", __func__, errno, strerror(errno)); + return 0; + } + } + else + { + if (pthread_create( &gQmiThreadID, 0, QmiWwanThread, (void *)profile.qmichannel) != 0) + { + dbg_time("%s Failed to create QmiWwanThread: %d (%s)", __func__, errno, strerror(errno)); + return 0; + } + } + + if ((read(qmidevice_control_fd[0], &triger_event, sizeof(triger_event)) != sizeof(triger_event)) + || (triger_event != RIL_INDICATE_DEVICE_CONNECTED)) { + dbg_time("%s Failed to init QMIThread: %d (%s)", __func__, errno, strerror(errno)); + return 0; + } + + if (!strncmp(profile.qmichannel, "/dev/cdc-wdm", strlen("/dev/cdc-wdm"))) { + if (QmiWwanInit()) { + dbg_time("%s Failed to QmiWwanInit: %d (%s)", __func__, errno, strerror(errno)); + return 0; + } + } + +#ifdef CONFIG_VERSION + requestBaseBandVersion(NULL); +#endif + requestSetEthMode(&profile); + if (profile.rawIP && !strncmp(profile.qmichannel, "/dev/cdc-wdm", strlen("/dev/cdc-wdm"))) { + char raw_ip_switch[128] = {0}; + sprintf(raw_ip_switch, "/sys/class/net/%s/qmi/raw_ip", profile.usbnet_adapter); + if (!access(raw_ip_switch, R_OK)) { + int raw_ip_fd = -1; + raw_ip_fd = open(raw_ip_switch, O_RDWR); + if (raw_ip_fd >= 0) { + write(raw_ip_fd, "1", strlen("1")); + close(raw_ip_fd); + raw_ip_fd = -1; + } else { + dbg_time("open %s failed, errno = %d(%s)\n", raw_ip_switch, errno, strerror(errno)); + } + } + } +#ifdef CONFIG_SIM + requestGetSIMStatus(&SIMStatus); + if ((SIMStatus == SIM_PIN) && profile.pincode) { + requestEnterSimPin(profile.pincode); + } +#endif +#ifdef CONFIG_APN + if (profile.apn || profile.user || profile.password) { + requestSetProfile(&profile); + } + requestGetProfile(&profile); +#endif + requestRegistrationState(&PSAttachedState); + + if (!requestQueryDataCall(&ConnectionStatus) && (QWDS_PKT_DATA_CONNECTED == ConnectionStatus)) + usbnet_link_change(1, &profile); + else + usbnet_link_change(0, &profile); + + send_signo_to_main(SIGUSR1); + + while (1) + { + struct pollfd pollfds[] = {{signal_control_fd[1], POLLIN, 0}, {qmidevice_control_fd[0], POLLIN, 0}}; + int ne, ret, nevents = sizeof(pollfds)/sizeof(pollfds[0]); + + do { + ret = poll(pollfds, nevents, (ConnectionStatus == QWDS_PKT_DATA_CONNECTED) ? 1000 : -1); + } while ((ret < 0) && (errno == EINTR)); + + if (ret == 0) + { + #if POLL_DATA_CALL_STATE_SECONDS + if (++slient_seconds >= POLL_DATA_CALL_STATE_SECONDS) + send_signo_to_main(SIGUSR2); + #endif + continue; + } + slient_seconds = 0; + + if (ret <= 0) { + dbg_time("%s poll=%d, errno: %d (%s)", __func__, ret, errno, strerror(errno)); + goto __main_quit; + } + + for (ne = 0; ne < nevents; ne++) { + int fd = pollfds[ne].fd; + short revents = pollfds[ne].revents; + + if (revents & (POLLERR | POLLHUP | POLLNVAL)) { + dbg_time("%s poll err/hup", __func__); + dbg_time("epoll fd = %d, events = 0x%04x", fd, revents); + main_send_event_to_qmidevice(RIL_REQUEST_QUIT); + if (revents & POLLHUP) + goto __main_quit; + } + + if ((revents & POLLIN) == 0) + continue; + + if (fd == signal_control_fd[1]) + { + if (read(fd, &signo, sizeof(signo)) == sizeof(signo)) + { + alarm(0); + switch (signo) + { + case SIGUSR1: + requestQueryDataCall(&ConnectionStatus); + if (QWDS_PKT_DATA_CONNECTED != ConnectionStatus) + { + usbnet_link_change(0, &profile); + requestRegistrationState(&PSAttachedState); + if (PSAttachedState == 1 && requestSetupDataCall(&profile) == 0) + { + //succssful setup data call + } + else + { + alarm(5); //try to setup data call 5 seconds later + } + } + break; + + case SIGUSR2: + if (QWDS_PKT_DATA_CONNECTED == ConnectionStatus) + { + requestQueryDataCall(&ConnectionStatus); + } + if (QWDS_PKT_DATA_CONNECTED != ConnectionStatus || check_ipv4_address(&profile) == 0) //local ip is different with remote ip + { + requestDeactivateDefaultPDP(); + #if defined(ANDROID) || defined(LINUX_RIL_SHLIB) + kill(getpid(), SIGTERM); //android will setup data call again + #else + send_signo_to_main(SIGUSR1); + #endif + } + break; + + case SIGTERM: + case SIGHUP: + case SIGINT: + if (QWDS_PKT_DATA_CONNECTED == ConnectionStatus) + requestDeactivateDefaultPDP(); + usbnet_link_change(0, &profile); + if (!strncmp(profile.qmichannel, "/dev/cdc-wdm", strlen("/dev/cdc-wdm"))) + QmiWwanDeInit(); + main_send_event_to_qmidevice(RIL_REQUEST_QUIT); + goto __main_quit; + break; + + default: + break; + } + } + } + + if (fd == qmidevice_control_fd[0]) { + if (read(fd, &triger_event, sizeof(triger_event)) == sizeof(triger_event)) { + switch (triger_event) { + case RIL_INDICATE_DEVICE_DISCONNECTED: + usbnet_link_change(0, &profile); + if (main_loop) + { + if (pthread_join(gQmiThreadID, NULL)) { + dbg_time("%s Error joining to listener thread (%s)", __func__, strerror(errno)); + } + profile.qmichannel = NULL; + profile.usbnet_adapter = save_usbnet_adapter; + goto __main_loop; + } + goto __main_quit; + break; + + case RIL_UNSOL_RESPONSE_VOICE_NETWORK_STATE_CHANGED: + requestRegistrationState(&PSAttachedState); + if (PSAttachedState == 1 && QWDS_PKT_DATA_DISCONNECTED == ConnectionStatus) + send_signo_to_main(SIGUSR1); + break; + + case RIL_UNSOL_DATA_CALL_LIST_CHANGED: + { + #if defined(ANDROID) || defined(LINUX_RIL_SHLIB) + UCHAR oldConnectionStatus = ConnectionStatus; + #endif + requestQueryDataCall(&ConnectionStatus); + if (QWDS_PKT_DATA_CONNECTED != ConnectionStatus) + { + usbnet_link_change(0, &profile); + #if defined(ANDROID) || defined(LINUX_RIL_SHLIB) + if (oldConnectionStatus == QWDS_PKT_DATA_CONNECTED) //connected change to disconnect + kill(getpid(), SIGTERM); //android will setup data call again + #else + send_signo_to_main(SIGUSR1); + #endif + } else if (QWDS_PKT_DATA_CONNECTED == ConnectionStatus) { + requestGetIPAddress(&profile); + usbnet_link_change(1, &profile); + } + } + break; + + default: + break; + } + } + } + } + } + +__main_quit: + usbnet_link_change(0, &profile); + if (pthread_join(gQmiThreadID, NULL)) { + dbg_time("%s Error joining to listener thread (%s)", __func__, strerror(errno)); + } + close(signal_control_fd[0]); + close(signal_control_fd[1]); + close(qmidevice_control_fd[0]); + close(qmidevice_control_fd[1]); + dbg_time("%s exit", __func__); + if (logfilefp) + fclose(logfilefp); + + return 0; +} diff --git a/root/package/link4all/quectel-CM/src.all/udhcpc.c b/root/package/link4all/quectel-CM/src.all/udhcpc.c new file mode 100755 index 00000000..5a723ce2 --- /dev/null +++ b/root/package/link4all/quectel-CM/src.all/udhcpc.c @@ -0,0 +1,372 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include "QMIThread.h" + +static int ifc_ctl_sock = -1; + +static int ifc_init(void) +{ + int ret; + if (ifc_ctl_sock == -1) { + ifc_ctl_sock = socket(AF_INET, SOCK_DGRAM, 0); + if (ifc_ctl_sock < 0) { + dbg_time("socket() failed: %s\n", strerror(errno)); + } + } + + ret = ifc_ctl_sock < 0 ? -1 : 0; + return ret; +} + +static void ifc_close(void) +{ + if (ifc_ctl_sock != -1) { + (void)close(ifc_ctl_sock); + ifc_ctl_sock = -1; + } +} + +static void ifc_init_ifr(const char *name, struct ifreq *ifr) +{ + memset(ifr, 0, sizeof(struct ifreq)); + strncpy(ifr->ifr_name, name, IFNAMSIZ); + ifr->ifr_name[IFNAMSIZ - 1] = 0; +} + +static int ifc_set_flags(const char *name, unsigned set, unsigned clr) +{ + struct ifreq ifr; + ifc_init_ifr(name, &ifr); + + if(ioctl(ifc_ctl_sock, SIOCGIFFLAGS, &ifr) < 0) return -1; + ifr.ifr_flags = (ifr.ifr_flags & (~clr)) | set; + return ioctl(ifc_ctl_sock, SIOCSIFFLAGS, &ifr); +} + +static int ifc_up(const char *name, int rawIP) +{ + int ret = ifc_set_flags(name, IFF_UP | (rawIP ? IFF_NOARP : 0), 0); + return ret; +} + +static int ifc_down(const char *name) +{ + int ret = ifc_set_flags(name, 0, IFF_UP); + return ret; +} + +static void init_sockaddr_in(struct sockaddr *sa, in_addr_t addr) +{ + struct sockaddr_in *sin = (struct sockaddr_in *) sa; + sin->sin_family = AF_INET; + sin->sin_port = 0; + sin->sin_addr.s_addr = addr; +} + +static int ifc_set_addr(const char *name, in_addr_t addr) +{ + struct ifreq ifr; + int ret; + + ifc_init_ifr(name, &ifr); + init_sockaddr_in(&ifr.ifr_addr, addr); + + ret = ioctl(ifc_ctl_sock, SIOCSIFADDR, &ifr); + return ret; +} + +static pthread_attr_t udhcpc_thread_attr; +static pthread_t udhcpc_thread_id; + +#ifdef ANDROID +void do_dhcp_request(PROFILE_T *profile); +static void* udhcpc_thread_function(void* arg) { + do_dhcp_request((PROFILE_T *)arg); + return NULL; +} +#else +//#define USE_DHCLIENT +static pid_t udhcpc_pid = 0; +static FILE * ql_popen(const char *program, const char *type) +{ + FILE *iop; + int pdes[2]; + pid_t pid; + char *argv[20]; + int argc = 0; + char *dup_program = strdup(program); + char *pos = dup_program; + + while (*pos != '\0') + { + while (isblank(*pos)) *pos++ = '\0'; + if (*pos != '\0') + { + argv[argc++] = pos; + while (*pos != '\0' && !isblank(*pos)) pos++; + //dbg_time("argv[%d] = %s", argc-1, argv[argc-1]); + } + } + argv[argc++] = NULL; + + if (pipe(pdes) < 0) { + return (NULL); + } + + switch (pid = fork()) { + case -1: /* Error. */ + (void)close(pdes[0]); + (void)close(pdes[1]); + return (NULL); + /* NOTREACHED */ + case 0: /* Child. */ + { + if (*type == 'r') { + (void) close(pdes[0]); + if (pdes[1] != STDOUT_FILENO) { + (void)dup2(pdes[1], STDOUT_FILENO); + (void)close(pdes[1]); + } + } else { + (void)close(pdes[1]); + if (pdes[0] != STDIN_FILENO) { + (void)dup2(pdes[0], STDIN_FILENO); + (void)close(pdes[0]); + } + } +#if 1 //child do not use these fds + if (cdc_wdm_fd > 0) { + close(cdc_wdm_fd); + } else { + unsigned int i; + for (i = 0; i < sizeof(qmiclientId)/sizeof(qmiclientId[0]); i++) { + if (qmiclientId[i] != 0) + close(qmiclientId[i]); + } + } +#endif + execvp(argv[0], argv); + _exit(127); + /* NOTREACHED */ + } + break; + default: + udhcpc_pid = pid; + free(dup_program); + break; + } + + /* Parent; assume fdopen can't fail. */ + if (*type == 'r') { + iop = fdopen(pdes[0], type); + (void)close(pdes[1]); + } else { + iop = fdopen(pdes[1], type); + (void)close(pdes[0]); + } + + return (iop); +} + +static int ql_pclose(FILE *iop) +{ + (void)fclose(iop); + udhcpc_pid = 0; + return 0; +} + +static void* udhcpc_thread_function(void* arg) { + FILE * udhcpc_fp; + char udhcpc_cmd[128]; + PROFILE_T *profile = (PROFILE_T *)arg; + char *ifname = profile->usbnet_adapter; + int need_add_default_route = 0; + +#ifdef USE_DHCLIENT + snprintf(udhcpc_cmd, sizeof(udhcpc_cmd), "dhclient -%d -d --no-pid %s", profile->IPType, (char *)ifname); +#else + if (profile->IPType == 0x04) + { + if (access("/usr/share/udhcpc/default.script", X_OK)) { + dbg_time("Fail to access /usr/share/udhcpc/default.script, errno: %d (%s)", errno, strerror(errno)); + } + + //-f,--foreground Run in foreground + //-b,--background Background if lease is not obtained + //-n,--now Exit if lease is not obtained + //-q,--quit Exit after obtaining lease + //-t,--retries N Send up to N discover packets (default 3) + // snprintf(udhcpc_cmd, sizeof(udhcpc_cmd), "busybox udhcpc -f -n -q -t 5 -i %s", (char *)ifname); + } + else + { + /* + DHCPv6: Dibbler - a portable DHCPv6 + 1. download from http://klub.com.pl/dhcpv6/ + 2. cross-compile + 2.1 ./configure --host=arm-linux-gnueabihf + 2.2 copy dibbler-client to your board + 3. mkdir -p /var/log/dibbler/ /var/lib/ on your board + 4. create /etc/dibbler/client.conf on your board, the content is + log-mode short + log-level 7 + iface wwan0 { + ia + option dns-server + } + 5. run "dibbler-client start" to get ipV6 address + 6. run "route -A inet6 add default dev wwan0" to add default route + */ + need_add_default_route = 1; + snprintf(udhcpc_cmd, sizeof(udhcpc_cmd), "dibbler-client run"); + } +#endif + + udhcpc_fp = ql_popen(udhcpc_cmd, "r"); + if (udhcpc_fp) { + char buf[0xff]; + + if (need_add_default_route) + { + snprintf(buf, sizeof(buf), "route %s add default %s", (profile->IPType == 0x04) ? "" : "-A inet6", ifname); + system(buf); + } + + while((fgets(buf, sizeof(buf), udhcpc_fp)) != NULL) { + if ((strlen(buf) > 1) && (buf[strlen(buf) - 1] == '\n')) + buf[strlen(buf) - 1] = '\0'; + dbg_time("%s", buf); + } + ql_pclose(udhcpc_fp); + } + + return NULL; +} +#endif + +void udhcpc_start(PROFILE_T *profile) { + const char *ifname = profile->usbnet_adapter; + +//because must use udhcpc to obtain IP when working on ETH mode, +//so it is better also use udhcpc to obtain IP when working on IP mode. +//use the same policy for all modules +#if 0 +#ifndef ANDROID + if (profile->rawIP && ((profile->IPType==0x04 && profile->ipv4.Address))) + { + char shell_cmd[128]; + unsigned char *ip = (unsigned char *)&profile->ipv4.Address; + unsigned char *gw = (unsigned char *)&profile->ipv4.Gateway; + unsigned char *netmask = (unsigned char *)&profile->ipv4.SubnetMask; + unsigned char *dns1 = (unsigned char *)&profile->ipv4.DnsPrimary; + unsigned char *dns2 = (unsigned char *)&profile->ipv4.DnsSecondary; + + snprintf(shell_cmd, sizeof(shell_cmd), "ifconfig %s %d.%d.%d.%d netmask %d.%d.%d.%d",ifname, + ip[3], ip[2], ip[1], ip[0], netmask[3], netmask[2], netmask[1], netmask[0]); + dbg_time("%s", shell_cmd); + system(shell_cmd); + + //Resetting default routes + snprintf(shell_cmd, sizeof(shell_cmd), "route del default gw 0.0.0.0 dev %s", ifname); + while(!system(shell_cmd)); + + snprintf(shell_cmd, sizeof(shell_cmd), "route add default gw %d.%d.%d.%d dev %s metric 0", + gw[3], gw[2], gw[1], gw[0], ifname); + dbg_time("%s", shell_cmd); + system(shell_cmd); + + #ifdef ANDROID + do_dhcp_request(profile); + return; + #endif + + //Adding DNS + if (profile->ipv4.DnsSecondary == 0) + profile->ipv4.DnsSecondary = profile->ipv4.DnsPrimary; + if (dns1[0]) + { + dbg_time("Adding DNS %d.%d.%d.%d %d.%d.%d.%d", dns1[3], dns1[2], dns1[1], dns1[0], dns2[3], dns2[2], dns2[1], dns2[0]); + snprintf(shell_cmd, sizeof(shell_cmd), "echo -n \"nameserver %d.%d.%d.%d\nnameserver %d.%d.%d.%d\n\" > /etc/resolv.conf", + dns1[3], dns1[2], dns1[1], dns1[0], dns2[3], dns2[2], dns2[1], dns2[0]); + system(shell_cmd); + } + + return; + } +#endif +#endif + + ifc_init(); + if (ifc_set_addr(profile->usbnet_adapter, 0)) { + dbg_time("failed to set ip addr for %s to 0.0.0.0: %s\n", ifname, strerror(errno)); + return; + } + + if (ifc_up(profile->usbnet_adapter, profile->rawIP)) { + dbg_time("failed to bring up interface %s: %s\n", ifname, strerror(errno)); + return; + } + ifc_close(); + + pthread_attr_init(&udhcpc_thread_attr); + pthread_attr_setdetachstate(&udhcpc_thread_attr, PTHREAD_CREATE_DETACHED); + if(pthread_create(&udhcpc_thread_id, &udhcpc_thread_attr, udhcpc_thread_function, (void*)profile) !=0 ) { + dbg_time("failed to create udhcpc_thread for %s: %s\n", ifname, strerror(errno)); + } + pthread_attr_destroy(&udhcpc_thread_attr); +} + +void udhcpc_stop(PROFILE_T *profile) { + const char *ifname = profile->usbnet_adapter; + +#ifdef ANDROID +#else + pid_t pid = udhcpc_pid; + //dbg_time("%s kill %d/%d", __func__, pid, getpid()); + if (pid > 0 && !kill(pid, 0)) + { + int kill_time = 50; + do { + kill(pid, SIGTERM); + usleep(100*1000); + } while(kill_time-- && !kill(pid, 0)); //wait udhcpc quit + if (!kill(pid, 0)) + kill(pid, SIGKILL); + //dbg_time("%s kill udhcpc_pid time=%d", __func__, (50 - kill_time) * 100); + } +#endif + ifc_init(); + ifc_set_addr(ifname, 0); + ifc_down(ifname); + ifc_close(); +} + +UINT ifc_get_addr(const char *ifname) { + int inet_sock; + struct ifreq ifr; + UINT addr = 0; + + inet_sock = socket(AF_INET, SOCK_DGRAM, 0); + strcpy(ifr.ifr_name, ifname); + + if (ioctl(inet_sock, SIOCGIFADDR, &ifr) < 0) { + goto error; + } + addr = ((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr.s_addr; +error: + close(inet_sock); + + return addr; +} diff --git a/root/package/link4all/quectel-CM/src.all/util.c b/root/package/link4all/quectel-CM/src.all/util.c new file mode 100755 index 00000000..b6db4108 --- /dev/null +++ b/root/package/link4all/quectel-CM/src.all/util.c @@ -0,0 +1,158 @@ +#include "QMIThread.h" +#include + +#if defined(__STDC__) +#include +#define __V(x) x +#else +#include +#define __V(x) (va_alist) va_dcl +#define const +#define volatile +#endif + +#ifdef ANDROID +#define LOG_TAG "NDIS" +#include "../ql-log.h" +#else +#include +#endif + +#ifndef ANDROID //defined in atchannel.c +static void setTimespecRelative(struct timespec *p_ts, long long msec) +{ + struct timeval tv; + + gettimeofday(&tv, (struct timezone *) NULL); + + /* what's really funny about this is that I know + pthread_cond_timedwait just turns around and makes this + a relative time again */ + p_ts->tv_sec = tv.tv_sec + (msec / 1000); + p_ts->tv_nsec = (tv.tv_usec + (msec % 1000) * 1000L ) * 1000L; +} + +int pthread_cond_timeout_np(pthread_cond_t *cond, + pthread_mutex_t * mutex, + unsigned msecs) { + if (msecs != 0) { + struct timespec ts; + setTimespecRelative(&ts, msecs); + return pthread_cond_timedwait(cond, mutex, &ts); + } else { + return pthread_cond_wait(cond, mutex); + } +} +#endif + +static const char * get_time(void) { + static char time_buf[50]; + struct timeval tv; + time_t time; + suseconds_t millitm; + struct tm *ti; + + gettimeofday (&tv, NULL); + + time= tv.tv_sec; + millitm = (tv.tv_usec + 500) / 1000; + + if (millitm == 1000) { + ++time; + millitm = 0; + } + + ti = localtime(&time); + sprintf(time_buf, "[%02d-%02d_%02d:%02d:%02d:%03d]", ti->tm_mon+1, ti->tm_mday, ti->tm_hour, ti->tm_min, ti->tm_sec, (int)millitm); + return time_buf; +} + +FILE *logfilefp = NULL; +static pthread_mutex_t printfMutex = PTHREAD_MUTEX_INITIALIZER; +static char line[1024]; +void dbg_time (const char *fmt, ...) { + va_list args; + va_start(args, fmt); + + pthread_mutex_lock(&printfMutex); +#ifdef ANDROID + vsnprintf(line, sizeof(line), fmt, args); + RLOGD("%s", line); +#else +#ifdef LINUX_RIL_SHLIB + line[0] = '\0'; +#else + snprintf(line, sizeof(line), "%s ", get_time()); +#endif + vsnprintf(line + strlen(line), sizeof(line) - strlen(line), fmt, args); + fprintf(stdout, "%s\n", line); +#endif + if (logfilefp) { + fprintf(logfilefp, "%s\n", line); + } + pthread_mutex_unlock(&printfMutex); +} + +const int i = 1; +#define is_bigendian() ( (*(char*)&i) == 0 ) + +USHORT le16_to_cpu(USHORT v16) { + USHORT tmp = v16; + if (is_bigendian()) { + unsigned char *s = (unsigned char *)(&v16); + unsigned char *d = (unsigned char *)(&tmp); + d[0] = s[1]; + d[1] = s[0]; + } + return tmp; +} + +UINT le32_to_cpu (UINT v32) { + UINT tmp = v32; + if (is_bigendian()) { + unsigned char *s = (unsigned char *)(&v32); + unsigned char *d = (unsigned char *)(&tmp); + d[0] = s[3]; + d[1] = s[2]; + d[2] = s[1]; + d[3] = s[0]; + } + return tmp; +} + +UINT ql_swap32(UINT v32) { + UINT tmp = v32; + { + unsigned char *s = (unsigned char *)(&v32); + unsigned char *d = (unsigned char *)(&tmp); + d[0] = s[3]; + d[1] = s[2]; + d[2] = s[1]; + d[3] = s[0]; + } + return tmp; +} + +USHORT cpu_to_le16(USHORT v16) { + USHORT tmp = v16; + if (is_bigendian()) { + unsigned char *s = (unsigned char *)(&v16); + unsigned char *d = (unsigned char *)(&tmp); + d[0] = s[1]; + d[1] = s[0]; + } + return tmp; +} + +UINT cpu_to_le32 (UINT v32) { + UINT tmp = v32; + if (is_bigendian()) { + unsigned char *s = (unsigned char *)(&v32); + unsigned char *d = (unsigned char *)(&tmp); + d[0] = s[3]; + d[1] = s[2]; + d[2] = s[1]; + d[3] = s[0]; + } + return tmp; +} diff --git a/root/package/link4all/quectel-CM/src.all/util.h b/root/package/link4all/quectel-CM/src.all/util.h new file mode 100755 index 00000000..cc35ef3a --- /dev/null +++ b/root/package/link4all/quectel-CM/src.all/util.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _UTILS_H_ +#define _UTILS_H_ + +#include + +struct listnode +{ + struct listnode *next; + struct listnode *prev; +}; + +#define node_to_item(node, container, member) \ + (container *) (((char*) (node)) - offsetof(container, member)) + +#define list_declare(name) \ + struct listnode name = { \ + .next = &name, \ + .prev = &name, \ + } + +#define list_for_each(node, list) \ + for (node = (list)->next; node != (list); node = node->next) + +#define list_for_each_reverse(node, list) \ + for (node = (list)->prev; node != (list); node = node->prev) + +void list_init(struct listnode *list); +void list_add_tail(struct listnode *list, struct listnode *item); +void list_add_head(struct listnode *head, struct listnode *item); +void list_remove(struct listnode *item); + +#define list_empty(list) ((list) == (list)->next) +#define list_head(list) ((list)->next) +#define list_tail(list) ((list)->prev) + +int epoll_register(int epoll_fd, int fd, unsigned int events); +int epoll_deregister(int epoll_fd, int fd); +#endif diff --git a/root/package/link4all/quectel-CM/src/20201022_104550_0000.qmdl2 b/root/package/link4all/quectel-CM/src/20201022_104550_0000.qmdl2 new file mode 100644 index 0000000000000000000000000000000000000000..3032c6cf15ef2a805448698adc36c1da46685438 GIT binary patch literal 175 zcmd;MU|?Wk1QKB2?ZP+##8_~-t{5T$CN28vYGYX;s^VSi5*Qd757!~7^%hcMVq{`u zbqojyW?&M^P07{I%}+_q)i27=FNrTmVW|0BCwp6I)3m9l-ZRKa)-iaM<|!B&D;OCV s85o*d8CqHyn)oJG0{IXj11kd%Do&{|HPuT`)#Ci0!eGE~UahJQ0D9{xpa1{> literal 0 HcmV?d00001 diff --git a/root/package/link4all/quectel-CM/src/Android.mk b/root/package/link4all/quectel-CM/src/Android.mk new file mode 100644 index 00000000..dfff43e2 --- /dev/null +++ b/root/package/link4all/quectel-CM/src/Android.mk @@ -0,0 +1,8 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) +LOCAL_SRC_FILES:= QmiWwanCM.c GobiNetCM.c main.c MPQMUX.c QMIThread.c util.c qmap_bridge_mode.c mbim-cm.c device.c udhcpc.c +LOCAL_CFLAGS += -pie -fPIE -Wall -DUSE_ANDROID +LOCAL_LDFLAGS += -pie -fPIE +LOCAL_MODULE_TAGS:= optional +LOCAL_MODULE:= QFirehose +include $(BUILD_EXECUTABLE) diff --git a/root/package/link4all/quectel-CM/src/GobiNetCM.c b/root/package/link4all/quectel-CM/src/GobiNetCM.c new file mode 100644 index 00000000..c536967b --- /dev/null +++ b/root/package/link4all/quectel-CM/src/GobiNetCM.c @@ -0,0 +1,243 @@ +/****************************************************************************** + @file GobiNetCM.c + @brief GobiNet driver. + + DESCRIPTION + Connectivity Management Tool for USB network adapter of Quectel wireless cellular modules. + + INITIALIZATION AND SEQUENCING REQUIREMENTS + None. + + --------------------------------------------------------------------------- + Copyright (c) 2016 - 2020 Quectel Wireless Solution, Co., Ltd. All Rights Reserved. + Quectel Wireless Solution Proprietary and Confidential. + --------------------------------------------------------------------------- +******************************************************************************/ +#include +#include +#include +#include +#include +#include "QMIThread.h" + +#ifdef CONFIG_GOBINET + +// 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 + +static int GobiNetSendQMI(PQCQMIMSG pRequest) { + int ret, fd; + + fd = qmiclientId[pRequest->QMIHdr.QMIType]; + + if (fd <= 0) { + dbg_time("%s QMIType: %d has no clientID", __func__, pRequest->QMIHdr.QMIType); + return -ENODEV; + } + + // Always ready to write + if (1 == 1) { + ssize_t nwrites = le16_to_cpu(pRequest->QMIHdr.Length) + 1 - sizeof(QCQMI_HDR); + ret = write(fd, &pRequest->MUXMsg, nwrites); + if (ret == nwrites) { + ret = 0; + } else { + dbg_time("%s write=%d, errno: %d (%s)", __func__, ret, errno, strerror(errno)); + } + } else { + dbg_time("%s poll=%d, errno: %d (%s)", __func__, ret, errno, strerror(errno)); + } + + return ret; +} + +static int GobiNetGetClientID(const char *qcqmi, UCHAR QMIType) { + int ClientId; + ClientId = open(qcqmi, O_RDWR | O_NONBLOCK | O_NOCTTY); + if (ClientId == -1) { + dbg_time("failed to open %s, errno: %d (%s)", qcqmi, errno, strerror(errno)); + return -1; + } + + if (ioctl(ClientId, IOCTL_QMI_GET_SERVICE_FILE, QMIType) != 0) { + dbg_time("failed to get ClientID for 0x%02x errno: %d (%s)", QMIType, errno, strerror(errno)); + close(ClientId); + ClientId = 0; + } + + switch (QMIType) { + case QMUX_TYPE_WDS: dbg_time("Get clientWDS = %d", ClientId); break; + case QMUX_TYPE_DMS: dbg_time("Get clientDMS = %d", ClientId); break; + case QMUX_TYPE_NAS: dbg_time("Get clientNAS = %d", ClientId); break; + case QMUX_TYPE_QOS: dbg_time("Get clientQOS = %d", ClientId); break; + case QMUX_TYPE_WMS: dbg_time("Get clientWMS = %d", ClientId); break; + case QMUX_TYPE_PDS: dbg_time("Get clientPDS = %d", ClientId); break; + case QMUX_TYPE_UIM: dbg_time("Get clientUIM = %d", ClientId); break; + case QMUX_TYPE_WDS_ADMIN: dbg_time("Get clientWDA = %d", ClientId); + break; + default: break; + } + + return ClientId; +} + +static int GobiNetDeInit(void) { + unsigned int i; + for (i = 0; i < sizeof(qmiclientId)/sizeof(qmiclientId[0]); i++) + { + if (qmiclientId[i] != 0) + { + close(qmiclientId[i]); + qmiclientId[i] = 0; + } + } + + return 0; +} + +static void * GobiNetThread(void *pData) { + PROFILE_T *profile = (PROFILE_T *)pData; + const char *qcqmi = (const char *)profile->qmichannel; + int wait_for_request_quit = 0; + + qmiclientId[QMUX_TYPE_WDS] = GobiNetGetClientID(qcqmi, QMUX_TYPE_WDS); + if (profile->enable_ipv6) + qmiclientId[QMUX_TYPE_WDS_IPV6] = GobiNetGetClientID(qcqmi, QMUX_TYPE_WDS); + qmiclientId[QMUX_TYPE_DMS] = GobiNetGetClientID(qcqmi, QMUX_TYPE_DMS); + qmiclientId[QMUX_TYPE_NAS] = GobiNetGetClientID(qcqmi, QMUX_TYPE_NAS); + qmiclientId[QMUX_TYPE_UIM] = GobiNetGetClientID(qcqmi, QMUX_TYPE_UIM); + if (profile->qmap_mode == 0 || profile->loopback_state) //when QMAP enabled, set data format in GobiNet Driver + qmiclientId[QMUX_TYPE_WDS_ADMIN] = GobiNetGetClientID(qcqmi, QMUX_TYPE_WDS_ADMIN); + + //donot check clientWDA, there is only one client for WDA, if quectel-CM is killed by SIGKILL, i cannot get client ID for WDA again! + if (qmiclientId[QMUX_TYPE_WDS] == 0) /*|| (clientWDA == -1)*/ { + GobiNetDeInit(); + dbg_time("%s Failed to open %s, errno: %d (%s)", __func__, qcqmi, errno, strerror(errno)); + qmidevice_send_event_to_main(RIL_INDICATE_DEVICE_DISCONNECTED); + pthread_exit(NULL); + return NULL; + } + + qmidevice_send_event_to_main(RIL_INDICATE_DEVICE_CONNECTED); + + while (1) { + struct pollfd pollfds[16] = {{qmidevice_control_fd[1], POLLIN, 0}}; + int ne, ret, nevents = 1; + unsigned int i; + + for (i = 0; i < sizeof(qmiclientId)/sizeof(qmiclientId[0]); i++) + { + if (qmiclientId[i] != 0) + { + pollfds[nevents].fd = qmiclientId[i]; + pollfds[nevents].events = POLLIN; + pollfds[nevents].revents = 0; + nevents++; + } + } + + do { + ret = poll(pollfds, nevents, wait_for_request_quit ? 1000: -1); + } while ((ret < 0) && (errno == EINTR)); + + if (ret == 0 && wait_for_request_quit) { + QmiThreadRecvQMI(NULL); //main thread may pending on QmiThreadSendQMI() + continue; + } + + if (ret <= 0) { + dbg_time("%s poll=%d, errno: %d (%s)", __func__, ret, errno, strerror(errno)); + break; + } + + for (ne = 0; ne < nevents; ne++) { + int fd = pollfds[ne].fd; + short revents = pollfds[ne].revents; + + if (revents & (POLLERR | POLLHUP | POLLNVAL)) { + dbg_time("%s poll err/hup/inval", __func__); + dbg_time("epoll fd = %d, events = 0x%04x", fd, revents); + if (fd == qmidevice_control_fd[1]) { + } else { + } + if (revents & (POLLERR | POLLHUP | POLLNVAL)) + goto __GobiNetThread_quit; + } + + if ((revents & POLLIN) == 0) + continue; + + if (fd == qmidevice_control_fd[1]) { + int triger_event; + if (read(fd, &triger_event, sizeof(triger_event)) == sizeof(triger_event)) { + //DBG("triger_event = 0x%x", triger_event); + switch (triger_event) { + case RIL_REQUEST_QUIT: + goto __GobiNetThread_quit; + break; + case SIG_EVENT_STOP: + wait_for_request_quit = 1; + break; + default: + break; + } + } + continue; + } + + { + ssize_t nreads; + static UCHAR QMIBuf[4096]; + PQCQMIMSG pResponse = (PQCQMIMSG)QMIBuf; + + nreads = read(fd, &pResponse->MUXMsg, sizeof(QMIBuf) - sizeof(QCQMI_HDR)); + if (nreads <= 0) + { + dbg_time("%s read=%d errno: %d (%s)", __func__, (int)nreads, errno, strerror(errno)); + break; + } + + for (i = 0; i < sizeof(qmiclientId)/sizeof(qmiclientId[0]); i++) + { + if (qmiclientId[i] == fd) + { + pResponse->QMIHdr.QMIType = i; + } + } + + pResponse->QMIHdr.IFType = USB_CTL_MSG_TYPE_QMI; + pResponse->QMIHdr.Length = cpu_to_le16(nreads + sizeof(QCQMI_HDR) - 1); + pResponse->QMIHdr.CtlFlags = 0x00; + pResponse->QMIHdr.ClientId = fd & 0xFF; + + QmiThreadRecvQMI(pResponse); + } + } + } + +__GobiNetThread_quit: + GobiNetDeInit(); + qmidevice_send_event_to_main(RIL_INDICATE_DEVICE_DISCONNECTED); + QmiThreadRecvQMI(NULL); //main thread may pending on QmiThreadSendQMI() + dbg_time("%s exit", __func__); + pthread_exit(NULL); + return NULL; +} + +#else +static int GobiNetSendQMI(PQCQMIMSG pRequest) {return -1;} +static void * GobiNetThread(void *pData) {dbg_time("please set CONFIG_GOBINET"); return NULL;} +#endif + +const struct qmi_device_ops gobi_qmidev_ops = { + .deinit = GobiNetDeInit, + .send = GobiNetSendQMI, + .read = GobiNetThread, +}; + diff --git a/root/package/link4all/quectel-CM/src/MPQCTL.h b/root/package/link4all/quectel-CM/src/MPQCTL.h new file mode 100644 index 00000000..c88bdc32 --- /dev/null +++ b/root/package/link4all/quectel-CM/src/MPQCTL.h @@ -0,0 +1,377 @@ +/*=========================================================================== + + M P Q C T L. H +DESCRIPTION: + + This module contains QMI QCTL module. + +INITIALIZATION AND SEQUENCING REQUIREMENTS: + +Copyright (C) 2011 by Qualcomm Technologies, Incorporated. All Rights Reserved. +===========================================================================*/ + +#ifndef MPQCTL_H +#define MPQCTL_H + +#include "MPQMI.h" + +#pragma pack(push, 1) + +// ================= QMICTL ================== + +// QMICTL Control Flags +#define QMICTL_CTL_FLAG_CMD 0x00 +#define QMICTL_CTL_FLAG_RSP 0x01 +#define QMICTL_CTL_FLAG_IND 0x02 + +#if 0 +typedef struct _QMICTL_TRANSACTION_ITEM +{ + LIST_ENTRY List; + UCHAR TransactionId; // QMICTL transaction id + PVOID Context; // Adapter or IocDev + PIRP Irp; +} QMICTL_TRANSACTION_ITEM, *PQMICTL_TRANSACTION_ITEM; +#endif + +typedef struct _QCQMICTL_MSG_HDR +{ + UCHAR CtlFlags; // 00-cmd, 01-rsp, 10-ind + UCHAR TransactionId; + USHORT QMICTLType; + USHORT Length; +} __attribute__ ((packed)) QCQMICTL_MSG_HDR, *PQCQMICTL_MSG_HDR; + +#define QCQMICTL_MSG_HDR_SIZE sizeof(QCQMICTL_MSG_HDR) + +typedef struct _QCQMICTL_MSG_HDR_RESP +{ + UCHAR CtlFlags; // 00-cmd, 01-rsp, 10-ind + UCHAR TransactionId; + USHORT QMICTLType; + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} __attribute__ ((packed)) QCQMICTL_MSG_HDR_RESP, *PQCQMICTL_MSG_HDR_RESP; + +typedef struct _QCQMICTL_MSG +{ + UCHAR CtlFlags; // 00-cmd, 01-rsp, 10-ind + UCHAR TransactionId; + USHORT QMICTLType; + USHORT Length; + UCHAR Payload; +} __attribute__ ((packed)) QCQMICTL_MSG, *PQCQMICTL_MSG; + +// TLV Header +typedef struct _QCQMICTL_TLV_HDR +{ + UCHAR TLVType; + USHORT TLVLength; +} __attribute__ ((packed)) QCQMICTL_TLV_HDR, *PQCQMICTL_TLV_HDR; + +#define QCQMICTL_TLV_HDR_SIZE sizeof(QCQMICTL_TLV_HDR) + +// QMICTL Type +#define QMICTL_SET_INSTANCE_ID_REQ 0x0020 +#define QMICTL_SET_INSTANCE_ID_RESP 0x0020 +#define QMICTL_GET_VERSION_REQ 0x0021 +#define QMICTL_GET_VERSION_RESP 0x0021 +#define QMICTL_GET_CLIENT_ID_REQ 0x0022 +#define QMICTL_GET_CLIENT_ID_RESP 0x0022 +#define QMICTL_RELEASE_CLIENT_ID_REQ 0x0023 +#define QMICTL_RELEASE_CLIENT_ID_RESP 0x0023 +#define QMICTL_REVOKE_CLIENT_ID_IND 0x0024 +#define QMICTL_INVALID_CLIENT_ID_IND 0x0025 +#define QMICTL_SET_DATA_FORMAT_REQ 0x0026 +#define QMICTL_SET_DATA_FORMAT_RESP 0x0026 +#define QMICTL_SYNC_REQ 0x0027 +#define QMICTL_SYNC_RESP 0x0027 +#define QMICTL_SYNC_IND 0x0027 + +#define QMICTL_FLAG_REQUEST 0x00 +#define QMICTL_FLAG_RESPONSE 0x01 +#define QMICTL_FLAG_INDICATION 0x02 + +// QMICTL Message Definitions + +typedef struct _QMICTL_SET_INSTANCE_ID_REQ_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_REQUEST + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_SET_INSTANCE_ID_REQ + USHORT Length; // 4 + UCHAR TLVType; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLVLength; // 1 + UCHAR Value; // Host-unique QMI instance for this device driver +} __attribute__ ((packed)) QMICTL_SET_INSTANCE_ID_REQ_MSG, *PQMICTL_SET_INSTANCE_ID_REQ_MSG; + +typedef struct _QMICTL_SET_INSTANCE_ID_RESP_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_RESPONSE + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_SET_INSTANCE_ID_RESP + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_RESULT_CODE + USHORT TLVLength; // 0x0004 + USHORT QMIResult; + USHORT QMIError; + UCHAR TLV2Type; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLV2Length; // 0x0002 + USHORT QMI_ID; // Upper byte is assigned by MSM, + // lower assigned by host +} __attribute__ ((packed)) QMICTL_SET_INSTANCE_ID_RESP_MSG, *PQMICTL_SET_INSTANCE_ID_RESP_MSG; + +typedef struct _QMICTL_GET_VERSION_REQ_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_REQUEST + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_GET_VERSION_REQ + USHORT Length; // 0 + UCHAR TLVType; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLVLength; // var + UCHAR QMUXTypes; // List of one byte QMUX_TYPE values + // 0xFF returns a list of versions for all + // QMUX_TYPEs implemented on the device +} __attribute__ ((packed)) QMICTL_GET_VERSION_REQ_MSG, *PQMICTL_GET_VERSION_REQ_MSG; + +typedef struct _QMUX_TYPE_VERSION_STRUCT +{ + UCHAR QMUXType; + USHORT MajorVersion; + USHORT MinorVersion; +} __attribute__ ((packed)) QMUX_TYPE_VERSION_STRUCT, *PQMUX_TYPE_VERSION_STRUCT; + +typedef struct _ADDENDUM_VERSION_PREAMBLE +{ + UCHAR LabelLength; + UCHAR Label; +} __attribute__ ((packed)) ADDENDUM_VERSION_PREAMBLE, *PADDENDUM_VERSION_PREAMBLE; + +#define QMICTL_GET_VERSION_RSP_TLV_TYPE_VERSION 0x01 +#define QMICTL_GET_VERSION_RSP_TLV_TYPE_ADD_VERSION 0x10 + +typedef struct _QMICTL_GET_VERSION_RESP_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_RESPONSE + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_GET_VERSION_RESP + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_RESULT_CODE + USHORT TLVLength; // 0x0004 + USHORT QMIResult; + USHORT QMIError; + UCHAR TLV2Type; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLV2Length; // var + UCHAR NumElements; // Num of QMUX_TYPE_VERSION_STRUCT + QMUX_TYPE_VERSION_STRUCT TypeVersion[0]; +} __attribute__ ((packed)) QMICTL_GET_VERSION_RESP_MSG, *PQMICTL_GET_VERSION_RESP_MSG; + +typedef struct _QMICTL_GET_CLIENT_ID_REQ_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_REQUEST + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_GET_CLIENT_ID_REQ + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLVLength; // 1 + UCHAR QMIType; // QMUX type +} __attribute__ ((packed)) QMICTL_GET_CLIENT_ID_REQ_MSG, *PQMICTL_GET_CLIENT_ID_REQ_MSG; + +typedef struct _QMICTL_GET_CLIENT_ID_RESP_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_RESPONSE + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_GET_CLIENT_ID_RESP + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_RESULT_CODE + USHORT TLVLength; // 0x0004 + USHORT QMIResult; // result code + USHORT QMIError; // error code + UCHAR TLV2Type; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLV2Length; // 2 + UCHAR QMIType; + UCHAR ClientId; +} __attribute__ ((packed)) QMICTL_GET_CLIENT_ID_RESP_MSG, *PQMICTL_GET_CLIENT_ID_RESP_MSG; + +typedef struct _QMICTL_RELEASE_CLIENT_ID_REQ_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_REQUEST + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_RELEASE_CLIENT_ID_REQ + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLVLength; // 0x0002 + UCHAR QMIType; + UCHAR ClientId; +} __attribute__ ((packed)) QMICTL_RELEASE_CLIENT_ID_REQ_MSG, *PQMICTL_RELEASE_CLIENT_ID_REQ_MSG; + +typedef struct _QMICTL_RELEASE_CLIENT_ID_RESP_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_RESPONSE + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_RELEASE_CLIENT_ID_RESP + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_RESULT_CODE + USHORT TLVLength; // 0x0004 + USHORT QMIResult; // result code + USHORT QMIError; // error code + UCHAR TLV2Type; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLV2Length; // 2 + UCHAR QMIType; + UCHAR ClientId; +} __attribute__ ((packed)) QMICTL_RELEASE_CLIENT_ID_RESP_MSG, *PQMICTL_RELEASE_CLIENT_ID_RESP_MSG; + +typedef struct _QMICTL_REVOKE_CLIENT_ID_IND_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_INDICATION + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_REVOKE_CLIENT_ID_IND + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLVLength; // 0x0002 + UCHAR QMIType; + UCHAR ClientId; +} __attribute__ ((packed)) QMICTL_REVOKE_CLIENT_ID_IND_MSG, *PQMICTL_REVOKE_CLIENT_ID_IND_MSG; + +typedef struct _QMICTL_INVALID_CLIENT_ID_IND_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_INDICATION + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_REVOKE_CLIENT_ID_IND + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLVLength; // 0x0002 + UCHAR QMIType; + UCHAR ClientId; +} __attribute__ ((packed)) QMICTL_INVALID_CLIENT_ID_IND_MSG, *PQMICTL_INVALID_CLIENT_ID_IND_MSG; + +typedef struct _QMICTL_SET_DATA_FORMAT_REQ_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_REQUEST + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_SET_DATA_FORMAT_REQ + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLVLength; // 1 + UCHAR DataFormat; // 0-default; 1-QoS hdr present +} __attribute__ ((packed)) QMICTL_SET_DATA_FORMAT_REQ_MSG, *PQMICTL_SET_DATA_FORMAT_REQ_MSG; + +#ifdef QC_IP_MODE +#define SET_DATA_FORMAT_TLV_TYPE_LINK_PROTO 0x10 +#define SET_DATA_FORMAT_LINK_PROTO_ETH 0x0001 +#define SET_DATA_FORMAT_LINK_PROTO_IP 0x0002 +typedef struct _QMICTL_SET_DATA_FORMAT_TLV_LINK_PROT +{ + UCHAR TLVType; // Link-Layer Protocol + USHORT TLVLength; // 2 + USHORT LinkProt; // 0x1: ETH; 0x2: IP +} QMICTL_SET_DATA_FORMAT_TLV_LINK_PROT, *PQMICTL_SET_DATA_FORMAT_TLV_LINK_PROT; + +#ifdef QCMP_UL_TLP +#define SET_DATA_FORMAT_TLV_TYPE_UL_TLP 0x11 +typedef struct _QMICTL_SET_DATA_FORMAT_TLV_UL_TLP +{ + UCHAR TLVType; // 0x11, Uplink TLP Setting + USHORT TLVLength; // 1 + UCHAR UlTlpSetting; // 0x0: Disable; 0x01: Enable +} QMICTL_SET_DATA_FORMAT_TLV_UL_TLP, *PQMICTL_SET_DATA_FORMAT_TLV_UL_TLP; +#endif // QCMP_UL_TLP + +#ifdef QCMP_DL_TLP +#define SET_DATA_FORMAT_TLV_TYPE_DL_TLP 0x13 +typedef struct _QMICTL_SET_DATA_FORMAT_TLV_DL_TLP +{ + UCHAR TLVType; // 0x11, Uplink TLP Setting + USHORT TLVLength; // 1 + UCHAR DlTlpSetting; // 0x0: Disable; 0x01: Enable +} QMICTL_SET_DATA_FORMAT_TLV_DL_TLP, *PQMICTL_SET_DATA_FORMAT_TLV_DL_TLP; +#endif // QCMP_DL_TLP + +#endif // QC_IP_MODE + +#ifdef MP_QCQOS_ENABLED +#define SET_DATA_FORMAT_TLV_TYPE_QOS_SETTING 0x12 +typedef struct _QMICTL_SET_DATA_FORMAT_TLV_QOS_SETTING +{ + UCHAR TLVType; // 0x12, QoS setting + USHORT TLVLength; // 1 + UCHAR QosSetting; // 0x0: Disable; 0x01: Enable +} QMICTL_SET_DATA_FORMAT_TLV_QOS_SETTING, *PQMICTL_SET_DATA_FORMAT_TLV_QOS_SETTING; +#endif // MP_QCQOS_ENABLED + +typedef struct _QMICTL_SET_DATA_FORMAT_RESP_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_RESPONSE + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_SET_DATA_FORMAT_RESP + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_RESULT_CODE + USHORT TLVLength; // 0x0004 + USHORT QMIResult; // result code + USHORT QMIError; // error code +} __attribute__ ((packed)) QMICTL_SET_DATA_FORMAT_RESP_MSG, *PQMICTL_SET_DATA_FORMAT_RESP_MSG; + +typedef struct _QMICTL_SYNC_REQ_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_REQUEST + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_CTL_SYNC_REQ + USHORT Length; // 0 +} __attribute__ ((packed)) QMICTL_SYNC_REQ_MSG, *PQMICTL_SYNC_REQ_MSG; + +typedef struct _QMICTL_SYNC_RESP_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_RESPONSE + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_CTL_SYNC_RESP + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_RESULT_CODE + USHORT TLVLength; // 0x0004 + USHORT QMIResult; + USHORT QMIError; +} __attribute__ ((packed)) QMICTL_SYNC_RESP_MSG, *PQMICTL_SYNC_RESP_MSG; + +typedef struct _QMICTL_SYNC_IND_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_INDICATION + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_REVOKE_CLIENT_ID_IND + USHORT Length; +} __attribute__ ((packed)) QMICTL_SYNC_IND_MSG, *PQMICTL_SYNC_IND_MSG; + +typedef struct _QMICTL_MSG +{ + union + { + // Message Header + QCQMICTL_MSG_HDR QMICTLMsgHdr; + QCQMICTL_MSG_HDR_RESP QMICTLMsgHdrRsp; + + // QMICTL Message + QMICTL_SET_INSTANCE_ID_REQ_MSG SetInstanceIdReq; + QMICTL_SET_INSTANCE_ID_RESP_MSG SetInstanceIdRsp; + QMICTL_GET_VERSION_REQ_MSG GetVersionReq; + QMICTL_GET_VERSION_RESP_MSG GetVersionRsp; + QMICTL_GET_CLIENT_ID_REQ_MSG GetClientIdReq; + QMICTL_GET_CLIENT_ID_RESP_MSG GetClientIdRsp; + QMICTL_RELEASE_CLIENT_ID_REQ_MSG ReleaseClientIdReq; + QMICTL_RELEASE_CLIENT_ID_RESP_MSG ReleaseClientIdRsp; + QMICTL_REVOKE_CLIENT_ID_IND_MSG RevokeClientIdInd; + QMICTL_INVALID_CLIENT_ID_IND_MSG InvalidClientIdInd; + QMICTL_SET_DATA_FORMAT_REQ_MSG SetDataFormatReq; + QMICTL_SET_DATA_FORMAT_RESP_MSG SetDataFormatRsp; + QMICTL_SYNC_REQ_MSG SyncReq; + QMICTL_SYNC_RESP_MSG SyncRsp; + QMICTL_SYNC_IND_MSG SyncInd; + }; +} __attribute__ ((packed)) QMICTL_MSG, *PQMICTL_MSG; +#pragma pack(pop) + +#endif // MPQCTL_H diff --git a/root/package/link4all/quectel-CM/src/MPQMI.h b/root/package/link4all/quectel-CM/src/MPQMI.h new file mode 100644 index 00000000..0092a47d --- /dev/null +++ b/root/package/link4all/quectel-CM/src/MPQMI.h @@ -0,0 +1,302 @@ +/*=========================================================================== + + M P Q M I. H +DESCRIPTION: + + This module contains forward references to the QMI module. + +INITIALIZATION AND SEQUENCING REQUIREMENTS: + +Copyright (C) 2011 by Qualcomm Technologies, Incorporated. All Rights Reserved. +===========================================================================*/ +/*=========================================================================== + + EDIT HISTORY FOR FILE + $Header: //depot/QMI/win/qcdrivers/ndis/MPQMI.h#3 $ + +when who what, where, why +-------- --- ---------------------------------------------------------- +11/20/04 hg Initial version. +===========================================================================*/ + +#ifndef USBQMI_H +#define USBQMI_H + +typedef char CHAR; +typedef unsigned char UCHAR; +typedef short SHORT; +typedef unsigned short USHORT; +typedef int INT; +typedef unsigned int UINT; +typedef long LONG; +typedef unsigned int ULONG; +typedef unsigned long long ULONG64; +typedef char *PCHAR; +typedef unsigned char *PUCHAR; +typedef int *PINT; +typedef int BOOL; + +#define TRUE (1 == 1) +#define FALSE (1 != 1) + +#define QMICTL_SUPPORTED_MAJOR_VERSION 1 +#define QMICTL_SUPPORTED_MINOR_VERSION 0 + +#pragma pack(push, 1) + +// ========= USB Control Message ========== + +#define USB_CTL_MSG_TYPE_QMI 0x01 + +// USB Control Message +typedef struct _QCUSB_CTL_MSG_HDR +{ + UCHAR IFType; +} __attribute__ ((packed)) QCUSB_CTL_MSG_HDR, *PQCUSB_CTL_MSG_HDR; + +#define QCUSB_CTL_MSG_HDR_SIZE sizeof(QCUSB_CTL_MSG_HDR) + +typedef struct _QCUSB_CTL_MSG +{ + UCHAR IFType; + UCHAR Message; +} __attribute__ ((packed)) QCUSB_CTL_MSG, *PQCUSB_CTL_MSG; + +#define QCTLV_TYPE_REQUIRED_PARAMETER 0x01 +#define QCTLV_TYPE_RESULT_CODE 0x02 + +// ================= QMI ================== + +// Define QMI Type +typedef enum _QMI_SERVICE_TYPE +{ + QMUX_TYPE_CTL = 0x00, + QMUX_TYPE_WDS = 0x01, + QMUX_TYPE_DMS = 0x02, + QMUX_TYPE_NAS = 0x03, + QMUX_TYPE_QOS = 0x04, + QMUX_TYPE_WMS = 0x05, + QMUX_TYPE_PDS = 0x06, + QMUX_TYPE_UIM = 0x0B, + QMUX_TYPE_WDS_IPV6 = 0x11, + QMUX_TYPE_WDS_ADMIN = 0x1A, + QMUX_TYPE_MAX = 0xFF, + QMUX_TYPE_ALL = 0xFF +} QMI_SERVICE_TYPE; + +typedef enum _QMI_RESULT_CODE_TYPE +{ + QMI_RESULT_SUCCESS = 0x0000, + QMI_RESULT_FAILURE = 0x0001 +} QMI_RESULT_CODE_TYPE; + +typedef enum _QMI_ERROR_CODE_TYPE +{ + QMI_ERR_NONE = 0x0000 + ,QMI_ERR_MALFORMED_MSG = 0x0001 + ,QMI_ERR_NO_MEMORY = 0x0002 + ,QMI_ERR_INTERNAL = 0x0003 + ,QMI_ERR_ABORTED = 0x0004 + ,QMI_ERR_CLIENT_IDS_EXHAUSTED = 0x0005 + ,QMI_ERR_UNABORTABLE_TRANSACTION = 0x0006 + ,QMI_ERR_INVALID_CLIENT_ID = 0x0007 + ,QMI_ERR_NO_THRESHOLDS = 0x0008 + ,QMI_ERR_INVALID_HANDLE = 0x0009 + ,QMI_ERR_INVALID_PROFILE = 0x000A + ,QMI_ERR_INVALID_PINID = 0x000B + ,QMI_ERR_INCORRECT_PIN = 0x000C + ,QMI_ERR_NO_NETWORK_FOUND = 0x000D + ,QMI_ERR_CALL_FAILED = 0x000E + ,QMI_ERR_OUT_OF_CALL = 0x000F + ,QMI_ERR_NOT_PROVISIONED = 0x0010 + ,QMI_ERR_MISSING_ARG = 0x0011 + ,QMI_ERR_ARG_TOO_LONG = 0x0013 + ,QMI_ERR_INVALID_TX_ID = 0x0016 + ,QMI_ERR_DEVICE_IN_USE = 0x0017 + ,QMI_ERR_OP_NETWORK_UNSUPPORTED = 0x0018 + ,QMI_ERR_OP_DEVICE_UNSUPPORTED = 0x0019 + ,QMI_ERR_NO_EFFECT = 0x001A + ,QMI_ERR_NO_FREE_PROFILE = 0x001B + ,QMI_ERR_INVALID_PDP_TYPE = 0x001C + ,QMI_ERR_INVALID_TECH_PREF = 0x001D + ,QMI_ERR_INVALID_PROFILE_TYPE = 0x001E + ,QMI_ERR_INVALID_SERVICE_TYPE = 0x001F + ,QMI_ERR_INVALID_REGISTER_ACTION = 0x0020 + ,QMI_ERR_INVALID_PS_ATTACH_ACTION = 0x0021 + ,QMI_ERR_AUTHENTICATION_FAILED = 0x0022 + ,QMI_ERR_PIN_BLOCKED = 0x0023 + ,QMI_ERR_PIN_PERM_BLOCKED = 0x0024 + ,QMI_ERR_SIM_NOT_INITIALIZED = 0x0025 + ,QMI_ERR_MAX_QOS_REQUESTS_IN_USE = 0x0026 + ,QMI_ERR_INCORRECT_FLOW_FILTER = 0x0027 + ,QMI_ERR_NETWORK_QOS_UNAWARE = 0x0028 + ,QMI_ERR_INVALID_QOS_ID = 0x0029 + ,QMI_ERR_INVALID_ID = 0x0029 + ,QMI_ERR_REQUESTED_NUM_UNSUPPORTED = 0x002A + ,QMI_ERR_INTERFACE_NOT_FOUND = 0x002B + ,QMI_ERR_FLOW_SUSPENDED = 0x002C + ,QMI_ERR_INVALID_DATA_FORMAT = 0x002D + ,QMI_ERR_GENERAL = 0x002E + ,QMI_ERR_UNKNOWN = 0x002F + ,QMI_ERR_INVALID_ARG = 0x0030 + ,QMI_ERR_INVALID_INDEX = 0x0031 + ,QMI_ERR_NO_ENTRY = 0x0032 + ,QMI_ERR_DEVICE_STORAGE_FULL = 0x0033 + ,QMI_ERR_DEVICE_NOT_READY = 0x0034 + ,QMI_ERR_NETWORK_NOT_READY = 0x0035 + ,QMI_ERR_CAUSE_CODE = 0x0036 + ,QMI_ERR_MESSAGE_NOT_SENT = 0x0037 + ,QMI_ERR_MESSAGE_DELIVERY_FAILURE = 0x0038 + ,QMI_ERR_INVALID_MESSAGE_ID = 0x0039 + ,QMI_ERR_ENCODING = 0x003A + ,QMI_ERR_AUTHENTICATION_LOCK = 0x003B + ,QMI_ERR_INVALID_TRANSITION = 0x003C + ,QMI_ERR_NOT_A_MCAST_IFACE = 0x003D + ,QMI_ERR_MAX_MCAST_REQUESTS_IN_USE = 0x003E + ,QMI_ERR_INVALID_MCAST_HANDLE = 0x003F + ,QMI_ERR_INVALID_IP_FAMILY_PREF = 0x0040 + ,QMI_ERR_SESSION_INACTIVE = 0x0041 + ,QMI_ERR_SESSION_INVALID = 0x0042 + ,QMI_ERR_SESSION_OWNERSHIP = 0x0043 + ,QMI_ERR_INSUFFICIENT_RESOURCES = 0x0044 + ,QMI_ERR_DISABLED = 0x0045 + ,QMI_ERR_INVALID_OPERATION = 0x0046 + ,QMI_ERR_INVALID_QMI_CMD = 0x0047 + ,QMI_ERR_TPDU_TYPE = 0x0048 + ,QMI_ERR_SMSC_ADDR = 0x0049 + ,QMI_ERR_INFO_UNAVAILABLE = 0x004A + ,QMI_ERR_SEGMENT_TOO_LONG = 0x004B + ,QMI_ERR_SEGMENT_ORDER = 0x004C + ,QMI_ERR_BUNDLING_NOT_SUPPORTED = 0x004D + ,QMI_ERR_OP_PARTIAL_FAILURE = 0x004E + ,QMI_ERR_POLICY_MISMATCH = 0x004F + ,QMI_ERR_SIM_FILE_NOT_FOUND = 0x0050 + ,QMI_ERR_EXTENDED_INTERNAL = 0x0051 + ,QMI_ERR_ACCESS_DENIED = 0x0052 + ,QMI_ERR_HARDWARE_RESTRICTED = 0x0053 + ,QMI_ERR_ACK_NOT_SENT = 0x0054 + ,QMI_ERR_INJECT_TIMEOUT = 0x0055 + ,QMI_ERR_INCOMPATIBLE_STATE = 0x005A + ,QMI_ERR_FDN_RESTRICT = 0x005B + ,QMI_ERR_SUPS_FAILURE_CAUSE = 0x005C + ,QMI_ERR_NO_RADIO = 0x005D + ,QMI_ERR_NOT_SUPPORTED = 0x005E + ,QMI_ERR_NO_SUBSCRIPTION = 0x005F + ,QMI_ERR_CARD_CALL_CONTROL_FAILED = 0x0060 + ,QMI_ERR_NETWORK_ABORTED = 0x0061 + ,QMI_ERR_MSG_BLOCKED = 0x0062 + ,QMI_ERR_INVALID_SESSION_TYPE = 0x0064 + ,QMI_ERR_INVALID_PB_TYPE = 0x0065 + ,QMI_ERR_NO_SIM = 0x0066 + ,QMI_ERR_PB_NOT_READY = 0x0067 + ,QMI_ERR_PIN_RESTRICTION = 0x0068 + ,QMI_ERR_PIN2_RESTRICTION = 0x0069 + ,QMI_ERR_PUK_RESTRICTION = 0x006A + ,QMI_ERR_PUK2_RESTRICTION = 0x006B + ,QMI_ERR_PB_ACCESS_RESTRICTED = 0x006C + ,QMI_ERR_PB_DELETE_IN_PROG = 0x006D + ,QMI_ERR_PB_TEXT_TOO_LONG = 0x006E + ,QMI_ERR_PB_NUMBER_TOO_LONG = 0x006F + ,QMI_ERR_PB_HIDDEN_KEY_RESTRICTION = 0x0070 +} QMI_ERROR_CODE_TYPE; + +#define QCQMI_CTL_FLAG_SERVICE 0x80 +#define QCQMI_CTL_FLAG_CTL_POINT 0x00 + +typedef struct _QCQMI_HDR +{ + UCHAR IFType; + USHORT Length; + UCHAR CtlFlags; // reserved + UCHAR QMIType; + UCHAR ClientId; +} __attribute__ ((packed)) QCQMI_HDR, *PQCQMI_HDR; + +#define QCQMI_HDR_SIZE (sizeof(QCQMI_HDR)-1) + +typedef struct _QCQMI +{ + UCHAR IFType; + USHORT Length; + UCHAR CtlFlags; // reserved + UCHAR QMIType; + UCHAR ClientId; + UCHAR SDU; +} __attribute__ ((packed)) QCQMI, *PQCQMI; + +typedef struct _QMI_SERVICE_VERSION +{ + USHORT Major; + USHORT Minor; + USHORT AddendumMajor; + USHORT AddendumMinor; +} __attribute__ ((packed)) QMI_SERVICE_VERSION, *PQMI_SERVICE_VERSION; + +// ================= QMUX ================== + +#define QMUX_MSG_OVERHEAD_BYTES 4 // Type(USHORT) Length(USHORT) -- header + +#define QMUX_BROADCAST_CID 0xFF + +typedef struct _QCQMUX_HDR +{ + UCHAR CtlFlags; // 0: single QMUX Msg; 1: + USHORT TransactionId; +} __attribute__ ((packed)) QCQMUX_HDR, *PQCQMUX_HDR; + +typedef struct _QCQMUX +{ + UCHAR CtlFlags; // 0: single QMUX Msg; 1: + USHORT TransactionId; + UCHAR Message; // Type(2), Length(2), Value +} __attribute__ ((packed)) QCQMUX, *PQCQMUX; + +#define QCQMUX_HDR_SIZE sizeof(QCQMUX_HDR) + +typedef struct _QCQMUX_MSG_HDR +{ + USHORT Type; + USHORT Length; +} __attribute__ ((packed)) QCQMUX_MSG_HDR, *PQCQMUX_MSG_HDR; + +#define QCQMUX_MSG_HDR_SIZE sizeof(QCQMUX_MSG_HDR) + +typedef struct _QCQMUX_MSG_HDR_RESP +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} __attribute__ ((packed)) QCQMUX_MSG_HDR_RESP, *PQCQMUX_MSG_HDR_RESP; + +typedef struct _QCQMUX_TLV +{ + UCHAR Type; + USHORT Length; + UCHAR Value; +} __attribute__ ((packed)) QCQMUX_TLV, *PQCQMUX_TLV; + +typedef struct _QMI_TLV_HDR +{ + UCHAR TLVType; + USHORT TLVLength; +} __attribute__ ((packed)) QMI_TLV_HDR, *PQMI_TLV_HDR; + +// QMUX Message Definitions -- QMI SDU +#define QMUX_CTL_FLAG_SINGLE_MSG 0x00 +#define QMUX_CTL_FLAG_COMPOUND_MSG 0x01 +#define QMUX_CTL_FLAG_TYPE_CMD 0x00 +#define QMUX_CTL_FLAG_TYPE_RSP 0x02 +#define QMUX_CTL_FLAG_TYPE_IND 0x04 +#define QMUX_CTL_FLAG_MASK_COMPOUND 0x01 +#define QMUX_CTL_FLAG_MASK_TYPE 0x06 // 00-cmd, 01-rsp, 10-ind + +#pragma pack(pop) + +#endif // USBQMI_H diff --git a/root/package/link4all/quectel-CM/src/MPQMUX.c b/root/package/link4all/quectel-CM/src/MPQMUX.c new file mode 100644 index 00000000..b12922dc --- /dev/null +++ b/root/package/link4all/quectel-CM/src/MPQMUX.c @@ -0,0 +1,446 @@ +/****************************************************************************** + @file MPQMUX.c + @brief QMI mux. + + DESCRIPTION + Connectivity Management Tool for USB network adapter of Quectel wireless cellular modules. + + INITIALIZATION AND SEQUENCING REQUIREMENTS + None. + + --------------------------------------------------------------------------- + Copyright (c) 2016 - 2020 Quectel Wireless Solution, Co., Ltd. All Rights Reserved. + Quectel Wireless Solution Proprietary and Confidential. + --------------------------------------------------------------------------- +******************************************************************************/ + +#include "QMIThread.h" +static char line[1024]; +static pthread_mutex_t dumpQMIMutex = PTHREAD_MUTEX_INITIALIZER; +#undef dbg +#define dbg( format, arg... ) do {if (strlen(line) < sizeof(line)) snprintf(&line[strlen(line)], sizeof(line) - strlen(line), format, ## arg);} while (0) + +PQMI_TLV_HDR GetTLV (PQCQMUX_MSG_HDR pQMUXMsgHdr, int TLVType); + +typedef struct { + UINT type; + const char *name; +} QMI_NAME_T; + +#define qmi_name_item(type) {type, #type} + +#if 0 +static const QMI_NAME_T qmi_IFType[] = { +{USB_CTL_MSG_TYPE_QMI, "USB_CTL_MSG_TYPE_QMI"}, +}; + +static const QMI_NAME_T qmi_CtlFlags[] = { +qmi_name_item(QMICTL_CTL_FLAG_CMD), +qmi_name_item(QCQMI_CTL_FLAG_SERVICE), +}; + +static const QMI_NAME_T qmi_QMIType[] = { +qmi_name_item(QMUX_TYPE_CTL), +qmi_name_item(QMUX_TYPE_WDS), +qmi_name_item(QMUX_TYPE_DMS), +qmi_name_item(QMUX_TYPE_NAS), +qmi_name_item(QMUX_TYPE_QOS), +qmi_name_item(QMUX_TYPE_WMS), +qmi_name_item(QMUX_TYPE_PDS), +qmi_name_item(QMUX_TYPE_WDS_ADMIN), +}; + +static const QMI_NAME_T qmi_ctl_CtlFlags[] = { +qmi_name_item(QMICTL_FLAG_REQUEST), +qmi_name_item(QMICTL_FLAG_RESPONSE), +qmi_name_item(QMICTL_FLAG_INDICATION), +}; +#endif + +static const QMI_NAME_T qmux_ctl_QMICTLType[] = { +// QMICTL Type +qmi_name_item(QMICTL_SET_INSTANCE_ID_REQ), // 0x0020 +qmi_name_item(QMICTL_SET_INSTANCE_ID_RESP), // 0x0020 +qmi_name_item(QMICTL_GET_VERSION_REQ), // 0x0021 +qmi_name_item(QMICTL_GET_VERSION_RESP), // 0x0021 +qmi_name_item(QMICTL_GET_CLIENT_ID_REQ), // 0x0022 +qmi_name_item(QMICTL_GET_CLIENT_ID_RESP), // 0x0022 +qmi_name_item(QMICTL_RELEASE_CLIENT_ID_REQ), // 0x0023 +qmi_name_item(QMICTL_RELEASE_CLIENT_ID_RESP), // 0x0023 +qmi_name_item(QMICTL_REVOKE_CLIENT_ID_IND), // 0x0024 +qmi_name_item(QMICTL_INVALID_CLIENT_ID_IND), // 0x0025 +qmi_name_item(QMICTL_SET_DATA_FORMAT_REQ), // 0x0026 +qmi_name_item(QMICTL_SET_DATA_FORMAT_RESP), // 0x0026 +qmi_name_item(QMICTL_SYNC_REQ), // 0x0027 +qmi_name_item(QMICTL_SYNC_RESP), // 0x0027 +qmi_name_item(QMICTL_SYNC_IND), // 0x0027 +}; + +static const QMI_NAME_T qmux_CtlFlags[] = { +qmi_name_item(QMUX_CTL_FLAG_TYPE_CMD), +qmi_name_item(QMUX_CTL_FLAG_TYPE_RSP), +qmi_name_item(QMUX_CTL_FLAG_TYPE_IND), +}; + + +static const QMI_NAME_T qmux_wds_Type[] = { +qmi_name_item(QMIWDS_SET_EVENT_REPORT_REQ), // 0x0001 +qmi_name_item(QMIWDS_SET_EVENT_REPORT_RESP), // 0x0001 +qmi_name_item(QMIWDS_EVENT_REPORT_IND), // 0x0001 +qmi_name_item(QMIWDS_START_NETWORK_INTERFACE_REQ), // 0x0020 +qmi_name_item(QMIWDS_START_NETWORK_INTERFACE_RESP), // 0x0020 +qmi_name_item(QMIWDS_STOP_NETWORK_INTERFACE_REQ), // 0x0021 +qmi_name_item(QMIWDS_STOP_NETWORK_INTERFACE_RESP), // 0x0021 +qmi_name_item(QMIWDS_GET_PKT_SRVC_STATUS_REQ), // 0x0022 +qmi_name_item(QMIWDS_GET_PKT_SRVC_STATUS_RESP), // 0x0022 +qmi_name_item(QMIWDS_GET_PKT_SRVC_STATUS_IND), // 0x0022 +qmi_name_item(QMIWDS_GET_CURRENT_CHANNEL_RATE_REQ), // 0x0023 +qmi_name_item(QMIWDS_GET_CURRENT_CHANNEL_RATE_RESP), // 0x0023 +qmi_name_item(QMIWDS_GET_PKT_STATISTICS_REQ), // 0x0024 +qmi_name_item(QMIWDS_GET_PKT_STATISTICS_RESP), // 0x0024 +qmi_name_item(QMIWDS_MODIFY_PROFILE_SETTINGS_REQ), // 0x0028 +qmi_name_item(QMIWDS_MODIFY_PROFILE_SETTINGS_RESP), // 0x0028 +qmi_name_item(QMIWDS_GET_PROFILE_SETTINGS_REQ), // 0x002B +qmi_name_item(QMIWDS_GET_PROFILE_SETTINGS_RESP), // 0x002BD +qmi_name_item(QMIWDS_GET_DEFAULT_SETTINGS_REQ), // 0x002C +qmi_name_item(QMIWDS_GET_DEFAULT_SETTINGS_RESP), // 0x002C +qmi_name_item(QMIWDS_GET_RUNTIME_SETTINGS_REQ), // 0x002D +qmi_name_item(QMIWDS_GET_RUNTIME_SETTINGS_RESP), // 0x002D +qmi_name_item(QMIWDS_GET_MIP_MODE_REQ), // 0x002F +qmi_name_item(QMIWDS_GET_MIP_MODE_RESP), // 0x002F +qmi_name_item(QMIWDS_GET_DATA_BEARER_REQ), // 0x0037 +qmi_name_item(QMIWDS_GET_DATA_BEARER_RESP), // 0x0037 +qmi_name_item(QMIWDS_DUN_CALL_INFO_REQ), // 0x0038 +qmi_name_item(QMIWDS_DUN_CALL_INFO_RESP), // 0x0038 +qmi_name_item(QMIWDS_DUN_CALL_INFO_IND), // 0x0038 +qmi_name_item(QMIWDS_SET_CLIENT_IP_FAMILY_PREF_REQ), // 0x004D +qmi_name_item(QMIWDS_SET_CLIENT_IP_FAMILY_PREF_RESP), // 0x004D +qmi_name_item(QMIWDS_SET_AUTO_CONNECT_REQ), // 0x0051 +qmi_name_item(QMIWDS_SET_AUTO_CONNECT_RESP), // 0x0051 +qmi_name_item(QMIWDS_BIND_MUX_DATA_PORT_REQ), // 0x00A2 +qmi_name_item(QMIWDS_BIND_MUX_DATA_PORT_RESP), // 0x00A2 +}; + +static const QMI_NAME_T qmux_dms_Type[] = { +// ======================= DMS ============================== +qmi_name_item(QMIDMS_SET_EVENT_REPORT_REQ), // 0x0001 +qmi_name_item(QMIDMS_SET_EVENT_REPORT_RESP), // 0x0001 +qmi_name_item(QMIDMS_EVENT_REPORT_IND), // 0x0001 +qmi_name_item(QMIDMS_GET_DEVICE_CAP_REQ), // 0x0020 +qmi_name_item(QMIDMS_GET_DEVICE_CAP_RESP), // 0x0020 +qmi_name_item(QMIDMS_GET_DEVICE_MFR_REQ), // 0x0021 +qmi_name_item(QMIDMS_GET_DEVICE_MFR_RESP), // 0x0021 +qmi_name_item(QMIDMS_GET_DEVICE_MODEL_ID_REQ), // 0x0022 +qmi_name_item(QMIDMS_GET_DEVICE_MODEL_ID_RESP), // 0x0022 +qmi_name_item(QMIDMS_GET_DEVICE_REV_ID_REQ), // 0x0023 +qmi_name_item(QMIDMS_GET_DEVICE_REV_ID_RESP), // 0x0023 +qmi_name_item(QMIDMS_GET_MSISDN_REQ), // 0x0024 +qmi_name_item(QMIDMS_GET_MSISDN_RESP), // 0x0024 +qmi_name_item(QMIDMS_GET_DEVICE_SERIAL_NUMBERS_REQ), // 0x0025 +qmi_name_item(QMIDMS_GET_DEVICE_SERIAL_NUMBERS_RESP), // 0x0025 +qmi_name_item(QMIDMS_UIM_SET_PIN_PROTECTION_REQ), // 0x0027 +qmi_name_item(QMIDMS_UIM_SET_PIN_PROTECTION_RESP), // 0x0027 +qmi_name_item(QMIDMS_UIM_VERIFY_PIN_REQ), // 0x0028 +qmi_name_item(QMIDMS_UIM_VERIFY_PIN_RESP), // 0x0028 +qmi_name_item(QMIDMS_UIM_UNBLOCK_PIN_REQ), // 0x0029 +qmi_name_item(QMIDMS_UIM_UNBLOCK_PIN_RESP), // 0x0029 +qmi_name_item(QMIDMS_UIM_CHANGE_PIN_REQ), // 0x002A +qmi_name_item(QMIDMS_UIM_CHANGE_PIN_RESP), // 0x002A +qmi_name_item(QMIDMS_UIM_GET_PIN_STATUS_REQ), // 0x002B +qmi_name_item(QMIDMS_UIM_GET_PIN_STATUS_RESP), // 0x002B +qmi_name_item(QMIDMS_GET_DEVICE_HARDWARE_REV_REQ), // 0x002C +qmi_name_item(QMIDMS_GET_DEVICE_HARDWARE_REV_RESP), // 0x002C +qmi_name_item(QMIDMS_GET_OPERATING_MODE_REQ), // 0x002D +qmi_name_item(QMIDMS_GET_OPERATING_MODE_RESP), // 0x002D +qmi_name_item(QMIDMS_SET_OPERATING_MODE_REQ), // 0x002E +qmi_name_item(QMIDMS_SET_OPERATING_MODE_RESP), // 0x002E +qmi_name_item(QMIDMS_GET_ACTIVATED_STATUS_REQ), // 0x0031 +qmi_name_item(QMIDMS_GET_ACTIVATED_STATUS_RESP), // 0x0031 +qmi_name_item(QMIDMS_ACTIVATE_AUTOMATIC_REQ), // 0x0032 +qmi_name_item(QMIDMS_ACTIVATE_AUTOMATIC_RESP), // 0x0032 +qmi_name_item(QMIDMS_ACTIVATE_MANUAL_REQ), // 0x0033 +qmi_name_item(QMIDMS_ACTIVATE_MANUAL_RESP), // 0x0033 +qmi_name_item(QMIDMS_UIM_GET_ICCID_REQ), // 0x003C +qmi_name_item(QMIDMS_UIM_GET_ICCID_RESP), // 0x003C +qmi_name_item(QMIDMS_UIM_GET_CK_STATUS_REQ), // 0x0040 +qmi_name_item(QMIDMS_UIM_GET_CK_STATUS_RESP), // 0x0040 +qmi_name_item(QMIDMS_UIM_SET_CK_PROTECTION_REQ), // 0x0041 +qmi_name_item(QMIDMS_UIM_SET_CK_PROTECTION_RESP), // 0x0041 +qmi_name_item(QMIDMS_UIM_UNBLOCK_CK_REQ), // 0x0042 +qmi_name_item(QMIDMS_UIM_UNBLOCK_CK_RESP), // 0x0042 +qmi_name_item(QMIDMS_UIM_GET_IMSI_REQ), // 0x0043 +qmi_name_item(QMIDMS_UIM_GET_IMSI_RESP), // 0x0043 +qmi_name_item(QMIDMS_UIM_GET_STATE_REQ), // 0x0044 +qmi_name_item(QMIDMS_UIM_GET_STATE_RESP), // 0x0044 +qmi_name_item(QMIDMS_GET_BAND_CAP_REQ), // 0x0045 +qmi_name_item(QMIDMS_GET_BAND_CAP_RESP), // 0x0045 +}; + +static const QMI_NAME_T qmux_nas_Type[] = { +// ======================= NAS ============================== +qmi_name_item(QMINAS_SET_EVENT_REPORT_REQ), // 0x0002 +qmi_name_item(QMINAS_SET_EVENT_REPORT_RESP), // 0x0002 +qmi_name_item(QMINAS_EVENT_REPORT_IND), // 0x0002 +qmi_name_item(QMINAS_GET_SIGNAL_STRENGTH_REQ), // 0x0020 +qmi_name_item(QMINAS_GET_SIGNAL_STRENGTH_RESP), // 0x0020 +qmi_name_item(QMINAS_PERFORM_NETWORK_SCAN_REQ), // 0x0021 +qmi_name_item(QMINAS_PERFORM_NETWORK_SCAN_RESP), // 0x0021 +qmi_name_item(QMINAS_INITIATE_NW_REGISTER_REQ), // 0x0022 +qmi_name_item(QMINAS_INITIATE_NW_REGISTER_RESP), // 0x0022 +qmi_name_item(QMINAS_INITIATE_ATTACH_REQ), // 0x0023 +qmi_name_item(QMINAS_INITIATE_ATTACH_RESP), // 0x0023 +qmi_name_item(QMINAS_GET_SERVING_SYSTEM_REQ), // 0x0024 +qmi_name_item(QMINAS_GET_SERVING_SYSTEM_RESP), // 0x0024 +qmi_name_item(QMINAS_SERVING_SYSTEM_IND), // 0x0024 +qmi_name_item(QMINAS_GET_HOME_NETWORK_REQ), // 0x0025 +qmi_name_item(QMINAS_GET_HOME_NETWORK_RESP), // 0x0025 +qmi_name_item(QMINAS_GET_PREFERRED_NETWORK_REQ), // 0x0026 +qmi_name_item(QMINAS_GET_PREFERRED_NETWORK_RESP), // 0x0026 +qmi_name_item(QMINAS_SET_PREFERRED_NETWORK_REQ), // 0x0027 +qmi_name_item(QMINAS_SET_PREFERRED_NETWORK_RESP), // 0x0027 +qmi_name_item(QMINAS_GET_FORBIDDEN_NETWORK_REQ), // 0x0028 +qmi_name_item(QMINAS_GET_FORBIDDEN_NETWORK_RESP), // 0x0028 +qmi_name_item(QMINAS_SET_FORBIDDEN_NETWORK_REQ), // 0x0029 +qmi_name_item(QMINAS_SET_FORBIDDEN_NETWORK_RESP), // 0x0029 +qmi_name_item(QMINAS_SET_TECHNOLOGY_PREF_REQ), // 0x002A +qmi_name_item(QMINAS_SET_TECHNOLOGY_PREF_RESP), // 0x002A +qmi_name_item(QMINAS_GET_RF_BAND_INFO_REQ), // 0x0031 +qmi_name_item(QMINAS_GET_RF_BAND_INFO_RESP), // 0x0031 +qmi_name_item(QMINAS_GET_PLMN_NAME_REQ), // 0x0044 +qmi_name_item(QMINAS_GET_PLMN_NAME_RESP), // 0x0044 +qmi_name_item(QUECTEL_PACKET_TRANSFER_START_IND), // 0X100 +qmi_name_item(QUECTEL_PACKET_TRANSFER_END_IND), // 0X101 +qmi_name_item(QMINAS_GET_SYS_INFO_REQ), // 0x004D +qmi_name_item(QMINAS_GET_SYS_INFO_RESP), // 0x004D +qmi_name_item(QMINAS_SYS_INFO_IND), // 0x004D +}; + +static const QMI_NAME_T qmux_wms_Type[] = { +// ======================= WMS ============================== +qmi_name_item(QMIWMS_SET_EVENT_REPORT_REQ), // 0x0001 +qmi_name_item(QMIWMS_SET_EVENT_REPORT_RESP), // 0x0001 +qmi_name_item(QMIWMS_EVENT_REPORT_IND), // 0x0001 +qmi_name_item(QMIWMS_RAW_SEND_REQ), // 0x0020 +qmi_name_item(QMIWMS_RAW_SEND_RESP), // 0x0020 +qmi_name_item(QMIWMS_RAW_WRITE_REQ), // 0x0021 +qmi_name_item(QMIWMS_RAW_WRITE_RESP), // 0x0021 +qmi_name_item(QMIWMS_RAW_READ_REQ), // 0x0022 +qmi_name_item(QMIWMS_RAW_READ_RESP), // 0x0022 +qmi_name_item(QMIWMS_MODIFY_TAG_REQ), // 0x0023 +qmi_name_item(QMIWMS_MODIFY_TAG_RESP), // 0x0023 +qmi_name_item(QMIWMS_DELETE_REQ), // 0x0024 +qmi_name_item(QMIWMS_DELETE_RESP), // 0x0024 +qmi_name_item(QMIWMS_GET_MESSAGE_PROTOCOL_REQ), // 0x0030 +qmi_name_item(QMIWMS_GET_MESSAGE_PROTOCOL_RESP), // 0x0030 +qmi_name_item(QMIWMS_LIST_MESSAGES_REQ), // 0x0031 +qmi_name_item(QMIWMS_LIST_MESSAGES_RESP), // 0x0031 +qmi_name_item(QMIWMS_GET_SMSC_ADDRESS_REQ), // 0x0034 +qmi_name_item(QMIWMS_GET_SMSC_ADDRESS_RESP), // 0x0034 +qmi_name_item(QMIWMS_SET_SMSC_ADDRESS_REQ), // 0x0035 +qmi_name_item(QMIWMS_SET_SMSC_ADDRESS_RESP), // 0x0035 +qmi_name_item(QMIWMS_GET_STORE_MAX_SIZE_REQ), // 0x0036 +qmi_name_item(QMIWMS_GET_STORE_MAX_SIZE_RESP), // 0x0036 +}; + +static const QMI_NAME_T qmux_wds_admin_Type[] = { +qmi_name_item(QMIWDS_ADMIN_SET_DATA_FORMAT_REQ), // 0x0020 +qmi_name_item(QMIWDS_ADMIN_SET_DATA_FORMAT_RESP), // 0x0020 +qmi_name_item(QMIWDS_ADMIN_GET_DATA_FORMAT_REQ), // 0x0021 +qmi_name_item(QMIWDS_ADMIN_GET_DATA_FORMAT_RESP), // 0x0021 +qmi_name_item(QMIWDS_ADMIN_SET_QMAP_SETTINGS_REQ), // 0x002B +qmi_name_item(QMIWDS_ADMIN_SET_QMAP_SETTINGS_RESP), // 0x002B +qmi_name_item(QMIWDS_ADMIN_GET_QMAP_SETTINGS_REQ), // 0x002C +qmi_name_item(QMIWDS_ADMIN_GET_QMAP_SETTINGS_RESP), // 0x002C +qmi_name_item(QMI_WDA_SET_LOOPBACK_CONFIG_REQ), // 0x002F +qmi_name_item(QMI_WDA_SET_LOOPBACK_CONFIG_RESP), // 0x002F +qmi_name_item(QMI_WDA_SET_LOOPBACK_CONFIG_IND), // 0x002F +}; + +static const QMI_NAME_T qmux_uim_Type[] = { +qmi_name_item( QMIUIM_READ_TRANSPARENT_REQ), // 0x0020 +qmi_name_item( QMIUIM_READ_TRANSPARENT_RESP), // 0x0020 +qmi_name_item( QMIUIM_READ_TRANSPARENT_IND), // 0x0020 +qmi_name_item( QMIUIM_READ_RECORD_REQ), // 0x0021 +qmi_name_item( QMIUIM_READ_RECORD_RESP), // 0x0021 +qmi_name_item( QMIUIM_READ_RECORD_IND), // 0x0021 +qmi_name_item( QMIUIM_WRITE_TRANSPARENT_REQ), // 0x0022 +qmi_name_item( QMIUIM_WRITE_TRANSPARENT_RESP), // 0x0022 +qmi_name_item( QMIUIM_WRITE_TRANSPARENT_IND), // 0x0022 +qmi_name_item( QMIUIM_WRITE_RECORD_REQ), // 0x0023 +qmi_name_item( QMIUIM_WRITE_RECORD_RESP), // 0x0023 +qmi_name_item( QMIUIM_WRITE_RECORD_IND), // 0x0023 +qmi_name_item( QMIUIM_SET_PIN_PROTECTION_REQ), // 0x0025 +qmi_name_item( QMIUIM_SET_PIN_PROTECTION_RESP), // 0x0025 +qmi_name_item( QMIUIM_SET_PIN_PROTECTION_IND), // 0x0025 +qmi_name_item( QMIUIM_VERIFY_PIN_REQ), // 0x0026 +qmi_name_item( QMIUIM_VERIFY_PIN_RESP), // 0x0026 +qmi_name_item( QMIUIM_VERIFY_PIN_IND), // 0x0026 +qmi_name_item( QMIUIM_UNBLOCK_PIN_REQ), // 0x0027 +qmi_name_item( QMIUIM_UNBLOCK_PIN_RESP), // 0x0027 +qmi_name_item( QMIUIM_UNBLOCK_PIN_IND), // 0x0027 +qmi_name_item( QMIUIM_CHANGE_PIN_REQ), // 0x0028 +qmi_name_item( QMIUIM_CHANGE_PIN_RESP), // 0x0028 +qmi_name_item( QMIUIM_CHANGE_PIN_IND), // 0x0028 +qmi_name_item( QMIUIM_DEPERSONALIZATION_REQ), // 0x0029 +qmi_name_item( QMIUIM_DEPERSONALIZATION_RESP), // 0x0029 +qmi_name_item( QMIUIM_EVENT_REG_REQ), // 0x002E +qmi_name_item( QMIUIM_EVENT_REG_RESP), // 0x002E +qmi_name_item( QMIUIM_GET_CARD_STATUS_REQ), // 0x002F +qmi_name_item( QMIUIM_GET_CARD_STATUS_RESP), // 0x002F +qmi_name_item( QMIUIM_STATUS_CHANGE_IND), // 0x0032 +}; + +static const char * qmi_name_get(const QMI_NAME_T *table, size_t size, int type, const char *tag) { + static char unknow[40]; + size_t i; + + if (qmux_CtlFlags == table) { + if (!strcmp(tag, "_REQ")) + tag = "_CMD"; + else if (!strcmp(tag, "_RESP")) + tag = "_RSP"; + } + + for (i = 0; i < size; i++) { + if (table[i].type == (UINT)type) { + if (!tag || (strstr(table[i].name, tag))) + return table[i].name; + } + } + sprintf(unknow, "unknow_%x", type); + return unknow; +} + +#define QMI_NAME(table, type) qmi_name_get(table, sizeof(table) / sizeof(table[0]), type, 0) +#define QMUX_NAME(table, type, tag) qmi_name_get(table, sizeof(table) / sizeof(table[0]), type, tag) + +void dump_tlv(PQCQMUX_MSG_HDR pQMUXMsgHdr) { + int TLVFind = 0; + int i; + //dbg("QCQMUX_TLV-----------------------------------\n"); + //dbg("{Type,\tLength,\tValue}\n"); + + while (1) { + PQMI_TLV_HDR TLVHdr = GetTLV(pQMUXMsgHdr, 0x1000 + (++TLVFind)); + if (TLVHdr == NULL) + break; + + //if ((TLVHdr->TLVType == 0x02) && ((USHORT *)(TLVHdr+1))[0]) + { + dbg("{%02x,\t%04x,\t", TLVHdr->TLVType, le16_to_cpu(TLVHdr->TLVLength)); + for (i = 0; i < le16_to_cpu(TLVHdr->TLVLength); i++) { + dbg("%02x ", ((UCHAR *)(TLVHdr+1))[i]); + } + dbg("}\n"); + } + } // while +} + +void dump_ctl(PQCQMICTL_MSG_HDR CTLHdr) { + const char *tag; + + //dbg("QCQMICTL_MSG--------------------------------------------\n"); + //dbg("CtlFlags: %02x\t\t%s\n", CTLHdr->CtlFlags, QMI_NAME(qmi_ctl_CtlFlags, CTLHdr->CtlFlags)); + dbg("TransactionId: %02x\n", CTLHdr->TransactionId); + switch (CTLHdr->CtlFlags) { + case QMICTL_FLAG_REQUEST: tag = "_REQ"; break; + case QMICTL_FLAG_RESPONSE: tag = "_RESP"; break; + case QMICTL_FLAG_INDICATION: tag = "_IND"; break; + default: tag = 0; break; + } + dbg("QMICTLType: %04x\t%s\n", le16_to_cpu(CTLHdr->QMICTLType), + QMUX_NAME(qmux_ctl_QMICTLType, le16_to_cpu(CTLHdr->QMICTLType), tag)); + dbg("Length: %04x\n", le16_to_cpu(CTLHdr->Length)); + + dump_tlv((PQCQMUX_MSG_HDR)(&CTLHdr->QMICTLType)); +} + +int dump_qmux(QMI_SERVICE_TYPE serviceType, PQCQMUX_HDR QMUXHdr) { + PQCQMUX_MSG_HDR QMUXMsgHdr = (PQCQMUX_MSG_HDR) (QMUXHdr + 1); + CHAR *tag; + + //dbg("QCQMUX--------------------------------------------\n"); + switch (QMUXHdr->CtlFlags&QMUX_CTL_FLAG_MASK_TYPE) { + case QMUX_CTL_FLAG_TYPE_CMD: tag = "_REQ"; break; + case QMUX_CTL_FLAG_TYPE_RSP: tag = "_RESP"; break; + case QMUX_CTL_FLAG_TYPE_IND: tag = "_IND"; break; + default: tag = 0; break; + } + //dbg("CtlFlags: %02x\t\t%s\n", QMUXHdr->CtlFlags, QMUX_NAME(qmux_CtlFlags, QMUXHdr->CtlFlags, tag)); + dbg("TransactionId: %04x\n", le16_to_cpu(QMUXHdr->TransactionId)); + + //dbg("QCQMUX_MSG_HDR-----------------------------------\n"); + switch (serviceType) { + case QMUX_TYPE_DMS: + dbg("Type: %04x\t%s\n", le16_to_cpu(QMUXMsgHdr->Type), + QMUX_NAME(qmux_dms_Type, le16_to_cpu(QMUXMsgHdr->Type), tag)); + break; + case QMUX_TYPE_NAS: + dbg("Type: %04x\t%s\n", le16_to_cpu(QMUXMsgHdr->Type), + QMUX_NAME(qmux_nas_Type, le16_to_cpu(QMUXMsgHdr->Type), tag)); + break; + case QMUX_TYPE_WDS: + case QMUX_TYPE_WDS_IPV6: + dbg("Type: %04x\t%s\n", le16_to_cpu(QMUXMsgHdr->Type), + QMUX_NAME(qmux_wds_Type, le16_to_cpu(QMUXMsgHdr->Type), tag)); + break; + case QMUX_TYPE_WMS: + dbg("Type: %04x\t%s\n", le16_to_cpu(QMUXMsgHdr->Type), + QMUX_NAME(qmux_wms_Type, le16_to_cpu(QMUXMsgHdr->Type), tag)); + break; + case QMUX_TYPE_WDS_ADMIN: + dbg("Type: %04x\t%s\n", le16_to_cpu(QMUXMsgHdr->Type), + QMUX_NAME(qmux_wds_admin_Type, le16_to_cpu(QMUXMsgHdr->Type), tag)); + break; + case QMUX_TYPE_UIM: + dbg("Type: %04x\t%s\n", le16_to_cpu(QMUXMsgHdr->Type), + QMUX_NAME(qmux_uim_Type, le16_to_cpu(QMUXMsgHdr->Type), tag)); + break; + case QMUX_TYPE_PDS: + case QMUX_TYPE_QOS: + case QMUX_TYPE_CTL: + default: + dbg("Type: %04x\t%s\n", le16_to_cpu(QMUXMsgHdr->Type), "PDS/QOS/CTL/unknown!"); + break; + } + dbg("Length: %04x\n", le16_to_cpu(QMUXMsgHdr->Length)); + + dump_tlv(QMUXMsgHdr); + + return 0; +} + +void dump_qmi(void *dataBuffer, int dataLen) +{ + PQCQMI_HDR QMIHdr = (PQCQMI_HDR)dataBuffer; + PQCQMUX_HDR QMUXHdr = (PQCQMUX_HDR) (QMIHdr + 1); + PQCQMICTL_MSG_HDR CTLHdr = (PQCQMICTL_MSG_HDR) (QMIHdr + 1); + + int i; + + if (!debug_qmi) + return; + + pthread_mutex_lock(&dumpQMIMutex); + line[0] = 0; + for (i = 0; i < dataLen; i++) { + dbg("%02x ", ((unsigned char *)dataBuffer)[i]); + } + dbg_time("%s", line); + line[0] = 0; + + //dbg("QCQMI_HDR-----------------------------------------"); + //dbg("IFType: %02x\t\t%s", QMIHdr->IFType, QMI_NAME(qmi_IFType, QMIHdr->IFType)); + //dbg("Length: %04x", le16_to_cpu(QMIHdr->Length)); + //dbg("CtlFlags: %02x\t\t%s", QMIHdr->CtlFlags, QMI_NAME(qmi_CtlFlags, QMIHdr->CtlFlags)); + //dbg("QMIType: %02x\t\t%s", QMIHdr->QMIType, QMI_NAME(qmi_QMIType, QMIHdr->QMIType)); + //dbg("ClientId: %02x", QMIHdr->ClientId); + + if (QMIHdr->QMIType == QMUX_TYPE_CTL) { + dump_ctl(CTLHdr); + } else { + dump_qmux(QMIHdr->QMIType, QMUXHdr); + } + dbg_time("%s", line); + pthread_mutex_unlock(&dumpQMIMutex); +} diff --git a/root/package/link4all/quectel-CM/src/MPQMUX.h b/root/package/link4all/quectel-CM/src/MPQMUX.h new file mode 100644 index 00000000..3c63b6ae --- /dev/null +++ b/root/package/link4all/quectel-CM/src/MPQMUX.h @@ -0,0 +1,3485 @@ +/*=========================================================================== + + M P Q M U X. H +DESCRIPTION: + + This file provides support for QMUX. + +INITIALIZATION AND SEQUENCING REQUIREMENTS: + +Copyright (C) 2011 by Qualcomm Technologies, Incorporated. All Rights Reserved. +===========================================================================*/ + +#ifndef MPQMUX_H +#define MPQMUX_H + +#include "MPQMI.h" + +#pragma pack(push, 1) + +#define QMIWDS_SET_EVENT_REPORT_REQ 0x0001 +#define QMIWDS_SET_EVENT_REPORT_RESP 0x0001 +#define QMIWDS_EVENT_REPORT_IND 0x0001 +#define QMIWDS_START_NETWORK_INTERFACE_REQ 0x0020 +#define QMIWDS_START_NETWORK_INTERFACE_RESP 0x0020 +#define QMIWDS_STOP_NETWORK_INTERFACE_REQ 0x0021 +#define QMIWDS_STOP_NETWORK_INTERFACE_RESP 0x0021 +#define QMIWDS_GET_PKT_SRVC_STATUS_REQ 0x0022 +#define QMIWDS_GET_PKT_SRVC_STATUS_RESP 0x0022 +#define QMIWDS_GET_PKT_SRVC_STATUS_IND 0x0022 +#define QMIWDS_GET_CURRENT_CHANNEL_RATE_REQ 0x0023 +#define QMIWDS_GET_CURRENT_CHANNEL_RATE_RESP 0x0023 +#define QMIWDS_GET_PKT_STATISTICS_REQ 0x0024 +#define QMIWDS_GET_PKT_STATISTICS_RESP 0x0024 +#define QMIWDS_MODIFY_PROFILE_SETTINGS_REQ 0x0028 +#define QMIWDS_MODIFY_PROFILE_SETTINGS_RESP 0x0028 +#define QMIWDS_GET_PROFILE_SETTINGS_REQ 0x002B +#define QMIWDS_GET_PROFILE_SETTINGS_RESP 0x002B +#define QMIWDS_GET_DEFAULT_SETTINGS_REQ 0x002C +#define QMIWDS_GET_DEFAULT_SETTINGS_RESP 0x002C +#define QMIWDS_GET_RUNTIME_SETTINGS_REQ 0x002D +#define QMIWDS_GET_RUNTIME_SETTINGS_RESP 0x002D +#define QMIWDS_GET_MIP_MODE_REQ 0x002F +#define QMIWDS_GET_MIP_MODE_RESP 0x002F +#define QMIWDS_GET_DATA_BEARER_REQ 0x0037 +#define QMIWDS_GET_DATA_BEARER_RESP 0x0037 +#define QMIWDS_DUN_CALL_INFO_REQ 0x0038 +#define QMIWDS_DUN_CALL_INFO_RESP 0x0038 +#define QMIWDS_DUN_CALL_INFO_IND 0x0038 +#define QMIWDS_SET_CLIENT_IP_FAMILY_PREF_REQ 0x004D +#define QMIWDS_SET_CLIENT_IP_FAMILY_PREF_RESP 0x004D +#define QMIWDS_SET_AUTO_CONNECT_REQ 0x0051 +#define QMIWDS_SET_AUTO_CONNECT_RESP 0x0051 +#define QMIWDS_BIND_MUX_DATA_PORT_REQ 0x00A2 +#define QMIWDS_BIND_MUX_DATA_PORT_RESP 0x00A2 + + +// Stats masks +#define QWDS_STAT_MASK_TX_PKT_OK 0x00000001 +#define QWDS_STAT_MASK_RX_PKT_OK 0x00000002 +#define QWDS_STAT_MASK_TX_PKT_ER 0x00000004 +#define QWDS_STAT_MASK_RX_PKT_ER 0x00000008 +#define QWDS_STAT_MASK_TX_PKT_OF 0x00000010 +#define QWDS_STAT_MASK_RX_PKT_OF 0x00000020 + +// TLV Types for xfer statistics +#define TLV_WDS_TX_GOOD_PKTS 0x10 +#define TLV_WDS_RX_GOOD_PKTS 0x11 +#define TLV_WDS_TX_ERROR 0x12 +#define TLV_WDS_RX_ERROR 0x13 +#define TLV_WDS_TX_OVERFLOW 0x14 +#define TLV_WDS_RX_OVERFLOW 0x15 +#define TLV_WDS_CHANNEL_RATE 0x16 +#define TLV_WDS_DATA_BEARER 0x17 +#define TLV_WDS_DORMANCY_STATUS 0x18 + +#define QWDS_PKT_DATA_UNKNOW 0x00 +#define QWDS_PKT_DATA_DISCONNECTED 0x01 +#define QWDS_PKT_DATA_CONNECTED 0x02 +#define QWDS_PKT_DATA_SUSPENDED 0x03 +#define QWDS_PKT_DATA_AUTHENTICATING 0x04 + +#define QMIWDS_ADMIN_SET_DATA_FORMAT_REQ 0x0020 +#define QMIWDS_ADMIN_SET_DATA_FORMAT_RESP 0x0020 +#define QMIWDS_ADMIN_GET_DATA_FORMAT_REQ 0x0021 +#define QMIWDS_ADMIN_GET_DATA_FORMAT_RESP 0x0021 +#define QMIWDS_ADMIN_SET_QMAP_SETTINGS_REQ 0x002B +#define QMIWDS_ADMIN_SET_QMAP_SETTINGS_RESP 0x002B +#define QMIWDS_ADMIN_GET_QMAP_SETTINGS_REQ 0x002C +#define QMIWDS_ADMIN_GET_QMAP_SETTINGS_RESP 0x002C +#define QMI_WDA_SET_LOOPBACK_CONFIG_REQ 0x002F +#define QMI_WDA_SET_LOOPBACK_CONFIG_RESP 0x002F +#define QMI_WDA_SET_LOOPBACK_CONFIG_IND 0x002F + +#define NETWORK_DESC_ENCODING_OCTET 0x00 +#define NETWORK_DESC_ENCODING_EXTPROTOCOL 0x01 +#define NETWORK_DESC_ENCODING_7BITASCII 0x02 +#define NETWORK_DESC_ENCODING_IA5 0x03 +#define NETWORK_DESC_ENCODING_UNICODE 0x04 +#define NETWORK_DESC_ENCODING_SHIFTJIS 0x05 +#define NETWORK_DESC_ENCODING_KOREAN 0x06 +#define NETWORK_DESC_ENCODING_LATINH 0x07 +#define NETWORK_DESC_ENCODING_LATIN 0x08 +#define NETWORK_DESC_ENCODING_GSM7BIT 0x09 +#define NETWORK_DESC_ENCODING_GSMDATA 0x0A +#define NETWORK_DESC_ENCODING_UNKNOWN 0xFF + +typedef struct _QMIWDS_ADMIN_SET_DATA_FORMAT +{ + USHORT Type; // QMUX type 0x0000 + USHORT Length; +} __attribute__ ((packed)) QMIWDS_ADMIN_SET_DATA_FORMAT, *PQMIWDS_ADMIN_SET_DATA_FORMAT; + +typedef struct _QMIWDS_ADMIN_SET_DATA_FORMAT_TLV_QOS +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR QOSSetting; +} __attribute__ ((packed)) QMIWDS_ADMIN_SET_DATA_FORMAT_TLV_QOS, *PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV_QOS; + +typedef struct _QMIWDS_ADMIN_SET_DATA_FORMAT_TLV +{ + UCHAR TLVType; + USHORT TLVLength; + ULONG Value; +} __attribute__ ((packed)) QMIWDS_ADMIN_SET_DATA_FORMAT_TLV, *PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV; + +typedef struct _QMIWDS_ENDPOINT_TLV +{ + UCHAR TLVType; + USHORT TLVLength; + ULONG ep_type; + ULONG iface_id; +} __attribute__ ((packed)) QMIWDS_ENDPOINT_TLV, *PQMIWDS_ENDPOINT_TLV; + +typedef struct _QMIWDS_ADMIN_SET_DATA_FORMAT_REQ_MSG +{ + USHORT Type; + USHORT Length; + QMIWDS_ADMIN_SET_DATA_FORMAT_TLV_QOS QosDataFormatTlv; + QMIWDS_ADMIN_SET_DATA_FORMAT_TLV UnderlyingLinkLayerProtocolTlv; + QMIWDS_ADMIN_SET_DATA_FORMAT_TLV UplinkDataAggregationProtocolTlv; + QMIWDS_ADMIN_SET_DATA_FORMAT_TLV DownlinkDataAggregationProtocolTlv; + QMIWDS_ADMIN_SET_DATA_FORMAT_TLV DownlinkDataAggregationMaxDatagramsTlv; + QMIWDS_ADMIN_SET_DATA_FORMAT_TLV DownlinkDataAggregationMaxSizeTlv; + QMIWDS_ENDPOINT_TLV epTlv; +#ifdef QUECTEL_UL_DATA_AGG + QMIWDS_ADMIN_SET_DATA_FORMAT_TLV DlMinimumPassingTlv; + QMIWDS_ADMIN_SET_DATA_FORMAT_TLV UplinkDataAggregationMaxDatagramsTlv; + QMIWDS_ADMIN_SET_DATA_FORMAT_TLV UplinkDataAggregationMaxSizeTlv; +#endif +} __attribute__ ((packed)) QMIWDS_ADMIN_SET_DATA_FORMAT_REQ_MSG, *PQMIWDS_ADMIN_SET_DATA_FORMAT_REQ_MSG; + +typedef struct _QMI_U8_TLV +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR TLVVaule; +} __attribute__ ((packed)) QMI_U8_TLV, *PQMI_U8_TLV; + +typedef struct _QMI_U32_TLV +{ + UCHAR TLVType; + USHORT TLVLength; + ULONG TLVVaule; +} __attribute__ ((packed)) QMI_U32_TLV, *PQMI_U32_TLV; + +typedef struct _QMI_WDA_SET_LOOPBACK_CONFIG_REQ_MSG { + USHORT Type; + USHORT Length; + QMI_U8_TLV loopback_state; //0x01 + QMI_U32_TLV replication_factor; //0x10 +} __attribute__ ((packed)) QMI_WDA_SET_LOOPBACK_CONFIG_REQ_MSG, *PQMI_WDA_SET_LOOPBACK_CONFIG_REQ_MSG; + +typedef struct _QMI_WDA_SET_LOOPBACK_CONFIG_IND_MSG +{ + USHORT Type; + USHORT Length; + QMI_U8_TLV loopback_state; //0x01 + QMI_U32_TLV replication_factor; //0x10 +} __attribute__ ((packed)) QMI_WDA_SET_LOOPBACK_CONFIG_IND_MSG, *PQMI_WDA_SET_LOOPBACK_CONFIG_IND_MSG; + +#if 0 +typedef enum _QMI_RETURN_CODES { + QMI_SUCCESS = 0, + QMI_SUCCESS_NOT_COMPLETE, + QMI_FAILURE +}QMI_RETURN_CODES; + +typedef struct _QMIWDS_GET_PKT_SRVC_STATUS_REQ_MSG +{ + USHORT Type; // 0x0022 + USHORT Length; // 0x0000 +} QMIWDS_GET_PKT_SRVC_STATUS_REQ_MSG, *PQMIWDS_GET_PKT_SRVC_STATUS_REQ_MSG; + +typedef struct _QMIWDS_GET_PKT_SRVC_STATUS_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLVType2; + USHORT TLVLength2; + UCHAR ConnectionStatus; // 0x01: QWDS_PKT_DATAC_DISCONNECTED + // 0x02: QWDS_PKT_DATA_CONNECTED + // 0x03: QWDS_PKT_DATA_SUSPENDED + // 0x04: QWDS_PKT_DATA_AUTHENTICATING +} QMIWDS_GET_PKT_SRVC_STATUS_RESP_MSG, *PQMIWDS_GET_PKT_SRVC_STATUS_RESP_MSG; + +typedef struct _QMIWDS_GET_PKT_SRVC_STATUS_IND_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR ConnectionStatus; // 0x01: QWDS_PKT_DATAC_DISCONNECTED + // 0x02: QWDS_PKT_DATA_CONNECTED + // 0x03: QWDS_PKT_DATA_SUSPENDED + UCHAR ReconfigRequired; // 0x00: No need to reconfigure + // 0x01: Reconfiguration required +} QMIWDS_GET_PKT_SRVC_STATUS_IND_MSG, *PQMIWDS_GET_PKT_SRVC_STATUS_IND_MSG; + +typedef struct _WDS_PKT_SRVC_IP_FAMILY_TLV +{ + UCHAR TLVType; // 0x12 + USHORT TLVLength; // 1 + UCHAR IpFamily; // IPV4-0x04, IPV6-0x06 +} WDS_PKT_SRVC_IP_FAMILY_TLV, *PWDS_PKT_SRVC_IP_FAMILY_TLV; + +typedef struct _QMIWDS_DUN_CALL_INFO_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + ULONG Mask; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR ReportConnectionStatus; +} QMIWDS_DUN_CALL_INFO_REQ_MSG, *PQMIWDS_DUN_CALL_INFO_REQ_MSG; + +typedef struct _QMIWDS_DUN_CALL_INFO_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} QMIWDS_DUN_CALL_INFO_RESP_MSG, *PQMIWDS_DUN_CALL_INFO_RESP_MSG; + +typedef struct _QMIWDS_DUN_CALL_INFO_IND_MSG +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR ConnectionStatus; +} QMIWDS_DUN_CALL_INFO_IND_MSG, *PQMIWDS_DUN_CALL_INFO_IND_MSG; + +typedef struct _QMIWDS_GET_CURRENT_CHANNEL_RATE_REQ_MSG +{ + USHORT Type; // QMUX type 0x0040 + USHORT Length; +} QMIWDS_GET_CURRENT_CHANNEL_RATE_REQ_MSG, *PQMIWDS_GET_CURRENT_CHANNEL_RATE_REQ_MSG; + +typedef struct _QMIWDS_GET_CURRENT_CHANNEL_RATE_RESP_MSG +{ + USHORT Type; // QMUX type 0x0040 + USHORT Length; + UCHAR TLVType; // 0x02 + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + + UCHAR TLV2Type; // 0x01 + USHORT TLV2Length; // 16 + //ULONG CallHandle; // Context corresponding to reported channel + ULONG CurrentTxRate; // bps + ULONG CurrentRxRate; // bps + ULONG ServingSystemTxRate; // bps + ULONG ServingSystemRxRate; // bps + +} QMIWDS_GET_CURRENT_CHANNEL_RATE_RESP_MSG, *PQMIWDS_GET_CURRENT_CHANNEL_RATE_RESP; + +#define QWDS_EVENT_REPORT_MASK_RATES 0x01 +#define QWDS_EVENT_REPORT_MASK_STATS 0x02 + +#ifdef QCUSB_MUX_PROTOCOL +#error code not present +#endif // QCUSB_MUX_PROTOCOL + +typedef struct _QMIWDS_SET_EVENT_REPORT_REQ_MSG +{ + USHORT Type; // QMUX type 0x0042 + USHORT Length; + + UCHAR TLVType; // 0x10 -- current channel rate indicator + USHORT TLVLength; // 1 + UCHAR Mode; // 0-do not report; 1-report when rate changes + + UCHAR TLV2Type; // 0x11 + USHORT TLV2Length; // 5 + UCHAR StatsPeriod; // seconds between reports; 0-do not report + ULONG StatsMask; // + + UCHAR TLV3Type; // 0x12 -- current data bearer indicator + USHORT TLV3Length; // 1 + UCHAR Mode3; // 0-do not report; 1-report when changes + + UCHAR TLV4Type; // 0x13 -- dormancy status indicator + USHORT TLV4Length; // 1 + UCHAR DormancyStatus; // 0-do not report; 1-report when changes +} QMIWDS_SET_EVENT_REPORT_REQ_MSG, *PQMIWDS_SET_EVENT_REPORT_REQ_MSG; + +typedef struct _QMIWDS_SET_EVENT_REPORT_RESP_MSG +{ + USHORT Type; // QMUX type 0x0042 + USHORT Length; + + UCHAR TLVType; // 0x02 result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_NO_BATTERY + // QMI_ERR_FAULT +} QMIWDS_SET_EVENT_REPORT_RESP_MSG, *PQMIWDS_SET_EVENT_REPORT_RESP_MSG; + +typedef struct _QMIWDS_EVENT_REPORT_IND_MSG +{ + USHORT Type; // QMUX type 0x0001 + USHORT Length; +} QMIWDS_EVENT_REPORT_IND_MSG, *PQMIWDS_EVENT_REPORT_IND_MSG; + +// PQCTLV_PKT_STATISTICS + +typedef struct _QMIWDS_EVENT_REPORT_IND_CHAN_RATE_TLV +{ + UCHAR Type; + USHORT Length; // 8 + ULONG TxRate; + ULONG RxRate; +} QMIWDS_EVENT_REPORT_IND_CHAN_RATE_TLV, *PQMIWDS_EVENT_REPORT_IND_CHAN_RATE_TLV; + +#ifdef QCUSB_MUX_PROTOCOL +#error code not present +#endif // QCUSB_MUX_PROTOCOL + +typedef struct _QMIWDS_GET_PKT_STATISTICS_REQ_MSG +{ + USHORT Type; // QMUX type 0x0041 + USHORT Length; + UCHAR TLVType; // 0x01 + USHORT TLVLength; // 4 + ULONG StateMask; // 0x00000001 tx success packets + // 0x00000002 rx success packets + // 0x00000004 rx packet errors (checksum) + // 0x00000008 rx packets dropped (memory) + +} QMIWDS_GET_PKT_STATISTICS_REQ_MSG, *PQMIWDS_GET_PKT_STATISTICS_REQ_MSG; + +typedef struct _QMIWDS_GET_PKT_STATISTICS_RESP_MSG +{ + USHORT Type; // QMUX type 0x0041 + USHORT Length; + UCHAR TLVType; // 0x02 + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} QMIWDS_GET_PKT_STATISTICS_RESP_MSG, *PQMIWDS_GET_PKT_STATISTICS_RESP_MSG; + +// optional TLV for stats +typedef struct _QCTLV_PKT_STATISTICS +{ + UCHAR TLVType; // see above definitions for TLV types + USHORT TLVLength; // 4 + ULONG Count; +} QCTLV_PKT_STATISTICS, *PQCTLV_PKT_STATISTICS; +#endif + +//#ifdef QC_IP_MODE + +/* + • Bit 0 – Profile identifier + • Bit 1 – Profile name + • Bit 2 – PDP type + • Bit 3 – APN name + • Bit 4 – DNS address + • Bit 5 – UMTS/GPRS granted QoS + • Bit 6 – Username + • Bit 7 – Authentication Protocol + • Bit 8 – IP address + • Bit 9 – Gateway information (address and subnet mask) + • Bit 10 – PCSCF address using a PCO flag + • Bit 11 – PCSCF server address list + • Bit 12 – PCSCF domain name list + • Bit 13 – MTU + • Bit 14 – Domain name list + • Bit 15 – IP family + • Bit 16 – IM_CM flag + • Bit 17 – Technology name + • Bit 18 – Operator reserved PCO +*/ +#define QMIWDS_GET_RUNTIME_SETTINGS_MASK_IPV4DNS_ADDR (1 << 4) +#define QMIWDS_GET_RUNTIME_SETTINGS_MASK_IPV4_ADDR (1 << 8) +#define QMIWDS_GET_RUNTIME_SETTINGS_MASK_IPV4GATEWAY_ADDR (1 << 9) +#define QMIWDS_GET_RUNTIME_SETTINGS_MASK_MTU (1 << 13) +#define QMIWDS_GET_RUNTIME_SETTINGS_MASK_PCSCF_SV_ADDR (1 << 11) +#define QMIWDS_GET_RUNTIME_SETTINGS_MASK_PCSCF_DOM_NAME (1 << 14) + +typedef struct _QMIWDS_GET_RUNTIME_SETTINGS_REQ_MSG +{ + USHORT Type; // QMIWDS_GET_RUNTIME_SETTINGS_REQ + USHORT Length; + UCHAR TLVType; // 0x10 + USHORT TLVLength; // 0x0004 + ULONG Mask; // mask, bit 8: IP addr -- 0x0100 +} __attribute__ ((packed)) QMIWDS_GET_RUNTIME_SETTINGS_REQ_MSG, *PQMIWDS_GET_RUNTIME_SETTINGS_REQ_MSG; + +typedef struct _QMIWDS_BIND_MUX_DATA_PORT_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + ULONG ep_type; + ULONG iface_id; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR MuxId; + UCHAR TLV3Type; + USHORT TLV3Length; + ULONG client_type; +} __attribute__ ((packed)) QMIWDS_BIND_MUX_DATA_PORT_REQ_MSG, *PQMIWDS_BIND_MUX_DATA_PORT_REQ_MSG; + +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4PRIMARYDNS 0x15 +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4SECONDARYDNS 0x16 +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4 0x1E +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4GATEWAY 0x20 +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4SUBNET 0x21 + +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6 0x25 +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6GATEWAY 0x26 +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6PRIMARYDNS 0x27 +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6SECONDARYDNS 0x28 +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_MTU 0x29 + +typedef struct _QMIWDS_GET_RUNTIME_SETTINGS_TLV_MTU +{ + UCHAR TLVType; // QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_MTU + USHORT TLVLength; // 4 + ULONG Mtu; // MTU +} __attribute__ ((packed)) QMIWDS_GET_RUNTIME_SETTINGS_TLV_MTU, *PQMIWDS_GET_RUNTIME_SETTINGS_TLV_MTU; + +typedef struct _QMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR +{ + UCHAR TLVType; // QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4 + USHORT TLVLength; // 4 + ULONG IPV4Address; // address +} __attribute__ ((packed)) QMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR, *PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR; + +typedef struct _QMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV6_ADDR +{ + UCHAR TLVType; // QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6 + USHORT TLVLength; // 16 + UCHAR IPV6Address[16]; // address + UCHAR PrefixLength; // prefix length +} __attribute__ ((packed)) QMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV6_ADDR, *PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV6_ADDR; + +typedef struct _QMIWDS_GET_RUNNING_SETTINGS_PCSCF_IPV6_ADDR +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR PCSCFNumber; +} __attribute__ ((packed)) QMIWDS_GET_RUNNING_SETTINGS_PCSCF_IPV6_ADDR, *PQMIWDS_GET_RUNNING_SETTINGS_PCSCF_IPV6_ADDR; + +typedef struct _QMIWDS_GET_RUNNING_SETTINGS_PCSCF_IPV4_ADDR +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR PCSCFNumber; +} __attribute__ ((packed)) QMIWDS_GET_RUNNING_SETTINGS_PCSCF_IPV4_ADDR, *PQMIWDS_GET_RUNNING_SETTINGS_PCSCF_IPV4_ADDR; + +typedef struct _QMIWDS_GET_RUNTIME_SETTINGS_RESP_MSG +{ + USHORT Type; // QMIWDS_GET_RUNTIME_SETTINGS_RESP + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_RESULT_CODE + USHORT TLVLength; // 0x0004 + USHORT QMUXResult; // result code + USHORT QMUXError; // error code +} __attribute__ ((packed)) QMIWDS_GET_RUNTIME_SETTINGS_RESP_MSG, *PQMIWDS_GET_RUNTIME_SETTINGS_RESP_MSG; + +//#endif // QC_IP_MODE + +typedef struct _QMIWDS_IP_FAMILY_TLV +{ + UCHAR TLVType; // 0x12 + USHORT TLVLength; // 1 + UCHAR IpFamily; // IPV4-0x04, IPV6-0x06 +} __attribute__ ((packed)) QMIWDS_IP_FAMILY_TLV, *PQMIWDS_IP_FAMILY_TLV; + +typedef struct _QMIWDS_PKT_SRVC_TLV +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR ConnectionStatus; + UCHAR ReconfigReqd; +} __attribute__ ((packed)) QMIWDS_PKT_SRVC_TLV, *PQMIWDS_PKT_SRVC_TLV; + +typedef struct _QMIWDS_CALL_END_REASON_TLV +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT CallEndReason; +} __attribute__ ((packed)) QMIWDS_CALL_END_REASON_TLV, *PQMIWDS_CALL_END_REASON_TLV; + +typedef struct _QMIWDS_CALL_END_REASON_V_TLV +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT CallEndReasonType; + USHORT CallEndReason; +} __attribute__ ((packed)) QMIWDS_CALL_END_REASON_V_TLV, *PQMIWDS_CALL_END_REASON_V_TLV; + +typedef struct _QMIWDS_SET_CLIENT_IP_FAMILY_PREF_REQ_MSG +{ + USHORT Type; // QMUX type 0x004D + USHORT Length; + UCHAR TLVType; // 0x01 + USHORT TLVLength; // 1 + UCHAR IpPreference; // IPV4-0x04, IPV6-0x06 +} __attribute__ ((packed)) QMIWDS_SET_CLIENT_IP_FAMILY_PREF_REQ_MSG, *PQMIWDS_SET_CLIENT_IP_FAMILY_PREF_REQ_MSG; + +typedef struct _QMIWDS_SET_CLIENT_IP_FAMILY_PREF_RESP_MSG +{ + USHORT Type; // QMUX type 0x0037 + USHORT Length; + UCHAR TLVType; // 0x02 + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS, QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INTERNAL, QMI_ERR_MALFORMED_MSG, QMI_ERR_INVALID_ARG +} __attribute__ ((packed)) QMIWDS_SET_CLIENT_IP_FAMILY_PREF_RESP_MSG, *PQMIWDS_SET_CLIENT_IP_FAMILY_PREF_RESP_MSG; + +typedef struct _QMIWDS_SET_AUTO_CONNECT_REQ_MSG +{ + USHORT Type; // QMUX type 0x0051 + USHORT Length; + UCHAR TLVType; // 0x01 + USHORT TLVLength; // 1 + UCHAR autoconnect_setting; // 0x00 ?C Disabled, 0x01 ?C Enabled, 0x02 ?C Paused (resume on power cycle) +} __attribute__ ((packed)) QMIWDS_SET_AUTO_CONNECT_REQ_MSG, *PQMIWDS_SET_AUTO_CONNECT_REQ_MSG; + +#if 0 +typedef struct _QMIWDS_GET_MIP_MODE_REQ_MSG +{ + USHORT Type; // QMUX type 0x0040 + USHORT Length; +} QMIWDS_GET_MIP_MODE_REQ_MSG, *PQMIWDS_GET_MIP_MODE_REQ_MSG; + +typedef struct _QMIWDS_GET_MIP_MODE_RESP_MSG +{ + USHORT Type; // QMUX type 0x0040 + USHORT Length; + UCHAR TLVType; // 0x02 + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + + UCHAR TLV2Type; // 0x01 + USHORT TLV2Length; // 20 + UCHAR MipMode; // +} QMIWDS_GET_MIP_MODE_RESP_MSG, *PQMIWDS_GET_MIP_MODE_RESP_MSG; +#endif + +typedef struct _QMIWDS_TECHNOLOGY_PREFERECE +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR TechPreference; +} __attribute__ ((packed)) QMIWDS_TECHNOLOGY_PREFERECE, *PQMIWDS_TECHNOLOGY_PREFERECE; + +typedef struct _QMIWDS_PROFILE_IDENTIFIER +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR ProfileIndex; +} __attribute__ ((packed)) QMIWDS_PROFILE_IDENTIFIER, *PQMIWDS_PROFILE_IDENTIFIER; + +#if 0 +typedef struct _QMIWDS_IPADDRESS +{ + UCHAR TLVType; + USHORT TLVLength; + ULONG IPv4Address; +}QMIWDS_IPADDRESS, *PQMIWDS_IPADDRESS; + +/* +typedef struct _QMIWDS_UMTS_QOS +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR TrafficClass; + ULONG MaxUplinkBitRate; + ULONG MaxDownlinkBitRate; + ULONG GuarUplinkBitRate; + ULONG GuarDownlinkBitRate; + UCHAR QOSDevOrder; + ULONG MAXSDUSize; + UCHAR SDUErrorRatio; + UCHAR ResidualBerRatio; + UCHAR DeliveryErrorSDUs; + ULONG TransferDelay; + ULONG TrafficHndPri; +}QMIWDS_UMTS_QOS, *PQMIWDS_UMTS_QOS; + +typedef struct _QMIWDS_GPRS_QOS +{ + UCHAR TLVType; + USHORT TLVLength; + ULONG PrecedenceClass; + ULONG DelayClass; + ULONG ReliabilityClass; + ULONG PeekThroClass; + ULONG MeanThroClass; +}QMIWDS_GPRS_QOS, *PQMIWDS_GPRS_QOS; +*/ +#endif + +typedef struct _QMIWDS_PROFILENAME +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR ProfileName; +} __attribute__ ((packed)) QMIWDS_PROFILENAME, *PQMIWDS_PROFILENAME; + +typedef struct _QMIWDS_PDPTYPE +{ + UCHAR TLVType; + USHORT TLVLength; +// 0 ?C PDP-IP (IPv4) +// 1 ?C PDP-PPP +// 2 ?C PDP-IPv6 +// 3 ?C PDP-IPv4v6 + UCHAR PdpType; +} __attribute__ ((packed)) QMIWDS_PDPTYPE, *PQMIWDS_PDPTYPE; + +typedef struct _QMIWDS_USERNAME +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR UserName; +} __attribute__ ((packed)) QMIWDS_USERNAME, *PQMIWDS_USERNAME; + +typedef struct _QMIWDS_PASSWD +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR Passwd; +} __attribute__ ((packed)) QMIWDS_PASSWD, *PQMIWDS_PASSWD; + +typedef struct _QMIWDS_AUTH_PREFERENCE +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR AuthPreference; +} __attribute__ ((packed)) QMIWDS_AUTH_PREFERENCE, *PQMIWDS_AUTH_PREFERENCE; + +typedef struct _QMIWDS_APNNAME +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR ApnName; +} __attribute__ ((packed)) QMIWDS_APNNAME, *PQMIWDS_APNNAME; + +typedef struct _QMIWDS_AUTOCONNECT +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR AutoConnect; +} __attribute__ ((packed)) QMIWDS_AUTOCONNECT, *PQMIWDS_AUTOCONNECT; + +typedef struct _QMIWDS_START_NETWORK_INTERFACE_REQ_MSG +{ + USHORT Type; + USHORT Length; +} __attribute__ ((packed)) QMIWDS_START_NETWORK_INTERFACE_REQ_MSG, *PQMIWDS_START_NETWORK_INTERFACE_REQ_MSG; + +typedef struct _QMIWDS_CALLENDREASON +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT Reason; +}__attribute__ ((packed)) QMIWDS_CALLENDREASON, *PQMIWDS_CALLENDREASON; + +typedef struct _QMIWDS_START_NETWORK_INTERFACE_RESP_MSG +{ + USHORT Type; // QMUX type 0x0040 + USHORT Length; + UCHAR TLVType; // 0x02 + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + + UCHAR TLV2Type; // 0x01 + USHORT TLV2Length; // 20 + ULONG Handle; // +} __attribute__ ((packed)) QMIWDS_START_NETWORK_INTERFACE_RESP_MSG, *PQMIWDS_START_NETWORK_INTERFACE_RESP_MSG; + +typedef struct _QMIWDS_STOP_NETWORK_INTERFACE_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + ULONG Handle; +} __attribute__ ((packed)) QMIWDS_STOP_NETWORK_INTERFACE_REQ_MSG, *PQMIWDS_STOP_NETWORK_INTERFACE_REQ_MSG; + +typedef struct _QMIWDS_STOP_NETWORK_INTERFACE_RESP_MSG +{ + USHORT Type; // QMUX type 0x0040 + USHORT Length; + UCHAR TLVType; // 0x02 + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + +} __attribute__ ((packed)) QMIWDS_STOP_NETWORK_INTERFACE_RESP_MSG, *PQMIWDS_STOP_NETWORK_INTERFACE_RESP_MSG; + +typedef struct _QMIWDS_GET_DEFAULT_SETTINGS_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR ProfileType; +} __attribute__ ((packed)) QMIWDS_GET_DEFAULT_SETTINGS_REQ_MSG, *PQMIWDS_GET_DEFAULT_SETTINGS_REQ_MSG; + +typedef struct _QMIWDS_GET_DEFAULT_SETTINGS_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} __attribute__ ((packed)) QMIWDS_GET_DEFAULT_SETTINGS_RESP_MSG, *PQMIWDS_GET_DEFAULT_SETTINGS_RESP_MSG; + +typedef struct _QMIWDS_MODIFY_PROFILE_SETTINGS_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR ProfileType; + UCHAR ProfileIndex; +} __attribute__ ((packed)) QMIWDS_MODIFY_PROFILE_SETTINGS_REQ_MSG, *PQMIWDS_MODIFY_PROFILE_SETTINGS_REQ_MSG; + +typedef struct _QMIWDS_MODIFY_PROFILE_SETTINGS_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} __attribute__ ((packed)) QMIWDS_MODIFY_PROFILE_SETTINGS_RESP_MSG, *PQMIWDS_MODIFY_PROFILE_SETTINGS_RESP_MSG; + +typedef struct _QMIWDS_GET_PROFILE_SETTINGS_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR ProfileType; + UCHAR ProfileIndex; +} __attribute__ ((packed)) QMIWDS_GET_PROFILE_SETTINGS_REQ_MSG, *PQMIWDS_GET_PROFILE_SETTINGS_REQ_MSG; + +#if 0 +typedef struct _QMIWDS_EVENT_REPORT_IND_DATA_BEARER_TLV +{ + UCHAR Type; + USHORT Length; + UCHAR DataBearer; +} QMIWDS_EVENT_REPORT_IND_DATA_BEARER_TLV, *PQMIWDS_EVENT_REPORT_IND_DATA_BEARER_TLV; + +typedef struct _QMIWDS_EVENT_REPORT_IND_DORMANCY_STATUS_TLV +{ + UCHAR Type; + USHORT Length; + UCHAR DormancyStatus; +} QMIWDS_EVENT_REPORT_IND_DORMANCY_STATUS_TLV, *PQMIWDS_EVENT_REPORT_IND_DORMANCY_STATUS_TLV; + + +typedef struct _QMIWDS_GET_DATA_BEARER_REQ_MSG +{ + USHORT Type; // QMUX type 0x0037 + USHORT Length; +} QMIWDS_GET_DATA_BEARER_REQ_MSG, *PQMIWDS_GET_DATA_BEARER_REQ_MSG; + +typedef struct _QMIWDS_GET_DATA_BEARER_RESP_MSG +{ + USHORT Type; // QMUX type 0x0037 + USHORT Length; + UCHAR TLVType; // 0x02 + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INTERNAL + // QMI_ERR_MALFORMED_MSG + // QMI_ERR_NO_MEMORY + // QMI_ERR_OUT_OF_CALL + // QMI_ERR_INFO_UNAVAILABLE + UCHAR TLV2Type; // 0x01 + USHORT TLV2Length; // + UCHAR Technology; // +} QMIWDS_GET_DATA_BEARER_RESP_MSG, *PQMIWDS_GET_DATA_BEARER_RESP_MSG; +#endif + +// ======================= DMS ============================== +#define QMIDMS_SET_EVENT_REPORT_REQ 0x0001 +#define QMIDMS_SET_EVENT_REPORT_RESP 0x0001 +#define QMIDMS_EVENT_REPORT_IND 0x0001 +#define QMIDMS_GET_DEVICE_CAP_REQ 0x0020 +#define QMIDMS_GET_DEVICE_CAP_RESP 0x0020 +#define QMIDMS_GET_DEVICE_MFR_REQ 0x0021 +#define QMIDMS_GET_DEVICE_MFR_RESP 0x0021 +#define QMIDMS_GET_DEVICE_MODEL_ID_REQ 0x0022 +#define QMIDMS_GET_DEVICE_MODEL_ID_RESP 0x0022 +#define QMIDMS_GET_DEVICE_REV_ID_REQ 0x0023 +#define QMIDMS_GET_DEVICE_REV_ID_RESP 0x0023 +#define QMIDMS_GET_MSISDN_REQ 0x0024 +#define QMIDMS_GET_MSISDN_RESP 0x0024 +#define QMIDMS_GET_DEVICE_SERIAL_NUMBERS_REQ 0x0025 +#define QMIDMS_GET_DEVICE_SERIAL_NUMBERS_RESP 0x0025 +#define QMIDMS_UIM_SET_PIN_PROTECTION_REQ 0x0027 +#define QMIDMS_UIM_SET_PIN_PROTECTION_RESP 0x0027 +#define QMIDMS_UIM_VERIFY_PIN_REQ 0x0028 +#define QMIDMS_UIM_VERIFY_PIN_RESP 0x0028 +#define QMIDMS_UIM_UNBLOCK_PIN_REQ 0x0029 +#define QMIDMS_UIM_UNBLOCK_PIN_RESP 0x0029 +#define QMIDMS_UIM_CHANGE_PIN_REQ 0x002A +#define QMIDMS_UIM_CHANGE_PIN_RESP 0x002A +#define QMIDMS_UIM_GET_PIN_STATUS_REQ 0x002B +#define QMIDMS_UIM_GET_PIN_STATUS_RESP 0x002B +#define QMIDMS_GET_DEVICE_HARDWARE_REV_REQ 0x002C +#define QMIDMS_GET_DEVICE_HARDWARE_REV_RESP 0x002C +#define QMIDMS_GET_OPERATING_MODE_REQ 0x002D +#define QMIDMS_GET_OPERATING_MODE_RESP 0x002D +#define QMIDMS_SET_OPERATING_MODE_REQ 0x002E +#define QMIDMS_SET_OPERATING_MODE_RESP 0x002E +#define QMIDMS_GET_ACTIVATED_STATUS_REQ 0x0031 +#define QMIDMS_GET_ACTIVATED_STATUS_RESP 0x0031 +#define QMIDMS_ACTIVATE_AUTOMATIC_REQ 0x0032 +#define QMIDMS_ACTIVATE_AUTOMATIC_RESP 0x0032 +#define QMIDMS_ACTIVATE_MANUAL_REQ 0x0033 +#define QMIDMS_ACTIVATE_MANUAL_RESP 0x0033 +#define QMIDMS_UIM_GET_ICCID_REQ 0x003C +#define QMIDMS_UIM_GET_ICCID_RESP 0x003C +#define QMIDMS_UIM_GET_CK_STATUS_REQ 0x0040 +#define QMIDMS_UIM_GET_CK_STATUS_RESP 0x0040 +#define QMIDMS_UIM_SET_CK_PROTECTION_REQ 0x0041 +#define QMIDMS_UIM_SET_CK_PROTECTION_RESP 0x0041 +#define QMIDMS_UIM_UNBLOCK_CK_REQ 0x0042 +#define QMIDMS_UIM_UNBLOCK_CK_RESP 0x0042 +#define QMIDMS_UIM_GET_IMSI_REQ 0x0043 +#define QMIDMS_UIM_GET_IMSI_RESP 0x0043 +#define QMIDMS_UIM_GET_STATE_REQ 0x0044 +#define QMIDMS_UIM_GET_STATE_RESP 0x0044 +#define QMIDMS_GET_BAND_CAP_REQ 0x0045 +#define QMIDMS_GET_BAND_CAP_RESP 0x0045 + +#if 0 +typedef struct _QMIDMS_GET_DEVICE_MFR_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; +} QMIDMS_GET_DEVICE_MFR_REQ_MSG, *PQMIDMS_GET_DEVICE_MFR_REQ_MSG; + +typedef struct _QMIDMS_GET_DEVICE_MFR_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR TLV2Type; // 0x01 - required parameter + USHORT TLV2Length; // length of the mfr string + UCHAR DeviceManufacturer; // first byte of string +} QMIDMS_GET_DEVICE_MFR_RESP_MSG, *PQMIDMS_GET_DEVICE_MFR_RESP_MSG; + +typedef struct _QMIDMS_GET_DEVICE_MODEL_ID_REQ_MSG +{ + USHORT Type; // QMUX type 0x0004 + USHORT Length; +} QMIDMS_GET_DEVICE_MODEL_ID_REQ_MSG, *PQMIDMS_GET_DEVICE_MODEL_ID_REQ_MSG; + +typedef struct _QMIDMS_GET_DEVICE_MODEL_ID_RESP_MSG +{ + USHORT Type; // QMUX type 0x0004 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR TLV2Type; // 0x01 - required parameter + USHORT TLV2Length; // length of the modem id string + UCHAR DeviceModelID; // device model id +} QMIDMS_GET_DEVICE_MODEL_ID_RESP_MSG, *PQMIDMS_GET_DEVICE_MODEL_ID_RESP_MSG; +#endif + +typedef struct _QMIDMS_GET_DEVICE_REV_ID_REQ_MSG +{ + USHORT Type; // QMUX type 0x0005 + USHORT Length; +} __attribute__ ((packed)) QMIDMS_GET_DEVICE_REV_ID_REQ_MSG, *PQMIDMS_GET_DEVICE_REV_ID_REQ_MSG; + +typedef struct _DEVICE_REV_ID +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR RevisionID; +} __attribute__ ((packed)) DEVICE_REV_ID, *PDEVICE_REV_ID; + +#if 0 +typedef struct _QMIDMS_GET_DEVICE_REV_ID_RESP_MSG +{ + USHORT Type; // QMUX type 0x0023 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} QMIDMS_GET_DEVICE_REV_ID_RESP_MSG, *PQMIDMS_GET_DEVICE_REV_ID_RESP_MSG; + +typedef struct _QMIDMS_GET_MSISDN_REQ_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; +} QMIDMS_GET_MSISDN_REQ_MSG, *PQMIDMS_GET_MSISDN_REQ_MSG; + +typedef struct _QCTLV_DEVICE_VOICE_NUMBERS +{ + UCHAR TLVType; // as defined above + USHORT TLVLength; // 4/7/7 + UCHAR VoideNumberString; // ESN, IMEI, or MEID + +} QCTLV_DEVICE_VOICE_NUMBERS, *PQCTLV_DEVICE_VOICE_NUMBERS; + + +typedef struct _QMIDMS_GET_MSISDN_RESP_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG +} QMIDMS_GET_MSISDN_RESP_MSG, *PQMIDMS_GET_MSISDN_RESP_MSG; +#endif + +typedef struct _QMIDMS_UIM_GET_IMSI_REQ_MSG +{ + USHORT Type; + USHORT Length; +} __attribute__ ((packed)) QMIDMS_UIM_GET_IMSI_REQ_MSG, *PQMIDMS_UIM_GET_IMSI_REQ_MSG; + +typedef struct _QMIDMS_UIM_GET_IMSI_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR IMSI; +} __attribute__ ((packed)) QMIDMS_UIM_GET_IMSI_RESP_MSG, *PQMIDMS_UIM_GET_IMSI_RESP_MSG; + +#if 0 +typedef struct _QMIDMS_GET_DEVICE_SERIAL_NUMBERS_REQ_MSG +{ + USHORT Type; // QMUX type 0x0007 + USHORT Length; +} QMIDMS_GET_DEVICE_SERIAL_NUMBERS_REQ_MSG, *PQMIDMS_GET_DEVICE_SERIAL_NUMBERS_REQ_MSG; + +#define QCTLV_TYPE_SER_NUM_ESN 0x10 +#define QCTLV_TYPE_SER_NUM_IMEI 0x11 +#define QCTLV_TYPE_SER_NUM_MEID 0x12 + +typedef struct _QCTLV_DEVICE_SERIAL_NUMBER +{ + UCHAR TLVType; // as defined above + USHORT TLVLength; // 4/7/7 + UCHAR SerialNumberString; // ESN, IMEI, or MEID + +} QCTLV_DEVICE_SERIAL_NUMBER, *PQCTLV_DEVICE_SERIAL_NUMBER; + +typedef struct _QMIDMS_GET_DEVICE_SERIAL_NUMBERS_RESP_MSG +{ + USHORT Type; // QMUX type 0x0007 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + // followed by optional TLV +} QMIDMS_GET_DEVICE_SERIAL_NUMBERS_RESP_MSG, *PQMIDMS_GET_DEVICE_SERIAL_NUMBERS_RESP; + +typedef struct _QMIDMS_GET_DMS_BAND_CAP +{ + USHORT Type; + USHORT Length; +} QMIDMS_GET_BAND_CAP_REQ_MSG, *PQMIDMS_GET_BAND_CAP_REQ_MSG; + +typedef struct _QMIDMS_GET_BAND_CAP_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_NONE + // QMI_ERR_INTERNAL + // QMI_ERR_MALFORMED_MSG + // QMI_ERR_NO_MEMORY + + UCHAR TLV2Type; // 0x01 + USHORT TLV2Length; // 2 + ULONG64 BandCap; +} QMIDMS_GET_BAND_CAP_RESP_MSG, *PQMIDMS_GET_BAND_CAP_RESP; + +typedef struct _QMIDMS_GET_DEVICE_CAP_REQ_MSG +{ + USHORT Type; // QMUX type 0x0002 + USHORT Length; +} QMIDMS_GET_DEVICE_CAP_REQ_MSG, *PQMIDMS_GET_DEVICE_CAP_REQ_MSG; + +typedef struct _QMIDMS_GET_DEVICE_CAP_RESP_MSG +{ + USHORT Type; // QMUX type 0x0002 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMUX_RESULT_SUCCESS + // QMUX_RESULT_FAILURE + USHORT QMUXError; // QMUX_ERR_INVALID_ARG + // QMUX_ERR_NO_MEMORY + // QMUX_ERR_INTERNAL + // QMUX_ERR_FAULT + UCHAR TLV2Type; // 0x01 + USHORT TLV2Length; // 2 + + ULONG MaxTxChannelRate; + ULONG MaxRxChannelRate; + UCHAR VoiceCap; + UCHAR SimCap; + + UCHAR RadioIfListCnt; // #elements in radio interface list + UCHAR RadioIfList; // N 1-byte elements +} QMIDMS_GET_DEVICE_CAP_RESP_MSG, *PQMIDMS_GET_DEVICE_CAP_RESP_MSG; + +typedef struct _QMIDMS_GET_ACTIVATED_STATUS_REQ_MSG +{ + USHORT Type; // QMUX type 0x0002 + USHORT Length; +} QMIDMS_GET_ACTIVATED_STATUS_REQ_MSG, *PQMIDMS_GET_ACTIVATES_STATUD_REQ_MSG; + +typedef struct _QMIDMS_GET_ACTIVATED_STATUS_RESP_MSG +{ + USHORT Type; // QMUX type 0x0002 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMUX_RESULT_SUCCESS + // QMUX_RESULT_FAILURE + USHORT QMUXError; // QMUX_ERR_INVALID_ARG + // QMUX_ERR_NO_MEMORY + // QMUX_ERR_INTERNAL + // QMUX_ERR_FAULT + UCHAR TLV2Type; // 0x01 + USHORT TLV2Length; // 2 + + USHORT ActivatedStatus; +} QMIDMS_GET_ACTIVATED_STATUS_RESP_MSG, *PQMIDMS_GET_ACTIVATED_STATUS_RESP_MSG; + +typedef struct _QMIDMS_GET_OPERATING_MODE_REQ_MSG +{ + USHORT Type; // QMUX type 0x0002 + USHORT Length; +} QMIDMS_GET_OPERATING_MODE_REQ_MSG, *PQMIDMS_GET_OPERATING_MODE_REQ_MSG; + +typedef struct _OFFLINE_REASON +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT OfflineReason; +} OFFLINE_REASON, *POFFLINE_REASON; + +typedef struct _HARDWARE_RESTRICTED_MODE +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR HardwareControlledMode; +} HARDWARE_RESTRICTED_MODE, *PHARDWARE_RESTRICTED_MODE; + +typedef struct _QMIDMS_GET_OPERATING_MODE_RESP_MSG +{ + USHORT Type; // QMUX type 0x0002 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMUX_RESULT_SUCCESS + // QMUX_RESULT_FAILURE + USHORT QMUXError; // QMUX_ERR_INVALID_ARG + // QMUX_ERR_NO_MEMORY + // QMUX_ERR_INTERNAL + // QMUX_ERR_FAULT + UCHAR TLV2Type; // 0x01 + USHORT TLV2Length; // 2 + + UCHAR OperatingMode; +} QMIDMS_GET_OPERATING_MODE_RESP_MSG, *PQMIDMS_GET_OPERATING_MODE_RESP_MSG; + +typedef struct _QMIDMS_UIM_GET_ICCID_REQ_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; +} QMIDMS_UIM_GET_ICCID_REQ_MSG, *PQMIDMS_UIM_GET_ICCID_REQ_MSG; + +typedef struct _QMIDMS_UIM_GET_ICCID_RESP_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR TLV2Type; // 0x01 - required parameter + USHORT TLV2Length; // var + UCHAR ICCID; // String of voice number +} QMIDMS_UIM_GET_ICCID_RESP_MSG, *PQMIDMS_UIM_GET_ICCID_RESP_MSG; +#endif + +typedef struct _QMIDMS_SET_OPERATING_MODE_REQ_MSG +{ + USHORT Type; // QMUX type 0x0002 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + UCHAR OperatingMode; +} __attribute__ ((packed)) QMIDMS_SET_OPERATING_MODE_REQ_MSG, *PQMIDMS_SET_OPERATING_MODE_REQ_MSG; + +typedef struct _QMIDMS_SET_OPERATING_MODE_RESP_MSG +{ + USHORT Type; // QMUX type 0x0002 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMUX_RESULT_SUCCESS + // QMUX_RESULT_FAILURE + USHORT QMUXError; // QMUX_ERR_INVALID_ARG + // QMUX_ERR_NO_MEMORY + // QMUX_ERR_INTERNAL + // QMUX_ERR_FAULT +} __attribute__ ((packed)) QMIDMS_SET_OPERATING_MODE_RESP_MSG, *PQMIDMS_SET_OPERATING_MODE_RESP_MSG; + +#if 0 +typedef struct _QMIDMS_ACTIVATE_AUTOMATIC_REQ_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // + UCHAR ActivateCodelen; + UCHAR ActivateCode; +} QMIDMS_ACTIVATE_AUTOMATIC_REQ_MSG, *PQMIDMS_ACTIVATE_AUTOMATIC_REQ_MSG; + +typedef struct _QMIDMS_ACTIVATE_AUTOMATIC_RESP_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} QMIDMS_ACTIVATE_AUTOMATIC_RESP_MSG, *PQMIDMS_ACTIVATE_AUTOMATIC_RESP_MSG; + + +typedef struct _SPC_MSG +{ + UCHAR SPC[6]; + USHORT SID; +} SPC_MSG, *PSPC_MSG; + +typedef struct _MDN_MSG +{ + UCHAR MDNLEN; + UCHAR MDN; +} MDN_MSG, *PMDN_MSG; + +typedef struct _MIN_MSG +{ + UCHAR MINLEN; + UCHAR MIN; +} MIN_MSG, *PMIN_MSG; + +typedef struct _PRL_MSG +{ + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // + USHORT PRLLEN; + UCHAR PRL; +} PRL_MSG, *PPRL_MSG; + +typedef struct _MN_HA_KEY_MSG +{ + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // + UCHAR MN_HA_KEY_LEN; + UCHAR MN_HA_KEY; +} MN_HA_KEY_MSG, *PMN_HA_KEY_MSG; + +typedef struct _MN_AAA_KEY_MSG +{ + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // + UCHAR MN_AAA_KEY_LEN; + UCHAR MN_AAA_KEY; +} MN_AAA_KEY_MSG, *PMN_AAA_KEY_MSG; + +typedef struct _QMIDMS_ACTIVATE_MANUAL_REQ_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // + UCHAR Value; +} QMIDMS_ACTIVATE_MANUAL_REQ_MSG, *PQMIDMS_ACTIVATE_MANUAL_REQ_MSG; + +typedef struct _QMIDMS_ACTIVATE_MANUAL_RESP_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} QMIDMS_ACTIVATE_MANUAL_RESP_MSG, *PQMIDMS_ACTIVATE_MANUAL_RESP_MSG; +#endif + +typedef struct _QMIDMS_UIM_GET_STATE_REQ_MSG +{ + USHORT Type; + USHORT Length; +} __attribute__ ((packed)) QMIDMS_UIM_GET_STATE_REQ_MSG, *PQMIDMS_UIM_GET_STATE_REQ_MSG; + +typedef struct _QMIDMS_UIM_GET_STATE_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR UIMState; +} __attribute__ ((packed)) QMIDMS_UIM_GET_STATE_RESP_MSG, *PQMIDMS_UIM_GET_STATE_RESP_MSG; + +typedef struct _QMIDMS_UIM_GET_PIN_STATUS_REQ_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; +} __attribute__ ((packed)) QMIDMS_UIM_GET_PIN_STATUS_REQ_MSG, *PQMIDMS_UIM_GET_PIN_STATUS_REQ_MSG; + +typedef struct _QMIDMS_UIM_PIN_STATUS +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR PINStatus; + UCHAR PINVerifyRetriesLeft; + UCHAR PINUnblockRetriesLeft; +} __attribute__ ((packed)) QMIDMS_UIM_PIN_STATUS, *PQMIDMS_UIM_PIN_STATUS; + +#define QMI_PIN_STATUS_NOT_INIT 0 +#define QMI_PIN_STATUS_NOT_VERIF 1 +#define QMI_PIN_STATUS_VERIFIED 2 +#define QMI_PIN_STATUS_DISABLED 3 +#define QMI_PIN_STATUS_BLOCKED 4 +#define QMI_PIN_STATUS_PERM_BLOCKED 5 +#define QMI_PIN_STATUS_UNBLOCKED 6 +#define QMI_PIN_STATUS_CHANGED 7 + + +typedef struct _QMIDMS_UIM_GET_PIN_STATUS_RESP_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR PinStatus; +} __attribute__ ((packed)) QMIDMS_UIM_GET_PIN_STATUS_RESP_MSG, *PQMIDMS_UIM_GET_PIN_STATUS_RESP_MSG; + +#if 0 +typedef struct _QMIDMS_UIM_GET_CK_STATUS_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR Facility; +} QMIDMS_UIM_GET_CK_STATUS_REQ_MSG, *PQMIDMS_UIM_GET_CK_STATUS_REQ_MSG; + + +typedef struct _QMIDMS_UIM_CK_STATUS +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR FacilityStatus; + UCHAR FacilityVerifyRetriesLeft; + UCHAR FacilityUnblockRetriesLeft; +} QMIDMS_UIM_CK_STATUS, *PQMIDMS_UIM_CK_STATUS; + +typedef struct _QMIDMS_UIM_CK_OPERATION_STATUS +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR OperationBlocking; +} QMIDMS_UIM_CK_OPERATION_STATUS, *PQMIDMS_UIM_CK_OPERATION_STATUS; + +typedef struct _QMIDMS_UIM_GET_CK_STATUS_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR CkStatus; +} QMIDMS_UIM_GET_CK_STATUS_RESP_MSG, *PQMIDMS_UIM_GET_CK_STATUS_RESP_MSG; +#endif + +typedef struct _QMIDMS_UIM_VERIFY_PIN_REQ_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + UCHAR PINID; + UCHAR PINLen; + UCHAR PINValue; +} __attribute__ ((packed)) QMIDMS_UIM_VERIFY_PIN_REQ_MSG, *PQMIDMS_UIM_VERIFY_PIN_REQ_MSG; + +typedef struct _QMIDMS_UIM_VERIFY_PIN_RESP_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR PINVerifyRetriesLeft; + UCHAR PINUnblockRetriesLeft; +} __attribute__ ((packed)) QMIDMS_UIM_VERIFY_PIN_RESP_MSG, *PQMIDMS_UIM_VERIFY_PIN_RESP_MSG; + +#if 0 +typedef struct _QMIDMS_UIM_SET_PIN_PROTECTION_REQ_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + UCHAR PINID; + UCHAR ProtectionSetting; + UCHAR PINLen; + UCHAR PINValue; +} QMIDMS_UIM_SET_PIN_PROTECTION_REQ_MSG, *PQMIDMS_UIM_SET_PIN_PROTECTION_REQ_MSG; + +typedef struct _QMIDMS_UIM_SET_PIN_PROTECTION_RESP_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR PINVerifyRetriesLeft; + UCHAR PINUnblockRetriesLeft; +} QMIDMS_UIM_SET_PIN_PROTECTION_RESP_MSG, *PQMIDMS_UIM_SET_PIN_PROTECTION_RESP_MSG; + +typedef struct _QMIDMS_UIM_SET_CK_PROTECTION_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR Facility; + UCHAR FacilityState; + UCHAR FacliltyLen; + UCHAR FacliltyValue; +} QMIDMS_UIM_SET_CK_PROTECTION_REQ_MSG, *PQMIDMS_UIM_SET_CK_PROTECTION_REQ_MSG; + +typedef struct _QMIDMS_UIM_SET_CK_PROTECTION_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR FacilityRetriesLeft; +} QMIDMS_UIM_SET_CK_PROTECTION_RESP_MSG, *PQMIDMS_UIM_SET_CK_PROTECTION_RESP_MSG; + + +typedef struct _UIM_PIN +{ + UCHAR PinLength; + UCHAR PinValue; +} UIM_PIN, *PUIM_PIN; + +typedef struct _QMIDMS_UIM_CHANGE_PIN_REQ_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + UCHAR PINID; + UCHAR PinDetails; +} QMIDMS_UIM_CHANGE_PIN_REQ_MSG, *PQMIDMS_UIM_CHANGE_PIN_REQ_MSG; + +typedef struct QMIDMS_UIM_CHANGE_PIN_RESP_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR PINVerifyRetriesLeft; + UCHAR PINUnblockRetriesLeft; +} QMIDMS_UIM_CHANGE_PIN_RESP_MSG, *PQMIDMS_UIM_CHANGE_PIN_RESP_MSG; + +typedef struct _UIM_PUK +{ + UCHAR PukLength; + UCHAR PukValue; +} UIM_PUK, *PUIM_PUK; + +typedef struct _QMIDMS_UIM_UNBLOCK_PIN_REQ_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + UCHAR PINID; + UCHAR PinDetails; +} QMIDMS_UIM_UNBLOCK_PIN_REQ_MSG, *PQMIDMS_UIM_BLOCK_PIN_REQ_MSG; + +typedef struct QMIDMS_UIM_UNBLOCK_PIN_RESP_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR PINVerifyRetriesLeft; + UCHAR PINUnblockRetriesLeft; +} QMIDMS_UIM_UNBLOCK_PIN_RESP_MSG, *PQMIDMS_UIM_UNBLOCK_PIN_RESP_MSG; + +typedef struct _QMIDMS_UIM_UNBLOCK_CK_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR Facility; + UCHAR FacliltyUnblockLen; + UCHAR FacliltyUnblockValue; +} QMIDMS_UIM_UNBLOCK_CK_REQ_MSG, *PQMIDMS_UIM_BLOCK_CK_REQ_MSG; + +typedef struct QMIDMS_UIM_UNBLOCK_CK_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR FacilityUnblockRetriesLeft; +} QMIDMS_UIM_UNBLOCK_CK_RESP_MSG, *PQMIDMS_UIM_UNBLOCK_CK_RESP_MSG; + +typedef struct _QMIDMS_SET_EVENT_REPORT_REQ_MSG +{ + USHORT Type; + USHORT Length; +} QMIDMS_SET_EVENT_REPORT_REQ_MSG, *PQMIDMS_SET_EVENT_REPORT_REQ_MSG; + +typedef struct _QMIDMS_SET_EVENT_REPORT_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG +} QMIDMS_SET_EVENT_REPORT_RESP_MSG, *PQMIDMS_SET_EVENT_REPORT_RESP_MSG; + +typedef struct _PIN_STATUS +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR ReportPinState; +} PIN_STATUS, *PPIN_STATUS; + +typedef struct _POWER_STATUS +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR PowerStatus; + UCHAR BatteryLvl; +} POWER_STATUS, *PPOWER_STATUS; + +typedef struct _ACTIVATION_STATE +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT ActivationState; +} ACTIVATION_STATE, *PACTIVATION_STATE; + +typedef struct _ACTIVATION_STATE_REQ +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR ActivationState; +} ACTIVATION_STATE_REQ, *PACTIVATION_STATE_REQ; + +typedef struct _OPERATING_MODE +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR OperatingMode; +} OPERATING_MODE, *POPERATING_MODE; + +typedef struct _UIM_STATE +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR UIMState; +} UIM_STATE, *PUIM_STATE; + +typedef struct _WIRELESS_DISABLE_STATE +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR WirelessDisableState; +} WIRELESS_DISABLE_STATE, *PWIRELESS_DISABLE_STATE; + +typedef struct _QMIDMS_EVENT_REPORT_IND_MSG +{ + USHORT Type; + USHORT Length; +} QMIDMS_EVENT_REPORT_IND_MSG, *PQMIDMS_EVENT_REPORT_IND_MSG; +#endif + +// ============================ END OF DMS =============================== + +// ======================= QOS ============================== +typedef struct _MPIOC_DEV_INFO MPIOC_DEV_INFO, *PMPIOC_DEV_INFO; + +#define QMI_QOS_SET_EVENT_REPORT_REQ 0x0001 +#define QMI_QOS_SET_EVENT_REPORT_RESP 0x0001 +#define QMI_QOS_EVENT_REPORT_IND 0x0001 + +#if 0 +typedef struct _QMI_QOS_SET_EVENT_REPORT_REQ_MSG +{ + USHORT Type; // QMUX type 0x0001 + USHORT Length; + // UCHAR TLVType; // 0x01 - physical link state + // USHORT TLVLength; // 1 + // UCHAR PhyLinkStatusRpt; // 0-enable; 1-disable + UCHAR TLVType2; // 0x02 = global flow reporting + USHORT TLVLength2; // 1 + UCHAR GlobalFlowRpt; // 1-enable; 0-disable +} QMI_QOS_SET_EVENT_REPORT_REQ_MSG, *PQMI_QOS_SET_EVENT_REPORT_REQ_MSG; + +typedef struct _QMI_QOS_SET_EVENT_REPORT_RESP_MSG +{ + USHORT Type; // QMUX type 0x0010 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMUX_RESULT_SUCCESS + // QMUX_RESULT_FAILURE + USHORT QMUXError; // QMUX_ERR_INVALID_ARG + // QMUX_ERR_NO_MEMORY + // QMUX_ERR_INTERNAL + // QMUX_ERR_FAULT +} QMI_QOS_SET_EVENT_REPORT_RESP_MSG, *PQMI_QOS_SET_EVENT_REPORT_RESP_MSG; + +typedef struct _QMI_QOS_EVENT_REPORT_IND_MSG +{ + USHORT Type; // QMUX type 0x0001 + USHORT Length; + UCHAR TLVs; +} QMI_QOS_EVENT_REPORT_IND_MSG, *PQMI_QOS_EVENT_REPORT_IND_MSG; + +#define QOS_EVENT_RPT_IND_FLOW_ACTIVATED 0x01 +#define QOS_EVENT_RPT_IND_FLOW_MODIFIED 0x02 +#define QOS_EVENT_RPT_IND_FLOW_DELETED 0x03 +#define QOS_EVENT_RPT_IND_FLOW_SUSPENDED 0x04 +#define QOS_EVENT_RPT_IND_FLOW_ENABLED 0x05 +#define QOS_EVENT_RPT_IND_FLOW_DISABLED 0x06 + +#define QOS_EVENT_RPT_IND_TLV_PHY_LINK_STATE_TYPE 0x01 +#define QOS_EVENT_RPT_IND_TLV_GLOBAL_FL_RPT_STATE 0x10 +#define QOS_EVENT_RPT_IND_TLV_GLOBAL_FL_RPT_TYPE 0x10 +#define QOS_EVENT_RPT_IND_TLV_TX_FLOW_TYPE 0x11 +#define QOS_EVENT_RPT_IND_TLV_RX_FLOW_TYPE 0x12 +#define QOS_EVENT_RPT_IND_TLV_TX_FILTER_TYPE 0x13 +#define QOS_EVENT_RPT_IND_TLV_RX_FILTER_TYPE 0x14 +#define QOS_EVENT_RPT_IND_TLV_FLOW_SPEC 0x10 +#define QOS_EVENT_RPT_IND_TLV_FILTER_SPEC 0x10 + +typedef struct _QOS_EVENT_RPT_IND_TLV_PHY_LINK_STATE +{ + UCHAR TLVType; // 0x01 + USHORT TLVLength; // 1 + UCHAR PhyLinkState; // 0-dormant, 1-active +} QOS_EVENT_RPT_IND_TLV_PHY_LINK_STATE, *PQOS_EVENT_RPT_IND_TLV_PHY_LINK_STATE; + +typedef struct _QOS_EVENT_RPT_IND_TLV_GLOBAL_FL_RPT +{ + UCHAR TLVType; // 0x10 + USHORT TLVLength; // 6 + ULONG QosId; + UCHAR NewFlow; // 1: newly added flow; 0: existing flow + UCHAR StateChange; // 1: activated; 2: modified; 3: deleted; + // 4: suspended(delete); 5: enabled; 6: disabled +} QOS_EVENT_RPT_IND_TLV_GLOBAL_FL_RPT, *PQOS_EVENT_RPT_IND_TLV_GLOBAL_FL_RPT; + +// QOS Flow + +typedef struct _QOS_EVENT_RPT_IND_TLV_FLOW +{ + UCHAR TLVType; // 0x10-TX flow; 0x11-RX flow + USHORT TLVLength; // var + // embedded TLV's +} QOS_EVENT_RPT_IND_TLV_TX_FLOW, *PQOS_EVENT_RPT_IND_TLV_TX_FLOW; + +#define QOS_FLOW_TLV_IP_FLOW_IDX_TYPE 0x10 +#define QOS_FLOW_TLV_IP_FLOW_TRAFFIC_CLASS_TYPE 0x11 +#define QOS_FLOW_TLV_IP_FLOW_DATA_RATE_MIN_MAX_TYPE 0x12 +#define QOS_FLOW_TLV_IP_FLOW_DATA_RATE_TOKEN_BUCKET_TYPE 0x13 +#define QOS_FLOW_TLV_IP_FLOW_LATENCY_TYPE 0x14 +#define QOS_FLOW_TLV_IP_FLOW_JITTER_TYPE 0x15 +#define QOS_FLOW_TLV_IP_FLOW_PKT_ERR_RATE_TYPE 0x16 +#define QOS_FLOW_TLV_IP_FLOW_MIN_PKT_SIZE_TYPE 0x17 +#define QOS_FLOW_TLV_IP_FLOW_MAX_PKT_SIZE_TYPE 0x18 +#define QOS_FLOW_TLV_IP_FLOW_3GPP_BIT_ERR_RATE_TYPE 0x19 +#define QOS_FLOW_TLV_IP_FLOW_3GPP_TRAF_PRIORITY_TYPE 0x1A +#define QOS_FLOW_TLV_IP_FLOW_3GPP2_PROFILE_ID_TYPE 0x1B + +typedef struct _QOS_FLOW_TLV_IP_FLOW_IDX +{ + UCHAR TLVType; // 0x10 + USHORT TLVLength; // 1 + UCHAR IpFlowIndex; +} QOS_FLOW_TLV_IP_FLOW_IDX, *PQOS_FLOW_TLV_IP_FLOW_IDX; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_TRAFFIC_CLASS +{ + UCHAR TLVType; // 0x11 + USHORT TLVLength; // 1 + UCHAR TrafficClass; +} QOS_FLOW_TLV_IP_FLOW_TRAFFIC_CLASS, *PQOS_FLOW_TLV_IP_FLOW_TRAFFIC_CLASS; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_DATA_RATE_MIN_MAX +{ + UCHAR TLVType; // 0x12 + USHORT TLVLength; // 8 + ULONG DataRateMax; + ULONG GuaranteedRate; +} QOS_FLOW_TLV_IP_FLOW_DATA_RATE_MIN_MAX, *PQOS_FLOW_TLV_IP_FLOW_DATA_RATE_MIN_MAX; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_DATA_RATE_TOKEN_BUCKET +{ + UCHAR TLVType; // 0x13 + USHORT TLVLength; // 12 + ULONG PeakRate; + ULONG TokenRate; + ULONG BucketSize; +} QOS_FLOW_TLV_IP_FLOW_DATA_RATE_TOKEN_BUCKET, *PQOS_FLOW_TLV_IP_FLOW_DATA_RATE_TOKEN_BUCKET; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_LATENCY +{ + UCHAR TLVType; // 0x14 + USHORT TLVLength; // 4 + ULONG IpFlowLatency; +} QOS_FLOW_TLV_IP_FLOW_LATENCY, *PQOS_FLOW_TLV_IP_FLOW_LATENCY; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_JITTER +{ + UCHAR TLVType; // 0x15 + USHORT TLVLength; // 4 + ULONG IpFlowJitter; +} QOS_FLOW_TLV_IP_FLOW_JITTER, *PQOS_FLOW_TLV_IP_FLOW_JITTER; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_PKT_ERR_RATE +{ + UCHAR TLVType; // 0x16 + USHORT TLVLength; // 4 + USHORT ErrRateMultiplier; + USHORT ErrRateExponent; +} QOS_FLOW_TLV_IP_FLOW_PKT_ERR_RATE, *PQOS_FLOW_TLV_IP_FLOW_PKT_ERR_RATE; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_MIN_PKT_SIZE +{ + UCHAR TLVType; // 0x17 + USHORT TLVLength; // 4 + ULONG MinPolicedPktSize; +} QOS_FLOW_TLV_IP_FLOW_MIN_PKT_SIZE, *PQOS_FLOW_TLV_IP_FLOW_MIN_PKT_SIZE; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_MAX_PKT_SIZE +{ + UCHAR TLVType; // 0x18 + USHORT TLVLength; // 4 + ULONG MaxAllowedPktSize; +} QOS_FLOW_TLV_IP_FLOW_MAX_PKT_SIZE, *PQOS_FLOW_TLV_IP_FLOW_MAX_PKT_SIZE; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_3GPP_BIT_ERR_RATE +{ + UCHAR TLVType; // 0x19 + USHORT TLVLength; // 1 + UCHAR ResidualBitErrorRate; +} QOS_FLOW_TLV_IP_FLOW_3GPP_BIT_ERR_RATE, *PQOS_FLOW_TLV_IP_FLOW_3GPP_BIT_ERR_RATE; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_3GPP_TRAF_PRIORITY +{ + UCHAR TLVType; // 0x1A + USHORT TLVLength; // 1 + UCHAR TrafficHandlingPriority; +} QOS_FLOW_TLV_IP_FLOW_3GPP_TRAF_PRIORITY, *PQOS_FLOW_TLV_IP_FLOW_3GPP_TRAF_PRIORITY; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_3GPP2_PROFILE_ID +{ + UCHAR TLVType; // 0x1B + USHORT TLVLength; // 2 + USHORT ProfileId; +} QOS_FLOW_TLV_IP_FLOW_3GPP2_PROFILE_ID, *PQOS_FLOW_TLV_IP_FLOW_3GPP2_PROFILE_ID; + +// QOS Filter + +#define QOS_FILTER_TLV_IP_FILTER_IDX_TYPE 0x10 +#define QOS_FILTER_TLV_IP_VERSION_TYPE 0x11 +#define QOS_FILTER_TLV_IPV4_SRC_ADDR_TYPE 0x12 +#define QOS_FILTER_TLV_IPV4_DEST_ADDR_TYPE 0x13 +#define QOS_FILTER_TLV_NEXT_HDR_PROTOCOL_TYPE 0x14 +#define QOS_FILTER_TLV_IPV4_TYPE_OF_SERVICE_TYPE 0x15 +#define QOS_FILTER_TLV_TCP_UDP_PORT_SRC_TCP_TYPE 0x1B +#define QOS_FILTER_TLV_TCP_UDP_PORT_DEST_TCP_TYPE 0x1C +#define QOS_FILTER_TLV_TCP_UDP_PORT_SRC_UDP_TYPE 0x1D +#define QOS_FILTER_TLV_TCP_UDP_PORT_DEST_UDP_TYPE 0x1E +#define QOS_FILTER_TLV_ICMP_FILTER_MSG_TYPE_TYPE 0x1F +#define QOS_FILTER_TLV_ICMP_FILTER_MSG_CODE_TYPE 0x20 +#define QOS_FILTER_TLV_TCP_UDP_PORT_SRC_TYPE 0x24 +#define QOS_FILTER_TLV_TCP_UDP_PORT_DEST_TYPE 0x25 + +typedef struct _QOS_EVENT_RPT_IND_TLV_FILTER +{ + UCHAR TLVType; // 0x12-TX filter; 0x13-RX filter + USHORT TLVLength; // var + // embedded TLV's +} QOS_EVENT_RPT_IND_TLV_RX_FILTER, *PQOS_EVENT_RPT_IND_TLV_RX_FILTER; + +typedef struct _QOS_FILTER_TLV_IP_FILTER_IDX +{ + UCHAR TLVType; // 0x10 + USHORT TLVLength; // 1 + UCHAR IpFilterIndex; +} QOS_FILTER_TLV_IP_FILTER_IDX, *PQOS_FILTER_TLV_IP_FILTER_IDX; + +typedef struct _QOS_FILTER_TLV_IP_VERSION +{ + UCHAR TLVType; // 0x11 + USHORT TLVLength; // 1 + UCHAR IpVersion; +} QOS_FILTER_TLV_IP_VERSION, *PQOS_FILTER_TLV_IP_VERSION; + +typedef struct _QOS_FILTER_TLV_IPV4_SRC_ADDR +{ + UCHAR TLVType; // 0x12 + USHORT TLVLength; // 8 + ULONG IpSrcAddr; + ULONG IpSrcSubnetMask; +} QOS_FILTER_TLV_IPV4_SRC_ADDR, *PQOS_FILTER_TLV_IPV4_SRC_ADDR; + +typedef struct _QOS_FILTER_TLV_IPV4_DEST_ADDR +{ + UCHAR TLVType; // 0x13 + USHORT TLVLength; // 8 + ULONG IpDestAddr; + ULONG IpDestSubnetMask; +} QOS_FILTER_TLV_IPV4_DEST_ADDR, *PQOS_FILTER_TLV_IPV4_DEST_ADDR; + +typedef struct _QOS_FILTER_TLV_NEXT_HDR_PROTOCOL +{ + UCHAR TLVType; // 0x14 + USHORT TLVLength; // 1 + UCHAR NextHdrProtocol; +} QOS_FILTER_TLV_NEXT_HDR_PROTOCOL, *PQOS_FILTER_TLV_NEXT_HDR_PROTOCOL; + +typedef struct _QOS_FILTER_TLV_IPV4_TYPE_OF_SERVICE +{ + UCHAR TLVType; // 0x15 + USHORT TLVLength; // 2 + UCHAR Ipv4TypeOfService; + UCHAR Ipv4TypeOfServiceMask; +} QOS_FILTER_TLV_IPV4_TYPE_OF_SERVICE, *PQOS_FILTER_TLV_IPV4_TYPE_OF_SERVICE; + +typedef struct _QOS_FILTER_TLV_TCP_UDP_PORT +{ + UCHAR TLVType; // source port: 0x1B-TCP; 0x1D-UDP + // dest port: 0x1C-TCP; 0x1E-UDP + USHORT TLVLength; // 4 + USHORT FilterPort; + USHORT FilterPortRange; +} QOS_FILTER_TLV_TCP_UDP_PORT, *PQOS_FILTER_TLV_TCP_UDP_PORT; + +typedef struct _QOS_FILTER_TLV_ICMP_FILTER_MSG_TYPE +{ + UCHAR TLVType; // 0x1F + USHORT TLVLength; // 1 + UCHAR IcmpFilterMsgType; +} QOS_FILTER_TLV_ICMP_FILTER_MSG_TYPE, *PQOS_FILTER_TLV_ICMP_FILTER_MSG_TYPE; + +typedef struct _QOS_FILTER_TLV_ICMP_FILTER_MSG_CODE +{ + UCHAR TLVType; // 0x20 + USHORT TLVLength; // 1 + UCHAR IcmpFilterMsgCode; +} QOS_FILTER_TLV_ICMP_FILTER_MSG_CODE, *PQOS_FILTER_TLV_ICMP_FILTER_MSG_CODE; + +#define QOS_FILTER_PRECEDENCE_INVALID 256 +#define QOS_FILTER_TLV_PRECEDENCE_TYPE 0x22 +#define QOS_FILTER_TLV_ID_TYPE 0x23 + +typedef struct _QOS_FILTER_TLV_PRECEDENCE +{ + UCHAR TLVType; // 0x22 + USHORT TLVLength; // 2 + USHORT Precedence; // precedence of the filter +} QOS_FILTER_TLV_PRECEDENCE, *PQOS_FILTER_TLV_PRECEDENCE; + +typedef struct _QOS_FILTER_TLV_ID +{ + UCHAR TLVType; // 0x23 + USHORT TLVLength; // 2 + USHORT FilterId; // filter ID +} QOS_FILTER_TLV_ID, *PQOS_FILTER_TLV_ID; + +#ifdef QCQOS_IPV6 + +#define QOS_FILTER_TLV_IPV6_SRC_ADDR_TYPE 0x16 +#define QOS_FILTER_TLV_IPV6_DEST_ADDR_TYPE 0x17 +#define QOS_FILTER_TLV_IPV6_NEXT_HDR_PROTOCOL_TYPE 0x14 // same as IPV4 +#define QOS_FILTER_TLV_IPV6_TRAFFIC_CLASS_TYPE 0x19 +#define QOS_FILTER_TLV_IPV6_FLOW_LABEL_TYPE 0x1A + +typedef struct _QOS_FILTER_TLV_IPV6_SRC_ADDR +{ + UCHAR TLVType; // 0x16 + USHORT TLVLength; // 17 + UCHAR IpSrcAddr[16]; + UCHAR IpSrcAddrPrefixLen; // [0..128] +} QOS_FILTER_TLV_IPV6_SRC_ADDR, *PQOS_FILTER_TLV_IPV6_SRC_ADDR; + +typedef struct _QOS_FILTER_TLV_IPV6_DEST_ADDR +{ + UCHAR TLVType; // 0x17 + USHORT TLVLength; // 17 + UCHAR IpDestAddr[16]; + UCHAR IpDestAddrPrefixLen; // [0..128] +} QOS_FILTER_TLV_IPV6_DEST_ADDR, *PQOS_FILTER_TLV_IPV6_DEST_ADDR; + +#define QOS_FILTER_IPV6_NEXT_HDR_PROTOCOL_TCP 0x06 +#define QOS_FILTER_IPV6_NEXT_HDR_PROTOCOL_UDP 0x11 + +typedef struct _QOS_FILTER_TLV_IPV6_TRAFFIC_CLASS +{ + UCHAR TLVType; // 0x19 + USHORT TLVLength; // 2 + UCHAR TrafficClass; + UCHAR TrafficClassMask; // compare the first 6 bits only +} QOS_FILTER_TLV_IPV6_TRAFFIC_CLASS, *PQOS_FILTER_TLV_IPV6_TRAFFIC_CLASS; + +typedef struct _QOS_FILTER_TLV_IPV6_FLOW_LABEL +{ + UCHAR TLVType; // 0x1A + USHORT TLVLength; // 4 + ULONG FlowLabel; +} QOS_FILTER_TLV_IPV6_FLOW_LABEL, *PQOS_FILTER_TLV_IPV6_FLOW_LABEL; + +#endif // QCQOS_IPV6 +#endif + +// ======================= WMS ============================== +#define QMIWMS_SET_EVENT_REPORT_REQ 0x0001 +#define QMIWMS_SET_EVENT_REPORT_RESP 0x0001 +#define QMIWMS_EVENT_REPORT_IND 0x0001 +#define QMIWMS_RAW_SEND_REQ 0x0020 +#define QMIWMS_RAW_SEND_RESP 0x0020 +#define QMIWMS_RAW_WRITE_REQ 0x0021 +#define QMIWMS_RAW_WRITE_RESP 0x0021 +#define QMIWMS_RAW_READ_REQ 0x0022 +#define QMIWMS_RAW_READ_RESP 0x0022 +#define QMIWMS_MODIFY_TAG_REQ 0x0023 +#define QMIWMS_MODIFY_TAG_RESP 0x0023 +#define QMIWMS_DELETE_REQ 0x0024 +#define QMIWMS_DELETE_RESP 0x0024 +#define QMIWMS_GET_MESSAGE_PROTOCOL_REQ 0x0030 +#define QMIWMS_GET_MESSAGE_PROTOCOL_RESP 0x0030 +#define QMIWMS_LIST_MESSAGES_REQ 0x0031 +#define QMIWMS_LIST_MESSAGES_RESP 0x0031 +#define QMIWMS_GET_SMSC_ADDRESS_REQ 0x0034 +#define QMIWMS_GET_SMSC_ADDRESS_RESP 0x0034 +#define QMIWMS_SET_SMSC_ADDRESS_REQ 0x0035 +#define QMIWMS_SET_SMSC_ADDRESS_RESP 0x0035 +#define QMIWMS_GET_STORE_MAX_SIZE_REQ 0x0036 +#define QMIWMS_GET_STORE_MAX_SIZE_RESP 0x0036 + + +#define WMS_MESSAGE_PROTOCOL_CDMA 0x00 +#define WMS_MESSAGE_PROTOCOL_WCDMA 0x01 + +#if 0 +typedef struct _QMIWMS_GET_MESSAGE_PROTOCOL_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; +} QMIWMS_GET_MESSAGE_PROTOCOL_REQ_MSG, *PQMIWMS_GET_MESSAGE_PROTOCOL_REQ_MSG; + +typedef struct _QMIWMS_GET_MESSAGE_PROTOCOL_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR MessageProtocol; +} QMIWMS_GET_MESSAGE_PROTOCOL_RESP_MSG, *PQMIWMS_GET_MESSAGE_PROTOCOL_RESP_MSG; + +typedef struct _QMIWMS_GET_STORE_MAX_SIZE_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR StorageType; +} QMIWMS_GET_STORE_MAX_SIZE_REQ_MSG, *PQMIWMS_GET_STORE_MAX_SIZE_REQ_MSG; + +typedef struct _QMIWMS_GET_STORE_MAX_SIZE_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLV2Type; + USHORT TLV2Length; + ULONG MemStoreMaxSize; +} QMIWMS_GET_STORE_MAX_SIZE_RESP_MSG, *PQMIWMS_GET_STORE_MAX_SIZE_RESP_MSG; + +typedef struct _REQUEST_TAG +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR TagType; +} REQUEST_TAG, *PREQUEST_TAG; + +typedef struct _QMIWMS_LIST_MESSAGES_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR StorageType; +} QMIWMS_LIST_MESSAGES_REQ_MSG, *PQMIWMS_LIST_MESSAGES_REQ_MSG; + +typedef struct _QMIWMS_MESSAGE +{ + ULONG MessageIndex; + UCHAR TagType; +} QMIWMS_MESSAGE, *PQMIWMS_MESSAGE; + +typedef struct _QMIWMS_LIST_MESSAGES_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLV2Type; + USHORT TLV2Length; + ULONG NumMessages; +} QMIWMS_LIST_MESSAGES_RESP_MSG, *PQMIWMS_LIST_MESSAGES_RESP_MSG; + +typedef struct _QMIWMS_RAW_READ_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR StorageType; + ULONG MemoryIndex; +} QMIWMS_RAW_READ_REQ_MSG, *PQMIWMS_RAW_READ_REQ_MSG; + +typedef struct _QMIWMS_RAW_READ_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR TagType; + UCHAR Format; + USHORT MessageLength; + UCHAR Message; +} QMIWMS_RAW_READ_RESP_MSG, *PQMIWMS_RAW_READ_RESP_MSG; + +typedef struct _QMIWMS_MODIFY_TAG_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR StorageType; + ULONG MemoryIndex; + UCHAR TagType; +} QMIWMS_MODIFY_TAG_REQ_MSG, *PQMIWMS_MODIFY_TAG_REQ_MSG; + +typedef struct _QMIWMS_MODIFY_TAG_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} QMIWMS_MODIFY_TAG_RESP_MSG, *PQMIWMS_MODIFY_TAG_RESP_MSG; + +typedef struct _QMIWMS_RAW_SEND_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR SmsFormat; + USHORT SmsLength; + UCHAR SmsMessage; +} QMIWMS_RAW_SEND_REQ_MSG, *PQMIWMS_RAW_SEND_REQ_MSG; + +typedef struct _RAW_SEND_CAUSE_CODE +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT CauseCode; +} RAW_SEND_CAUSE_CODE, *PRAW_SEND_CAUSE_CODE; + + +typedef struct _QMIWMS_RAW_SEND_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} QMIWMS_RAW_SEND_RESP_MSG, *PQMIWMS_RAW_SEND_RESP_MSG; + + +typedef struct _WMS_DELETE_MESSAGE_INDEX +{ + UCHAR TLVType; + USHORT TLVLength; + ULONG MemoryIndex; +} WMS_DELETE_MESSAGE_INDEX, *PWMS_DELETE_MESSAGE_INDEX; + +typedef struct _WMS_DELETE_MESSAGE_TAG +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR MessageTag; +} WMS_DELETE_MESSAGE_TAG, *PWMS_DELETE_MESSAGE_TAG; + +typedef struct _QMIWMS_DELETE_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR StorageType; +} QMIWMS_DELETE_REQ_MSG, *PQMIWMS_DELETE_REQ_MSG; + +typedef struct _QMIWMS_DELETE_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} QMIWMS_DELETE_RESP_MSG, *PQMIWMS_DELETE_RESP_MSG; + + +typedef struct _QMIWMS_GET_SMSC_ADDRESS_REQ_MSG +{ + USHORT Type; + USHORT Length; +} QMIWMS_GET_SMSC_ADDRESS_REQ_MSG, *PQMIWMS_GET_SMSC_ADDRESS_REQ_MSG; + +typedef struct _QMIWMS_SMSC_ADDRESS +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR SMSCAddressType[3]; + UCHAR SMSCAddressLength; + UCHAR SMSCAddressDigits; +} QMIWMS_SMSC_ADDRESS, *PQMIWMS_SMSC_ADDRESS; + + +typedef struct _QMIWMS_GET_SMSC_ADDRESS_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR SMSCAddress; +} QMIWMS_GET_SMSC_ADDRESS_RESP_MSG, *PQMIWMS_GET_SMSC_ADDRESS_RESP_MSG; + +typedef struct _QMIWMS_SET_SMSC_ADDRESS_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR SMSCAddress; +} QMIWMS_SET_SMSC_ADDRESS_REQ_MSG, *PQMIWMS_SET_SMSC_ADDRESS_REQ_MSG; + +typedef struct _QMIWMS_SET_SMSC_ADDRESS_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} QMIWMS_SET_SMSC_ADDRESS_RESP_MSG, *PQMIWMS_SET_SMSC_ADDRESS_RESP_MSG; + +typedef struct _QMIWMS_SET_EVENT_REPORT_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR ReportNewMessage; +} QMIWMS_SET_EVENT_REPORT_REQ_MSG, *PQMIWMS_SET_EVENT_REPORT_REQ_MSG; + +typedef struct _QMIWMS_SET_EVENT_REPORT_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} QMIWMS_SET_EVENT_REPORT_RESP_MSG, *PQMIWMS_SET_EVENT_REPORT_RESP_MSG; + +typedef struct _QMIWMS_EVENT_REPORT_IND_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR StorageType; + ULONG StorageIndex; +} QMIWMS_EVENT_REPORT_IND_MSG, *PQMIWMS_EVENT_REPORT_IND_MSG; +#endif + +// ======================= End of WMS ============================== + + +// ======================= NAS ============================== +#define QMINAS_SET_EVENT_REPORT_REQ 0x0002 +#define QMINAS_SET_EVENT_REPORT_RESP 0x0002 +#define QMINAS_EVENT_REPORT_IND 0x0002 +#define QMINAS_GET_SIGNAL_STRENGTH_REQ 0x0020 +#define QMINAS_GET_SIGNAL_STRENGTH_RESP 0x0020 +#define QMINAS_PERFORM_NETWORK_SCAN_REQ 0x0021 +#define QMINAS_PERFORM_NETWORK_SCAN_RESP 0x0021 +#define QMINAS_INITIATE_NW_REGISTER_REQ 0x0022 +#define QMINAS_INITIATE_NW_REGISTER_RESP 0x0022 +#define QMINAS_INITIATE_ATTACH_REQ 0x0023 +#define QMINAS_INITIATE_ATTACH_RESP 0x0023 +#define QMINAS_GET_SERVING_SYSTEM_REQ 0x0024 +#define QMINAS_GET_SERVING_SYSTEM_RESP 0x0024 +#define QMINAS_SERVING_SYSTEM_IND 0x0024 +#define QMINAS_GET_HOME_NETWORK_REQ 0x0025 +#define QMINAS_GET_HOME_NETWORK_RESP 0x0025 +#define QMINAS_GET_PREFERRED_NETWORK_REQ 0x0026 +#define QMINAS_GET_PREFERRED_NETWORK_RESP 0x0026 +#define QMINAS_SET_PREFERRED_NETWORK_REQ 0x0027 +#define QMINAS_SET_PREFERRED_NETWORK_RESP 0x0027 +#define QMINAS_GET_FORBIDDEN_NETWORK_REQ 0x0028 +#define QMINAS_GET_FORBIDDEN_NETWORK_RESP 0x0028 +#define QMINAS_SET_FORBIDDEN_NETWORK_REQ 0x0029 +#define QMINAS_SET_FORBIDDEN_NETWORK_RESP 0x0029 +#define QMINAS_SET_TECHNOLOGY_PREF_REQ 0x002A +#define QMINAS_SET_TECHNOLOGY_PREF_RESP 0x002A +#define QMINAS_GET_RF_BAND_INFO_REQ 0x0031 +#define QMINAS_GET_RF_BAND_INFO_RESP 0x0031 +#define QMINAS_GET_PLMN_NAME_REQ 0x0044 +#define QMINAS_GET_PLMN_NAME_RESP 0x0044 +#define QUECTEL_PACKET_TRANSFER_START_IND 0X100 +#define QUECTEL_PACKET_TRANSFER_END_IND 0X101 +#define QMINAS_GET_SYS_INFO_REQ 0x004D +#define QMINAS_GET_SYS_INFO_RESP 0x004D +#define QMINAS_SYS_INFO_IND 0x004D +#define QMINAS_GET_SIG_INFO_REQ 0x004F +#define QMINAS_GET_SIG_INFO_RESP 0x004F + +typedef struct _QMINAS_GET_HOME_NETWORK_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; +} __attribute__ ((packed)) QMINAS_GET_HOME_NETWORK_REQ_MSG, *PQMINAS_GET_HOME_NETWORK_REQ_MSG; + +typedef struct _HOME_NETWORK_SYSTEMID +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT SystemID; + USHORT NetworkID; +} __attribute__ ((packed)) HOME_NETWORK_SYSTEMID, *PHOME_NETWORK_SYSTEMID; + +typedef struct _HOME_NETWORK +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT MobileCountryCode; + USHORT MobileNetworkCode; + UCHAR NetworkDesclen; + UCHAR NetworkDesc; +} __attribute__ ((packed)) HOME_NETWORK, *PHOME_NETWORK; + +#if 0 +typedef struct _HOME_NETWORK_EXT +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT MobileCountryCode; + USHORT MobileNetworkCode; + UCHAR NetworkDescDisp; + UCHAR NetworkDescEncoding; + UCHAR NetworkDesclen; + UCHAR NetworkDesc; +} HOME_NETWORK_EXT, *PHOME_NETWORK_EXT; + +typedef struct _QMINAS_GET_HOME_NETWORK_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} QMINAS_GET_HOME_NETWORK_RESP_MSG, *PQMINAS_GET_HOME_NETWORK_RESP_MSG; + +typedef struct _QMINAS_GET_PREFERRED_NETWORK_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; +} QMINAS_GET_PREFERRED_NETWORK_REQ_MSG, *PQMINAS_GET_PREFERRED_NETWORK_REQ_MSG; + + +typedef struct _PREFERRED_NETWORK +{ + USHORT MobileCountryCode; + USHORT MobileNetworkCode; + USHORT RadioAccess; +} PREFERRED_NETWORK, *PPREFERRED_NETWORK; + +typedef struct _QMINAS_GET_PREFERRED_NETWORK_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR TLV2Type; // 0x01 - required parameter + USHORT TLV2Length; // length of the mfr string + USHORT NumPreferredNetwork; +} QMINAS_GET_PREFERRED_NETWORK_RESP_MSG, *PQMINAS_GET_PREFERRED_NETWORK_RESP_MSG; + +typedef struct _QMINAS_GET_FORBIDDEN_NETWORK_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; +} QMINAS_GET_FORBIDDEN_NETWORK_REQ_MSG, *PQMINAS_GET_FORBIDDEN_NETWORK_REQ_MSG; + +typedef struct _FORBIDDEN_NETWORK +{ + USHORT MobileCountryCode; + USHORT MobileNetworkCode; +} FORBIDDEN_NETWORK, *PFORBIDDEN_NETWORK; + +typedef struct _QMINAS_GET_FORBIDDEN_NETWORK_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR TLV2Type; // 0x01 - required parameter + USHORT TLV2Length; // length of the mfr string + USHORT NumForbiddenNetwork; +} QMINAS_GET_FORBIDDEN_NETWORK_RESP_MSG, *PQMINAS_GET_FORBIDDEN_NETWORK_RESP_MSG; + +typedef struct _QMINAS_GET_SERVING_SYSTEM_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; +} QMINAS_GET_SERVING_SYSTEM_REQ_MSG, *PQMINAS_GET_SERVING_SYSTEM_REQ_MSG; + +typedef struct _QMINAS_ROAMING_INDICATOR_MSG +{ + UCHAR TLVType; // 0x01 - required parameter + USHORT TLVLength; // length of the mfr string + UCHAR RoamingIndicator; +} QMINAS_ROAMING_INDICATOR_MSG, *PQMINAS_ROAMING_INDICATOR_MSG; +#endif + +typedef struct _QMINAS_DATA_CAP +{ + UCHAR TLVType; // 0x01 - required parameter + USHORT TLVLength; // length of the mfr string + UCHAR DataCapListLen; + UCHAR DataCap; +} __attribute__ ((packed)) QMINAS_DATA_CAP, *PQMINAS_DATA_CAP; + +typedef struct _QMINAS_CURRENT_PLMN_MSG +{ + UCHAR TLVType; // 0x01 - required parameter + USHORT TLVLength; // length of the mfr string + USHORT MobileCountryCode; + USHORT MobileNetworkCode; + UCHAR NetworkDesclen; + UCHAR NetworkDesc; +} __attribute__ ((packed)) QMINAS_CURRENT_PLMN_MSG, *PQMINAS_CURRENT_PLMN_MSG; + +typedef struct _QMINAS_GET_SERVING_SYSTEM_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} __attribute__ ((packed)) QMINAS_GET_SERVING_SYSTEM_RESP_MSG, *PQMINAS_GET_SERVING_SYSTEM_RESP_MSG; + +typedef struct _SERVING_SYSTEM +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR RegistrationState; + UCHAR CSAttachedState; + UCHAR PSAttachedState; + UCHAR RegistredNetwork; + UCHAR InUseRadioIF; + UCHAR RadioIF; +} __attribute__ ((packed)) SERVING_SYSTEM, *PSERVING_SYSTEM; + +typedef struct _QMINAS_GET_SYS_INFO_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} __attribute__ ((packed)) QMINAS_GET_SYS_INFO_RESP_MSG, *PQMINAS_GET_SYS_INFO_RESP_MSG; + +typedef struct _QMINAS_SYS_INFO_IND_MSG +{ + USHORT Type; + USHORT Length; +} __attribute__ ((packed)) QMINAS_SYS_INFO_IND_MSG, *PQMINAS_SYS_INFO_IND_MSG; + +typedef struct _SERVICE_STATUS_INFO +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR SrvStatus; + UCHAR true_srv_status; + UCHAR IsPrefDataPath; +} __attribute__ ((packed)) SERVICE_STATUS_INFO, *PSERVICE_STATUS_INFO; + +typedef struct _CDMA_SYSTEM_INFO +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR SrvDomainValid; + UCHAR SrvDomain; + UCHAR SrvCapabilityValid; + UCHAR SrvCapability; + UCHAR RoamStatusValid; + UCHAR RoamStatus; + UCHAR IsSysForbiddenValid; + UCHAR IsSysForbidden; + UCHAR IsSysPrlMatchValid; + UCHAR IsSysPrlMatch; + UCHAR PRevInUseValid; + UCHAR PRevInUse; + UCHAR BSPRevValid; + UCHAR BSPRev; + UCHAR CCSSupportedValid; + UCHAR CCSSupported; + UCHAR CDMASysIdValid; + USHORT SID; + USHORT NID; + UCHAR BSInfoValid; + USHORT BaseID; + ULONG BaseLAT; + ULONG BaseLONG; + UCHAR PacketZoneValid; + USHORT PacketZone; + UCHAR NetworkIdValid; + UCHAR MCC[3]; + UCHAR MNC[3]; +} __attribute__ ((packed)) CDMA_SYSTEM_INFO, *PCDMA_SYSTEM_INFO; + +typedef struct _HDR_SYSTEM_INFO +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR SrvDomainValid; + UCHAR SrvDomain; + UCHAR SrvCapabilityValid; + UCHAR SrvCapability; + UCHAR RoamStatusValid; + UCHAR RoamStatus; + UCHAR IsSysForbiddenValid; + UCHAR IsSysForbidden; + UCHAR IsSysPrlMatchValid; + UCHAR IsSysPrlMatch; + UCHAR HdrPersonalityValid; + UCHAR HdrPersonality; + UCHAR HdrActiveProtValid; + UCHAR HdrActiveProt; + UCHAR is856SysIdValid; + UCHAR is856SysId[16]; +} __attribute__ ((packed)) HDR_SYSTEM_INFO, *PHDR_SYSTEM_INFO; + +typedef struct _GSM_SYSTEM_INFO +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR SrvDomainValid; + UCHAR SrvDomain; + UCHAR SrvCapabilityValid; + UCHAR SrvCapability; + UCHAR RoamStatusValid; + UCHAR RoamStatus; + UCHAR IsSysForbiddenValid; + UCHAR IsSysForbidden; + UCHAR LacValid; + USHORT Lac; + UCHAR CellIdValid; + ULONG CellId; + UCHAR RegRejectInfoValid; + UCHAR RejectSrvDomain; + UCHAR RejCause; + UCHAR NetworkIdValid; + UCHAR MCC[3]; + UCHAR MNC[3]; + UCHAR EgprsSuppValid; + UCHAR EgprsSupp; + UCHAR DtmSuppValid; + UCHAR DtmSupp; +} __attribute__ ((packed)) GSM_SYSTEM_INFO, *PGSM_SYSTEM_INFO; + +typedef struct _WCDMA_SYSTEM_INFO +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR SrvDomainValid; + UCHAR SrvDomain; + UCHAR SrvCapabilityValid; + UCHAR SrvCapability; + UCHAR RoamStatusValid; + UCHAR RoamStatus; + UCHAR IsSysForbiddenValid; + UCHAR IsSysForbidden; + UCHAR LacValid; + USHORT Lac; + UCHAR CellIdValid; + ULONG CellId; + UCHAR RegRejectInfoValid; + UCHAR RejectSrvDomain; + UCHAR RejCause; + UCHAR NetworkIdValid; + UCHAR MCC[3]; + UCHAR MNC[3]; + UCHAR HsCallStatusValid; + UCHAR HsCallStatus; + UCHAR HsIndValid; + UCHAR HsInd; + UCHAR PscValid; + UCHAR Psc; +} __attribute__ ((packed)) WCDMA_SYSTEM_INFO, *PWCDMA_SYSTEM_INFO; + +typedef struct _LTE_SYSTEM_INFO +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR SrvDomainValid; + UCHAR SrvDomain; + UCHAR SrvCapabilityValid; + UCHAR SrvCapability; + UCHAR RoamStatusValid; + UCHAR RoamStatus; + UCHAR IsSysForbiddenValid; + UCHAR IsSysForbidden; + UCHAR LacValid; + USHORT Lac; + UCHAR CellIdValid; + ULONG CellId; + UCHAR RegRejectInfoValid; + UCHAR RejectSrvDomain; + UCHAR RejCause; + UCHAR NetworkIdValid; + UCHAR MCC[3]; + UCHAR MNC[3]; + UCHAR TacValid; + USHORT Tac; +} __attribute__ ((packed)) LTE_SYSTEM_INFO, *PLTE_SYSTEM_INFO; + +typedef struct _TDSCDMA_SYSTEM_INFO +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR SrvDomainValid; + UCHAR SrvDomain; + UCHAR SrvCapabilityValid; + UCHAR SrvCapability; + UCHAR RoamStatusValid; + UCHAR RoamStatus; + UCHAR IsSysForbiddenValid; + UCHAR IsSysForbidden; + UCHAR LacValid; + USHORT Lac; + UCHAR CellIdValid; + ULONG CellId; + UCHAR RegRejectInfoValid; + UCHAR RejectSrvDomain; + UCHAR RejCause; + UCHAR NetworkIdValid; + UCHAR MCC[3]; + UCHAR MNC[3]; + UCHAR HsCallStatusValid; + UCHAR HsCallStatus; + UCHAR HsIndValid; + UCHAR HsInd; + UCHAR CellParameterIdValid; + USHORT CellParameterId; + UCHAR CellBroadcastCapValid; + ULONG CellBroadcastCap; + UCHAR CsBarStatusValid; + ULONG CsBarStatus; + UCHAR PsBarStatusValid; + ULONG PsBarStatus; + UCHAR CipherDomainValid; + UCHAR CipherDomain; +} __attribute__ ((packed)) TDSCDMA_SYSTEM_INFO, *PTDSCDMA_SYSTEM_INFO; + +typedef enum { + NAS_SYS_SRV_STATUS_NO_SRV_V01 = 0, + NAS_SYS_SRV_STATUS_LIMITED_V01 = 1, + NAS_SYS_SRV_STATUS_SRV_V01 = 2, + NAS_SYS_SRV_STATUS_LIMITED_REGIONAL_V01 = 3, + NAS_SYS_SRV_STATUS_PWR_SAVE_V01 = 4, +}nas_service_status_enum_type_v01; + +typedef enum { + SYS_SRV_DOMAIN_NO_SRV_V01 = 0, + SYS_SRV_DOMAIN_CS_ONLY_V01 = 1, + SYS_SRV_DOMAIN_PS_ONLY_V01 = 2, + SYS_SRV_DOMAIN_CS_PS_V01 = 3, + SYS_SRV_DOMAIN_CAMPED_V01 = 4, +}nas_service_domain_enum_type_v01; + +typedef struct { + UCHAR TLVType; + USHORT TLVLength; + + uint8_t srv_domain_valid; + uint8_t srv_domain; + uint8_t srv_capability_valid; + uint8_t srv_capability; + uint8_t roam_status_valid; + uint8_t roam_status; + uint8_t is_sys_forbidden_valid; + uint8_t is_sys_forbidden; + + uint8_t lac_valid; + uint16_t lac; + uint8_t cell_id_valid; + uint32_t cell_id; + uint8_t reg_reject_info_valid; + uint8_t reject_srv_domain; + uint8_t rej_cause; + uint8_t network_id_valid; + UCHAR MCC[3]; + UCHAR MNC[3]; + + uint8_t tac_valid; + uint16_t tac; +} __attribute__ ((packed)) NR5G_SYSTEM_INFO, *PNR5G_SYSTEM_INFO; + +#if 0 +typedef struct _QMINAS_SERVING_SYSTEM_IND_MSG +{ + USHORT Type; + USHORT Length; +} QMINAS_SERVING_SYSTEM_IND_MSG, *PQMINAS_SERVING_SYSTEM_IND_MSG; + +typedef struct _QMINAS_SET_PREFERRED_NETWORK_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT NumPreferredNetwork; + USHORT MobileCountryCode; + USHORT MobileNetworkCode; + USHORT RadioAccess; +} QMINAS_SET_PREFERRED_NETWORK_REQ_MSG, *PQMINAS_SET_PREFERRED_NETWORK_REQ_MSG; + +typedef struct _QMINAS_SET_PREFERRED_NETWORK_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} QMINAS_SET_PREFERRED_NETWORK_RESP_MSG, *PQMINAS_SET_PREFERRED_NETWORK_RESP_MSG; + +typedef struct _QMINAS_SET_FORBIDDEN_NETWORK_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT NumForbiddenNetwork; + USHORT MobileCountryCode; + USHORT MobileNetworkCode; +} QMINAS_SET_FORBIDDEN_NETWORK_REQ_MSG, *PQMINAS_SET_FORBIDDEN_NETWORK_REQ_MSG; + +typedef struct _QMINAS_SET_FORBIDDEN_NETWORK_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} QMINAS_SET_FORBIDDEN_NETWORK_RESP_MSG, *PQMINAS_SET_FORBIDDEN_NETWORK_RESP_MSG; + +typedef struct _QMINAS_PERFORM_NETWORK_SCAN_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; +} QMINAS_PERFORM_NETWORK_SCAN_REQ_MSG, *PQMINAS_PERFORM_NETWORK_SCAN_REQ_MSG; + +typedef struct _VISIBLE_NETWORK +{ + USHORT MobileCountryCode; + USHORT MobileNetworkCode; + UCHAR NetworkStatus; + UCHAR NetworkDesclen; +} VISIBLE_NETWORK, *PVISIBLE_NETWORK; + +typedef struct _QMINAS_PERFORM_NETWORK_SCAN_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} QMINAS_PERFORM_NETWORK_SCAN_RESP_MSG, *PQMINAS_PERFORM_NETWORK_SCAN_RESP_MSG; + +typedef struct _QMINAS_PERFORM_NETWORK_SCAN_NETWORK_INFO +{ + UCHAR TLVType; // 0x010 - required parameter + USHORT TLVLength; // length + USHORT NumNetworkInstances; +} QMINAS_PERFORM_NETWORK_SCAN_NETWORK_INFO, *PQMINAS_PERFORM_NETWORK_SCAN_NETWORK_INFO; + +typedef struct _QMINAS_PERFORM_NETWORK_SCAN_RAT_INFO +{ + UCHAR TLVType; // 0x011 - required parameter + USHORT TLVLength; // length + USHORT NumInst; +} QMINAS_PERFORM_NETWORK_SCAN_RAT_INFO, *PQMINAS_PERFORM_NETWORK_SCAN_RAT_INFO; + +typedef struct _QMINAS_PERFORM_NETWORK_SCAN_RAT +{ + USHORT MCC; + USHORT MNC; + UCHAR RAT; +} QMINAS_PERFORM_NETWORK_SCAN_RAT, *PQMINAS_PERFORM_NETWORK_SCAN_RAT; + + +typedef struct _QMINAS_MANUAL_NW_REGISTER +{ + UCHAR TLV2Type; // 0x02 - result code + USHORT TLV2Length; // 4 + USHORT MobileCountryCode; + USHORT MobileNetworkCode; + UCHAR RadioAccess; +} QMINAS_MANUAL_NW_REGISTER, *PQMINAS_MANUAL_NW_REGISTER; + +typedef struct _QMINAS_INITIATE_NW_REGISTER_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + UCHAR RegisterAction; +} QMINAS_INITIATE_NW_REGISTER_REQ_MSG, *PQMINAS_INITIATE_NW_REGISTER_REQ_MSG; + +typedef struct _QMINAS_INITIATE_NW_REGISTER_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} QMINAS_INITIATE_NW_REGISTER_RESP_MSG, *PQMINAS_INITIATE_NW_REGISTER_RESP_MSG; + +typedef struct _QMINAS_SET_TECHNOLOGY_PREF_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT TechPref; + UCHAR Duration; +} QMINAS_SET_TECHNOLOGY_PREF_REQ_MSG, *PQMINAS_SET_TECHNOLOGY_PREF_REQ_MSG; + +typedef struct _QMINAS_SET_TECHNOLOGY_PREF_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} QMINAS_SET_TECHNOLOGY_PREF_RESP_MSG, *PQMINAS_SET_TECHNOLOGY_PREF_RESP_MSG; + +typedef struct _QMINAS_GET_SIGNAL_STRENGTH_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; +} QMINAS_GET_SIGNAL_STRENGTH_REQ_MSG, *PQMINAS_GET_SIGNAL_STRENGTH_REQ_MSG; + +typedef struct _QMINAS_SIGNAL_STRENGTH +{ + CHAR SigStrength; + UCHAR RadioIf; +} QMINAS_SIGNAL_STRENGTH, *PQMINAS_SIGNAL_STRENGTH; + +typedef struct _QMINAS_SIGNAL_STRENGTH_LIST +{ + UCHAR TLV3Type; + USHORT TLV3Length; + USHORT NumInstance; +} QMINAS_SIGNAL_STRENGTH_LIST, *PQMINAS_SIGNAL_STRENGTH_LIST; + + +typedef struct _QMINAS_GET_SIGNAL_STRENGTH_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR TLV2Type; + USHORT TLV2Length; + CHAR SignalStrength; + UCHAR RadioIf; +} QMINAS_GET_SIGNAL_STRENGTH_RESP_MSG, *PQMINAS_GET_SIGNAL_STRENGTH_RESP_MSG; + + +typedef struct _QMINAS_SET_EVENT_REPORT_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR ReportSigStrength; + UCHAR NumTresholds; + CHAR TresholdList[2]; +} QMINAS_SET_EVENT_REPORT_REQ_MSG, *PQMINAS_SET_EVENT_REPORT_REQ_MSG; + +typedef struct _QMINAS_SET_EVENT_REPORT_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} QMINAS_SET_EVENT_REPORT_RESP_MSG, *PQMINAS_SET_EVENT_REPORT_RESP_MSG; + +typedef struct _QMINAS_SIGNAL_STRENGTH_TLV +{ + UCHAR TLVType; + USHORT TLVLength; + CHAR SigStrength; + UCHAR RadioIf; +} QMINAS_SIGNAL_STRENGTH_TLV, *PQMINAS_SIGNAL_STRENGTH_TLV; + +typedef struct _QMINAS_REJECT_CAUSE_TLV +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR ServiceDomain; + USHORT RejectCause; +} QMINAS_REJECT_CAUSE_TLV, *PQMINAS_REJECT_CAUSE_TLV; + +typedef struct _QMINAS_EVENT_REPORT_IND_MSG +{ + USHORT Type; + USHORT Length; +} QMINAS_EVENT_REPORT_IND_MSG, *PQMINAS_EVENT_REPORT_IND_MSG; + +typedef struct _QMINAS_GET_RF_BAND_INFO_REQ_MSG +{ + USHORT Type; + USHORT Length; +} QMINAS_GET_RF_BAND_INFO_REQ_MSG, *PQMINAS_GET_RF_BAND_INFO_REQ_MSG; + +typedef struct _QMINASRF_BAND_INFO +{ + UCHAR RadioIf; + USHORT ActiveBand; + USHORT ActiveChannel; +} QMINASRF_BAND_INFO, *PQMINASRF_BAND_INFO; + +typedef struct _QMINAS_GET_RF_BAND_INFO_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR NumInstances; +} QMINAS_GET_RF_BAND_INFO_RESP_MSG, *PQMINAS_GET_RF_BAND_INFO_RESP_MSG; + + +typedef struct _QMINAS_GET_PLMN_NAME_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT MCC; + USHORT MNC; +} QMINAS_GET_PLMN_NAME_REQ_MSG, *PQMINAS_GET_PLMN_NAME_REQ_MSG; + +typedef struct _QMINAS_GET_PLMN_NAME_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} QMINAS_GET_PLMN_NAME_RESP_MSG, *PQMINAS_GET_PLMN_NAME_RESP_MSG; + +typedef struct _QMINAS_GET_PLMN_NAME_SPN +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR SPN_Enc; + UCHAR SPN_Len; +} QMINAS_GET_PLMN_NAME_SPN, *PQMINAS_GET_PLMN_NAME_SPN; + +typedef struct _QMINAS_GET_PLMN_NAME_PLMN +{ + UCHAR PLMN_Enc; + UCHAR PLMN_Ci; + UCHAR PLMN_SpareBits; + UCHAR PLMN_Len; +} QMINAS_GET_PLMN_NAME_PLMN, *PQMINAS_GET_PLMN_NAME_PLMN; + +typedef struct _QMINAS_INITIATE_ATTACH_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR PsAttachAction; +} QMINAS_INITIATE_ATTACH_REQ_MSG, *PQMINAS_INITIATE_ATTACH_REQ_MSG; + +typedef struct _QMINAS_INITIATE_ATTACH_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} QMINAS_INITIATE_ATTACH_RESP_MSG, *PQMINAS_INITIATE_ATTACH_RESP_MSG; +#endif +typedef struct { + UCHAR TLVType; + USHORT TLVLength; + CHAR rssi; + SHORT ecio; +} __attribute__ ((packed)) QMINAS_SIG_INFO_CDMA_TLV_MSG, *PQMINAS_SIG_INFO_CDMA_TLV_MSG; + +typedef struct { + UCHAR TLVType; + USHORT TLVLength; + CHAR rssi; + SHORT ecio; + CHAR sinr; + INT io; +} __attribute__ ((packed)) QMINAS_SIG_INFO_HDR_TLV_MSG, *PQMINAS_SIG_INFO_HDR_TLV_MSG; + +typedef struct { + UCHAR TLVType; + USHORT TLVLength; + CHAR rssi; +} __attribute__ ((packed)) QMINAS_SIG_INFO_GSM_TLV_MSG, *PQMINAS_SIG_INFO_GSM_TLV_MSG; + +typedef struct { + UCHAR TLVType; + USHORT TLVLength; + CHAR rssi; + SHORT ecio; +} __attribute__ ((packed)) QMINAS_SIG_INFO_WCDMA_TLV_MSG, *PQMINAS_SIG_INFO_WCDMA_TLV_MSG; + +typedef struct { + UCHAR TLVType; + USHORT TLVLength; + CHAR rssi; + CHAR rsrq; + SHORT rsrp; + SHORT snr; +} __attribute__ ((packed)) QMINAS_SIG_INFO_LTE_TLV_MSG, *PQMINAS_SIG_INFO_LTE_TLV_MSG; + +typedef struct { + UCHAR TLVType; + USHORT TLVLength; + CHAR rscp; +} __attribute__ ((packed)) QMINAS_SIG_INFO_TDSCDMA_TLV_MSG, *PQMINAS_SIG_INFO_TDSCDMA_TLV_MSG; +// ======================= End of NAS ============================== + +// ======================= UIM ============================== +#define QMIUIM_READ_TRANSPARENT_REQ 0x0020 +#define QMIUIM_READ_TRANSPARENT_RESP 0x0020 +#define QMIUIM_READ_TRANSPARENT_IND 0x0020 +#define QMIUIM_READ_RECORD_REQ 0x0021 +#define QMIUIM_READ_RECORD_RESP 0x0021 +#define QMIUIM_READ_RECORD_IND 0x0021 +#define QMIUIM_WRITE_TRANSPARENT_REQ 0x0022 +#define QMIUIM_WRITE_TRANSPARENT_RESP 0x0022 +#define QMIUIM_WRITE_TRANSPARENT_IND 0x0022 +#define QMIUIM_WRITE_RECORD_REQ 0x0023 +#define QMIUIM_WRITE_RECORD_RESP 0x0023 +#define QMIUIM_WRITE_RECORD_IND 0x0023 +#define QMIUIM_SET_PIN_PROTECTION_REQ 0x0025 +#define QMIUIM_SET_PIN_PROTECTION_RESP 0x0025 +#define QMIUIM_SET_PIN_PROTECTION_IND 0x0025 +#define QMIUIM_VERIFY_PIN_REQ 0x0026 +#define QMIUIM_VERIFY_PIN_RESP 0x0026 +#define QMIUIM_VERIFY_PIN_IND 0x0026 +#define QMIUIM_UNBLOCK_PIN_REQ 0x0027 +#define QMIUIM_UNBLOCK_PIN_RESP 0x0027 +#define QMIUIM_UNBLOCK_PIN_IND 0x0027 +#define QMIUIM_CHANGE_PIN_REQ 0x0028 +#define QMIUIM_CHANGE_PIN_RESP 0x0028 +#define QMIUIM_CHANGE_PIN_IND 0x0028 +#define QMIUIM_DEPERSONALIZATION_REQ 0x0029 +#define QMIUIM_DEPERSONALIZATION_RESP 0x0029 +#define QMIUIM_EVENT_REG_REQ 0x002E +#define QMIUIM_EVENT_REG_RESP 0x002E +#define QMIUIM_GET_CARD_STATUS_REQ 0x002F +#define QMIUIM_GET_CARD_STATUS_RESP 0x002F +#define QMIUIM_STATUS_CHANGE_IND 0x0032 + + +typedef struct _QMIUIM_GET_CARD_STATUS_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} __attribute__ ((packed)) QMIUIM_GET_CARD_STATUS_RESP_MSG, *PQMIUIM_GET_CARD_STATUS_RESP_MSG; + +#define UIM_CARD_STATE_ABSENT 0x00 +#define UIM_CARD_STATE_PRESENT 0x01 +#define UIM_CARD_STATE_ERROR 0x02 + +typedef struct _QMIUIM_CARD_STATUS +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT IndexGWPri; + USHORT Index1XPri; + USHORT IndexGWSec; + USHORT Index1XSec; + UCHAR NumSlot; + UCHAR CardState; + UCHAR UPINState; + UCHAR UPINRetries; + UCHAR UPUKRetries; + UCHAR ErrorCode; + UCHAR NumApp; + UCHAR AppType; + UCHAR AppState; + UCHAR PersoState; + UCHAR PersoFeature; + UCHAR PersoRetries; + UCHAR PersoUnblockRetries; + UCHAR AIDLength; +} __attribute__ ((packed)) QMIUIM_CARD_STATUS, *PQMIUIM_CARD_STATUS; + +typedef struct _QMIUIM_PIN_STATE +{ + UCHAR UnivPIN; + UCHAR PIN1State; + UCHAR PIN1Retries; + UCHAR PUK1Retries; + UCHAR PIN2State; + UCHAR PIN2Retries; + UCHAR PUK2Retries; +} __attribute__ ((packed)) QMIUIM_PIN_STATE, *PQMIUIM_PIN_STATE; + +typedef struct _QMIUIM_VERIFY_PIN_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR Session_Type; + UCHAR Aid_Len; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR PINID; + UCHAR PINLen; + UCHAR PINValue; +} __attribute__ ((packed)) QMIUIM_VERIFY_PIN_REQ_MSG, *PQMIUIM_VERIFY_PIN_REQ_MSG; + +typedef struct _QMIUIM_VERIFY_PIN_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR PINVerifyRetriesLeft; + UCHAR PINUnblockRetriesLeft; +} __attribute__ ((packed)) QMIUIM_VERIFY_PIN_RESP_MSG, *PQMIUIM_VERIFY_PIN_RESP_MSG; + +typedef struct _QMIUIM_READ_TRANSPARENT_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR Session_Type; + UCHAR Aid_Len; + UCHAR TLV2Type; + USHORT TLV2Length; + USHORT file_id; + UCHAR path_len; + UCHAR path[]; +} __attribute__ ((packed)) QMIUIM_READ_TRANSPARENT_REQ_MSG, *PQMIUIM_READ_TRANSPARENT_REQ_MSG; + +typedef struct _READ_TRANSPARENT_TLV +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT Offset; + USHORT Length; +} __attribute__ ((packed)) READ_TRANSPARENT_TLV, *PREAD_TRANSPARENT_TLV; + +typedef struct _QMIUIM_CONTENT +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT content_len; + UCHAR content[]; +} __attribute__ ((packed)) QMIUIM_CONTENT, *PQMIUIM_CONTENT; + +typedef struct _QMIUIM_READ_TRANSPARENT_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} __attribute__ ((packed)) QMIUIM_READ_TRANSPARENT_RESP_MSG, *PQMIUIM_READ_TRANSPARENT_RESP_MSG; + +typedef struct _QMUX_MSG +{ + QCQMUX_HDR QMUXHdr; + union + { + // Message Header + QCQMUX_MSG_HDR QMUXMsgHdr; + QCQMUX_MSG_HDR_RESP QMUXMsgHdrResp; + + // QMIWDS Message +#if 0 + QMIWDS_GET_PKT_SRVC_STATUS_REQ_MSG PacketServiceStatusReq; + QMIWDS_GET_PKT_SRVC_STATUS_RESP_MSG PacketServiceStatusRsp; + QMIWDS_GET_PKT_SRVC_STATUS_IND_MSG PacketServiceStatusInd; + QMIWDS_EVENT_REPORT_IND_MSG EventReportInd; + QMIWDS_GET_CURRENT_CHANNEL_RATE_REQ_MSG GetCurrChannelRateReq; + QMIWDS_GET_CURRENT_CHANNEL_RATE_RESP_MSG GetCurrChannelRateRsp; + QMIWDS_GET_PKT_STATISTICS_REQ_MSG GetPktStatsReq; + QMIWDS_GET_PKT_STATISTICS_RESP_MSG GetPktStatsRsp; + QMIWDS_SET_EVENT_REPORT_REQ_MSG EventReportReq; + QMIWDS_SET_EVENT_REPORT_RESP_MSG EventReportRsp; +#endif + //#ifdef QC_IP_MODE + QMIWDS_GET_RUNTIME_SETTINGS_REQ_MSG GetRuntimeSettingsReq; + QMIWDS_GET_RUNTIME_SETTINGS_RESP_MSG GetRuntimeSettingsRsp; + //#endif // QC_IP_MODE + QMIWDS_SET_CLIENT_IP_FAMILY_PREF_REQ_MSG SetClientIpFamilyPrefReq; + QMIWDS_SET_CLIENT_IP_FAMILY_PREF_RESP_MSG SetClientIpFamilyPrefResp; + QMIWDS_SET_AUTO_CONNECT_REQ_MSG SetAutoConnectReq; +#if 0 + QMIWDS_GET_MIP_MODE_REQ_MSG GetMipModeReq; + QMIWDS_GET_MIP_MODE_RESP_MSG GetMipModeResp; +#endif + QMIWDS_START_NETWORK_INTERFACE_REQ_MSG StartNwInterfaceReq; + QMIWDS_START_NETWORK_INTERFACE_RESP_MSG StartNwInterfaceResp; + QMIWDS_STOP_NETWORK_INTERFACE_REQ_MSG StopNwInterfaceReq; + QMIWDS_STOP_NETWORK_INTERFACE_RESP_MSG StopNwInterfaceResp; + QMIWDS_GET_DEFAULT_SETTINGS_REQ_MSG GetDefaultSettingsReq; + QMIWDS_GET_DEFAULT_SETTINGS_RESP_MSG GetDefaultSettingsResp; + QMIWDS_MODIFY_PROFILE_SETTINGS_REQ_MSG ModifyProfileSettingsReq; + QMIWDS_MODIFY_PROFILE_SETTINGS_RESP_MSG ModifyProfileSettingsResp; + QMIWDS_GET_PROFILE_SETTINGS_REQ_MSG GetProfileSettingsReq; +#if 0 + QMIWDS_GET_DATA_BEARER_REQ_MSG GetDataBearerReq; + QMIWDS_GET_DATA_BEARER_RESP_MSG GetDataBearerResp; + QMIWDS_DUN_CALL_INFO_REQ_MSG DunCallInfoReq; + QMIWDS_DUN_CALL_INFO_RESP_MSG DunCallInfoResp; +#endif + QMIWDS_BIND_MUX_DATA_PORT_REQ_MSG BindMuxDataPortReq; + + // QMIDMS Messages +#if 0 + QMIDMS_GET_DEVICE_MFR_REQ_MSG GetDeviceMfrReq; + QMIDMS_GET_DEVICE_MFR_RESP_MSG GetDeviceMfrRsp; + QMIDMS_GET_DEVICE_MODEL_ID_REQ_MSG GetDeviceModeIdReq; + QMIDMS_GET_DEVICE_MODEL_ID_RESP_MSG GetDeviceModeIdRsp; + QMIDMS_GET_DEVICE_REV_ID_REQ_MSG GetDeviceRevIdReq; + QMIDMS_GET_DEVICE_REV_ID_RESP_MSG GetDeviceRevIdRsp; + QMIDMS_GET_MSISDN_REQ_MSG GetMsisdnReq; + QMIDMS_GET_MSISDN_RESP_MSG GetMsisdnRsp; + QMIDMS_GET_DEVICE_SERIAL_NUMBERS_REQ_MSG GetDeviceSerialNumReq; + QMIDMS_GET_DEVICE_SERIAL_NUMBERS_RESP_MSG GetDeviceSerialNumRsp; + QMIDMS_GET_DEVICE_CAP_REQ_MSG GetDeviceCapReq; + QMIDMS_GET_DEVICE_CAP_RESP_MSG GetDeviceCapResp; + QMIDMS_GET_BAND_CAP_REQ_MSG GetBandCapReq; + QMIDMS_GET_BAND_CAP_RESP_MSG GetBandCapRsp; + QMIDMS_GET_ACTIVATED_STATUS_REQ_MSG GetActivatedStatusReq; + QMIDMS_GET_ACTIVATED_STATUS_RESP_MSG GetActivatedStatusResp; + QMIDMS_GET_OPERATING_MODE_REQ_MSG GetOperatingModeReq; + QMIDMS_GET_OPERATING_MODE_RESP_MSG GetOperatingModeResp; +#endif + QMIDMS_SET_OPERATING_MODE_REQ_MSG SetOperatingModeReq; + QMIDMS_SET_OPERATING_MODE_RESP_MSG SetOperatingModeResp; +#if 0 + QMIDMS_UIM_GET_ICCID_REQ_MSG GetICCIDReq; + QMIDMS_UIM_GET_ICCID_RESP_MSG GetICCIDResp; + QMIDMS_ACTIVATE_AUTOMATIC_REQ_MSG ActivateAutomaticReq; + QMIDMS_ACTIVATE_AUTOMATIC_RESP_MSG ActivateAutomaticResp; + QMIDMS_ACTIVATE_MANUAL_REQ_MSG ActivateManualReq; + QMIDMS_ACTIVATE_MANUAL_RESP_MSG ActivateManualResp; +#endif + QMIDMS_UIM_GET_PIN_STATUS_REQ_MSG UIMGetPinStatusReq; + QMIDMS_UIM_GET_PIN_STATUS_RESP_MSG UIMGetPinStatusResp; + QMIDMS_UIM_VERIFY_PIN_REQ_MSG UIMVerifyPinReq; + QMIDMS_UIM_VERIFY_PIN_RESP_MSG UIMVerifyPinResp; +#if 0 + QMIDMS_UIM_SET_PIN_PROTECTION_REQ_MSG UIMSetPinProtectionReq; + QMIDMS_UIM_SET_PIN_PROTECTION_RESP_MSG UIMSetPinProtectionResp; + QMIDMS_UIM_CHANGE_PIN_REQ_MSG UIMChangePinReq; + QMIDMS_UIM_CHANGE_PIN_RESP_MSG UIMChangePinResp; + QMIDMS_UIM_UNBLOCK_PIN_REQ_MSG UIMUnblockPinReq; + QMIDMS_UIM_UNBLOCK_PIN_RESP_MSG UIMUnblockPinResp; + QMIDMS_SET_EVENT_REPORT_REQ_MSG DmsSetEventReportReq; + QMIDMS_SET_EVENT_REPORT_RESP_MSG DmsSetEventReportResp; + QMIDMS_EVENT_REPORT_IND_MSG DmsEventReportInd; +#endif + QMIDMS_UIM_GET_STATE_REQ_MSG UIMGetStateReq; + QMIDMS_UIM_GET_STATE_RESP_MSG UIMGetStateResp; + QMIDMS_UIM_GET_IMSI_REQ_MSG UIMGetIMSIReq; + QMIDMS_UIM_GET_IMSI_RESP_MSG UIMGetIMSIResp; +#if 0 + QMIDMS_UIM_GET_CK_STATUS_REQ_MSG UIMGetCkStatusReq; + QMIDMS_UIM_GET_CK_STATUS_RESP_MSG UIMGetCkStatusResp; + QMIDMS_UIM_SET_CK_PROTECTION_REQ_MSG UIMSetCkProtectionReq; + QMIDMS_UIM_SET_CK_PROTECTION_RESP_MSG UIMSetCkProtectionResp; + QMIDMS_UIM_UNBLOCK_CK_REQ_MSG UIMUnblockCkReq; + QMIDMS_UIM_UNBLOCK_CK_RESP_MSG UIMUnblockCkResp; +#endif + + // QMIQOS Messages +#if 0 + QMI_QOS_SET_EVENT_REPORT_REQ_MSG QosSetEventReportReq; + QMI_QOS_SET_EVENT_REPORT_RESP_MSG QosSetEventReportRsp; + QMI_QOS_EVENT_REPORT_IND_MSG QosEventReportInd; +#endif + + // QMIWMS Messages +#if 0 + QMIWMS_GET_MESSAGE_PROTOCOL_REQ_MSG GetMessageProtocolReq; + QMIWMS_GET_MESSAGE_PROTOCOL_RESP_MSG GetMessageProtocolResp; + QMIWMS_GET_SMSC_ADDRESS_REQ_MSG GetSMSCAddressReq; + QMIWMS_GET_SMSC_ADDRESS_RESP_MSG GetSMSCAddressResp; + QMIWMS_SET_SMSC_ADDRESS_REQ_MSG SetSMSCAddressReq; + QMIWMS_SET_SMSC_ADDRESS_RESP_MSG SetSMSCAddressResp; + QMIWMS_GET_STORE_MAX_SIZE_REQ_MSG GetStoreMaxSizeReq; + QMIWMS_GET_STORE_MAX_SIZE_RESP_MSG GetStoreMaxSizeResp; + QMIWMS_LIST_MESSAGES_REQ_MSG ListMessagesReq; + QMIWMS_LIST_MESSAGES_RESP_MSG ListMessagesResp; + QMIWMS_RAW_READ_REQ_MSG RawReadMessagesReq; + QMIWMS_RAW_READ_RESP_MSG RawReadMessagesResp; + QMIWMS_SET_EVENT_REPORT_REQ_MSG WmsSetEventReportReq; + QMIWMS_SET_EVENT_REPORT_RESP_MSG WmsSetEventReportResp; + QMIWMS_EVENT_REPORT_IND_MSG WmsEventReportInd; + QMIWMS_DELETE_REQ_MSG WmsDeleteReq; + QMIWMS_DELETE_RESP_MSG WmsDeleteResp; + QMIWMS_RAW_SEND_REQ_MSG RawSendMessagesReq; + QMIWMS_RAW_SEND_RESP_MSG RawSendMessagesResp; + QMIWMS_MODIFY_TAG_REQ_MSG WmsModifyTagReq; + QMIWMS_MODIFY_TAG_RESP_MSG WmsModifyTagResp; +#endif + + // QMINAS Messages +#if 0 + QMINAS_GET_HOME_NETWORK_REQ_MSG GetHomeNetworkReq; + QMINAS_GET_HOME_NETWORK_RESP_MSG GetHomeNetworkResp; + QMINAS_GET_PREFERRED_NETWORK_REQ_MSG GetPreferredNetworkReq; + QMINAS_GET_PREFERRED_NETWORK_RESP_MSG GetPreferredNetworkResp; + QMINAS_GET_FORBIDDEN_NETWORK_REQ_MSG GetForbiddenNetworkReq; + QMINAS_GET_FORBIDDEN_NETWORK_RESP_MSG GetForbiddenNetworkResp; + QMINAS_GET_SERVING_SYSTEM_REQ_MSG GetServingSystemReq; +#endif + QMINAS_GET_SERVING_SYSTEM_RESP_MSG GetServingSystemResp; + QMINAS_GET_SYS_INFO_RESP_MSG GetSysInfoResp; + QMINAS_SYS_INFO_IND_MSG NasSysInfoInd; +#if 0 + QMINAS_SERVING_SYSTEM_IND_MSG NasServingSystemInd; + QMINAS_SET_PREFERRED_NETWORK_REQ_MSG SetPreferredNetworkReq; + QMINAS_SET_PREFERRED_NETWORK_RESP_MSG SetPreferredNetworkResp; + QMINAS_SET_FORBIDDEN_NETWORK_REQ_MSG SetForbiddenNetworkReq; + QMINAS_SET_FORBIDDEN_NETWORK_RESP_MSG SetForbiddenNetworkResp; + QMINAS_PERFORM_NETWORK_SCAN_REQ_MSG PerformNetworkScanReq; + QMINAS_PERFORM_NETWORK_SCAN_RESP_MSG PerformNetworkScanResp; + QMINAS_INITIATE_NW_REGISTER_REQ_MSG InitiateNwRegisterReq; + QMINAS_INITIATE_NW_REGISTER_RESP_MSG InitiateNwRegisterResp; + QMINAS_SET_TECHNOLOGY_PREF_REQ_MSG SetTechnologyPrefReq; + QMINAS_SET_TECHNOLOGY_PREF_RESP_MSG SetTechnologyPrefResp; + QMINAS_GET_SIGNAL_STRENGTH_REQ_MSG GetSignalStrengthReq; + QMINAS_GET_SIGNAL_STRENGTH_RESP_MSG GetSignalStrengthResp; + QMINAS_SET_EVENT_REPORT_REQ_MSG SetEventReportReq; + QMINAS_SET_EVENT_REPORT_RESP_MSG SetEventReportResp; + QMINAS_EVENT_REPORT_IND_MSG NasEventReportInd; + QMINAS_GET_RF_BAND_INFO_REQ_MSG GetRFBandInfoReq; + QMINAS_GET_RF_BAND_INFO_RESP_MSG GetRFBandInfoResp; + QMINAS_INITIATE_ATTACH_REQ_MSG InitiateAttachReq; + QMINAS_INITIATE_ATTACH_RESP_MSG InitiateAttachResp; + QMINAS_GET_PLMN_NAME_REQ_MSG GetPLMNNameReq; + QMINAS_GET_PLMN_NAME_RESP_MSG GetPLMNNameResp; +#endif + + // QMIUIM Messages + QMIUIM_GET_CARD_STATUS_RESP_MSG UIMGetCardStatus; + QMIUIM_VERIFY_PIN_REQ_MSG UIMUIMVerifyPinReq; + QMIUIM_VERIFY_PIN_RESP_MSG UIMUIMVerifyPinResp; +#if 0 + QMIUIM_SET_PIN_PROTECTION_REQ_MSG UIMUIMSetPinProtectionReq; + QMIUIM_SET_PIN_PROTECTION_RESP_MSG UIMUIMSetPinProtectionResp; + QMIUIM_CHANGE_PIN_REQ_MSG UIMUIMChangePinReq; + QMIUIM_CHANGE_PIN_RESP_MSG UIMUIMChangePinResp; + QMIUIM_UNBLOCK_PIN_REQ_MSG UIMUIMUnblockPinReq; + QMIUIM_UNBLOCK_PIN_RESP_MSG UIMUIMUnblockPinResp; +#endif + QMIUIM_READ_TRANSPARENT_REQ_MSG UIMUIMReadTransparentReq; + QMIUIM_READ_TRANSPARENT_RESP_MSG UIMUIMReadTransparentResp; + + QMIWDS_ADMIN_SET_DATA_FORMAT_REQ_MSG SetDataFormatReq; + QMI_WDA_SET_LOOPBACK_CONFIG_REQ_MSG SetLoopBackReq; + QMI_WDA_SET_LOOPBACK_CONFIG_IND_MSG SetLoopBackInd; + }; +} __attribute__ ((packed)) QMUX_MSG, *PQMUX_MSG; + +#pragma pack(pop) + +#endif // MPQMUX_H diff --git a/root/package/link4all/quectel-CM/src/Makefile b/root/package/link4all/quectel-CM/src/Makefile new file mode 100644 index 00000000..55974625 --- /dev/null +++ b/root/package/link4all/quectel-CM/src/Makefile @@ -0,0 +1,37 @@ +# ifneq ($(CROSS_COMPILE),) +# CROSS-COMPILE:=$(CROSS_COMPILE) +# endif +# #CROSS-COMPILE:=/workspace/buildroot/buildroot-qemu_mips_malta_defconfig/output/host/usr/bin/mips-buildroot-linux-uclibc- +# #CROSS-COMPILE:=/workspace/buildroot/buildroot-qemu_arm_vexpress_defconfig/output/host/usr/bin/arm-buildroot-linux-uclibcgnueabi- +# #CROSS-COMPILE:=/workspace/buildroot-git/qemu_mips64_malta/output/host/usr/bin/mips-gnu-linux- +# ifeq ($(CC),cc) +# CC:=$(CROSS-COMPILE)gcc +# endif +# LD:=$(CROSS-COMPILE)ld + +QL_CM_SRC=QmiWwanCM.c GobiNetCM.c main.c MPQMUX.c QMIThread.c util.c qmap_bridge_mode.c mbim-cm.c device.c +# ifeq (1,1) +QL_CM_DHCP=udhcpc.c +# else +# LIBMNL=libmnl/ifutils.c libmnl/attr.c libmnl/callback.c libmnl/nlmsg.c libmnl/socket.c +# DHCP=libmnl/dhcp/dhcpclient.c libmnl/dhcp/dhcpmsg.c libmnl/dhcp/packet.c +# QL_CM_DHCP=udhcpc_netlink.c +# QL_CM_DHCP+=${LIBMNL} +# endif + +CFLAGS+=-Wall -Wextra -Wfatal-errors -Wunused -Werror #-Wformat-truncation=2 -Wformat-overflow=2 + +all: clean + $(CC) ${CFLAGS} -s ${QL_CM_SRC} ${QL_CM_DHCP} -o quectel-CM -lpthread -luci + +debug: clean + $(CC) ${CFLAGS} -g -DCM_DEBUG ${QL_CM_SRC} ${QL_CM_DHCP} -o quectel-CM -lpthread -luci + +qmi-proxy: + $(CC) ${CFLAGS} -s quectel-qmi-proxy.c -o quectel-qmi-proxy -lpthread + +mbim-proxy: + $(CC) ${CFLAGS} -s quectel-mbim-proxy.c -o quectel-mbim-proxy -lpthread + +clean: + rm -rf *.o libmnl/*.o quectel-CM quectel-qmi-proxy quectel-mbim-proxy diff --git a/root/package/link4all/quectel-CM/src/NOTICE b/root/package/link4all/quectel-CM/src/NOTICE new file mode 100644 index 00000000..0a062cf0 --- /dev/null +++ b/root/package/link4all/quectel-CM/src/NOTICE @@ -0,0 +1,7 @@ +This program is totally open souce code, and public domain software for customers of Quectel company. + +The APIs of QMI WWAMN interfaces are defined by Qualcomm. And this program complies with Qualcomm QMI WWAN interfaces specification. + +Customers are free to modify the source codes and redistribute them. + +For those who is not Quectel's customer, all rights are closed, and any copying and commercial development over this progrma is not allowed. diff --git a/root/package/link4all/quectel-CM/src/QMIThread.c b/root/package/link4all/quectel-CM/src/QMIThread.c new file mode 100644 index 00000000..2ed00f4e --- /dev/null +++ b/root/package/link4all/quectel-CM/src/QMIThread.c @@ -0,0 +1,2196 @@ +/****************************************************************************** + @file QMIThread.c + @brief QMI WWAN connectivity manager. + + DESCRIPTION + Connectivity Management Tool for USB network adapter of Quectel wireless cellular modules. + + INITIALIZATION AND SEQUENCING REQUIREMENTS + None. + + --------------------------------------------------------------------------- + Copyright (c) 2016 - 2020 Quectel Wireless Solution, Co., Ltd. All Rights Reserved. + Quectel Wireless Solution Proprietary and Confidential. + --------------------------------------------------------------------------- +******************************************************************************/ +#include "QMIThread.h" +#ifndef MIN +#define MIN(a, b) ((a) < (b)? (a): (b)) +#endif + +extern char *strndup (const char *__string, size_t __n); + +#define qmi_rsp_check_and_return() do { \ + if (err < 0 || pResponse == NULL) { \ + dbg_time("%s err = %d", __func__, err); \ + return err; \ + } \ + pMUXMsg = &pResponse->MUXMsg; \ + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { \ + USHORT QMUXError = le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); \ + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, \ + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), QMUXError); \ + free(pResponse); \ + return QMUXError; \ + } \ +} while(0) + +#define qmi_rsp_check() do { \ + if (err < 0 || pResponse == NULL) { \ + dbg_time("%s err = %d", __func__, err); \ + return err; \ + } \ + pMUXMsg = &pResponse->MUXMsg; \ + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { \ + USHORT QMUXError = le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); \ + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, \ + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), QMUXError); \ + } \ +} while(0) + +int qmiclientId[QMUX_TYPE_WDS_ADMIN + 1]; //GobiNet use fd to indicate client ID, so type of qmiclientId must be int +static uint32_t WdsConnectionIPv4Handle = 0; +static uint32_t WdsConnectionIPv6Handle = 0; +static int s_is_cdma = 0; +static int s_hdr_personality = 0; // 0x01-HRPD, 0x02-eHRPD +static char *qstrcpy(char *to, const char *from) { //no __strcpy_chk + char *save = to; + for (; (*to = *from) != '\0'; ++from, ++to); + return(save); +} + +static int s_9x07 = -1; + +typedef USHORT (*CUSTOMQMUX)(PQMUX_MSG pMUXMsg, void *arg); + +// To retrieve the ith (Index) TLV +PQMI_TLV_HDR GetTLV (PQCQMUX_MSG_HDR pQMUXMsgHdr, int TLVType) { + int TLVFind = 0; + USHORT Length = le16_to_cpu(pQMUXMsgHdr->Length); + PQMI_TLV_HDR pTLVHdr = (PQMI_TLV_HDR)(pQMUXMsgHdr + 1); + + while (Length >= sizeof(QMI_TLV_HDR)) { + TLVFind++; + if (TLVType > 0x1000) { + if ((TLVFind + 0x1000) == TLVType) + return pTLVHdr; + } else if (pTLVHdr->TLVType == TLVType) { + return pTLVHdr; + } + + Length -= (le16_to_cpu((pTLVHdr->TLVLength)) + sizeof(QMI_TLV_HDR)); + pTLVHdr = (PQMI_TLV_HDR)(((UCHAR *)pTLVHdr) + le16_to_cpu(pTLVHdr->TLVLength) + sizeof(QMI_TLV_HDR)); + } + + return NULL; +} + +static USHORT GetQMUXTransactionId(void) { + static int TransactionId = 0; + if (++TransactionId > 0xFFFF) + TransactionId = 1; + return TransactionId; +} + +static PQCQMIMSG ComposeQMUXMsg(UCHAR QMIType, USHORT Type, CUSTOMQMUX customQmuxMsgFunction, void *arg) { + UCHAR QMIBuf[WDM_DEFAULT_BUFSIZE]; + PQCQMIMSG pRequest = (PQCQMIMSG)QMIBuf; + int Length; + + memset(QMIBuf, 0x00, sizeof(QMIBuf)); + pRequest->QMIHdr.IFType = USB_CTL_MSG_TYPE_QMI; + pRequest->QMIHdr.CtlFlags = 0x00; + pRequest->QMIHdr.QMIType = QMIType; + pRequest->QMIHdr.ClientId = qmiclientId[QMIType] & 0xFF; + + if (qmiclientId[QMIType] == 0) { + dbg_time("QMIType %d has no clientID", QMIType); + return NULL; + } + + pRequest->MUXMsg.QMUXHdr.CtlFlags = QMUX_CTL_FLAG_SINGLE_MSG | QMUX_CTL_FLAG_TYPE_CMD; + pRequest->MUXMsg.QMUXHdr.TransactionId = cpu_to_le16(GetQMUXTransactionId()); + pRequest->MUXMsg.QMUXMsgHdr.Type = cpu_to_le16(Type); + if (customQmuxMsgFunction) + pRequest->MUXMsg.QMUXMsgHdr.Length = cpu_to_le16(customQmuxMsgFunction(&pRequest->MUXMsg, arg) - sizeof(QCQMUX_MSG_HDR)); + else + pRequest->MUXMsg.QMUXMsgHdr.Length = cpu_to_le16(0x0000); + + pRequest->QMIHdr.Length = cpu_to_le16(le16_to_cpu(pRequest->MUXMsg.QMUXMsgHdr.Length) + sizeof(QCQMUX_MSG_HDR) + sizeof(QCQMUX_HDR) + + sizeof(QCQMI_HDR) - 1); + Length = le16_to_cpu(pRequest->QMIHdr.Length) + 1; + + pRequest = (PQCQMIMSG)malloc(Length); + if (pRequest == NULL) { + dbg_time("%s fail to malloc", __func__); + } else { + memcpy(pRequest, QMIBuf, Length); + } + + return pRequest; +} + +#if 0 +static USHORT NasSetEventReportReq(PQMUX_MSG pMUXMsg, void *arg) { + pMUXMsg->SetEventReportReq.TLVType = 0x10; + pMUXMsg->SetEventReportReq.TLVLength = 0x04; + pMUXMsg->SetEventReportReq.ReportSigStrength = 0x00; + pMUXMsg->SetEventReportReq.NumTresholds = 2; + pMUXMsg->SetEventReportReq.TresholdList[0] = -113; + pMUXMsg->SetEventReportReq.TresholdList[1] = -50; + return sizeof(QMINAS_SET_EVENT_REPORT_REQ_MSG); +} + +static USHORT WdsSetEventReportReq(PQMUX_MSG pMUXMsg, void *arg) { + pMUXMsg->EventReportReq.TLVType = 0x10; // 0x10 -- current channel rate indicator + pMUXMsg->EventReportReq.TLVLength = 0x0001; // 1 + pMUXMsg->EventReportReq.Mode = 0x00; // 0-do not report; 1-report when rate changes + + pMUXMsg->EventReportReq.TLV2Type = 0x11; // 0x11 + pMUXMsg->EventReportReq.TLV2Length = 0x0005; // 5 + pMUXMsg->EventReportReq.StatsPeriod = 0x00; // seconds between reports; 0-do not report + pMUXMsg->EventReportReq.StatsMask = 0x000000ff; // + + pMUXMsg->EventReportReq.TLV3Type = 0x12; // 0x12 -- current data bearer indicator + pMUXMsg->EventReportReq.TLV3Length = 0x0001; // 1 + pMUXMsg->EventReportReq.Mode3 = 0x01; // 0-do not report; 1-report when changes + + pMUXMsg->EventReportReq.TLV4Type = 0x13; // 0x13 -- dormancy status indicator + pMUXMsg->EventReportReq.TLV4Length = 0x0001; // 1 + pMUXMsg->EventReportReq.DormancyStatus = 0x00; // 0-do not report; 1-report when changes + return sizeof(QMIWDS_SET_EVENT_REPORT_REQ_MSG); +} + +static USHORT DmsSetEventReportReq(PQMUX_MSG pMUXMsg) { + PPIN_STATUS pPinState = (PPIN_STATUS)(&pMUXMsg->DmsSetEventReportReq + 1); + PUIM_STATE pUimState = (PUIM_STATE)(pPinState + 1); + // Pin State + pPinState->TLVType = 0x12; + pPinState->TLVLength = 0x01; + pPinState->ReportPinState = 0x01; + // UIM State + pUimState->TLVType = 0x15; + pUimState->TLVLength = 0x01; + pUimState->UIMState = 0x01; + return sizeof(QMIDMS_SET_EVENT_REPORT_REQ_MSG) + sizeof(PIN_STATUS) + sizeof(UIM_STATE); +} +#endif + +static USHORT WdsStartNwInterfaceReq(PQMUX_MSG pMUXMsg, void *arg) { + PQMIWDS_TECHNOLOGY_PREFERECE pTechPref; + PQMIWDS_AUTH_PREFERENCE pAuthPref; + PQMIWDS_USERNAME pUserName; + PQMIWDS_PASSWD pPasswd; + PQMIWDS_APNNAME pApnName; + PQMIWDS_IP_FAMILY_TLV pIpFamily; + USHORT TLVLength = 0; + UCHAR *pTLV; + PROFILE_T *profile = (PROFILE_T *)arg; + const char *profile_user = profile->user; + const char *profile_password = profile->password; + int profile_auth = profile->auth; + + if (s_is_cdma && (profile_user == NULL || profile_user[0] == '\0') && (profile_password == NULL || profile_password[0] == '\0')) { + profile_user = "ctnet@mycdma.cn"; + profile_password = "vnet.mobi"; + profile_auth = 2; //chap + } + + pTLV = (UCHAR *)(&pMUXMsg->StartNwInterfaceReq + 1); + pMUXMsg->StartNwInterfaceReq.Length = 0; + + // Set technology Preferece + pTechPref = (PQMIWDS_TECHNOLOGY_PREFERECE)(pTLV + TLVLength); + pTechPref->TLVType = 0x30; + pTechPref->TLVLength = cpu_to_le16(0x01); + if (s_is_cdma == 0) + pTechPref->TechPreference = 0x01; + else + pTechPref->TechPreference = 0x02; + TLVLength +=(le16_to_cpu(pTechPref->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + + // Set APN Name + if (profile->apn && !s_is_cdma) { //cdma no apn + pApnName = (PQMIWDS_APNNAME)(pTLV + TLVLength); + pApnName->TLVType = 0x14; + pApnName->TLVLength = cpu_to_le16(strlen(profile->apn)); + qstrcpy((char *)&pApnName->ApnName, profile->apn); + TLVLength +=(le16_to_cpu(pApnName->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + // Set User Name + if (profile_user) { + pUserName = (PQMIWDS_USERNAME)(pTLV + TLVLength); + pUserName->TLVType = 0x17; + pUserName->TLVLength = cpu_to_le16(strlen(profile_user)); + qstrcpy((char *)&pUserName->UserName, profile_user); + TLVLength += (le16_to_cpu(pUserName->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + // Set Password + if (profile_password) { + pPasswd = (PQMIWDS_PASSWD)(pTLV + TLVLength); + pPasswd->TLVType = 0x18; + pPasswd->TLVLength = cpu_to_le16(strlen(profile_password)); + qstrcpy((char *)&pPasswd->Passwd, profile_password); + TLVLength += (le16_to_cpu(pPasswd->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + // Set Auth Protocol + if (profile_user && profile_password) { + pAuthPref = (PQMIWDS_AUTH_PREFERENCE)(pTLV + TLVLength); + pAuthPref->TLVType = 0x16; + pAuthPref->TLVLength = cpu_to_le16(0x01); + pAuthPref->AuthPreference = profile_auth; // 0 ~ None, 1 ~ Pap, 2 ~ Chap, 3 ~ MsChapV2 + TLVLength += (le16_to_cpu(pAuthPref->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + // Add IP Family Preference + pIpFamily = (PQMIWDS_IP_FAMILY_TLV)(pTLV + TLVLength); + pIpFamily->TLVType = 0x19; + pIpFamily->TLVLength = cpu_to_le16(0x01); + pIpFamily->IpFamily = profile->curIpFamily; + TLVLength += (le16_to_cpu(pIpFamily->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + + //Set Profile Index + if (profile->pdp && !s_is_cdma) { //cdma only support one pdp, so no need to set profile index + PQMIWDS_PROFILE_IDENTIFIER pProfileIndex = (PQMIWDS_PROFILE_IDENTIFIER)(pTLV + TLVLength); + pProfileIndex->TLVLength = cpu_to_le16(0x01); + pProfileIndex->TLVType = 0x31; + pProfileIndex->ProfileIndex = profile->pdp; + if (s_is_cdma && s_hdr_personality == 0x02) { + pProfileIndex->TLVType = 0x32; //profile_index_3gpp2 + pProfileIndex->ProfileIndex = 101; + } + TLVLength += (le16_to_cpu(pProfileIndex->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + return sizeof(QMIWDS_START_NETWORK_INTERFACE_REQ_MSG) + TLVLength; +} + +static USHORT WdsStopNwInterfaceReq(PQMUX_MSG pMUXMsg, void *arg) { + pMUXMsg->StopNwInterfaceReq.TLVType = 0x01; + pMUXMsg->StopNwInterfaceReq.TLVLength = cpu_to_le16(0x04); + if (*((int *)arg) == IpFamilyV4) + pMUXMsg->StopNwInterfaceReq.Handle = cpu_to_le32(WdsConnectionIPv4Handle); + else + pMUXMsg->StopNwInterfaceReq.Handle = cpu_to_le32(WdsConnectionIPv6Handle); + return sizeof(QMIWDS_STOP_NETWORK_INTERFACE_REQ_MSG); +} + +static USHORT WdsSetClientIPFamilyPref(PQMUX_MSG pMUXMsg, void *arg) { + pMUXMsg->SetClientIpFamilyPrefReq.TLVType = 0x01; + pMUXMsg->SetClientIpFamilyPrefReq.TLVLength = cpu_to_le16(0x01); + pMUXMsg->SetClientIpFamilyPrefReq.IpPreference = *((UCHAR *)arg); + return sizeof(QMIWDS_SET_CLIENT_IP_FAMILY_PREF_REQ_MSG); +} + +static USHORT WdsSetAutoConnect(PQMUX_MSG pMUXMsg, void *arg) { + pMUXMsg->SetAutoConnectReq.TLVType = 0x01; + pMUXMsg->SetAutoConnectReq.TLVLength = cpu_to_le16(0x01); + pMUXMsg->SetAutoConnectReq.autoconnect_setting = *((UCHAR *)arg); + return sizeof(QMIWDS_SET_AUTO_CONNECT_REQ_MSG); +} + +enum peripheral_ep_type { + DATA_EP_TYPE_RESERVED = 0x0, + DATA_EP_TYPE_HSIC = 0x1, + DATA_EP_TYPE_HSUSB = 0x2, + DATA_EP_TYPE_PCIE = 0x3, + DATA_EP_TYPE_EMBEDDED = 0x4, + DATA_EP_TYPE_BAM_DMUX = 0x5, +}; + +static USHORT WdsSetQMUXBindMuxDataPort(PQMUX_MSG pMUXMsg, void *arg) { + QMAP_SETTING *qmap_settings = (QMAP_SETTING *)arg; + + pMUXMsg->BindMuxDataPortReq.TLVType = 0x10; + pMUXMsg->BindMuxDataPortReq.TLVLength = cpu_to_le16(0x08); + pMUXMsg->BindMuxDataPortReq.ep_type = cpu_to_le32(qmap_settings->ep_type); + pMUXMsg->BindMuxDataPortReq.iface_id = cpu_to_le32(qmap_settings->iface_id); + pMUXMsg->BindMuxDataPortReq.TLV2Type = 0x11; + pMUXMsg->BindMuxDataPortReq.TLV2Length = cpu_to_le16(0x01); + pMUXMsg->BindMuxDataPortReq.MuxId = qmap_settings->MuxId; + pMUXMsg->BindMuxDataPortReq.TLV3Type = 0x13; + pMUXMsg->BindMuxDataPortReq.TLV3Length = cpu_to_le16(0x04); + pMUXMsg->BindMuxDataPortReq.client_type = cpu_to_le32(1); //WDS_CLIENT_TYPE_TETHERED + + return sizeof(QMIWDS_BIND_MUX_DATA_PORT_REQ_MSG); +} + +static int qmap_version = 0x05; +static USHORT WdaSetDataFormat(PQMUX_MSG pMUXMsg, void *arg) { + QMAP_SETTING *qmap_settings = (QMAP_SETTING *)arg; + + if (qmap_settings->rx_urb_size == 0) { + PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV_QOS pWdsAdminQosTlv; + PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV linkProto; + PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV dlTlp; + + pWdsAdminQosTlv = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV_QOS)(&pMUXMsg->QMUXMsgHdr + 1); + pWdsAdminQosTlv->TLVType = 0x10; + pWdsAdminQosTlv->TLVLength = cpu_to_le16(0x0001); + pWdsAdminQosTlv->QOSSetting = 0; /* no-QOS header */ + + linkProto = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV)(pWdsAdminQosTlv + 1); + linkProto->TLVType = 0x11; + linkProto->TLVLength = cpu_to_le16(4); + linkProto->Value = cpu_to_le32(0x01); /* Set Ethernet mode */ + + dlTlp = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV)(linkProto + 1);; + dlTlp->TLVType = 0x13; + dlTlp->TLVLength = cpu_to_le16(4); + dlTlp->Value = cpu_to_le32(0x00); + + if (sizeof(*linkProto) != 7 ) + dbg_time("%s sizeof(*linkProto) = %zu, is not 7!", __func__, sizeof(*linkProto) ); + + return sizeof(QCQMUX_MSG_HDR) + sizeof(*pWdsAdminQosTlv) + sizeof(*linkProto) + sizeof(*dlTlp); + } + else { + //Indicates whether the Quality of Service(QOS) data format is used by the client. + pMUXMsg->SetDataFormatReq.QosDataFormatTlv.TLVType = 0x10; + pMUXMsg->SetDataFormatReq.QosDataFormatTlv.TLVLength = cpu_to_le16(0x0001); + pMUXMsg->SetDataFormatReq.QosDataFormatTlv.QOSSetting = 0; /* no-QOS header */ + + //Underlying Link Layer Protocol + pMUXMsg->SetDataFormatReq.UnderlyingLinkLayerProtocolTlv.TLVType = 0x11; + pMUXMsg->SetDataFormatReq.UnderlyingLinkLayerProtocolTlv.TLVLength = cpu_to_le16(4); + pMUXMsg->SetDataFormatReq.UnderlyingLinkLayerProtocolTlv.Value = cpu_to_le32(0x02); /* Set IP mode */ + + //Uplink (UL) data aggregation protocol to be used for uplink data transfer. + pMUXMsg->SetDataFormatReq.UplinkDataAggregationProtocolTlv.TLVType = 0x12; + pMUXMsg->SetDataFormatReq.UplinkDataAggregationProtocolTlv.TLVLength = cpu_to_le16(4); + pMUXMsg->SetDataFormatReq.UplinkDataAggregationProtocolTlv.Value = cpu_to_le32(qmap_version); //UL QMAP is enabled + + //Downlink (DL) data aggregation protocol to be used for downlink data transfer + pMUXMsg->SetDataFormatReq.DownlinkDataAggregationProtocolTlv.TLVType = 0x13; + pMUXMsg->SetDataFormatReq.DownlinkDataAggregationProtocolTlv.TLVLength = cpu_to_le16(4); + pMUXMsg->SetDataFormatReq.DownlinkDataAggregationProtocolTlv.Value = cpu_to_le32(qmap_version); //DL QMAP is enabled + + //Maximum number of datagrams in a single aggregated packet on downlink + pMUXMsg->SetDataFormatReq.DownlinkDataAggregationMaxDatagramsTlv.TLVType = 0x15; + pMUXMsg->SetDataFormatReq.DownlinkDataAggregationMaxDatagramsTlv.TLVLength = cpu_to_le16(4); + pMUXMsg->SetDataFormatReq.DownlinkDataAggregationMaxDatagramsTlv.Value = cpu_to_le32(qmap_settings->rx_urb_size/512); + + //Maximum size in bytes of a single aggregated packet allowed on downlink + pMUXMsg->SetDataFormatReq.DownlinkDataAggregationMaxSizeTlv.TLVType = 0x16; + pMUXMsg->SetDataFormatReq.DownlinkDataAggregationMaxSizeTlv.TLVLength = cpu_to_le16(4); + pMUXMsg->SetDataFormatReq.DownlinkDataAggregationMaxSizeTlv.Value = cpu_to_le32(qmap_settings->rx_urb_size); + + //Peripheral End Point ID + pMUXMsg->SetDataFormatReq.epTlv.TLVType = 0x17; + pMUXMsg->SetDataFormatReq.epTlv.TLVLength = cpu_to_le16(8); + pMUXMsg->SetDataFormatReq.epTlv.ep_type = cpu_to_le32(qmap_settings->ep_type); + pMUXMsg->SetDataFormatReq.epTlv.iface_id = cpu_to_le32(qmap_settings->iface_id); + +#ifdef QUECTEL_UL_DATA_AGG + if (!qmap_settings->ul_data_aggregation_max_datagrams) { + return ((size_t)&((QMIWDS_ADMIN_SET_DATA_FORMAT_REQ_MSG *)0)->DlMinimumPassingTlv); + } + + //Maximum number of datagrams in a single aggregated packet on uplink + pMUXMsg->SetDataFormatReq.DlMinimumPassingTlv.TLVType = 0x19; + pMUXMsg->SetDataFormatReq.DlMinimumPassingTlv.TLVLength = cpu_to_le16(4); + pMUXMsg->SetDataFormatReq.DlMinimumPassingTlv.Value = cpu_to_le32(qmap_settings->dl_minimum_padding); + + //Maximum number of datagrams in a single aggregated packet on uplink + pMUXMsg->SetDataFormatReq.UplinkDataAggregationMaxDatagramsTlv.TLVType = 0x1B; + pMUXMsg->SetDataFormatReq.UplinkDataAggregationMaxDatagramsTlv.TLVLength = cpu_to_le16(4); + pMUXMsg->SetDataFormatReq.UplinkDataAggregationMaxDatagramsTlv.Value = cpu_to_le32(qmap_settings->ul_data_aggregation_max_datagrams); + + //Maximum size in bytes of a single aggregated packet allowed on downlink + pMUXMsg->SetDataFormatReq.UplinkDataAggregationMaxSizeTlv.TLVType = 0x1C; + pMUXMsg->SetDataFormatReq.UplinkDataAggregationMaxSizeTlv.TLVLength = cpu_to_le16(4); + pMUXMsg->SetDataFormatReq.UplinkDataAggregationMaxSizeTlv.Value = cpu_to_le32(qmap_settings->ul_data_aggregation_max_size); +#endif + + return sizeof(QMIWDS_ADMIN_SET_DATA_FORMAT_REQ_MSG); + } +} + +#ifdef CONFIG_SIM +static USHORT DmsUIMVerifyPinReqSend(PQMUX_MSG pMUXMsg, void *arg) { + pMUXMsg->UIMVerifyPinReq.TLVType = 0x01; + pMUXMsg->UIMVerifyPinReq.PINID = 0x01; //Pin1, not Puk + pMUXMsg->UIMVerifyPinReq.PINLen = strlen((const char *)arg); + qstrcpy((PCHAR)&pMUXMsg->UIMVerifyPinReq.PINValue, ((const char *)arg)); + pMUXMsg->UIMVerifyPinReq.TLVLength = cpu_to_le16(2 + strlen((const char *)arg)); + return sizeof(QMIDMS_UIM_VERIFY_PIN_REQ_MSG) + (strlen((const char *)arg) - 1); +} + +static USHORT UimVerifyPinReqSend(PQMUX_MSG pMUXMsg, void *arg) +{ + pMUXMsg->UIMUIMVerifyPinReq.TLVType = 0x01; + pMUXMsg->UIMUIMVerifyPinReq.TLVLength = cpu_to_le16(0x02); + pMUXMsg->UIMUIMVerifyPinReq.Session_Type = 0x00; + pMUXMsg->UIMUIMVerifyPinReq.Aid_Len = 0x00; + pMUXMsg->UIMUIMVerifyPinReq.TLV2Type = 0x02; + pMUXMsg->UIMUIMVerifyPinReq.TLV2Length = cpu_to_le16(2 + strlen((const char *)arg)); + pMUXMsg->UIMUIMVerifyPinReq.PINID = 0x01; //Pin1, not Puk + pMUXMsg->UIMUIMVerifyPinReq.PINLen= strlen((const char *)arg); + qstrcpy((PCHAR)&pMUXMsg->UIMUIMVerifyPinReq.PINValue, ((const char *)arg)); + return sizeof(QMIUIM_VERIFY_PIN_REQ_MSG) + (strlen((const char *)arg) - 1); +} + +#ifdef CONFIG_IMSI_ICCID +static USHORT UimReadTransparentIMSIReqSend(PQMUX_MSG pMUXMsg, void *arg) { + PREAD_TRANSPARENT_TLV pReadTransparent; + + pMUXMsg->UIMUIMReadTransparentReq.TLVType = 0x01; + pMUXMsg->UIMUIMReadTransparentReq.TLVLength = cpu_to_le16(0x02); + if (!strcmp((char *)arg, "EF_ICCID")) { + pMUXMsg->UIMUIMReadTransparentReq.Session_Type = 0x06; + pMUXMsg->UIMUIMReadTransparentReq.Aid_Len = 0x00; + + pMUXMsg->UIMUIMReadTransparentReq.TLV2Type = 0x02; + pMUXMsg->UIMUIMReadTransparentReq.file_id = cpu_to_le16(0x2FE2); + pMUXMsg->UIMUIMReadTransparentReq.path_len = 0x02; + pMUXMsg->UIMUIMReadTransparentReq.path[0] = 0x00; + pMUXMsg->UIMUIMReadTransparentReq.path[1] = 0x3F; + } + else if(!strcmp((char *)arg, "EF_IMSI")) { + pMUXMsg->UIMUIMReadTransparentReq.Session_Type = 0x00; + pMUXMsg->UIMUIMReadTransparentReq.Aid_Len = 0x00; + + pMUXMsg->UIMUIMReadTransparentReq.TLV2Type = 0x02; + pMUXMsg->UIMUIMReadTransparentReq.file_id = cpu_to_le16(0x6F07); + pMUXMsg->UIMUIMReadTransparentReq.path_len = 0x04; + pMUXMsg->UIMUIMReadTransparentReq.path[0] = 0x00; + pMUXMsg->UIMUIMReadTransparentReq.path[1] = 0x3F; + pMUXMsg->UIMUIMReadTransparentReq.path[2] = 0xFF; + pMUXMsg->UIMUIMReadTransparentReq.path[3] = 0x7F; + } + + pMUXMsg->UIMUIMReadTransparentReq.TLV2Length = cpu_to_le16(3 + pMUXMsg->UIMUIMReadTransparentReq.path_len); + + pReadTransparent = (PREAD_TRANSPARENT_TLV)(&pMUXMsg->UIMUIMReadTransparentReq.path[pMUXMsg->UIMUIMReadTransparentReq.path_len]); + pReadTransparent->TLVType = 0x03; + pReadTransparent->TLVLength = cpu_to_le16(0x04); + pReadTransparent->Offset = cpu_to_le16(0x00); + pReadTransparent->Length = cpu_to_le16(0x00); + + return (sizeof(QMIUIM_READ_TRANSPARENT_REQ_MSG) + pMUXMsg->UIMUIMReadTransparentReq.path_len + sizeof(READ_TRANSPARENT_TLV)); +} +#endif +#endif + +#ifdef CONFIG_APN +static USHORT WdsGetProfileSettingsReqSend(PQMUX_MSG pMUXMsg, void *arg) { + PROFILE_T *profile = (PROFILE_T *)arg; + pMUXMsg->GetProfileSettingsReq.Length = cpu_to_le16(sizeof(QMIWDS_GET_PROFILE_SETTINGS_REQ_MSG) - 4); + pMUXMsg->GetProfileSettingsReq.TLVType = 0x01; + pMUXMsg->GetProfileSettingsReq.TLVLength = cpu_to_le16(0x02); + pMUXMsg->GetProfileSettingsReq.ProfileType = 0x00; // 0 ~ 3GPP, 1 ~ 3GPP2 + pMUXMsg->GetProfileSettingsReq.ProfileIndex = profile->pdp; + return sizeof(QMIWDS_GET_PROFILE_SETTINGS_REQ_MSG); +} + +static USHORT WdsModifyProfileSettingsReq(PQMUX_MSG pMUXMsg, void *arg) { + USHORT TLVLength = 0; + UCHAR *pTLV; + PROFILE_T *profile = (PROFILE_T *)arg; + PQMIWDS_PDPTYPE pPdpType; + + pMUXMsg->ModifyProfileSettingsReq.Length = cpu_to_le16(sizeof(QMIWDS_MODIFY_PROFILE_SETTINGS_REQ_MSG) - 4); + pMUXMsg->ModifyProfileSettingsReq.TLVType = 0x01; + pMUXMsg->ModifyProfileSettingsReq.TLVLength = cpu_to_le16(0x02); + pMUXMsg->ModifyProfileSettingsReq.ProfileType = 0x00; // 0 ~ 3GPP, 1 ~ 3GPP2 + pMUXMsg->ModifyProfileSettingsReq.ProfileIndex = profile->pdp; + + pTLV = (UCHAR *)(&pMUXMsg->ModifyProfileSettingsReq + 1); + + pPdpType = (PQMIWDS_PDPTYPE)(pTLV + TLVLength); + pPdpType->TLVType = 0x11; + pPdpType->TLVLength = cpu_to_le16(0x01); +// 0 ?C PDP-IP (IPv4) +// 1 ?C PDP-PPP +// 2 ?C PDP-IPv6 +// 3 ?C PDP-IPv4v6 + if (profile->enable_ipv4 && profile->enable_ipv6) + pPdpType->PdpType = 3; + else if (profile->enable_ipv6) + pPdpType->PdpType = 2; + else + pPdpType->PdpType = 0; + TLVLength +=(le16_to_cpu(pPdpType->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + + // Set APN Name + if (profile->apn) { + PQMIWDS_APNNAME pApnName = (PQMIWDS_APNNAME)(pTLV + TLVLength); + pApnName->TLVType = 0x14; + pApnName->TLVLength = cpu_to_le16(strlen(profile->apn)); + qstrcpy((char *)&pApnName->ApnName, profile->apn); + TLVLength +=(le16_to_cpu(pApnName->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + // Set User Name + if (profile->user) { + PQMIWDS_USERNAME pUserName = (PQMIWDS_USERNAME)(pTLV + TLVLength); + pUserName->TLVType = 0x1B; + pUserName->TLVLength = cpu_to_le16(strlen(profile->user)); + qstrcpy((char *)&pUserName->UserName, profile->user); + TLVLength += (le16_to_cpu(pUserName->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + // Set Password + if (profile->password) { + PQMIWDS_PASSWD pPasswd = (PQMIWDS_PASSWD)(pTLV + TLVLength); + pPasswd->TLVType = 0x1C; + pPasswd->TLVLength = cpu_to_le16(strlen(profile->password)); + qstrcpy((char *)&pPasswd->Passwd, profile->password); + TLVLength +=(le16_to_cpu(pPasswd->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + // Set Auth Protocol + if (profile->user && profile->password) { + PQMIWDS_AUTH_PREFERENCE pAuthPref = (PQMIWDS_AUTH_PREFERENCE)(pTLV + TLVLength); + pAuthPref->TLVType = 0x1D; + pAuthPref->TLVLength = cpu_to_le16(0x01); + pAuthPref->AuthPreference = profile->auth; // 0 ~ None, 1 ~ Pap, 2 ~ Chap, 3 ~ MsChapV2 + TLVLength += (le16_to_cpu(pAuthPref->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + return sizeof(QMIWDS_MODIFY_PROFILE_SETTINGS_REQ_MSG) + TLVLength; +} +#endif + +static USHORT WdsGetRuntimeSettingReq(PQMUX_MSG pMUXMsg, void *arg) +{ + (void)arg; + pMUXMsg->GetRuntimeSettingsReq.TLVType = 0x10; + pMUXMsg->GetRuntimeSettingsReq.TLVLength = cpu_to_le16(0x04); + // the following mask also applies to IPV6 + pMUXMsg->GetRuntimeSettingsReq.Mask = cpu_to_le32(QMIWDS_GET_RUNTIME_SETTINGS_MASK_IPV4DNS_ADDR | + QMIWDS_GET_RUNTIME_SETTINGS_MASK_IPV4_ADDR | + QMIWDS_GET_RUNTIME_SETTINGS_MASK_MTU | + QMIWDS_GET_RUNTIME_SETTINGS_MASK_IPV4GATEWAY_ADDR) | + QMIWDS_GET_RUNTIME_SETTINGS_MASK_PCSCF_SV_ADDR | + QMIWDS_GET_RUNTIME_SETTINGS_MASK_PCSCF_DOM_NAME; + + return sizeof(QMIWDS_GET_RUNTIME_SETTINGS_REQ_MSG); +} + +static PQCQMIMSG s_pRequest; +static PQCQMIMSG s_pResponse; +static pthread_mutex_t s_commandmutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t s_commandcond = PTHREAD_COND_INITIALIZER; + +static int is_response(const PQCQMIMSG pRequest, const PQCQMIMSG pResponse) { + if ((pRequest->QMIHdr.QMIType == pResponse->QMIHdr.QMIType) + && (pRequest->QMIHdr.ClientId == pResponse->QMIHdr.ClientId)) { + USHORT requestTID, responseTID; + if (pRequest->QMIHdr.QMIType == QMUX_TYPE_CTL) { + requestTID = pRequest->CTLMsg.QMICTLMsgHdr.TransactionId; + responseTID = pResponse->CTLMsg.QMICTLMsgHdr.TransactionId; + } else { + requestTID = le16_to_cpu(pRequest->MUXMsg.QMUXHdr.TransactionId); + responseTID = le16_to_cpu(pResponse->MUXMsg.QMUXHdr.TransactionId); + } + return (requestTID == responseTID); + } + return 0; +} + + +int (*qmidev_send)(PQCQMIMSG pRequest); + +int QmiThreadSendQMITimeout(PQCQMIMSG pRequest, PQCQMIMSG *ppResponse, unsigned msecs, const char *funcname) { + int ret; + + static int flag = 0; + if (!flag) { + cond_setclock_attr(&s_commandcond, CLOCK_MONOTONIC); + flag = 1; + } + + if (!pRequest) + return -EINVAL; + + pthread_mutex_lock(&s_commandmutex); + + if (ppResponse) + *ppResponse = NULL; + + dump_qmi(pRequest, le16_to_cpu(pRequest->QMIHdr.Length) + 1); + + s_pRequest = pRequest; + s_pResponse = NULL; + + ret = qmidev_send(pRequest); + + if (ret == 0) { + ret = pthread_cond_timeout_np(&s_commandcond, &s_commandmutex, msecs); + if (!ret) { + if (s_pResponse && ppResponse) { + *ppResponse = s_pResponse; + } else { + if (s_pResponse) { + free(s_pResponse); + s_pResponse = NULL; + } + } + } else { + dbg_time("%s message timeout", funcname); + } + } + + pthread_mutex_unlock(&s_commandmutex); + + return ret; +} + +void QmiThreadRecvQMI(PQCQMIMSG pResponse) { + pthread_mutex_lock(&s_commandmutex); + if (pResponse == NULL) { + if (s_pRequest) { + free(s_pRequest); + s_pRequest = NULL; + s_pResponse = NULL; + pthread_cond_signal(&s_commandcond); + } + pthread_mutex_unlock(&s_commandmutex); + return; + } + dump_qmi(pResponse, le16_to_cpu(pResponse->QMIHdr.Length) + 1); + if (s_pRequest && is_response(s_pRequest, pResponse)) { + free(s_pRequest); + s_pRequest = NULL; + s_pResponse = malloc(le16_to_cpu(pResponse->QMIHdr.Length) + 1); + if (s_pResponse != NULL) { + memcpy(s_pResponse, pResponse, le16_to_cpu(pResponse->QMIHdr.Length) + 1); + } + pthread_cond_signal(&s_commandcond); + } else if ((pResponse->QMIHdr.QMIType == QMUX_TYPE_CTL) + && (le16_to_cpu(pResponse->CTLMsg.QMICTLMsgHdrRsp.QMICTLType == QMICTL_REVOKE_CLIENT_ID_IND))) { + qmidevice_send_event_to_main(MODEM_REPORT_RESET_EVENT); + } else if ((pResponse->QMIHdr.QMIType == QMUX_TYPE_NAS) + && (le16_to_cpu(pResponse->MUXMsg.QMUXMsgHdrResp.Type) == QMINAS_SERVING_SYSTEM_IND)) { + qmidevice_send_event_to_main(RIL_UNSOL_RESPONSE_VOICE_NETWORK_STATE_CHANGED); + } else if ((pResponse->QMIHdr.QMIType == QMUX_TYPE_WDS) + && (le16_to_cpu(pResponse->MUXMsg.QMUXMsgHdrResp.Type) == QMIWDS_GET_PKT_SRVC_STATUS_IND)) { + qmidevice_send_event_to_main(RIL_UNSOL_DATA_CALL_LIST_CHANGED); + } else if ((pResponse->QMIHdr.QMIType == QMUX_TYPE_NAS) + && (le16_to_cpu(pResponse->MUXMsg.QMUXMsgHdrResp.Type) == QMINAS_SYS_INFO_IND)) { + qmidevice_send_event_to_main(RIL_UNSOL_RESPONSE_VOICE_NETWORK_STATE_CHANGED); + } else if ((pResponse->QMIHdr.QMIType == QMUX_TYPE_WDS_ADMIN) + && (le16_to_cpu(pResponse->MUXMsg.QMUXMsgHdrResp.Type) == QMI_WDA_SET_LOOPBACK_CONFIG_IND)) { + qmidevice_send_event_to_main_ext(RIL_UNSOL_LOOPBACK_CONFIG_IND, + &pResponse->MUXMsg.SetLoopBackInd, sizeof(pResponse->MUXMsg.SetLoopBackInd)); + } else { + if (debug_qmi) + dbg_time("nobody care this qmi msg!!"); + } + pthread_mutex_unlock(&s_commandmutex); +} + +int requestSetEthMode(PROFILE_T *profile) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse = NULL; + PQMUX_MSG pMUXMsg; + int err; + PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV linkProto; + UCHAR IpPreference; + UCHAR autoconnect_setting = 0; + QMAP_SETTING qmap_settings = {0}; + + qmap_settings.size = sizeof(qmap_settings); + + if (profile->qmap_mode) { + profile->rawIP = 1; + s_9x07 = profile->rawIP; + + qmap_settings.MuxId = profile->muxid; + + if (qmidev_is_pciemhi(profile->qmichannel)) { //SDX20_PCIE + qmap_settings.rx_urb_size = profile->qmap_size; //SDX24&SDX55 support 32KB + qmap_settings.ep_type = DATA_EP_TYPE_PCIE; + qmap_settings.iface_id = 0x04; + } + else { // for MDM9x07&MDM9x40&SDX20 USB + qmap_settings.rx_urb_size = profile->qmap_size; //SDX24&SDX55 support 32KB + qmap_settings.ep_type = DATA_EP_TYPE_HSUSB; + qmap_settings.iface_id = 0x04; + } + + qmap_settings.ul_data_aggregation_max_datagrams = 11; //by test result, 11 can get best TPUT + qmap_settings.ul_data_aggregation_max_size = 8*1024; + qmap_settings.dl_minimum_padding = 0; //no effect when register to real netowrk + if(profile->qmap_version != 0x09) + profile->qmap_version = 0x05; + + qmap_version = profile->qmap_version; + if (profile->rmnet_info.size) { + qmap_settings.rx_urb_size = profile->rmnet_info.rx_urb_size; + qmap_settings.ep_type = profile->rmnet_info.ep_type; + qmap_settings.iface_id = profile->rmnet_info.iface_id; + qmap_settings.dl_minimum_padding = profile->rmnet_info.dl_minimum_padding; + qmap_version = profile->rmnet_info.qmap_version; + } + + if (qmidev_is_gobinet(profile->qmichannel)) { //GobiNet set data format in GobiNet driver + goto skip_WdaSetDataFormat; + } else if (profile->qmap_mode > 1) {//QMAP MUX enabled, set data format in quectel-qmi-proxy + goto skip_WdaSetDataFormat; + } + } + + pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS_ADMIN, QMIWDS_ADMIN_SET_DATA_FORMAT_REQ, WdaSetDataFormat, (void *)&qmap_settings); + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + + linkProto = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x11); + if (linkProto != NULL) { + profile->rawIP = (le32_to_cpu(linkProto->Value) == 2); + s_9x07 = profile->rawIP; + } + + linkProto = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x16); + if (linkProto != NULL && profile->qmap_mode) { + qmap_settings.rx_urb_size = le32_to_cpu(linkProto->Value); + dbg_time("qmap_settings.rx_urb_size = %u", qmap_settings.rx_urb_size); //must same as rx_urb_size defined in GobiNet&qmi_wwan driver + } + +#ifdef QUECTEL_UL_DATA_AGG + if (qmap_settings.ul_data_aggregation_max_datagrams) + { + linkProto = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x17); + if (linkProto != NULL) { + qmap_settings.ul_data_aggregation_max_datagrams = MIN(qmap_settings.ul_data_aggregation_max_datagrams, le32_to_cpu(linkProto->Value)); + dbg_time("qmap_settings.ul_data_aggregation_max_datagrams = %u", qmap_settings.ul_data_aggregation_max_datagrams); + } + + linkProto = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x18); + if (linkProto != NULL) { + qmap_settings.ul_data_aggregation_max_size = MIN(qmap_settings.ul_data_aggregation_max_size, le32_to_cpu(linkProto->Value)); + dbg_time("qmap_settings.ul_data_aggregation_max_size = %u", qmap_settings.ul_data_aggregation_max_size); + } + + linkProto = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x1A); + if (linkProto != NULL) { + qmap_settings.dl_minimum_padding = le32_to_cpu(linkProto->Value); + dbg_time("qmap_settings.dl_minimum_padding = %u", qmap_settings.dl_minimum_padding); + } + + if (qmap_settings.ul_data_aggregation_max_datagrams > 1) { + ql_set_driver_qmap_setting(profile, &qmap_settings); + } + } +#endif + + free(pResponse); + +skip_WdaSetDataFormat: + if (profile->enable_ipv4) { + if (profile->qmapnet_adapter) { + // bind wds mux data port + pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS, QMIWDS_BIND_MUX_DATA_PORT_REQ , WdsSetQMUXBindMuxDataPort, (void *)&qmap_settings); + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + if (pResponse) free(pResponse); + } + + // set ipv4 + IpPreference = IpFamilyV4; + pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS, QMIWDS_SET_CLIENT_IP_FAMILY_PREF_REQ, WdsSetClientIPFamilyPref, (void *)&IpPreference); + err = QmiThreadSendQMI(pRequest, &pResponse); + if (pResponse) free(pResponse); + } + + if (profile->enable_ipv6) { + if (profile->qmapnet_adapter) { + // bind wds ipv6 mux data port + pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS_IPV6, QMIWDS_BIND_MUX_DATA_PORT_REQ , WdsSetQMUXBindMuxDataPort, (void *)&qmap_settings); + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + if (pResponse) free(pResponse); + } + + // set ipv6 + IpPreference = IpFamilyV6; + pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS_IPV6, QMIWDS_SET_CLIENT_IP_FAMILY_PREF_REQ, WdsSetClientIPFamilyPref, (void *)&IpPreference); + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + if (pResponse) free(pResponse); + } + + pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS, QMIWDS_SET_AUTO_CONNECT_REQ , WdsSetAutoConnect, (void *)&autoconnect_setting); + QmiThreadSendQMI(pRequest, &pResponse); + if (pResponse) free(pResponse); + + return 0; +} + +#ifdef CONFIG_SIM +int requestGetPINStatus(SIM_Status *pSIMStatus) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + PQMIDMS_UIM_PIN_STATUS pPin1Status = NULL; + //PQMIDMS_UIM_PIN_STATUS pPin2Status = NULL; + + if (s_9x07 && qmiclientId[QMUX_TYPE_UIM]) + pRequest = ComposeQMUXMsg(QMUX_TYPE_UIM, QMIUIM_GET_CARD_STATUS_REQ, NULL, NULL); + else + pRequest = ComposeQMUXMsg(QMUX_TYPE_DMS, QMIDMS_UIM_GET_PIN_STATUS_REQ, NULL, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + + pPin1Status = (PQMIDMS_UIM_PIN_STATUS)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x11); + //pPin2Status = (PQMIDMS_UIM_PIN_STATUS)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x12); + + if (pPin1Status != NULL) { + if (pPin1Status->PINStatus == QMI_PIN_STATUS_NOT_VERIF) { + *pSIMStatus = SIM_PIN; + } else if (pPin1Status->PINStatus == QMI_PIN_STATUS_BLOCKED) { + *pSIMStatus = SIM_PUK; + } else if (pPin1Status->PINStatus == QMI_PIN_STATUS_PERM_BLOCKED) { + *pSIMStatus = SIM_BAD; + } + } + + free(pResponse); + return 0; +} + +int requestGetSIMStatus(SIM_Status *pSIMStatus) { //RIL_REQUEST_GET_SIM_STATUS + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + const char * SIM_Status_String[] = { + "SIM_ABSENT", + "SIM_NOT_READY", + "SIM_READY", /* SIM_READY means the radio state is RADIO_STATE_SIM_READY */ + "SIM_PIN", + "SIM_PUK", + "SIM_NETWORK_PERSONALIZATION" + }; + + if (s_9x07 && qmiclientId[QMUX_TYPE_UIM]) + pRequest = ComposeQMUXMsg(QMUX_TYPE_UIM, QMIUIM_GET_CARD_STATUS_REQ, NULL, NULL); + else + pRequest = ComposeQMUXMsg(QMUX_TYPE_DMS, QMIDMS_UIM_GET_STATE_REQ, NULL, NULL); + + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + + *pSIMStatus = SIM_ABSENT; + if (s_9x07 && qmiclientId[QMUX_TYPE_UIM]) + { + PQMIUIM_CARD_STATUS pCardStatus = NULL; + PQMIUIM_PIN_STATE pPINState = NULL; + UCHAR CardState = 0x01; + UCHAR PIN1State = QMI_PIN_STATUS_NOT_VERIF; + //UCHAR PIN1Retries; + //UCHAR PUK1Retries; + //UCHAR PIN2State; + //UCHAR PIN2Retries; + //UCHAR PUK2Retries; + + pCardStatus = (PQMIUIM_CARD_STATUS)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x10); + if (pCardStatus != NULL) + { + pPINState = (PQMIUIM_PIN_STATE)((PUCHAR)pCardStatus + sizeof(QMIUIM_CARD_STATUS) + pCardStatus->AIDLength); + CardState = pCardStatus->CardState; + if (CardState == UIM_CARD_STATE_PRESENT) { + if (pPINState->UnivPIN == 1) + { + PIN1State = pCardStatus->UPINState; + //PIN1Retries = pCardStatus->UPINRetries; + //PUK1Retries = pCardStatus->UPUKRetries; + } + else + { + PIN1State = pPINState->PIN1State; + //PIN1Retries = pPINState->PIN1Retries; + //PUK1Retries = pPINState->PUK1Retries; + } + //PIN2State = pPINState->PIN2State; + //PIN2Retries = pPINState->PIN2Retries; + //PUK2Retries = pPINState->PUK2Retries; + } + } + + *pSIMStatus = SIM_ABSENT; + if ((CardState == 0x01) && ((PIN1State == QMI_PIN_STATUS_VERIFIED)|| (PIN1State == QMI_PIN_STATUS_DISABLED))) + { + *pSIMStatus = SIM_READY; + } + else if (CardState == 0x01) + { + if (PIN1State == QMI_PIN_STATUS_NOT_VERIF) + { + *pSIMStatus = SIM_PIN; + } + if ( PIN1State == QMI_PIN_STATUS_BLOCKED) + { + *pSIMStatus = SIM_PUK; + } + else if (PIN1State == QMI_PIN_STATUS_PERM_BLOCKED) + { + *pSIMStatus = SIM_BAD; + } + else if (PIN1State == QMI_PIN_STATUS_NOT_INIT || PIN1State == QMI_PIN_STATUS_VERIFIED || PIN1State == QMI_PIN_STATUS_DISABLED) + { + *pSIMStatus = SIM_READY; + } + } + else if (CardState == 0x00 || CardState == 0x02) + { + } + else + { + } + } + else + { + //UIM state. Values: + // 0x00 UIM initialization completed + // 0x01 UIM is locked or the UIM failed + // 0x02 UIM is not present + // 0x03 Reserved + // 0xFF UIM state is currently + //unavailable + if (pResponse->MUXMsg.UIMGetStateResp.UIMState == 0x00) { + *pSIMStatus = SIM_READY; + } else if (pResponse->MUXMsg.UIMGetStateResp.UIMState == 0x01) { + *pSIMStatus = SIM_ABSENT; + err = requestGetPINStatus(pSIMStatus); + } else if ((pResponse->MUXMsg.UIMGetStateResp.UIMState == 0x02) || (pResponse->MUXMsg.UIMGetStateResp.UIMState == 0xFF)) { + *pSIMStatus = SIM_ABSENT; + } else { + *pSIMStatus = SIM_ABSENT; + } + } + dbg_time("%s SIMStatus: %s", __func__, SIM_Status_String[*pSIMStatus]); + + free(pResponse); + + return 0; +} + +int requestEnterSimPin(const CHAR *pPinCode) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + + if (s_9x07 && qmiclientId[QMUX_TYPE_UIM]) + pRequest = ComposeQMUXMsg(QMUX_TYPE_UIM, QMIUIM_VERIFY_PIN_REQ, UimVerifyPinReqSend, (void *)pPinCode); + else + pRequest = ComposeQMUXMsg(QMUX_TYPE_DMS, QMIDMS_UIM_VERIFY_PIN_REQ, DmsUIMVerifyPinReqSend, (void *)pPinCode); + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + + free(pResponse); + return 0; +} + +#ifdef CONFIG_IMSI_ICCID +int requestGetICCID(void) { //RIL_REQUEST_GET_IMSI + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + PQMIUIM_CONTENT pUimContent; + int err; + + if (s_9x07 && qmiclientId[QMUX_TYPE_UIM]) { + pRequest = ComposeQMUXMsg(QMUX_TYPE_UIM, QMIUIM_READ_TRANSPARENT_REQ, UimReadTransparentIMSIReqSend, (void *)"EF_ICCID"); + err = QmiThreadSendQMI(pRequest, &pResponse); + } else { + return 0; + } + qmi_rsp_check_and_return(); + + pUimContent = (PQMIUIM_CONTENT)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x11); + if (pUimContent != NULL) { + static char DeviceICCID[32] = {'\0'}; + int i = 0, j = 0; + + for (i = 0, j = 0; i < le16_to_cpu(pUimContent->content_len); ++i) { + char charmaps[] = "0123456789ABCDEF"; + + DeviceICCID[j++] = charmaps[(pUimContent->content[i] & 0x0F)]; + DeviceICCID[j++] = charmaps[((pUimContent->content[i] & 0xF0) >> 0x04)]; + } + DeviceICCID[j] = '\0'; + + dbg_time("%s DeviceICCID: %s", __func__, DeviceICCID); + } + + free(pResponse); + return 0; +} + +int requestGetIMSI(void) { //RIL_REQUEST_GET_IMSI + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + PQMIUIM_CONTENT pUimContent; + int err; + + if (s_9x07 && qmiclientId[QMUX_TYPE_UIM]) { + pRequest = ComposeQMUXMsg(QMUX_TYPE_UIM, QMIUIM_READ_TRANSPARENT_REQ, UimReadTransparentIMSIReqSend, (void *)"EF_IMSI"); + err = QmiThreadSendQMI(pRequest, &pResponse); + } else { + return 0; + } + qmi_rsp_check_and_return(); + + pUimContent = (PQMIUIM_CONTENT)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x11); + if (pUimContent != NULL) { + static char DeviceIMSI[32] = {'\0'}; + int i = 0, j = 0; + + for (i = 0, j = 0; i < le16_to_cpu(pUimContent->content[0]); ++i) { + if (i != 0) + DeviceIMSI[j++] = (pUimContent->content[i+1] & 0x0F) + '0'; + DeviceIMSI[j++] = ((pUimContent->content[i+1] & 0xF0) >> 0x04) + '0'; + } + DeviceIMSI[j] = '\0'; + + dbg_time("%s DeviceIMSI: %s", __func__, DeviceIMSI); + } + + free(pResponse); + return 0; +} +#endif +#endif + +#if 1 +static void quectel_convert_cdma_mcc_2_ascii_mcc( USHORT *p_mcc, USHORT mcc ) +{ + unsigned int d1, d2, d3, buf = mcc + 111; + + if ( mcc == 0x3FF ) // wildcard + { + *p_mcc = 3; + } + else + { + d3 = buf % 10; + buf = ( d3 == 0 ) ? (buf-10)/10 : buf/10; + + d2 = buf % 10; + buf = ( d2 == 0 ) ? (buf-10)/10 : buf/10; + + d1 = ( buf == 10 ) ? 0 : buf; + +//dbg_time("d1:%d, d2:%d,d3:%d",d1,d2,d3); + if ( d1<10 && d2<10 && d3<10 ) + { + *p_mcc = d1*100+d2*10+d3; +#if 0 + *(p_mcc+0) = '0' + d1; + *(p_mcc+1) = '0' + d2; + *(p_mcc+2) = '0' + d3; +#endif + } + else + { + //dbg_time( "invalid digits %d %d %d", d1, d2, d3 ); + *p_mcc = 0; + } + } +} + +static void quectel_convert_cdma_mnc_2_ascii_mnc( USHORT *p_mnc, USHORT imsi_11_12) +{ + unsigned int d1, d2, buf = imsi_11_12 + 11; + + if ( imsi_11_12 == 0x7F ) // wildcard + { + *p_mnc = 7; + } + else + { + d2 = buf % 10; + buf = ( d2 == 0 ) ? (buf-10)/10 : buf/10; + + d1 = ( buf == 10 ) ? 0 : buf; + + if ( d1<10 && d2<10 ) + { + *p_mnc = d1*10 + d2; + } + else + { + //dbg_time( "invalid digits %d %d", d1, d2, 0 ); + *p_mnc = 0; + } + } +} + +int requestGetHomeNetwork(USHORT *p_mcc, USHORT *p_mnc, USHORT *p_sid, USHORT *p_nid) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + PHOME_NETWORK pHomeNetwork; + PHOME_NETWORK_SYSTEMID pHomeNetworkSystemID; + + pRequest = ComposeQMUXMsg(QMUX_TYPE_NAS, QMINAS_GET_HOME_NETWORK_REQ, NULL, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + + pHomeNetwork = (PHOME_NETWORK)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x01); + if (pHomeNetwork && p_mcc && p_mnc ) { + *p_mcc = le16_to_cpu(pHomeNetwork->MobileCountryCode); + *p_mnc = le16_to_cpu(pHomeNetwork->MobileNetworkCode); + //dbg_time("%s MobileCountryCode: %d, MobileNetworkCode: %d", __func__, *pMobileCountryCode, *pMobileNetworkCode); + } + + pHomeNetworkSystemID = (PHOME_NETWORK_SYSTEMID)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x10); + if (pHomeNetworkSystemID && p_sid && p_nid) { + *p_sid = le16_to_cpu(pHomeNetworkSystemID->SystemID); //china-hefei: sid 14451 + *p_nid = le16_to_cpu(pHomeNetworkSystemID->NetworkID); + //dbg_time("%s SystemID: %d, NetworkID: %d", __func__, *pSystemID, *pNetworkID); + } + + free(pResponse); + + return 0; +} +#endif + +#if 0 +// Lookup table for carriers known to produce SIMs which incorrectly indicate MNC length. +static const char * MCCMNC_CODES_HAVING_3DIGITS_MNC[] = { + "302370", "302720", "310260", + "405025", "405026", "405027", "405028", "405029", "405030", "405031", "405032", + "405033", "405034", "405035", "405036", "405037", "405038", "405039", "405040", + "405041", "405042", "405043", "405044", "405045", "405046", "405047", "405750", + "405751", "405752", "405753", "405754", "405755", "405756", "405799", "405800", + "405801", "405802", "405803", "405804", "405805", "405806", "405807", "405808", + "405809", "405810", "405811", "405812", "405813", "405814", "405815", "405816", + "405817", "405818", "405819", "405820", "405821", "405822", "405823", "405824", + "405825", "405826", "405827", "405828", "405829", "405830", "405831", "405832", + "405833", "405834", "405835", "405836", "405837", "405838", "405839", "405840", + "405841", "405842", "405843", "405844", "405845", "405846", "405847", "405848", + "405849", "405850", "405851", "405852", "405853", "405875", "405876", "405877", + "405878", "405879", "405880", "405881", "405882", "405883", "405884", "405885", + "405886", "405908", "405909", "405910", "405911", "405912", "405913", "405914", + "405915", "405916", "405917", "405918", "405919", "405920", "405921", "405922", + "405923", "405924", "405925", "405926", "405927", "405928", "405929", "405930", + "405931", "405932", "502142", "502143", "502145", "502146", "502147", "502148" +}; + +static const char * MCC_CODES_HAVING_3DIGITS_MNC[] = { + "302", //Canada + "310", //United States of America + "311", //United States of America + "312", //United States of America + "313", //United States of America + "314", //United States of America + "315", //United States of America + "316", //United States of America + "334", //Mexico + "338", //Jamaica + "342", //Barbados + "344", //Antigua and Barbuda + "346", //Cayman Islands + "348", //British Virgin Islands + "365", //Anguilla + "708", //Honduras (Republic of) + "722", //Argentine Republic + "732" //Colombia (Republic of) +}; + +int requestGetIMSI(const char **pp_imsi, USHORT *pMobileCountryCode, USHORT *pMobileNetworkCode) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + + if (pp_imsi) *pp_imsi = NULL; + if (pMobileCountryCode) *pMobileCountryCode = 0; + if (pMobileNetworkCode) *pMobileNetworkCode = 0; + + pRequest = ComposeQMUXMsg(QMUX_TYPE_DMS, QMIDMS_UIM_GET_IMSI_REQ, NULL, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + + if (pMUXMsg->UIMGetIMSIResp.TLV2Type == 0x01 && le16_to_cpu(pMUXMsg->UIMGetIMSIResp.TLV2Length) >= 5) { + int mnc_len = 2; + unsigned i; + char tmp[4]; + + if (pp_imsi) *pp_imsi = strndup((const char *)(&pMUXMsg->UIMGetIMSIResp.IMSI), le16_to_cpu(pMUXMsg->UIMGetIMSIResp.TLV2Length)); + + for (i = 0; i < sizeof(MCCMNC_CODES_HAVING_3DIGITS_MNC)/sizeof(MCCMNC_CODES_HAVING_3DIGITS_MNC[0]); i++) { + if (!strncmp((const char *)(&pMUXMsg->UIMGetIMSIResp.IMSI), MCCMNC_CODES_HAVING_3DIGITS_MNC[i], 6)) { + mnc_len = 3; + break; + } + } + if (mnc_len == 2) { + for (i = 0; i < sizeof(MCC_CODES_HAVING_3DIGITS_MNC)/sizeof(MCC_CODES_HAVING_3DIGITS_MNC[0]); i++) { + if (!strncmp((const char *)(&pMUXMsg->UIMGetIMSIResp.IMSI), MCC_CODES_HAVING_3DIGITS_MNC[i], 3)) { + mnc_len = 3; + break; + } + } + } + + tmp[0] = (&pMUXMsg->UIMGetIMSIResp.IMSI)[0]; + tmp[1] = (&pMUXMsg->UIMGetIMSIResp.IMSI)[1]; + tmp[2] = (&pMUXMsg->UIMGetIMSIResp.IMSI)[2]; + tmp[3] = 0; + if (pMobileCountryCode) *pMobileCountryCode = atoi(tmp); + tmp[0] = (&pMUXMsg->UIMGetIMSIResp.IMSI)[3]; + tmp[1] = (&pMUXMsg->UIMGetIMSIResp.IMSI)[4]; + tmp[2] = 0; + if (mnc_len == 3) { + tmp[2] = (&pMUXMsg->UIMGetIMSIResp.IMSI)[6]; + } + if (pMobileNetworkCode) *pMobileNetworkCode = atoi(tmp); + } + + free(pResponse); + + return 0; +} +#endif + +struct wwan_data_class_str class2str[] = { + {WWAN_DATA_CLASS_NONE, "UNKNOWN"}, + {WWAN_DATA_CLASS_GPRS, "GPRS"}, + {WWAN_DATA_CLASS_EDGE, "EDGE"}, + {WWAN_DATA_CLASS_UMTS, "UMTS"}, + {WWAN_DATA_CLASS_HSDPA, "HSDPA"}, + {WWAN_DATA_CLASS_HSUPA, "HSUPA"}, + {WWAN_DATA_CLASS_LTE, "LTE"}, + {WWAN_DATA_CLASS_5G_NSA, "5G_NSA"}, + {WWAN_DATA_CLASS_5G_SA, "5G_SA"}, + {WWAN_DATA_CLASS_1XRTT, "1XRTT"}, + {WWAN_DATA_CLASS_1XEVDO, "1XEVDO"}, + {WWAN_DATA_CLASS_1XEVDO_REVA, "1XEVDO_REVA"}, + {WWAN_DATA_CLASS_1XEVDV, "1XEVDV"}, + {WWAN_DATA_CLASS_3XRTT, "3XRTT"}, + {WWAN_DATA_CLASS_1XEVDO_REVB, "1XEVDO_REVB"}, + {WWAN_DATA_CLASS_UMB, "UMB"}, + {WWAN_DATA_CLASS_CUSTOM, "CUSTOM"}, +}; + +CHAR *wwan_data_class2str(ULONG class) +{ + unsigned int i = 0; + for (i = 0; i < sizeof(class2str)/sizeof(class2str[0]); i++) { + if (class2str[i].class == class) { + return class2str[i].str; + } + } + return "UNKNOWN"; +} + +static USHORT char2ushort(UCHAR str[3]) { + int i; + char temp[4]; + USHORT ret= 0; + + memcpy(temp, str, 3); + temp[3] = '\0'; + + for (i = 0; i < 4; i++) { + if ((UCHAR)temp[i] == 0xFF) { + temp[i] = '\0'; + } + } + ret = (USHORT)atoi(temp); + + return ret; +} + +int requestRegistrationState2(UCHAR *pPSAttachedState) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + USHORT MobileCountryCode = 0; + USHORT MobileNetworkCode = 0; + const char *pDataCapStr = "UNKNOW"; + LONG remainingLen; + PSERVICE_STATUS_INFO pServiceStatusInfo; + int is_lte = 0; + PCDMA_SYSTEM_INFO pCdmaSystemInfo; + PHDR_SYSTEM_INFO pHdrSystemInfo; + PGSM_SYSTEM_INFO pGsmSystemInfo; + PWCDMA_SYSTEM_INFO pWcdmaSystemInfo; + PLTE_SYSTEM_INFO pLteSystemInfo; + PTDSCDMA_SYSTEM_INFO pTdscdmaSystemInfo; + PNR5G_SYSTEM_INFO pNr5gSystemInfo; + UCHAR DeviceClass = 0; + ULONG DataCapList = 0; + + /* Additional LTE System Info - Availability of Dual connectivity of E-UTRA with NR5G */ + uint8_t endc_available_valid = 0; /**< Must be set to true if endc_available is being passed */ + uint8_t endc_available = 0x00; + /**< + Upper layer indication in LTE SIB2. Values: \n + - 0x00 -- 5G Not available \n + - 0x01 -- 5G Available + + */ + /* Additional LTE System Info - DCNR restriction Info */ + uint8_t restrict_dcnr_valid = 0; /**< Must be set to true if restrict_dcnr is being passed */ + uint8_t restrict_dcnr = 0x01; + /**< + DCNR restriction in NAS attach/TAU accept. Values: \n + - 0x00 -- Not restricted \n + - 0x01 -- Restricted + */ + + *pPSAttachedState = 0; + pRequest = ComposeQMUXMsg(QMUX_TYPE_NAS, QMINAS_GET_SYS_INFO_REQ, NULL, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + + pServiceStatusInfo = (PSERVICE_STATUS_INFO)(((PCHAR)&pMUXMsg->GetSysInfoResp) + QCQMUX_MSG_HDR_SIZE); + remainingLen = le16_to_cpu(pMUXMsg->GetSysInfoResp.Length); + + s_is_cdma = 0; + s_hdr_personality = 0; + while (remainingLen > 0) { + switch (pServiceStatusInfo->TLVType) { + case 0x10: // CDMA + if (pServiceStatusInfo->SrvStatus == 0x02) { + DataCapList = WWAN_DATA_CLASS_1XRTT| + WWAN_DATA_CLASS_1XEVDO| + WWAN_DATA_CLASS_1XEVDO_REVA| + WWAN_DATA_CLASS_1XEVDV| + WWAN_DATA_CLASS_1XEVDO_REVB; + DeviceClass = DEVICE_CLASS_CDMA; + s_is_cdma = (0 == is_lte); + } + break; + case 0x11: // HDR + if (pServiceStatusInfo->SrvStatus == 0x02) { + DataCapList = WWAN_DATA_CLASS_3XRTT| + WWAN_DATA_CLASS_UMB; + DeviceClass = DEVICE_CLASS_CDMA; + s_is_cdma = (0 == is_lte); + } + break; + case 0x12: // GSM + if (pServiceStatusInfo->SrvStatus == 0x02) { + DataCapList = WWAN_DATA_CLASS_GPRS| + WWAN_DATA_CLASS_EDGE; + DeviceClass = DEVICE_CLASS_GSM; + } + break; + case 0x13: // WCDMA + if (pServiceStatusInfo->SrvStatus == 0x02) { + DataCapList = WWAN_DATA_CLASS_UMTS; + DeviceClass = DEVICE_CLASS_GSM; + } + break; + case 0x14: // LTE + if (pServiceStatusInfo->SrvStatus == 0x02) { + DataCapList = WWAN_DATA_CLASS_LTE; + DeviceClass = DEVICE_CLASS_GSM; + is_lte = 1; + s_is_cdma = 0; + } + break; + case 0x4A: // NR5G Service Status Info + if (pServiceStatusInfo->SrvStatus == NAS_SYS_SRV_STATUS_SRV_V01) { + DataCapList |= WWAN_DATA_CLASS_5G_SA; + DeviceClass = DEVICE_CLASS_GSM; + is_lte = 1; + s_is_cdma = 0; + } + break; + case 0x4B: // NR5G System Info + pNr5gSystemInfo = (PNR5G_SYSTEM_INFO)pServiceStatusInfo; + if (pNr5gSystemInfo->srv_domain_valid == 0x01) { + if (pNr5gSystemInfo->srv_domain & SYS_SRV_DOMAIN_PS_ONLY_V01) { + *pPSAttachedState = 1; + } + } + + if (pNr5gSystemInfo->network_id_valid == 0x01) { + MobileCountryCode = (USHORT)char2ushort(pNr5gSystemInfo->MCC); + MobileNetworkCode = (USHORT)char2ushort(pNr5gSystemInfo->MNC); + } + break; + case 0x4E: //Additional LTE System Info - Availability of Dual Connectivity of E-UTRA with NR5G + endc_available_valid = 1; + endc_available = pServiceStatusInfo->SrvStatus; + break; + + case 0x4F: //Additional LTE System Info - DCNR restriction Info + restrict_dcnr_valid = 1; + restrict_dcnr = pServiceStatusInfo->SrvStatus; + break; + + case 0x24: // TDSCDMA + if (pServiceStatusInfo->SrvStatus == 0x02) { + pDataCapStr = "TD-SCDMA"; + } + break; + case 0x15: // CDMA + // CDMA_SYSTEM_INFO + pCdmaSystemInfo = (PCDMA_SYSTEM_INFO)pServiceStatusInfo; + if (pCdmaSystemInfo->SrvDomainValid == 0x01) { + if (pCdmaSystemInfo->SrvDomain & 0x02) { + *pPSAttachedState = 1; + s_is_cdma = (0 == is_lte); + } + } +#if 0 + if (pCdmaSystemInfo->SrvCapabilityValid == 0x01) { + *pPSAttachedState = 0; + if (pCdmaSystemInfo->SrvCapability & 0x02) { + *pPSAttachedState = 1; + s_is_cdma = (0 == is_lte); + } + } +#endif + if (pCdmaSystemInfo->NetworkIdValid == 0x01) { + MobileCountryCode = (USHORT)char2ushort(pCdmaSystemInfo->MCC); + MobileNetworkCode = (USHORT)char2ushort(pCdmaSystemInfo->MNC); + } + break; + case 0x16: // HDR + // HDR_SYSTEM_INFO + pHdrSystemInfo = (PHDR_SYSTEM_INFO)pServiceStatusInfo; + if (pHdrSystemInfo->SrvDomainValid == 0x01) { + if (pHdrSystemInfo->SrvDomain & 0x02) { + *pPSAttachedState = 1; + s_is_cdma = (0 == is_lte); + } + } +#if 0 + if (pHdrSystemInfo->SrvCapabilityValid == 0x01) { + *pPSAttachedState = 0; + if (pHdrSystemInfo->SrvCapability & 0x02) { + *pPSAttachedState = 1; + s_is_cdma = (0 == is_lte); + } + } +#endif + if (*pPSAttachedState && pHdrSystemInfo->HdrPersonalityValid == 0x01) { + if (pHdrSystemInfo->HdrPersonality == 0x03) + s_hdr_personality = 0x02; + //else if (pHdrSystemInfo->HdrPersonality == 0x02) + // s_hdr_personality = 0x01; + } + USHORT cmda_mcc = 0, cdma_mnc = 0; + if(!requestGetHomeNetwork(&cmda_mcc, &cdma_mnc,NULL, NULL) && cmda_mcc) { + quectel_convert_cdma_mcc_2_ascii_mcc(&MobileCountryCode, cmda_mcc); + quectel_convert_cdma_mnc_2_ascii_mnc(&MobileNetworkCode, cdma_mnc); + } + break; + case 0x17: // GSM + // GSM_SYSTEM_INFO + pGsmSystemInfo = (PGSM_SYSTEM_INFO)pServiceStatusInfo; + if (pGsmSystemInfo->SrvDomainValid == 0x01) { + if (pGsmSystemInfo->SrvDomain & 0x02) { + *pPSAttachedState = 1; + } + } +#if 0 + if (pGsmSystemInfo->SrvCapabilityValid == 0x01) { + *pPSAttachedState = 0; + if (pGsmSystemInfo->SrvCapability & 0x02) { + *pPSAttachedState = 1; + } + } +#endif + if (pGsmSystemInfo->NetworkIdValid == 0x01) { + MobileCountryCode = (USHORT)char2ushort(pGsmSystemInfo->MCC); + MobileNetworkCode = (USHORT)char2ushort(pGsmSystemInfo->MNC); + } + break; + case 0x18: // WCDMA + // WCDMA_SYSTEM_INFO + pWcdmaSystemInfo = (PWCDMA_SYSTEM_INFO)pServiceStatusInfo; + if (pWcdmaSystemInfo->SrvDomainValid == 0x01) { + if (pWcdmaSystemInfo->SrvDomain & 0x02) { + *pPSAttachedState = 1; + } + } +#if 0 + if (pWcdmaSystemInfo->SrvCapabilityValid == 0x01) { + *pPSAttachedState = 0; + if (pWcdmaSystemInfo->SrvCapability & 0x02) { + *pPSAttachedState = 1; + } + } +#endif + if (pWcdmaSystemInfo->NetworkIdValid == 0x01) { + MobileCountryCode = (USHORT)char2ushort(pWcdmaSystemInfo->MCC); + MobileNetworkCode = (USHORT)char2ushort(pWcdmaSystemInfo->MNC); + } + break; + case 0x19: // LTE_SYSTEM_INFO + // LTE_SYSTEM_INFO + pLteSystemInfo = (PLTE_SYSTEM_INFO)pServiceStatusInfo; + if (pLteSystemInfo->SrvDomainValid == 0x01) { + if (pLteSystemInfo->SrvDomain & 0x02) { + *pPSAttachedState = 1; + is_lte = 1; + s_is_cdma = 0; + } + } +#if 0 + if (pLteSystemInfo->SrvCapabilityValid == 0x01) { + *pPSAttachedState = 0; + if (pLteSystemInfo->SrvCapability & 0x02) { + *pPSAttachedState = 1; + is_lte = 1; + s_is_cdma = 0; + } + } +#endif + if (pLteSystemInfo->NetworkIdValid == 0x01) { + MobileCountryCode = (USHORT)char2ushort(pLteSystemInfo->MCC); + MobileNetworkCode = (USHORT)char2ushort(pLteSystemInfo->MNC); + } + break; + case 0x25: // TDSCDMA + // TDSCDMA_SYSTEM_INFO + pTdscdmaSystemInfo = (PTDSCDMA_SYSTEM_INFO)pServiceStatusInfo; + if (pTdscdmaSystemInfo->SrvDomainValid == 0x01) { + if (pTdscdmaSystemInfo->SrvDomain & 0x02) { + *pPSAttachedState = 1; + } + } +#if 0 + if (pTdscdmaSystemInfo->SrvCapabilityValid == 0x01) { + *pPSAttachedState = 0; + if (pTdscdmaSystemInfo->SrvCapability & 0x02) { + *pPSAttachedState = 1; + } + } +#endif + if (pTdscdmaSystemInfo->NetworkIdValid == 0x01) { + MobileCountryCode = (USHORT)char2ushort(pTdscdmaSystemInfo->MCC); + MobileNetworkCode = (USHORT)char2ushort(pTdscdmaSystemInfo->MNC); + } + break; + default: + break; + } /* switch (pServiceStatusInfo->TLYType) */ + + remainingLen -= (le16_to_cpu(pServiceStatusInfo->TLVLength) + 3); + pServiceStatusInfo = (PSERVICE_STATUS_INFO)((PCHAR)&pServiceStatusInfo->TLVLength + le16_to_cpu(pServiceStatusInfo->TLVLength) + sizeof(USHORT)); + } /* while (remainingLen > 0) */ + + if (DataCapList & WWAN_DATA_CLASS_LTE) { + if (endc_available_valid && restrict_dcnr_valid) { + if (endc_available && !restrict_dcnr) { + DataCapList |= WWAN_DATA_CLASS_5G_NSA; + } + } + } + + if (DeviceClass == DEVICE_CLASS_CDMA) { + if (s_hdr_personality == 2) { + pDataCapStr = s_hdr_personality == 2 ? "eHRPD" : "HRPD"; + } else if (DataCapList & WWAN_DATA_CLASS_1XEVDO_REVB) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_1XEVDO_REVB); + } else if (DataCapList & WWAN_DATA_CLASS_1XEVDO_REVA) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_1XEVDO_REVA); + } else if (DataCapList & WWAN_DATA_CLASS_1XEVDO) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_1XEVDO); + } else if (DataCapList & WWAN_DATA_CLASS_1XRTT) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_1XRTT); + } else if (DataCapList & WWAN_DATA_CLASS_3XRTT) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_3XRTT); + } else if (DataCapList & WWAN_DATA_CLASS_UMB) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_UMB); + } + } else { + if (DataCapList & WWAN_DATA_CLASS_5G_SA) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_5G_SA); + } else if (DataCapList & WWAN_DATA_CLASS_5G_NSA) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_5G_NSA); + } else if (DataCapList & WWAN_DATA_CLASS_LTE) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_LTE); + } else if ((DataCapList & WWAN_DATA_CLASS_HSDPA) && (DataCapList & WWAN_DATA_CLASS_HSUPA)) { + pDataCapStr = "HSDPA_HSUPA"; + } else if (DataCapList & WWAN_DATA_CLASS_HSDPA) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_HSDPA); + } else if (DataCapList & WWAN_DATA_CLASS_HSUPA) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_HSUPA); + } else if (DataCapList & WWAN_DATA_CLASS_UMTS) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_UMTS); + } else if (DataCapList & WWAN_DATA_CLASS_EDGE) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_EDGE); + } else if (DataCapList & WWAN_DATA_CLASS_GPRS) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_GPRS); + } + } + + dbg_time("%s MCC: %d, MNC: %d, PS: %s, DataCap: %s", __func__, + MobileCountryCode, MobileNetworkCode, (*pPSAttachedState == 1) ? "Attached" : "Detached" , pDataCapStr); + + free(pResponse); + + return 0; +} + +int requestRegistrationState(UCHAR *pPSAttachedState) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + PQMINAS_CURRENT_PLMN_MSG pCurrentPlmn; + PSERVING_SYSTEM pServingSystem; + PQMINAS_DATA_CAP pDataCap; + USHORT MobileCountryCode = 0; + USHORT MobileNetworkCode = 0; + const char *pDataCapStr = "UNKNOW"; + + if (s_9x07) { + return requestRegistrationState2(pPSAttachedState); + } + + pRequest = ComposeQMUXMsg(QMUX_TYPE_NAS, QMINAS_GET_SERVING_SYSTEM_REQ, NULL, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + + pCurrentPlmn = (PQMINAS_CURRENT_PLMN_MSG)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x12); + if (pCurrentPlmn) { + MobileCountryCode = le16_to_cpu(pCurrentPlmn->MobileCountryCode); + MobileNetworkCode = le16_to_cpu(pCurrentPlmn->MobileNetworkCode); + } + + *pPSAttachedState = 0; + pServingSystem = (PSERVING_SYSTEM)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x01); + if (pServingSystem) { + //Packet-switched domain attach state of the mobile. + //0x00 PS_UNKNOWN ?Unknown or not applicable + //0x01 PS_ATTACHED ?Attached + //0x02 PS_DETACHED ?Detached + *pPSAttachedState = pServingSystem->RegistrationState; + if (pServingSystem->RegistrationState == 0x01) //0x01 ?C REGISTERED ?C Registered with a network + *pPSAttachedState = pServingSystem->PSAttachedState; + else { + //MobileCountryCode = MobileNetworkCode = 0; + *pPSAttachedState = 0x02; + } + } + + pDataCap = (PQMINAS_DATA_CAP)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x11); + if (pDataCap && pDataCap->DataCapListLen) { + UCHAR *DataCap = &pDataCap->DataCap; + if (pDataCap->DataCapListLen == 2) { + if ((DataCap[0] == 0x06) && ((DataCap[1] == 0x08) || (DataCap[1] == 0x0A))) + DataCap[0] = DataCap[1]; + } + switch (DataCap[0]) { + case 0x01: pDataCapStr = "GPRS"; break; + case 0x02: pDataCapStr = "EDGE"; break; + case 0x03: pDataCapStr = "HSDPA"; break; + case 0x04: pDataCapStr = "HSUPA"; break; + case 0x05: pDataCapStr = "UMTS"; break; + case 0x06: pDataCapStr = "1XRTT"; break; + case 0x07: pDataCapStr = "1XEVDO"; break; + case 0x08: pDataCapStr = "1XEVDO_REVA"; break; + case 0x09: pDataCapStr = "GPRS"; break; + case 0x0A: pDataCapStr = "1XEVDO_REVB"; break; + case 0x0B: pDataCapStr = "LTE"; break; + case 0x0C: pDataCapStr = "HSDPA"; break; + case 0x0D: pDataCapStr = "HSDPA"; break; + default: pDataCapStr = "UNKNOW"; break; + } + } + + if (pServingSystem && pServingSystem->RegistrationState == 0x01 && pServingSystem->InUseRadioIF && pServingSystem->RadioIF == 0x09) { + pDataCapStr = "TD-SCDMA"; + } + + s_is_cdma = 0; + if (pServingSystem && pServingSystem->RegistrationState == 0x01 && pServingSystem->InUseRadioIF && (pServingSystem->RadioIF == 0x01 || pServingSystem->RadioIF == 0x02)) { + USHORT cmda_mcc = 0, cdma_mnc = 0; + s_is_cdma = 1; + if(!requestGetHomeNetwork(&cmda_mcc, &cdma_mnc,NULL, NULL) && cmda_mcc) { + quectel_convert_cdma_mcc_2_ascii_mcc(&MobileCountryCode, cmda_mcc); + quectel_convert_cdma_mnc_2_ascii_mnc(&MobileNetworkCode, cdma_mnc); + } + if (1) { + PQCQMUX_TLV pTLV = (PQCQMUX_TLV)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x23); + if (pTLV) + s_hdr_personality = pTLV->Value; + else + s_hdr_personality = 0; + if (s_hdr_personality == 2) + pDataCapStr = "eHRPD"; + } + } + + dbg_time("%s MCC: %d, MNC: %d, PS: %s, DataCap: %s", __func__, + MobileCountryCode, MobileNetworkCode, (*pPSAttachedState == 1) ? "Attached" : "Detached" , pDataCapStr); + + free(pResponse); + + return 0; +} + +int requestQueryDataCall(UCHAR *pConnectionStatus, int curIpFamily) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + PQMIWDS_PKT_SRVC_TLV pPktSrvc; + UCHAR oldConnectionStatus = *pConnectionStatus; + UCHAR QMIType = (curIpFamily == IpFamilyV4) ? QMUX_TYPE_WDS : QMUX_TYPE_WDS_IPV6; + + pRequest = ComposeQMUXMsg(QMIType, QMIWDS_GET_PKT_SRVC_STATUS_REQ, NULL, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + + *pConnectionStatus = QWDS_PKT_DATA_DISCONNECTED; + pPktSrvc = (PQMIWDS_PKT_SRVC_TLV)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x01); + if (pPktSrvc) { + *pConnectionStatus = pPktSrvc->ConnectionStatus; + if ((le16_to_cpu(pPktSrvc->TLVLength) == 2) && (pPktSrvc->ReconfigReqd == 0x01)) + *pConnectionStatus = QWDS_PKT_DATA_DISCONNECTED; + } + + if (*pConnectionStatus == QWDS_PKT_DATA_DISCONNECTED) { + if (curIpFamily == IpFamilyV4) + WdsConnectionIPv4Handle = 0; + else + WdsConnectionIPv6Handle = 0; + } + + if (oldConnectionStatus != *pConnectionStatus || debug_qmi) { + dbg_time("%s %sConnectionStatus: %s", __func__, (curIpFamily == IpFamilyV4) ? "IPv4" : "IPv6", + (*pConnectionStatus == QWDS_PKT_DATA_CONNECTED) ? "CONNECTED" : "DISCONNECTED"); + } + + free(pResponse); + return 0; +} + +int requestSetupDataCall(PROFILE_T *profile, int curIpFamily) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err = 0; + UCHAR QMIType = (curIpFamily == IpFamilyV4) ? QMUX_TYPE_WDS : QMUX_TYPE_WDS_IPV6; + +//DualIPSupported means can get ipv4 & ipv6 address at the same time, one wds for ipv4, the other wds for ipv6 + profile->curIpFamily = curIpFamily; + pRequest = ComposeQMUXMsg(QMIType, QMIWDS_START_NETWORK_INTERFACE_REQ, WdsStartNwInterfaceReq, profile); + err = QmiThreadSendQMITimeout(pRequest, &pResponse, 120 * 1000, __func__); + qmi_rsp_check(); + + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + PQMI_TLV_HDR pTLVHdr; + + pTLVHdr = GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x10); + if (pTLVHdr) { + uint16_t *data16 = (uint16_t *)(pTLVHdr+1); + uint16_t call_end_reason = le16_to_cpu(data16[0]); + dbg_time("call_end_reason is %d", call_end_reason); + } + + pTLVHdr = GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x11); + if (pTLVHdr) { + uint16_t *data16 = (uint16_t *)(pTLVHdr+1); + uint16_t call_end_reason_type = le16_to_cpu(data16[0]); + uint16_t verbose_call_end_reason = le16_to_cpu(data16[1]); + + dbg_time("call_end_reason_type is %d", call_end_reason_type); + dbg_time("call_end_reason_verbose is %d", verbose_call_end_reason); + } + + free(pResponse); + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + if (curIpFamily == IpFamilyV4) { + WdsConnectionIPv4Handle = le32_to_cpu(pResponse->MUXMsg.StartNwInterfaceResp.Handle); + dbg_time("%s WdsConnectionIPv4Handle: 0x%08x", __func__, WdsConnectionIPv4Handle); + } else { + WdsConnectionIPv6Handle = le32_to_cpu(pResponse->MUXMsg.StartNwInterfaceResp.Handle); + dbg_time("%s WdsConnectionIPv6Handle: 0x%08x", __func__, WdsConnectionIPv6Handle); + } + + free(pResponse); + + return 0; +} + +int requestDeactivateDefaultPDP(PROFILE_T *profile, int curIpFamily) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + UCHAR QMIType = (curIpFamily == 0x04) ? QMUX_TYPE_WDS : QMUX_TYPE_WDS_IPV6; + + (void)profile; + if (curIpFamily == IpFamilyV4 && WdsConnectionIPv4Handle == 0) + return 0; + if (curIpFamily == IpFamilyV6 && WdsConnectionIPv6Handle == 0) + return 0; + + dbg_time("%s WdsConnectionIPv%dHandle", __func__, curIpFamily == IpFamilyV4 ? 4 : 6); + + pRequest = ComposeQMUXMsg(QMIType, QMIWDS_STOP_NETWORK_INTERFACE_REQ , WdsStopNwInterfaceReq, &curIpFamily); + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + + if (curIpFamily == IpFamilyV4) + WdsConnectionIPv4Handle = 0; + else + WdsConnectionIPv6Handle = 0; + free(pResponse); + return 0; +} + +int requestGetIPAddress(PROFILE_T *profile, int curIpFamily) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR pIpv4Addr; + PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV6_ADDR pIpv6Addr = NULL; + PQMIWDS_GET_RUNTIME_SETTINGS_TLV_MTU pMtu; + IPV4_T *pIpv4 = &profile->ipv4; + IPV6_T *pIpv6 = &profile->ipv6; + UCHAR QMIType = (curIpFamily == 0x04) ? QMUX_TYPE_WDS : QMUX_TYPE_WDS_IPV6; + PQMIWDS_GET_RUNNING_SETTINGS_PCSCF_IPV6_ADDR pPCSCFIpv6Addr; + PQMIWDS_GET_RUNNING_SETTINGS_PCSCF_IPV4_ADDR pPCSCFIpv4Addr; + + if (curIpFamily == IpFamilyV4) { + memset(pIpv4, 0x00, sizeof(IPV4_T)); + if (WdsConnectionIPv4Handle == 0) + return 0; + } else if (curIpFamily == IpFamilyV6) { + memset(pIpv6, 0x00, sizeof(IPV6_T)); + if (WdsConnectionIPv6Handle == 0) + return 0; + } + + pRequest = ComposeQMUXMsg(QMIType, QMIWDS_GET_RUNTIME_SETTINGS_REQ, WdsGetRuntimeSettingReq, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + + pPCSCFIpv6Addr = (PQMIWDS_GET_RUNNING_SETTINGS_PCSCF_IPV6_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x2e); // 0x2e - pcscf ipv6 address + if (pPCSCFIpv6Addr) { + if (pPCSCFIpv6Addr->PCSCFNumber == 1) { + UCHAR *PCSCFIpv6Addr1 = (UCHAR *)(pPCSCFIpv6Addr + 1); + memcpy(profile->PCSCFIpv6Addr1, PCSCFIpv6Addr1, 16); + }else if (pPCSCFIpv6Addr->PCSCFNumber == 2) { + UCHAR *PCSCFIpv6Addr1 = (UCHAR *)(pPCSCFIpv6Addr + 1); + UCHAR *PCSCFIpv6Addr2 = PCSCFIpv6Addr1 + 16; + memcpy(profile->PCSCFIpv6Addr1, PCSCFIpv6Addr1, 16); + memcpy(profile->PCSCFIpv6Addr2, PCSCFIpv6Addr2, 16); + } + } + + pPCSCFIpv4Addr = (PQMIWDS_GET_RUNNING_SETTINGS_PCSCF_IPV4_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x23); // 0x23 - pcscf ipv4 address + if (pPCSCFIpv4Addr) { + if (pPCSCFIpv4Addr->PCSCFNumber == 1) { + UCHAR *PCSCFIpv4Addr1 = (UCHAR *)(pPCSCFIpv4Addr + 1); + memcpy(&profile->PCSCFIpv4Addr1, PCSCFIpv4Addr1, 4); + }else if (pPCSCFIpv4Addr->PCSCFNumber == 2) { + UCHAR *PCSCFIpv4Addr1 = (UCHAR *)(pPCSCFIpv4Addr + 1); + UCHAR *PCSCFIpv4Addr2 = PCSCFIpv4Addr1 + 4; + memcpy(&profile->PCSCFIpv4Addr1, PCSCFIpv4Addr1, 4); + memcpy(&profile->PCSCFIpv4Addr2, PCSCFIpv4Addr2, 4); + } + } + + pIpv4Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4PRIMARYDNS); + if (pIpv4Addr) { + pIpv4->DnsPrimary = pIpv4Addr->IPV4Address; + } + + pIpv4Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4SECONDARYDNS); + if (pIpv4Addr) { + pIpv4->DnsSecondary = pIpv4Addr->IPV4Address; + } + + pIpv4Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4GATEWAY); + if (pIpv4Addr) { + pIpv4->Gateway = pIpv4Addr->IPV4Address; + } + + pIpv4Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4SUBNET); + if (pIpv4Addr) { + pIpv4->SubnetMask = pIpv4Addr->IPV4Address; + } + + pIpv4Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4); + if (pIpv4Addr) { + pIpv4->Address = pIpv4Addr->IPV4Address; + } + + pIpv6Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV6_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6PRIMARYDNS); + if (pIpv6Addr) { + memcpy(pIpv6->DnsPrimary, pIpv6Addr->IPV6Address, 16); + } + + pIpv6Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV6_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6SECONDARYDNS); + if (pIpv6Addr) { + memcpy(pIpv6->DnsSecondary, pIpv6Addr->IPV6Address, 16); + } + + pIpv6Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV6_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6GATEWAY); + if (pIpv6Addr) { + memcpy(pIpv6->Gateway, pIpv6Addr->IPV6Address, 16); + pIpv6->PrefixLengthGateway = pIpv6Addr->PrefixLength; + } + + pIpv6Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV6_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6); + if (pIpv6Addr) { + memcpy(pIpv6->Address, pIpv6Addr->IPV6Address, 16); + pIpv6->PrefixLengthIPAddr = pIpv6Addr->PrefixLength; + } + + pMtu = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_MTU)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_MTU); + if (pMtu) { + if (curIpFamily == IpFamilyV4) + pIpv4->Mtu = le32_to_cpu(pMtu->Mtu); + else + pIpv6->Mtu = le32_to_cpu(pMtu->Mtu); + } + + free(pResponse); + return 0; +} + +#ifdef CONFIG_APN +int requestSetProfile(PROFILE_T *profile) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + + if (!profile->pdp) + return 0; + + dbg_time("%s[%d] %s/%s/%s/%d", __func__, profile->pdp, profile->apn, profile->user, profile->password, profile->auth); + pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS, QMIWDS_MODIFY_PROFILE_SETTINGS_REQ, WdsModifyProfileSettingsReq, profile); + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + + free(pResponse); + return 0; +} + +int requestGetProfile(PROFILE_T *profile) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + char *apn = NULL; + char *user = NULL; + char *password = NULL; + int auth = 0; + PQMIWDS_APNNAME pApnName; + PQMIWDS_USERNAME pUserName; + PQMIWDS_PASSWD pPassWd; + PQMIWDS_AUTH_PREFERENCE pAuthPref; + + if (!profile->pdp) + return 0; + + pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS, QMIWDS_GET_PROFILE_SETTINGS_REQ, WdsGetProfileSettingsReqSend, profile); + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + + pApnName = (PQMIWDS_APNNAME)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x14); + pUserName = (PQMIWDS_USERNAME)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x1B); + pPassWd = (PQMIWDS_PASSWD)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x1C); + pAuthPref = (PQMIWDS_AUTH_PREFERENCE)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x1D); + + if (pApnName/* && le16_to_cpu(pApnName->TLVLength)*/) + apn = strndup((const char *)(&pApnName->ApnName), le16_to_cpu(pApnName->TLVLength)); + if (pUserName/* && pUserName->UserName*/) + user = strndup((const char *)(&pUserName->UserName), le16_to_cpu(pUserName->TLVLength)); + if (pPassWd/* && le16_to_cpu(pPassWd->TLVLength)*/) + password = strndup((const char *)(&pPassWd->Passwd), le16_to_cpu(pPassWd->TLVLength)); + if (pAuthPref/* && le16_to_cpu(pAuthPref->TLVLength)*/) { + auth = pAuthPref->AuthPreference; + } + +#if 0 + if (profile) { + profile->apn = apn; + profile->user = user; + profile->password = password; + profile->auth = auth; + } +#endif + + dbg_time("%s[%d] %s/%s/%s/%d", __func__, profile->pdp, apn, user, password, auth); + + free(pResponse); + return 0; +} +#endif + +#ifdef CONFIG_SIGNALINFO +int requestGetSignalInfo(void) +{ + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + + pRequest = ComposeQMUXMsg(QMUX_TYPE_NAS, QMINAS_GET_SIG_INFO_REQ, NULL, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + + + // CDMA + { + PQMINAS_SIG_INFO_CDMA_TLV_MSG ptlv = (PQMINAS_SIG_INFO_CDMA_TLV_MSG)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x10); + if (ptlv && ptlv->TLVLength) + { + dbg_time("%s CDMA: RSSI %d dBm, ECIO %.1lf dBm", __func__, + ptlv->rssi, (-0.5) * (double)ptlv->ecio); + } + } + + // HDR + { + PQMINAS_SIG_INFO_HDR_TLV_MSG ptlv = (PQMINAS_SIG_INFO_HDR_TLV_MSG)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x11); + if (ptlv && ptlv->TLVLength) + { + dbg_time("%s HDR: RSSI %d dBm, ECIO %.1lf dBm, IO %d dBm", __func__, + ptlv->rssi, (-0.5) * (double)ptlv->ecio, ptlv->io); + } + } + + // GSM + { + PQMINAS_SIG_INFO_GSM_TLV_MSG ptlv = (PQMINAS_SIG_INFO_GSM_TLV_MSG)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x12); + if (ptlv && ptlv->TLVLength) + { + dbg_time("%s GSM: RSSI %d dBm", __func__, ptlv->rssi); + } + } + + // WCDMA + { + PQMINAS_SIG_INFO_WCDMA_TLV_MSG ptlv = (PQMINAS_SIG_INFO_WCDMA_TLV_MSG)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x13); + if (ptlv && ptlv->TLVLength) + { + dbg_time("%s WCDMA: RSSI %d dBm, ECIO %.1lf dBm", __func__, + ptlv->rssi, (-0.5) * (double)ptlv->ecio); + } + } + + // LTE + { + PQMINAS_SIG_INFO_LTE_TLV_MSG ptlv = (PQMINAS_SIG_INFO_LTE_TLV_MSG)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x14); + if (ptlv && ptlv->TLVLength) + { + dbg_time("%s LTE: RSSI %d dBm, RSRQ %d dB, RSRP %d dBm, SNR %.1lf dB", __func__, + ptlv->rssi, ptlv->rsrq, ptlv->rsrp, (0.1) * (double)ptlv->snr); + } + } + + // TDSCDMA + { + PQMINAS_SIG_INFO_TDSCDMA_TLV_MSG ptlv = (PQMINAS_SIG_INFO_TDSCDMA_TLV_MSG)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x15); + if (ptlv && ptlv->TLVLength) + { + dbg_time("%s LTE: RSCP %d dBm", __func__, ptlv->rscp); + } + } + free(pResponse); + return 0; +} +#endif + +#ifdef CONFIG_VERSION +int requestBaseBandVersion(const char **pp_reversion) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + PDEVICE_REV_ID revId; + int err; + + if (pp_reversion) *pp_reversion = NULL; + + pRequest = ComposeQMUXMsg(QMUX_TYPE_DMS, QMIDMS_GET_DEVICE_REV_ID_REQ, NULL, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + + revId = (PDEVICE_REV_ID)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x01); + + if (revId && le16_to_cpu(revId->TLVLength)) + { + char *DeviceRevisionID = strndup((const char *)(&revId->RevisionID), le16_to_cpu(revId->TLVLength)); + dbg_time("%s %s", __func__, DeviceRevisionID); + if (s_9x07 == -1) { //fail to get QMUX_TYPE_WDS_ADMIN + if (strncmp(DeviceRevisionID, "EC20", strlen("EC20"))) + s_9x07 = 1; + else + s_9x07 = DeviceRevisionID[5] == 'F' || DeviceRevisionID[6] == 'F'; //EC20CF,EC20EF,EC20CEF + } + if (pp_reversion) *pp_reversion = DeviceRevisionID; + } + + free(pResponse); + return 0; +} +#endif + +#ifdef CONFIG_RESET_RADIO +static USHORT DmsSetOperatingModeReq(PQMUX_MSG pMUXMsg, void *arg) { + pMUXMsg->SetOperatingModeReq.TLVType = 0x01; + pMUXMsg->SetOperatingModeReq.TLVLength = cpu_to_le16(1); + pMUXMsg->SetOperatingModeReq.OperatingMode = *((UCHAR *)arg); + + return sizeof(QMIDMS_SET_OPERATING_MODE_REQ_MSG); +} + +int requestSetOperatingMode(UCHAR OperatingMode) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + + dbg_time("%s(%d)", __func__, OperatingMode); + + pRequest = ComposeQMUXMsg(QMUX_TYPE_DMS, QMIDMS_SET_OPERATING_MODE_REQ, DmsSetOperatingModeReq, &OperatingMode); + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + + free(pResponse); + return 0; +} +#endif + +static USHORT WdaSetLoopBackReq(PQMUX_MSG pMUXMsg, void *arg) { + (void)arg; + pMUXMsg->SetLoopBackReq.loopback_state.TLVType = 0x01; + pMUXMsg->SetLoopBackReq.loopback_state.TLVLength = cpu_to_le16(1); + + pMUXMsg->SetLoopBackReq.replication_factor.TLVType = 0x10; + pMUXMsg->SetLoopBackReq.replication_factor.TLVLength = cpu_to_le16(4); + + return sizeof(QMI_WDA_SET_LOOPBACK_CONFIG_REQ_MSG); +} + +int requestSetLoopBackState(UCHAR loopback_state, ULONG replication_factor) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + + dbg_time("%s(loopback_state=%d, replication_factor=%u)", __func__, loopback_state, replication_factor); + + pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS_ADMIN, QMI_WDA_SET_LOOPBACK_CONFIG_REQ, WdaSetLoopBackReq, NULL); + pRequest->MUXMsg.SetLoopBackReq.loopback_state.TLVVaule = loopback_state; + pRequest->MUXMsg.SetLoopBackReq.replication_factor.TLVVaule = cpu_to_le16(replication_factor); + + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + + free(pResponse); + return 0; +} diff --git a/root/package/link4all/quectel-CM/src/QMIThread.h b/root/package/link4all/quectel-CM/src/QMIThread.h new file mode 100644 index 00000000..39d6edcb --- /dev/null +++ b/root/package/link4all/quectel-CM/src/QMIThread.h @@ -0,0 +1,331 @@ +#ifndef __QMI_THREAD_H__ +#define __QMI_THREAD_H__ + +#define CONFIG_GOBINET +#define CONFIG_QMIWWAN +#define CONFIG_SIM +#define CONFIG_APN +#define CONFIG_VERSION +// #define CONFIG_SIGNALINFO +#define CONFIG_DEFAULT_PDP 1 +//#define CONFIG_IMSI_ICCID +#define QUECTEL_UL_DATA_AGG +//#define QUECTEL_QMI_MERGE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "MPQMI.h" +#include "MPQCTL.h" +#include "MPQMUX.h" +#include "util.h" + +#define DEVICE_CLASS_UNKNOWN 0 +#define DEVICE_CLASS_CDMA 1 +#define DEVICE_CLASS_GSM 2 + +#define WWAN_DATA_CLASS_NONE 0x00000000 +#define WWAN_DATA_CLASS_GPRS 0x00000001 +#define WWAN_DATA_CLASS_EDGE 0x00000002 /* EGPRS */ +#define WWAN_DATA_CLASS_UMTS 0x00000004 +#define WWAN_DATA_CLASS_HSDPA 0x00000008 +#define WWAN_DATA_CLASS_HSUPA 0x00000010 +#define WWAN_DATA_CLASS_LTE 0x00000020 +#define WWAN_DATA_CLASS_5G_NSA 0x00000040 +#define WWAN_DATA_CLASS_5G_SA 0x00000080 +#define WWAN_DATA_CLASS_1XRTT 0x00010000 +#define WWAN_DATA_CLASS_1XEVDO 0x00020000 +#define WWAN_DATA_CLASS_1XEVDO_REVA 0x00040000 +#define WWAN_DATA_CLASS_1XEVDV 0x00080000 +#define WWAN_DATA_CLASS_3XRTT 0x00100000 +#define WWAN_DATA_CLASS_1XEVDO_REVB 0x00200000 /* for future use */ +#define WWAN_DATA_CLASS_UMB 0x00400000 +#define WWAN_DATA_CLASS_CUSTOM 0x80000000 + +struct wwan_data_class_str { + ULONG class; + CHAR *str; +}; + +#pragma pack(push, 1) + +typedef struct _QCQMIMSG { + QCQMI_HDR QMIHdr; + union { + QMICTL_MSG CTLMsg; + QMUX_MSG MUXMsg; + }; +} __attribute__ ((packed)) QCQMIMSG, *PQCQMIMSG; + +typedef struct __IPV4 { + uint32_t Address; + uint32_t Gateway; + uint32_t SubnetMask; + uint32_t DnsPrimary; + uint32_t DnsSecondary; + uint32_t Mtu; +} IPV4_T; + +typedef struct __IPV6 { + UCHAR Address[16]; + UCHAR Gateway[16]; + UCHAR SubnetMask[16]; + UCHAR DnsPrimary[16]; + UCHAR DnsSecondary[16]; + UCHAR PrefixLengthIPAddr; + UCHAR PrefixLengthGateway; + ULONG Mtu; +} IPV6_T; + +typedef struct { + UINT size; + UINT rx_urb_size; + UINT ep_type; + UINT iface_id; + UINT MuxId; + UINT ul_data_aggregation_max_datagrams; //0x17 + UINT ul_data_aggregation_max_size ;//0x18 + UINT dl_minimum_padding; //0x1A +} QMAP_SETTING; + +//Configured downlink data aggregationprotocol +#define WDA_DL_DATA_AGG_DISABLED (0x00) //DL data aggregation is disabled (default) +#define WDA_DL_DATA_AGG_TLP_ENABLED (0x01) // DL TLP is enabled +#define WDA_DL_DATA_AGG_QC_NCM_ENABLED (0x02) // DL QC_NCM isenabled +#define WDA_DL_DATA_AGG_MBIM_ENABLED (0x03) // DL MBIM isenabled +#define WDA_DL_DATA_AGG_RNDIS_ENABLED (0x04) // DL RNDIS is enabled +#define WDA_DL_DATA_AGG_QMAP_ENABLED (0x05) // DL QMAP isenabled +#define WDA_DL_DATA_AGG_QMAP_V2_ENABLED (0x06) // DL QMAP V2 is enabled +#define WDA_DL_DATA_AGG_QMAP_V3_ENABLED (0x07) // DL QMAP V3 is enabled +#define WDA_DL_DATA_AGG_QMAP_V4_ENABLED (0x08) // DL QMAP V4 is enabled +#define WDA_DL_DATA_AGG_QMAP_V5_ENABLED (0x09) // DL QMAP V5 is enabled + +typedef struct { + unsigned int size; + unsigned int rx_urb_size; + unsigned int ep_type; + unsigned int iface_id; + unsigned int qmap_mode; + unsigned int qmap_version; + unsigned int dl_minimum_padding; + char ifname[8][16]; + unsigned char mux_id[8]; +} RMNET_INFO; + +#define IpFamilyV4 (0x04) +#define IpFamilyV6 (0x06) + +struct __PROFILE; +struct qmi_device_ops { + int (*init)(struct __PROFILE *profile); + int (*deinit)(void); + int (*send)(PQCQMIMSG pRequest); + void* (*read)(void *pData); + + + // int (*thread_read)(struct __PROFILE *profile); + // int (*init)(struct __PROFILE *profile); + // int (*open)(struct __PROFILE *profile); + // int (*close)(struct __PROFILE *profile); + // int (*reopen)(struct __PROFILE *profile); + // int (*start_network)(struct __PROFILE *profile); + // int (*stop_network)(struct __PROFILE *profile); + // int (*query_network)(struct __PROFILE *profile); +}; +extern int (*qmidev_send)(PQCQMIMSG pRequest); + +#ifndef bool +#define bool uint8_t +#endif +typedef struct __PROFILE { + char *qmichannel; + char *usbnet_adapter; + char *qmapnet_adapter; + char *driver_name; + int qmap_mode; + int qmap_size; + int qmap_version; + const char *apn; + const char *user; + const char *password; + const char *pincode; + int auth; + int pdp; + int curIpFamily; + int rawIP; + int muxid; + int enable_bridge; + IPV4_T ipv4; + IPV6_T ipv6; + UINT PCSCFIpv4Addr1; + UINT PCSCFIpv4Addr2; + UCHAR PCSCFIpv6Addr1[16]; + UCHAR PCSCFIpv6Addr2[16]; + bool enable_ipv4; + bool enable_ipv6; + int apntype; + bool reattach_flag; + int hardware_interface; + int software_interface; + int busnum; + int devnum; + int usbmon_fd; + int usbmon_logfile_fd; + bool loopback_state; + int replication_factor; + const struct qmi_device_ops *qmi_ops; + RMNET_INFO rmnet_info; +} PROFILE_T; + +#ifdef QUECTEL_QMI_MERGE +#define MERGE_PACKET_IDENTITY 0x2c7c +#define MERGE_PACKET_VERSION 0x0001 +#define MERGE_PACKET_MAX_PAYLOAD_SIZE 56 +typedef struct __QMI_MSG_HEADER { + uint16_t idenity; + uint16_t version; + uint16_t cur_len; + uint16_t total_len; +} QMI_MSG_HEADER; + +typedef struct __QMI_MSG_PACKET { + QMI_MSG_HEADER header; + uint16_t len; + char buf[4096]; +} QMI_MSG_PACKET; +#endif + +typedef enum { + SIM_ABSENT = 0, + SIM_NOT_READY = 1, + SIM_READY = 2, /* SIM_READY means the radio state is RADIO_STATE_SIM_READY */ + SIM_PIN = 3, + SIM_PUK = 4, + SIM_NETWORK_PERSONALIZATION = 5, + SIM_BAD = 6, +} SIM_Status; + +#pragma pack(pop) + +#define WDM_DEFAULT_BUFSIZE 256 +#define RIL_REQUEST_QUIT 0x1000 +#define RIL_INDICATE_DEVICE_CONNECTED 0x1002 +#define RIL_INDICATE_DEVICE_DISCONNECTED 0x1003 +#define RIL_UNSOL_RESPONSE_VOICE_NETWORK_STATE_CHANGED 0x1004 +#define RIL_UNSOL_DATA_CALL_LIST_CHANGED 0x1005 +#define MODEM_REPORT_RESET_EVENT 0x1006 +#define RIL_UNSOL_LOOPBACK_CONFIG_IND 0x1007 + +extern int pthread_cond_timeout_np(pthread_cond_t *cond, pthread_mutex_t * mutex, unsigned msecs); +extern int QmiThreadSendQMITimeout(PQCQMIMSG pRequest, PQCQMIMSG *ppResponse, unsigned msecs, const char *funcname); +#define QmiThreadSendQMI(pRequest, ppResponse) QmiThreadSendQMITimeout(pRequest, ppResponse, 30 * 1000, __func__) +extern void QmiThreadRecvQMI(PQCQMIMSG pResponse); +extern void udhcpc_start(PROFILE_T *profile); +extern void udhcpc_stop(PROFILE_T *profile); +extern void ql_set_driver_link_state(PROFILE_T *profile, int link_state); +extern void ql_set_driver_qmap_setting(PROFILE_T *profile, QMAP_SETTING *qmap_settings); +extern void ql_get_driver_rmnet_info(PROFILE_T *profile, RMNET_INFO *rmnet_info); +extern void dump_qmi(void *dataBuffer, int dataLen); +extern void qmidevice_send_event_to_main(int triger_event); +extern void qmidevice_send_event_to_main_ext(int triger_event, void *data, unsigned len); +extern int requestSetEthMode(PROFILE_T *profile); +extern int requestGetSIMStatus(SIM_Status *pSIMStatus); +extern int requestEnterSimPin(const CHAR *pPinCode); +extern int requestGetICCID(void); +extern int requestGetIMSI(void); +extern int requestRegistrationState(UCHAR *pPSAttachedState); +extern int requestQueryDataCall(UCHAR *pConnectionStatus, int curIpFamily); +extern int requestSetupDataCall(PROFILE_T *profile, int curIpFamily); +extern int requestDeactivateDefaultPDP(PROFILE_T *profile, int curIpFamily); +extern int requestSetProfile(PROFILE_T *profile); +extern int requestGetProfile(PROFILE_T *profile); +extern int requestGetSignalInfo(void); +extern int requestBaseBandVersion(const char **pp_reversion); +extern int requestGetIPAddress(PROFILE_T *profile, int curIpFamily); +extern int requestSetOperatingMode(UCHAR OperatingMode); +extern int requestSetLoopBackState(UCHAR loopback_state, ULONG replication_factor); + +extern void cond_setclock_attr(pthread_cond_t *cond, clockid_t clock); +extern int mbim_main(PROFILE_T *profile); +extern int get_driver_type(PROFILE_T *profile); +extern BOOL qmidevice_detect(char *qmichannel, char *usbnet_adapter, unsigned bufsize, int *pbusnum, int *pdevnum); +int mhidevice_detect(char *qmichannel, char *usbnet_adapter, PROFILE_T *profile); +extern int ql_bridge_mode_detect(PROFILE_T *profile); +extern int ql_enable_qmi_wwan_rawip_mode(PROFILE_T *profile); +extern int ql_driver_type_detect(PROFILE_T *profile); +extern int ql_qmap_mode_detect(PROFILE_T *profile); +extern const struct qmi_device_ops gobi_qmidev_ops; +extern const struct qmi_device_ops qmiwwan_qmidev_ops; + +#define qmidev_is_gobinet(_qmichannel) (strncmp(_qmichannel, "/dev/qcqmi", strlen("/dev/qcqmi")) == 0) +#define qmidev_is_qmiwwan(_qmichannel) (strncmp(_qmichannel, "/dev/cdc-wdm", strlen("/dev/cdc-wdm")) == 0) +#define qmidev_is_pciemhi(_qmichannel) (strncmp(_qmichannel, "/dev/mhi_", strlen("/dev/mhi_")) == 0) + +#define driver_is_qmi(_drv_name) (strncasecmp(_drv_name, "qmi_wwan", strlen("qmi_wwan")) == 0) +#define driver_is_mbim(_drv_name) (strncasecmp(_drv_name, "cdc_mbim", strlen("cdc_mbim")) == 0) + +extern FILE *logfilefp; +extern int debug_qmi; +extern int qmidevice_control_fd[2]; +extern int qmiclientId[QMUX_TYPE_WDS_ADMIN + 1]; +extern USHORT le16_to_cpu(USHORT v16); +extern UINT le32_to_cpu (UINT v32); +extern UINT ql_swap32(UINT v32); +extern USHORT cpu_to_le16(USHORT v16); +extern UINT cpu_to_le32(UINT v32); +extern void update_resolv_conf(int iptype, const char *ifname, const char *dns1, const char *dns2); +void update_ipv4_address(const char *ifname, const char *ip, const char *gw, unsigned prefix); +void update_ipv6_address(const char *ifname, const char *ip, const char *gw, unsigned prefix); +int reattach_driver(PROFILE_T *profile); + +enum +{ + DRV_INVALID, + SOFTWARE_QMI, + SOFTWARE_MBIM, + HARDWARE_PCIE, + HARDWARE_USB, +}; + +enum +{ + SIG_EVENT_START, + SIG_EVENT_CHECK, + SIG_EVENT_STOP, +}; + +#define CM_MAX_BUFF 256 +#define strset(k, v) {if (k) free(k); k = strdup(v);} +#define mfree(v) {if (v) {free(v); v = NULL;} + +#ifdef CM_DEBUG +#define dbg_time(fmt, args...) do { \ + fprintf(stdout, "[%15s-%04d: %s] " fmt "\n", __FILE__, __LINE__, get_time(), ##args); \ + if (logfilefp) fprintf(logfilefp, "[%s-%04d: %s] " fmt "\n", __FILE__, __LINE__, get_time(), ##args); \ +} while(0) +#else +#define dbg_time(fmt, args...) do { \ + fprintf(stdout, "[%s] " fmt "\n", get_time(), ##args); \ + if (logfilefp) fprintf(logfilefp, "[%s] " fmt "\n", get_time(), ##args); \ +} while(0) +#endif +#endif diff --git a/root/package/link4all/quectel-CM/src/QmiWwanCM.c b/root/package/link4all/quectel-CM/src/QmiWwanCM.c new file mode 100644 index 00000000..8061c3bf --- /dev/null +++ b/root/package/link4all/quectel-CM/src/QmiWwanCM.c @@ -0,0 +1,430 @@ +/****************************************************************************** + @file QmiWwanCM.c + @brief QMI WWAN connectivity manager. + + DESCRIPTION + Connectivity Management Tool for USB network adapter of Quectel wireless cellular modules. + + INITIALIZATION AND SEQUENCING REQUIREMENTS + None. + + --------------------------------------------------------------------------- + Copyright (c) 2016 - 2020 Quectel Wireless Solution, Co., Ltd. All Rights Reserved. + Quectel Wireless Solution Proprietary and Confidential. + --------------------------------------------------------------------------- +******************************************************************************/ + +#include +#include +#include +#include +#include +typedef unsigned short sa_family_t; +#include +#include "QMIThread.h" + +#ifdef CONFIG_QMIWWAN +static int cdc_wdm_fd = -1; +static UCHAR GetQCTLTransactionId(void) { + static int TransactionId = 0; + if (++TransactionId > 0xFF) + TransactionId = 1; + return TransactionId; +} + +typedef USHORT (*CUSTOMQCTL)(PQMICTL_MSG pCTLMsg, void *arg); + +static PQCQMIMSG ComposeQCTLMsg(USHORT QMICTLType, CUSTOMQCTL customQctlMsgFunction, void *arg) { + UCHAR QMIBuf[WDM_DEFAULT_BUFSIZE]; + PQCQMIMSG pRequest = (PQCQMIMSG)QMIBuf; + int Length; + + pRequest->QMIHdr.IFType = USB_CTL_MSG_TYPE_QMI; + pRequest->QMIHdr.CtlFlags = 0x00; + pRequest->QMIHdr.QMIType = QMUX_TYPE_CTL; + pRequest->QMIHdr.ClientId= 0x00; + + pRequest->CTLMsg.QMICTLMsgHdr.CtlFlags = QMICTL_FLAG_REQUEST; + pRequest->CTLMsg.QMICTLMsgHdr.TransactionId = GetQCTLTransactionId(); + pRequest->CTLMsg.QMICTLMsgHdr.QMICTLType = cpu_to_le16(QMICTLType); + if (customQctlMsgFunction) + pRequest->CTLMsg.QMICTLMsgHdr.Length = cpu_to_le16(customQctlMsgFunction(&pRequest->CTLMsg, arg) - sizeof(QCQMICTL_MSG_HDR)); + else + pRequest->CTLMsg.QMICTLMsgHdr.Length = cpu_to_le16(0x0000); + + pRequest->QMIHdr.Length = cpu_to_le16(le16_to_cpu(pRequest->CTLMsg.QMICTLMsgHdr.Length) + sizeof(QCQMICTL_MSG_HDR) + sizeof(QCQMI_HDR) - 1); + Length = le16_to_cpu(pRequest->QMIHdr.Length) + 1; + + pRequest = (PQCQMIMSG)malloc(Length); + if (pRequest == NULL) { + dbg_time("%s fail to malloc", __func__); + } else { + memcpy(pRequest, QMIBuf, Length); + } + + return pRequest; +} + +static USHORT CtlGetVersionReq(PQMICTL_MSG QCTLMsg, void *arg) +{ + (void)arg; + QCTLMsg->GetVersionReq.TLVType = QCTLV_TYPE_REQUIRED_PARAMETER; + QCTLMsg->GetVersionReq.TLVLength = cpu_to_le16(0x0001); + QCTLMsg->GetVersionReq.QMUXTypes = QMUX_TYPE_ALL; + return sizeof(QMICTL_GET_VERSION_REQ_MSG); +} + +static USHORT CtlGetClientIdReq(PQMICTL_MSG QCTLMsg, void *arg) { + QCTLMsg->GetClientIdReq.TLVType = QCTLV_TYPE_REQUIRED_PARAMETER; + QCTLMsg->GetClientIdReq.TLVLength = cpu_to_le16(0x0001); + QCTLMsg->GetClientIdReq.QMIType = ((UCHAR *)arg)[0]; + return sizeof(QMICTL_GET_CLIENT_ID_REQ_MSG); +} + +static USHORT CtlReleaseClientIdReq(PQMICTL_MSG QCTLMsg, void *arg) { + QCTLMsg->ReleaseClientIdReq.TLVType = QCTLV_TYPE_REQUIRED_PARAMETER; + QCTLMsg->ReleaseClientIdReq.TLVLength = cpu_to_le16(0x0002); + QCTLMsg->ReleaseClientIdReq.QMIType = ((UCHAR *)arg)[0]; + QCTLMsg->ReleaseClientIdReq.ClientId = ((UCHAR *)arg)[1] ; + return sizeof(QMICTL_RELEASE_CLIENT_ID_REQ_MSG); +} + +static int QmiWwanSendQMI(PQCQMIMSG pRequest) { + struct pollfd pollfds[]= {{cdc_wdm_fd, POLLOUT, 0}}; + int ret; + + if (cdc_wdm_fd == -1) { + dbg_time("%s cdc_wdm_fd = -1", __func__); + return -ENODEV; + } + + if (pRequest->QMIHdr.QMIType == QMUX_TYPE_WDS_IPV6) + pRequest->QMIHdr.QMIType = QMUX_TYPE_WDS; + + do { + ret = poll(pollfds, sizeof(pollfds)/sizeof(pollfds[0]), 5000); + } while ((ret < 0) && (errno == EINTR)); + + if (pollfds[0].revents & POLLOUT) { + ssize_t nwrites = le16_to_cpu(pRequest->QMIHdr.Length) + 1; + ret = write(cdc_wdm_fd, pRequest, nwrites); + if (ret == nwrites) { + ret = 0; + } else { + dbg_time("%s write=%d, errno: %d (%s)", __func__, ret, errno, strerror(errno)); + } + } else { + dbg_time("%s poll=%d, revents = 0x%x, errno: %d (%s)", __func__, ret, pollfds[0].revents, errno, strerror(errno)); + } + + return ret; +} + +static int QmiWwanGetClientID(UCHAR QMIType) { + PQCQMIMSG pResponse; + + QmiThreadSendQMI(ComposeQCTLMsg(QMICTL_GET_CLIENT_ID_REQ, CtlGetClientIdReq, &QMIType), &pResponse); + + if (pResponse) { + USHORT QMUXResult = cpu_to_le16(pResponse->CTLMsg.QMICTLMsgHdrRsp.QMUXResult); // QMI_RESULT_SUCCESS + USHORT QMUXError = cpu_to_le16(pResponse->CTLMsg.QMICTLMsgHdrRsp.QMUXError); // QMI_ERR_INVALID_ARG + //UCHAR QMIType = pResponse->CTLMsg.GetClientIdRsp.QMIType; + UCHAR ClientId = pResponse->CTLMsg.GetClientIdRsp.ClientId; + + if (!QMUXResult && !QMUXError && (QMIType == pResponse->CTLMsg.GetClientIdRsp.QMIType)) { + switch (QMIType) { + case QMUX_TYPE_WDS: dbg_time("Get clientWDS = %d", ClientId); break; + case QMUX_TYPE_DMS: dbg_time("Get clientDMS = %d", ClientId); break; + case QMUX_TYPE_NAS: dbg_time("Get clientNAS = %d", ClientId); break; + case QMUX_TYPE_QOS: dbg_time("Get clientQOS = %d", ClientId); break; + case QMUX_TYPE_WMS: dbg_time("Get clientWMS = %d", ClientId); break; + case QMUX_TYPE_PDS: dbg_time("Get clientPDS = %d", ClientId); break; + case QMUX_TYPE_UIM: dbg_time("Get clientUIM = %d", ClientId); break; + case QMUX_TYPE_WDS_ADMIN: dbg_time("Get clientWDA = %d", ClientId); + break; + default: break; + } + return ClientId; + } + } + return 0; +} + +static int QmiWwanReleaseClientID(QMI_SERVICE_TYPE QMIType, UCHAR ClientId) { + UCHAR argv[] = {QMIType, ClientId}; + QmiThreadSendQMI(ComposeQCTLMsg(QMICTL_RELEASE_CLIENT_ID_REQ, CtlReleaseClientIdReq, argv), NULL); + return 0; +} + +static int QmiWwanInit(PROFILE_T *profile) { + unsigned i; + int ret; + PQCQMIMSG pResponse; + + if (profile->qmap_mode == 0 || profile->qmap_mode == 1) + { + for (i = 0; i < 10; i++) { + ret = QmiThreadSendQMITimeout(ComposeQCTLMsg(QMICTL_SYNC_REQ, NULL, NULL), NULL, 1 * 1000, __func__); + if (!ret) + break; + sleep(1); + } + if (ret) + return ret; + } + + QmiThreadSendQMI(ComposeQCTLMsg(QMICTL_GET_VERSION_REQ, CtlGetVersionReq, NULL), &pResponse); + if (profile->qmap_mode) { + if (pResponse) { + if (pResponse->CTLMsg.QMICTLMsgHdrRsp.QMUXResult == 0 && pResponse->CTLMsg.QMICTLMsgHdrRsp.QMUXError == 0) { + uint8_t NumElements = 0; + + for (NumElements = 0; NumElements < pResponse->CTLMsg.GetVersionRsp.NumElements; NumElements++) { +#if 0 + dbg_time("QMUXType = %02x Version = %d.%d", + pResponse->CTLMsg.GetVersionRsp.TypeVersion[NumElements].QMUXType, + pResponse->CTLMsg.GetVersionRsp.TypeVersion[NumElements].MajorVersion, + pResponse->CTLMsg.GetVersionRsp.TypeVersion[NumElements].MinorVersion); +#endif + if (pResponse->CTLMsg.GetVersionRsp.TypeVersion[NumElements].QMUXType == QMUX_TYPE_WDS_ADMIN) + profile->qmap_version = (pResponse->CTLMsg.GetVersionRsp.TypeVersion[NumElements].MinorVersion > 16); + } + } + } + } + if (pResponse) free(pResponse); + qmiclientId[QMUX_TYPE_WDS] = QmiWwanGetClientID(QMUX_TYPE_WDS); + if (profile->enable_ipv6) + qmiclientId[QMUX_TYPE_WDS_IPV6] = QmiWwanGetClientID(QMUX_TYPE_WDS); + qmiclientId[QMUX_TYPE_DMS] = QmiWwanGetClientID(QMUX_TYPE_DMS); + qmiclientId[QMUX_TYPE_NAS] = QmiWwanGetClientID(QMUX_TYPE_NAS); + qmiclientId[QMUX_TYPE_UIM] = QmiWwanGetClientID(QMUX_TYPE_UIM); + if (profile->qmap_mode == 0 || profile->qmap_mode == 1) + qmiclientId[QMUX_TYPE_WDS_ADMIN] = QmiWwanGetClientID(QMUX_TYPE_WDS_ADMIN); + + return 0; +} + +static int QmiWwanDeInit(void) { + unsigned int i; + for (i = 0; i < sizeof(qmiclientId)/sizeof(qmiclientId[0]); i++) + { + if (qmiclientId[i] != 0) + { + QmiWwanReleaseClientID(i, qmiclientId[i]); + qmiclientId[i] = 0; + } + } + + return 0; +} + +#define QUECTEL_QMI_PROXY "quectel-qmi-proxy" +static int qmi_proxy_open(const char *name) { + int sockfd = -1; + int reuse_addr = 1; + struct sockaddr_un sockaddr; + socklen_t alen; + + /*Create server socket*/ + (sockfd = socket(AF_LOCAL, SOCK_STREAM, 0)); + if (sockfd < 0) + return sockfd; + + memset(&sockaddr, 0, sizeof(sockaddr)); + sockaddr.sun_family = AF_LOCAL; + sockaddr.sun_path[0] = 0; + memcpy(sockaddr.sun_path + 1, name, strlen(name) ); + + alen = strlen(name) + offsetof(struct sockaddr_un, sun_path) + 1; + if(connect(sockfd, (struct sockaddr *)&sockaddr, alen) < 0) { + close(sockfd); + dbg_time("%s connect %s errno: %d (%s)\n", __func__, name, errno, strerror(errno)); + return -1; + } + (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse_addr,sizeof(reuse_addr))); + + dbg_time("connect to %s sockfd = %d\n", name, sockfd); + + return sockfd; +} + +static ssize_t qmi_proxy_read (int fd, void *buf, size_t size) { + ssize_t nreads; + PQCQMI_HDR pHdr = (PQCQMI_HDR)buf; + + (void)size; + nreads = read(fd, pHdr, sizeof(QCQMI_HDR)); + if (nreads == sizeof(QCQMI_HDR)) { + nreads += read(fd, pHdr+1, le16_to_cpu(pHdr->Length) + 1 - sizeof(QCQMI_HDR)); + } + + return nreads; +} + +#ifdef QUECTEL_QMI_MERGE +static int QmiWwanMergeQmiRsp(void *buf, ssize_t *src_size) { + static QMI_MSG_PACKET s_QMIPacket; + QMI_MSG_HEADER *header = NULL; + ssize_t size = *src_size; + + if((uint16_t)size < sizeof(QMI_MSG_HEADER)) + return -1; + + header = (QMI_MSG_HEADER *)buf; + if(le16_to_cpu(header->idenity) != MERGE_PACKET_IDENTITY || le16_to_cpu(header->version) != MERGE_PACKET_VERSION || le16_to_cpu(header->cur_len) > le16_to_cpu(header->total_len)) + return -1; + + if(le16_to_cpu(header->cur_len) == le16_to_cpu(header->total_len)) { + *src_size = le16_to_cpu(header->total_len); + memcpy(buf, buf + sizeof(QMI_MSG_HEADER), *src_size); + s_QMIPacket.len = 0; + return 0; + } + + memcpy(s_QMIPacket.buf + s_QMIPacket.len, buf + sizeof(QMI_MSG_HEADER), le16_to_cpu(header->cur_len)); + s_QMIPacket.len += le16_to_cpu(header->cur_len); + + if (le16_to_cpu(header->cur_len) < MERGE_PACKET_MAX_PAYLOAD_SIZE || s_QMIPacket.len >= le16_to_cpu(header->total_len)) { + memcpy(buf, s_QMIPacket.buf, s_QMIPacket.len); + *src_size = s_QMIPacket.len; + s_QMIPacket.len = 0; + return 0; + } + + return -1; +} +#endif + +static void * QmiWwanThread(void *pData) { + PROFILE_T *profile = (PROFILE_T *)pData; + const char *cdc_wdm = (const char *)profile->qmichannel; + int wait_for_request_quit = 0; + char servername[32]; + int num = cdc_wdm[strlen(cdc_wdm)-1]-'0'; + + sprintf(servername, QUECTEL_QMI_PROXY"%d", num); + + if (profile->qmap_mode == 0 || profile->qmap_mode == 1) + cdc_wdm_fd = open(cdc_wdm, O_RDWR | O_NONBLOCK | O_NOCTTY); + else + cdc_wdm_fd = qmi_proxy_open(servername); + + if (cdc_wdm_fd == -1) { + dbg_time("%s Failed to open %s, errno: %d (%s)", __func__, cdc_wdm, errno, strerror(errno)); + qmidevice_send_event_to_main(RIL_INDICATE_DEVICE_DISCONNECTED); + pthread_exit(NULL); + return NULL; + } + + fcntl(cdc_wdm_fd, F_SETFL, fcntl(cdc_wdm_fd,F_GETFL) | O_NONBLOCK); + fcntl(cdc_wdm_fd, F_SETFD, FD_CLOEXEC); + + dbg_time("cdc_wdm_fd = %d", cdc_wdm_fd); + + qmidevice_send_event_to_main(RIL_INDICATE_DEVICE_CONNECTED); + while (1) { + struct pollfd pollfds[] = {{qmidevice_control_fd[1], POLLIN, 0}, {cdc_wdm_fd, POLLIN, 0}}; + int ne, ret, nevents = sizeof(pollfds)/sizeof(pollfds[0]); + + do { + ret = poll(pollfds, nevents, wait_for_request_quit ? 1000 : -1); + } while ((ret < 0) && (errno == EINTR)); + + if (ret == 0 && wait_for_request_quit) { + QmiThreadRecvQMI(NULL); + continue; + } + + if (ret <= 0) { + dbg_time("%s poll=%d, errno: %d (%s)", __func__, ret, errno, strerror(errno)); + break; + } + + for (ne = 0; ne < nevents; ne++) { + int fd = pollfds[ne].fd; + short revents = pollfds[ne].revents; + + //dbg_time("{%d, %x, %x}", pollfds[ne].fd, pollfds[ne].events, pollfds[ne].revents); + + if (revents & (POLLERR | POLLHUP | POLLNVAL)) { + dbg_time("%s poll err/hup/inval", __func__); + dbg_time("poll fd = %d, events = 0x%04x", fd, revents); + if (fd == cdc_wdm_fd) { + } else { + } + if (revents & (POLLHUP | POLLNVAL)) //EC20 bug, Can get POLLERR + goto __QmiWwanThread_quit; + } + + if ((revents & POLLIN) == 0) + continue; + + if (fd == qmidevice_control_fd[1]) { + int triger_event; + if (read(fd, &triger_event, sizeof(triger_event)) == sizeof(triger_event)) { + //DBG("triger_event = 0x%x", triger_event); + switch (triger_event) { + case RIL_REQUEST_QUIT: + goto __QmiWwanThread_quit; + break; + case SIG_EVENT_STOP: + wait_for_request_quit = 1; + break; + default: + break; + } + } + } + + if (fd == cdc_wdm_fd) { + ssize_t nreads; + static UCHAR QMIBuf[4096]; + PQCQMIMSG pResponse = (PQCQMIMSG)QMIBuf; + + if (profile->qmap_mode == 0 || profile->qmap_mode == 1) + nreads = read(fd, QMIBuf, sizeof(QMIBuf)); + else + nreads = qmi_proxy_read(fd, QMIBuf, sizeof(QMIBuf)); + //dbg_time("%s read=%d errno: %d (%s)", __func__, (int)nreads, errno, strerror(errno)); + if (nreads <= 0) { + dbg_time("%s read=%d errno: %d (%s)", __func__, (int)nreads, errno, strerror(errno)); + break; + } +#ifdef QUECTEL_QMI_MERGE + if((profile->qmap_mode == 0 || profile->qmap_mode == 1) && QmiWwanMergeQmiRsp(QMIBuf, &nreads)) + continue; +#endif + if (nreads != (le16_to_cpu(pResponse->QMIHdr.Length) + 1)) { + dbg_time("%s nreads=%d, pQCQMI->QMIHdr.Length = %d", __func__, (int)nreads, le16_to_cpu(pResponse->QMIHdr.Length)); + continue; + } + + QmiThreadRecvQMI(pResponse); + } + } + } + +__QmiWwanThread_quit: + if (cdc_wdm_fd != -1) { close(cdc_wdm_fd); cdc_wdm_fd = -1; } + qmidevice_send_event_to_main(RIL_INDICATE_DEVICE_DISCONNECTED); + QmiThreadRecvQMI(NULL); //main thread may pending on QmiThreadSendQMI() + dbg_time("%s exit", __func__); + pthread_exit(NULL); + return NULL; +} + +#else +static int QmiWwanSendQMI(PQCQMIMSG pRequest) {return -1;} +static int QmiWwanInit(PROFILE_T *profile) {return -1;} +static int QmiWwanDeInit(void) {return -1;} +static void * QmiWwanThread(void *pData) {dbg_time("please set CONFIG_QMIWWAN"); return NULL;} +#endif + +const struct qmi_device_ops qmiwwan_qmidev_ops = { + .init = QmiWwanInit, + .deinit = QmiWwanDeInit, + .send = QmiWwanSendQMI, + .read = QmiWwanThread, +}; + diff --git a/root/package/link4all/quectel-CM/src/ReleaseNote.txt b/root/package/link4all/quectel-CM/src/ReleaseNote.txt new file mode 100644 index 00000000..10033042 --- /dev/null +++ b/root/package/link4all/quectel-CM/src/ReleaseNote.txt @@ -0,0 +1,69 @@ +Release Notes + +[WCDMA<E_QConnectManager_Linux&Android_V1.5.3] +Date: 2019/12/11 +enhancement: +1. support show SignalInfo, controlled by macro CONFIG_SIGNALINFO +2. support show 5G_NSA/5G_NA +3. support Microsoft Extend MBIM message + +[WCDMA<E_QConnectManager_Linux&Android_V1.2.1] +Date: 2019/02/26 +enhancement: +1. Implement help message. + +root@ubuntu:# ./quectel-CM -h +[02-26_10:39:21:353] Usage: ./quectel-CM [options] +[02-26_10:39:21:353] -s [apn [user password auth]] Set apn/user/password/auth get from your network provider +[02-26_10:39:21:353] -p pincode Verify sim card pin if sim card is locked +[02-26_10:39:21:353] -f logfilename Save log message of this program to file +[02-26_10:39:21:353] -i interface Specify network interface(default auto-detect) +[02-26_10:39:21:353] -4 IPv4 protocol +[02-26_10:39:21:353] -6 IPv6 protocol +[02-26_10:39:21:353] -m muxID Specify muxid when set multi-pdn data connection. +[02-26_10:39:21:353] -n channelID Specify channelID when set multi-pdn data connection(default 1). +[02-26_10:39:21:353] [Examples] +[02-26_10:39:21:353] Example 1: ./quectel-CM +[02-26_10:39:21:353] Example 2: ./quectel-CM -s 3gnet +[02-26_10:39:21:353] Example 3: ./quectel-CM -s 3gnet carl 1234 0 -p 1234 -f gobinet_log.txt +root@ubuntu:# +2. Support bridge mode when set multi-pdn data connections. +3. Host device can access network in bridge mode. + +[WCDMA<E_QConnectManager_Linux&Android_V1.1.46] +Date: 2019/02/18 +enhancement: +1. support only IPV6 data call. quectel-CM now support three dialing methods: IPV4 only, IPV6 only, IPV4V6. + ./quectel-CM -4(or no argument) only IPV4 + -6 only IPV6 + -4 -6 IPV4 && IPV6 + +[WCDMA<E_QConnectManager_Linux&Android_V1.1.45] +Date: 2018/09/13 +enhancement: +1. support EG12 PCIE interface + +[WCDMA<E_QConnectManager_Linux&Android_V1.1.44] +Date: 2018/09/10 +enhancement: +1. support setup IPV4&IPV6 data call. + +[WCDMA<E_QConnectManager_Linux&Android_V1.1.43] +[WCDMA<E_QConnectManager_Linux&Android_V1.1.42] +Date: 2018/08/29 +enhancement: +1. support QMI_WWAN's QMAP fucntion and bridge mode, please contact Quectel FAE to get qmi_wwan.c patch. + when enable QMI_WWAN's QMAP IP Mux function, must run 'quectel-qmi-proxy -d /dev/cdc-wdmX' before quectel-CM + +[WCDMA<E_QConnectManager_Linux&Android_V1.1.41] +Date: 2018/05/24 +enhancement: +1. fix a cdma data call error + +[WCDMA<E_QConnectManager_Linux&Android_V1.1.40] +Date: 2018/05/12 +enhancement: +1. support GobiNet's QMAP fucntion and bridge mode. + 'Quectel_WCDMA<E_Linux&Android_GobiNet_Driver_V1.3.5' and later version is required to use QMAP and bridge mode. + for detail, please refer to GobiNet Driver + diff --git a/root/package/link4all/quectel-CM/src/default.script b/root/package/link4all/quectel-CM/src/default.script new file mode 100644 index 00000000..26b95c1b --- /dev/null +++ b/root/package/link4all/quectel-CM/src/default.script @@ -0,0 +1,63 @@ +#!/bin/sh +# Busybox udhcpc dispatcher script. Copyright (C) 2009 by Axel Beckert. +# +# Based on the busybox example scripts and the old udhcp source +# package default.* scripts. + +RESOLV_CONF="/etc/resolv.conf" + +case $1 in + bound|renew) + [ -n "$broadcast" ] && BROADCAST="broadcast $broadcast" + [ -n "$subnet" ] && NETMASK="netmask $subnet" + + /sbin/ifconfig $interface $ip $BROADCAST $NETMASK + + if [ -n "$router" ]; then + echo "$0: Resetting default routes" + while /sbin/route del default gw 0.0.0.0 dev $interface; do :; done + + metric=0 + for i in $router; do + /sbin/route add default gw $i dev $interface metric $metric + metric=$(($metric + 1)) + done + fi + + # Update resolver configuration file + R="" + [ -n "$domain" ] && R="domain $domain +" + for i in $dns; do + echo "$0: Adding DNS $i" + R="${R}nameserver $i +" + done + + if [ -x /sbin/resolvconf ]; then + echo -n "$R" | resolvconf -a "${interface}.udhcpc" + else + echo -n "$R" > "$RESOLV_CONF" + fi + ;; + + deconfig) + if [ -x /sbin/resolvconf ]; then + resolvconf -d "${interface}.udhcpc" + fi + /sbin/ifconfig $interface 0.0.0.0 + ;; + + leasefail) + echo "$0: Lease failed: $message" + ;; + + nak) + echo "$0: Received a NAK: $message" + ;; + + *) + echo "$0: Unknown udhcpc command: $1"; + exit 1; + ;; +esac diff --git a/root/package/link4all/quectel-CM/src/default.script_ip b/root/package/link4all/quectel-CM/src/default.script_ip new file mode 100644 index 00000000..24f8e59c --- /dev/null +++ b/root/package/link4all/quectel-CM/src/default.script_ip @@ -0,0 +1,61 @@ +#!/bin/sh +# Busybox udhcpc dispatcher script. Copyright (C) 2009 by Axel Beckert. +# +# Based on the busybox example scripts and the old udhcp source +# package default.* scripts. + +RESOLV_CONF="/etc/resolv.conf" +IPCMD=`which ip` + +case $1 in + bound|renew) + $IPCMD address add broadcast $broadcast $ip/$subnet dev $interface + + if [ -n "$router" ]; then + echo "$0: Resetting default routes" + while $IPCMD route del default dev $interface; do :; done + + metric=0 + for i in $router; do + $IPCMD route add default dev $interface via $router metric $metric + metric=$(($metric + 1)) + done + fi + + # Update resolver configuration file + R="" + [ -n "$domain" ] && R="domain $domain +" + for i in $dns; do + echo "$0: Adding DNS $i" + R="${R}nameserver $i +" + done + + if [ -x /sbin/resolvconf ]; then + echo -n "$R" | resolvconf -a "${interface}.udhcpc" + else + echo -n "$R" > "$RESOLV_CONF" + fi + ;; + + deconfig) + if [ -x /sbin/resolvconf ]; then + resolvconf -d "${interface}.udhcpc" + fi + $IPCMD address flush dev $interface + ;; + + leasefail) + echo "$0: Lease failed: $message" + ;; + + nak) + echo "$0: Received a NAK: $message" + ;; + + *) + echo "$0: Unknown udhcpc command: $1"; + exit 1; + ;; +esac diff --git a/root/package/link4all/quectel-CM/src/device.bak.c b/root/package/link4all/quectel-CM/src/device.bak.c new file mode 100644 index 00000000..e2510d8d --- /dev/null +++ b/root/package/link4all/quectel-CM/src/device.bak.c @@ -0,0 +1,558 @@ +/****************************************************************************** + @file device.c + @brief QMI device dirver. + + DESCRIPTION + Connectivity Management Tool for USB network adapter of Quectel wireless cellular modules. + + INITIALIZATION AND SEQUENCING REQUIREMENTS + None. + + --------------------------------------------------------------------------- + Copyright (c) 2016 - 2020 Quectel Wireless Solution, Co., Ltd. All Rights Reserved. + Quectel Wireless Solution Proprietary and Confidential. + --------------------------------------------------------------------------- +******************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "QMIThread.h" +#include "ethtool-copy.h" + +#define USB_CLASS_COMM 2 +#define USB_CLASS_VENDOR_SPEC 0xff +#define USB_CDC_SUBCLASS_MBIM 0x0e + +#define USB_INTERFACE_IS_QMI(_Class, _SubClass) (_Class == 0xff && _SubClass == 0xff) +#define USB_INTERFACE_IS_MBIM(_Class, _SubClass) (_Class == USB_CLASS_COMM && _SubClass == USB_CDC_SUBCLASS_MBIM) + +#define CM_MAX_PATHLEN 256 + +#define CM_INVALID_VAL (~((int)0)) +/* get first line from file 'fname' + * And convert the content into a hex number, then return this number */ +static int file_get_value(const char *fname, int base) +{ + FILE *fp = NULL; + long num; + char buff[32 + 1] = {'\0'}; + char *endptr = NULL; + + fp = fopen(fname, "r"); + if (!fp) goto error; + if (fgets(buff, sizeof(buff), fp) == NULL) + goto error; + fclose(fp); + + num = (int)strtol(buff, &endptr, base); + if (errno == ERANGE && (num == LONG_MAX || num == LONG_MIN)) + goto error; + /* if there is no digit in buff */ + if (endptr == buff) + goto error; + + if (debug_qmi) + dbg_time("(%s) = %lx", fname, num); + return (int)num; + +error: + if (fp) fclose(fp); + return CM_INVALID_VAL; +} + +/* + * This function will search the directory 'dirname' and return the first child. + * '.' and '..' is ignored by default + */ +static int dir_get_child(const char *dirname, char *buff, unsigned bufsize) +{ + struct dirent *entptr = NULL; + DIR *dirptr = opendir(dirname); + if (!dirptr) + goto error; + while ((entptr = readdir(dirptr))) { + if (entptr->d_name[0] == '.') + continue; + snprintf(buff, bufsize, "%s", entptr->d_name); + break; + } + + closedir(dirptr); + return 0; +error: + buff[0] = '\0'; + if (dirptr) closedir(dirptr); + return -1; +} + +static int conf_get_val(const char *fname, const char *key) +{ + char buff[CM_MAX_BUFF] = {'\0'}; + FILE *fp = fopen(fname, "r"); + if (!fp) + goto error; + + while (fgets(buff, CM_MAX_BUFF, fp)) { + char prefix[CM_MAX_BUFF] = {'\0'}; + char tail[CM_MAX_BUFF] = {'\0'}; + /* To eliminate cppcheck warnning: Assume string length is no more than 15 */ + sscanf(buff, "%15[^=]=%15s", prefix, tail); + if (!strncasecmp(prefix, key, strlen(key))) { + fclose(fp); + return atoi(tail); + } + } + +error: + fclose(fp); + return CM_INVALID_VAL; +} + +static int detect_path_cdc_wdm_or_qcqmi(char *path, size_t len) +{ + (void)len; + size_t offset = strlen(path); + + if (!access(path, R_OK)) + { + // int bNumEndpoints; + int bInterfaceClass, bInterfaceSubClass; + + path[offset] = '\0'; + // should we check endpint??? why bNumEndpoints == 3???, for unisoc mbim bNumEndpoints = 1 + // strcat(path, "/bNumEndpoints"); + // bNumEndpoints = file_get_value(path, 16); + // if (bNumEndpoints != 3) + // return -1; + + path[offset] = '\0'; + strcat(path, "/bInterfaceClass"); + bInterfaceClass = file_get_value(path, 16); + + path[offset] = '\0'; + strcat(path, "/bInterfaceSubClass"); + bInterfaceSubClass = file_get_value(path, 16); + + if (USB_INTERFACE_IS_QMI(bInterfaceClass, bInterfaceSubClass)) + ; //qmi_wwan / GobiNet + else if (USB_INTERFACE_IS_MBIM(bInterfaceClass, bInterfaceSubClass)) + ; //cdc_mbim + else + return -1; + + path[offset] = '\0'; + strcat(path, "/GobiQMI"); + if (!access(path, R_OK)) + return 0; + + path[offset] = '\0'; + strcat(path, "/usbmisc"); + if (!access(path, R_OK)) + return 0; + + path[offset] = '\0'; + strcat(path, "/usb"); + if (!access(path, R_OK)) + return 0; + } + + return -1; +} + +/* To detect the device info of the modem. + * return: + * FALSE -> fail + * TRUE -> ok + */ +BOOL qmidevice_detect(char *qmichannel, char *usbnet_adapter, unsigned bufsize, int *pbusnum, int *pdevnum) { + struct dirent* ent = NULL; + DIR *pDir; + const char *rootdir = "/sys/bus/usb/devices"; + struct { + char path[255*2]; + char uevent[255*3]; + } *pl; + pl = (typeof(pl)) malloc(sizeof(*pl)); + memset(pl, 0x00, sizeof(*pl)); + + pDir = opendir(rootdir); + if (!pDir) { + dbg_time("opendir %s failed: %s", rootdir, strerror(errno)); + goto error; + } + + while ((ent = readdir(pDir)) != NULL) { + int idVendor; + int idProduct; + char netcard[32+1] = {'\0'}; + char device[32+1] = {'\0'}; + char devname[32+1+6] = {'\0'}; + int busnum, devnum; + int bNumInterfaces; + + if (ent->d_name[0] == 'u') + continue; + + snprintf(pl->path, sizeof(pl->path), "%s/%s/idVendor", rootdir, ent->d_name); + idVendor = file_get_value(pl->path, 16); + if (idVendor == CM_INVALID_VAL) + continue; + + snprintf(pl->path, sizeof(pl->path), "%s/%s/idProduct", rootdir, ent->d_name); + idProduct = file_get_value(pl->path, 16); + if (idProduct == CM_INVALID_VAL) + continue; + + snprintf(pl->path, sizeof(pl->path), "%s/%s/busnum", rootdir, ent->d_name); + busnum = file_get_value(pl->path, 10); + if (busnum == CM_INVALID_VAL) + continue; + + snprintf(pl->path, sizeof(pl->path), "%s/%s/devnum", rootdir, ent->d_name); + devnum = file_get_value(pl->path, 10); + if (devnum == CM_INVALID_VAL) + continue; + + if (idVendor == 0x2c7c || idVendor == 0x05c6) { + dbg_time("Find %s/%s idVendor=0x%x idProduct=0x%x, bus=0x%03x, dev=0x%03x", + rootdir, ent->d_name, idVendor, idProduct, busnum, devnum); + } + + snprintf(pl->path, sizeof(pl->path), "%s/%s/bNumInterfaces", rootdir, ent->d_name); + bNumInterfaces = file_get_value(pl->path, 10); + if (bNumInterfaces == CM_INVALID_VAL) + continue; + + /* get network interface */ + /* NOTICE: there is a case that, bNumberInterface=6, but the net interface is 8 */ + /* toolchain-mips_24kc_gcc-5.4.0_musl donot support GLOB_BRACE */ + bNumInterfaces += 8; + while (bNumInterfaces >= 0) { //RG500U's MBIM is at inteface 0 + snprintf(pl->path, sizeof(pl->path), "%s/%s:1.%d/net", rootdir, ent->d_name, bNumInterfaces-1); + dir_get_child(pl->path, netcard, sizeof(netcard)); + if (netcard[0]) + break; + bNumInterfaces--; + } + + if (netcard[0] == '\0') { //for centos 2.6.x + const char *n= "usb0"; + const char *c = "qcqmi0"; + + snprintf(pl->path, sizeof(pl->path), "%s/%s:1.4/net:%s", rootdir, ent->d_name, n); + if (!access(pl->path, F_OK)) { + snprintf(pl->path, sizeof(pl->path), "%s/%s:1.4/GobiQMI:%s", rootdir, ent->d_name, c); + if (!access(pl->path, F_OK)) { + snprintf(qmichannel, bufsize, "/dev/%s", c); + snprintf(usbnet_adapter, bufsize, "%s", n); + break; + } + } + } + + if (netcard[0] == '\0') + continue; + + /* not '-i iface' */ + if (usbnet_adapter[0] && strcmp(usbnet_adapter, netcard)) + continue; + + pl->path[strlen(pl->path)-strlen("/net")] = '\0'; + if (detect_path_cdc_wdm_or_qcqmi(pl->path, sizeof(pl->path))) + continue; + + /* get device(qcqmiX|cdc-wdmX) */ + dir_get_child(pl->path, device, sizeof(device)); + if (device[0] == '\0') + continue; + + /* There is a chance that, no device(qcqmiX|cdc-wdmX) is generated. We should warn user about that! */ + snprintf(devname, sizeof(devname), "/dev/%s", device); + if (access(devname, R_OK | F_OK) && errno == ENOENT) { + int major; + int minor; + int ret; + + dbg_time("%s access failed, errno: %d (%s)", devname, errno, strerror(errno)); + snprintf(pl->uevent, sizeof(pl->uevent), "%s/%s/uevent", pl->path, device); + major = conf_get_val(pl->uevent, "MAJOR"); + minor = conf_get_val(pl->uevent, "MINOR"); + if(major == CM_INVALID_VAL || minor == CM_INVALID_VAL) + dbg_time("get major and minor failed"); + + ret = mknod(devname, S_IFCHR|0666, (((major & 0xfff) << 8) | (minor & 0xff) | ((minor & 0xfff00) << 12))); + if (ret) + dbg_time("please mknod %s c %d %d", devname, major, minor); + } + + if (netcard[0] && device[0]) { + snprintf(qmichannel, bufsize, "/dev/%s", device); + snprintf(usbnet_adapter, bufsize, "%s", netcard); + dbg_time("Auto find qmichannel = %s", qmichannel); + dbg_time("Auto find usbnet_adapter = %s", usbnet_adapter); + *pbusnum = busnum; + *pdevnum = devnum; + break; + } + } + closedir(pDir); + + if (qmichannel[0] == '\0' || usbnet_adapter[0] == '\0') { + dbg_time("network interface '%s' or qmidev '%s' is not exist", usbnet_adapter, qmichannel); + goto error; + } + free(pl); + return TRUE; +error: + free(pl); + return FALSE; +} + +int mhidevice_detect(char *qmichannel, char *usbnet_adapter, PROFILE_T *profile) { + if (!access("/sys/class/net/pcie_mhi0", F_OK)) + strcpy(usbnet_adapter, "pcie_mhi0"); + else if (!access("/sys/class/net/rmnet_mhi0", F_OK)) + strcpy(usbnet_adapter, "rmnet_mhi0"); + else { + dbg_time("qmidevice_detect failed"); + goto error; + } + + if (!access("/dev/mhi_MBIM", F_OK)) { + strcpy(qmichannel, "/dev/mhi_MBIM"); + profile->software_interface = SOFTWARE_MBIM; + } + else if (!access("/dev/mhi_QMI0", F_OK)) { + strcpy(qmichannel, "/dev/mhi_QMI0"); + profile->software_interface = SOFTWARE_QMI; + } + else { + goto error; + } + + return 1; +error: + return 0; +} + +int get_driver_type(PROFILE_T *profile) +{ + char path[CM_MAX_PATHLEN+1] = {'\0'}; + int bInterfaceClass; + int type = DRV_INVALID; + + snprintf(path, sizeof(path), "/sys/class/net/%s/device/bInterfaceClass", profile->usbnet_adapter); + bInterfaceClass = file_get_value(path, 16); + + /* QMI_WWAN */ + if (bInterfaceClass == USB_CLASS_VENDOR_SPEC) + type = SOFTWARE_QMI; + + /* CDC_MBIM */ + if (bInterfaceClass == USB_CLASS_COMM) + type = SOFTWARE_MBIM; + + return type; +} + +struct usbfs_getdriver +{ + unsigned int interface; + char driver[255 + 1]; +}; + +struct usbfs_ioctl +{ + int ifno; /* interface 0..N ; negative numbers reserved */ + int ioctl_code; /* MUST encode size + direction of data so the + * macros in give correct values */ + void *data; /* param buffer (in, or out) */ +}; + +#define IOCTL_USBFS_DISCONNECT _IO('U', 22) +#define IOCTL_USBFS_CONNECT _IO('U', 23) + +int usbfs_is_kernel_driver_alive(int fd, int ifnum) +{ + struct usbfs_getdriver getdrv; + getdrv.interface = ifnum; + if (ioctl(fd, USBDEVFS_GETDRIVER, &getdrv) < 0) { + dbg_time("%s ioctl USBDEVFS_GETDRIVER failed, kernel driver may be inactive", __func__); + return 0; + } + dbg_time("%s find interface %d has match the driver %s", __func__, ifnum, getdrv.driver); + return 1; +} + +void usbfs_detach_kernel_driver(int fd, int ifnum) +{ + struct usbfs_ioctl operate; + operate.data = NULL; + operate.ifno = ifnum; + operate.ioctl_code = IOCTL_USBFS_DISCONNECT; + if (ioctl(fd, USBDEVFS_IOCTL, &operate) < 0) { + dbg_time("%s detach kernel driver failed", __func__); + } else { + dbg_time("%s detach kernel driver success", __func__); + } +} + +void usbfs_attach_kernel_driver(int fd, int ifnum) +{ + struct usbfs_ioctl operate; + operate.data = NULL; + operate.ifno = ifnum; + operate.ioctl_code = IOCTL_USBFS_CONNECT; + if (ioctl(fd, USBDEVFS_IOCTL, &operate) < 0) { + dbg_time("%s detach kernel driver failed", __func__); + } else { + dbg_time("%s detach kernel driver success", __func__); + } +} + +int reattach_driver(PROFILE_T *profile) +{ + int ifnum = 4; + int fd; + char devpath[128] = {'\0'}; + snprintf(devpath, sizeof(devpath), "/dev/bus/usb/%03d/%03d", profile->busnum, profile->devnum); + fd = open(devpath, O_RDWR | O_NOCTTY); + if (fd < 0) + { + dbg_time("%s fail to open %s", __func__, devpath); + return -1; + } + usbfs_detach_kernel_driver(fd, ifnum); + usbfs_attach_kernel_driver(fd, ifnum); + close(fd); + return 0; +} + +#define SIOCETHTOOL 0x8946 +int ql_get_netcard_driver_info(const char *devname) +{ + int fd = -1; + struct ethtool_drvinfo drvinfo; + struct ifreq ifr; /* ifreq suitable for ethtool ioctl */ + + memset(&ifr, 0, sizeof(ifr)); + strcpy(ifr.ifr_name, devname); + + fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd < 0) { + dbg_time("Cannot get control socket: errno(%d)(%s)", errno, strerror(errno)); + return -1; + } + + drvinfo.cmd = ETHTOOL_GDRVINFO; + ifr.ifr_data = (void *)&drvinfo; + + if (ioctl(fd, SIOCETHTOOL, &ifr) < 0) { + dbg_time("ioctl() error: errno(%d)(%s)", errno, strerror(errno)); + return -1; + } + + dbg_time("netcard driver = %s, driver version = %s", drvinfo.driver, drvinfo.version); + + close(fd); + + return 0; +} + +static void *catch_log(void *arg) +{ + PROFILE_T *profile = (PROFILE_T *)arg; + int nreads = 0; + char tbuff[256+32]; + time_t t; + struct tm *tm; + char filter[10]; + size_t tsize = strlen("2020/06/22_22:50:07 "); + + sprintf(filter, ":%d:%03d:", profile->busnum, profile->devnum); + + while(1) { + nreads = read(profile->usbmon_fd, tbuff + tsize, sizeof(tbuff) - tsize - 1); + if (nreads <= 0) { + break; + } + + tbuff[tsize+nreads] = '\0'; // printf("%s", buff); + + if (!strstr(tbuff+tsize, filter)) + continue; + + time(&t); + tm = localtime(&t); + + sprintf(tbuff, "%04d/%02d/%02d_%02d:%02d:%02d", + tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); + tbuff[tsize-1] = ' '; + + write(profile->usbmon_logfile_fd, tbuff, strlen(tbuff)); + } + + return NULL; +} + +int ql_capture_usbmon_log(PROFILE_T *profile, const char *log_path) +{ + char usbmon_path[256]; + pthread_t pt; + pthread_attr_t attr; + + if (access("/sys/module/usbmon", F_OK)) { + dbg_time("usbmon is not load, please execute \"modprobe usbmon\" or \"insmod usbmon.ko\""); + return -1; + } + + if (access("/sys/kernel/debug/usb", F_OK)) { + dbg_time("debugfs is not mount, please execute \"mount -t debugfs none_debugs /sys/kernel/debug\""); + return -1; + } + + sprintf(usbmon_path, "/sys/kernel/debug/usb/usbmon/%du", profile->busnum); + profile->usbmon_fd = open(usbmon_path, O_RDONLY); + if (profile->usbmon_fd < 0) { + dbg_time("open %s error(%d) (%s)", usbmon_path, errno, strerror(errno)); + return -1; + } + + sprintf(usbmon_path, "cat /sys/kernel/debug/usb/devices >> %s", log_path); + system(usbmon_path); + + profile->usbmon_logfile_fd = open(log_path, O_WRONLY | O_CREAT | O_APPEND, 0644); + if (profile->usbmon_logfile_fd < 0) { + dbg_time("open %s error(%d) (%s)", log_path, errno, strerror(errno)); + close(profile->usbmon_fd); + profile->usbmon_fd = -1; + return -1; + } + + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + + pthread_create(&pt, &attr, catch_log, (void *)profile); + + return 0; +} + +void ql_stop_usbmon_log(PROFILE_T *profile) { + if (profile->usbmon_fd > 0) + close(profile->usbmon_fd); + if (profile->usbmon_logfile_fd > 0) + close(profile->usbmon_logfile_fd); +} diff --git a/root/package/link4all/quectel-CM/src/device.c b/root/package/link4all/quectel-CM/src/device.c new file mode 100644 index 00000000..e2510d8d --- /dev/null +++ b/root/package/link4all/quectel-CM/src/device.c @@ -0,0 +1,558 @@ +/****************************************************************************** + @file device.c + @brief QMI device dirver. + + DESCRIPTION + Connectivity Management Tool for USB network adapter of Quectel wireless cellular modules. + + INITIALIZATION AND SEQUENCING REQUIREMENTS + None. + + --------------------------------------------------------------------------- + Copyright (c) 2016 - 2020 Quectel Wireless Solution, Co., Ltd. All Rights Reserved. + Quectel Wireless Solution Proprietary and Confidential. + --------------------------------------------------------------------------- +******************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "QMIThread.h" +#include "ethtool-copy.h" + +#define USB_CLASS_COMM 2 +#define USB_CLASS_VENDOR_SPEC 0xff +#define USB_CDC_SUBCLASS_MBIM 0x0e + +#define USB_INTERFACE_IS_QMI(_Class, _SubClass) (_Class == 0xff && _SubClass == 0xff) +#define USB_INTERFACE_IS_MBIM(_Class, _SubClass) (_Class == USB_CLASS_COMM && _SubClass == USB_CDC_SUBCLASS_MBIM) + +#define CM_MAX_PATHLEN 256 + +#define CM_INVALID_VAL (~((int)0)) +/* get first line from file 'fname' + * And convert the content into a hex number, then return this number */ +static int file_get_value(const char *fname, int base) +{ + FILE *fp = NULL; + long num; + char buff[32 + 1] = {'\0'}; + char *endptr = NULL; + + fp = fopen(fname, "r"); + if (!fp) goto error; + if (fgets(buff, sizeof(buff), fp) == NULL) + goto error; + fclose(fp); + + num = (int)strtol(buff, &endptr, base); + if (errno == ERANGE && (num == LONG_MAX || num == LONG_MIN)) + goto error; + /* if there is no digit in buff */ + if (endptr == buff) + goto error; + + if (debug_qmi) + dbg_time("(%s) = %lx", fname, num); + return (int)num; + +error: + if (fp) fclose(fp); + return CM_INVALID_VAL; +} + +/* + * This function will search the directory 'dirname' and return the first child. + * '.' and '..' is ignored by default + */ +static int dir_get_child(const char *dirname, char *buff, unsigned bufsize) +{ + struct dirent *entptr = NULL; + DIR *dirptr = opendir(dirname); + if (!dirptr) + goto error; + while ((entptr = readdir(dirptr))) { + if (entptr->d_name[0] == '.') + continue; + snprintf(buff, bufsize, "%s", entptr->d_name); + break; + } + + closedir(dirptr); + return 0; +error: + buff[0] = '\0'; + if (dirptr) closedir(dirptr); + return -1; +} + +static int conf_get_val(const char *fname, const char *key) +{ + char buff[CM_MAX_BUFF] = {'\0'}; + FILE *fp = fopen(fname, "r"); + if (!fp) + goto error; + + while (fgets(buff, CM_MAX_BUFF, fp)) { + char prefix[CM_MAX_BUFF] = {'\0'}; + char tail[CM_MAX_BUFF] = {'\0'}; + /* To eliminate cppcheck warnning: Assume string length is no more than 15 */ + sscanf(buff, "%15[^=]=%15s", prefix, tail); + if (!strncasecmp(prefix, key, strlen(key))) { + fclose(fp); + return atoi(tail); + } + } + +error: + fclose(fp); + return CM_INVALID_VAL; +} + +static int detect_path_cdc_wdm_or_qcqmi(char *path, size_t len) +{ + (void)len; + size_t offset = strlen(path); + + if (!access(path, R_OK)) + { + // int bNumEndpoints; + int bInterfaceClass, bInterfaceSubClass; + + path[offset] = '\0'; + // should we check endpint??? why bNumEndpoints == 3???, for unisoc mbim bNumEndpoints = 1 + // strcat(path, "/bNumEndpoints"); + // bNumEndpoints = file_get_value(path, 16); + // if (bNumEndpoints != 3) + // return -1; + + path[offset] = '\0'; + strcat(path, "/bInterfaceClass"); + bInterfaceClass = file_get_value(path, 16); + + path[offset] = '\0'; + strcat(path, "/bInterfaceSubClass"); + bInterfaceSubClass = file_get_value(path, 16); + + if (USB_INTERFACE_IS_QMI(bInterfaceClass, bInterfaceSubClass)) + ; //qmi_wwan / GobiNet + else if (USB_INTERFACE_IS_MBIM(bInterfaceClass, bInterfaceSubClass)) + ; //cdc_mbim + else + return -1; + + path[offset] = '\0'; + strcat(path, "/GobiQMI"); + if (!access(path, R_OK)) + return 0; + + path[offset] = '\0'; + strcat(path, "/usbmisc"); + if (!access(path, R_OK)) + return 0; + + path[offset] = '\0'; + strcat(path, "/usb"); + if (!access(path, R_OK)) + return 0; + } + + return -1; +} + +/* To detect the device info of the modem. + * return: + * FALSE -> fail + * TRUE -> ok + */ +BOOL qmidevice_detect(char *qmichannel, char *usbnet_adapter, unsigned bufsize, int *pbusnum, int *pdevnum) { + struct dirent* ent = NULL; + DIR *pDir; + const char *rootdir = "/sys/bus/usb/devices"; + struct { + char path[255*2]; + char uevent[255*3]; + } *pl; + pl = (typeof(pl)) malloc(sizeof(*pl)); + memset(pl, 0x00, sizeof(*pl)); + + pDir = opendir(rootdir); + if (!pDir) { + dbg_time("opendir %s failed: %s", rootdir, strerror(errno)); + goto error; + } + + while ((ent = readdir(pDir)) != NULL) { + int idVendor; + int idProduct; + char netcard[32+1] = {'\0'}; + char device[32+1] = {'\0'}; + char devname[32+1+6] = {'\0'}; + int busnum, devnum; + int bNumInterfaces; + + if (ent->d_name[0] == 'u') + continue; + + snprintf(pl->path, sizeof(pl->path), "%s/%s/idVendor", rootdir, ent->d_name); + idVendor = file_get_value(pl->path, 16); + if (idVendor == CM_INVALID_VAL) + continue; + + snprintf(pl->path, sizeof(pl->path), "%s/%s/idProduct", rootdir, ent->d_name); + idProduct = file_get_value(pl->path, 16); + if (idProduct == CM_INVALID_VAL) + continue; + + snprintf(pl->path, sizeof(pl->path), "%s/%s/busnum", rootdir, ent->d_name); + busnum = file_get_value(pl->path, 10); + if (busnum == CM_INVALID_VAL) + continue; + + snprintf(pl->path, sizeof(pl->path), "%s/%s/devnum", rootdir, ent->d_name); + devnum = file_get_value(pl->path, 10); + if (devnum == CM_INVALID_VAL) + continue; + + if (idVendor == 0x2c7c || idVendor == 0x05c6) { + dbg_time("Find %s/%s idVendor=0x%x idProduct=0x%x, bus=0x%03x, dev=0x%03x", + rootdir, ent->d_name, idVendor, idProduct, busnum, devnum); + } + + snprintf(pl->path, sizeof(pl->path), "%s/%s/bNumInterfaces", rootdir, ent->d_name); + bNumInterfaces = file_get_value(pl->path, 10); + if (bNumInterfaces == CM_INVALID_VAL) + continue; + + /* get network interface */ + /* NOTICE: there is a case that, bNumberInterface=6, but the net interface is 8 */ + /* toolchain-mips_24kc_gcc-5.4.0_musl donot support GLOB_BRACE */ + bNumInterfaces += 8; + while (bNumInterfaces >= 0) { //RG500U's MBIM is at inteface 0 + snprintf(pl->path, sizeof(pl->path), "%s/%s:1.%d/net", rootdir, ent->d_name, bNumInterfaces-1); + dir_get_child(pl->path, netcard, sizeof(netcard)); + if (netcard[0]) + break; + bNumInterfaces--; + } + + if (netcard[0] == '\0') { //for centos 2.6.x + const char *n= "usb0"; + const char *c = "qcqmi0"; + + snprintf(pl->path, sizeof(pl->path), "%s/%s:1.4/net:%s", rootdir, ent->d_name, n); + if (!access(pl->path, F_OK)) { + snprintf(pl->path, sizeof(pl->path), "%s/%s:1.4/GobiQMI:%s", rootdir, ent->d_name, c); + if (!access(pl->path, F_OK)) { + snprintf(qmichannel, bufsize, "/dev/%s", c); + snprintf(usbnet_adapter, bufsize, "%s", n); + break; + } + } + } + + if (netcard[0] == '\0') + continue; + + /* not '-i iface' */ + if (usbnet_adapter[0] && strcmp(usbnet_adapter, netcard)) + continue; + + pl->path[strlen(pl->path)-strlen("/net")] = '\0'; + if (detect_path_cdc_wdm_or_qcqmi(pl->path, sizeof(pl->path))) + continue; + + /* get device(qcqmiX|cdc-wdmX) */ + dir_get_child(pl->path, device, sizeof(device)); + if (device[0] == '\0') + continue; + + /* There is a chance that, no device(qcqmiX|cdc-wdmX) is generated. We should warn user about that! */ + snprintf(devname, sizeof(devname), "/dev/%s", device); + if (access(devname, R_OK | F_OK) && errno == ENOENT) { + int major; + int minor; + int ret; + + dbg_time("%s access failed, errno: %d (%s)", devname, errno, strerror(errno)); + snprintf(pl->uevent, sizeof(pl->uevent), "%s/%s/uevent", pl->path, device); + major = conf_get_val(pl->uevent, "MAJOR"); + minor = conf_get_val(pl->uevent, "MINOR"); + if(major == CM_INVALID_VAL || minor == CM_INVALID_VAL) + dbg_time("get major and minor failed"); + + ret = mknod(devname, S_IFCHR|0666, (((major & 0xfff) << 8) | (minor & 0xff) | ((minor & 0xfff00) << 12))); + if (ret) + dbg_time("please mknod %s c %d %d", devname, major, minor); + } + + if (netcard[0] && device[0]) { + snprintf(qmichannel, bufsize, "/dev/%s", device); + snprintf(usbnet_adapter, bufsize, "%s", netcard); + dbg_time("Auto find qmichannel = %s", qmichannel); + dbg_time("Auto find usbnet_adapter = %s", usbnet_adapter); + *pbusnum = busnum; + *pdevnum = devnum; + break; + } + } + closedir(pDir); + + if (qmichannel[0] == '\0' || usbnet_adapter[0] == '\0') { + dbg_time("network interface '%s' or qmidev '%s' is not exist", usbnet_adapter, qmichannel); + goto error; + } + free(pl); + return TRUE; +error: + free(pl); + return FALSE; +} + +int mhidevice_detect(char *qmichannel, char *usbnet_adapter, PROFILE_T *profile) { + if (!access("/sys/class/net/pcie_mhi0", F_OK)) + strcpy(usbnet_adapter, "pcie_mhi0"); + else if (!access("/sys/class/net/rmnet_mhi0", F_OK)) + strcpy(usbnet_adapter, "rmnet_mhi0"); + else { + dbg_time("qmidevice_detect failed"); + goto error; + } + + if (!access("/dev/mhi_MBIM", F_OK)) { + strcpy(qmichannel, "/dev/mhi_MBIM"); + profile->software_interface = SOFTWARE_MBIM; + } + else if (!access("/dev/mhi_QMI0", F_OK)) { + strcpy(qmichannel, "/dev/mhi_QMI0"); + profile->software_interface = SOFTWARE_QMI; + } + else { + goto error; + } + + return 1; +error: + return 0; +} + +int get_driver_type(PROFILE_T *profile) +{ + char path[CM_MAX_PATHLEN+1] = {'\0'}; + int bInterfaceClass; + int type = DRV_INVALID; + + snprintf(path, sizeof(path), "/sys/class/net/%s/device/bInterfaceClass", profile->usbnet_adapter); + bInterfaceClass = file_get_value(path, 16); + + /* QMI_WWAN */ + if (bInterfaceClass == USB_CLASS_VENDOR_SPEC) + type = SOFTWARE_QMI; + + /* CDC_MBIM */ + if (bInterfaceClass == USB_CLASS_COMM) + type = SOFTWARE_MBIM; + + return type; +} + +struct usbfs_getdriver +{ + unsigned int interface; + char driver[255 + 1]; +}; + +struct usbfs_ioctl +{ + int ifno; /* interface 0..N ; negative numbers reserved */ + int ioctl_code; /* MUST encode size + direction of data so the + * macros in give correct values */ + void *data; /* param buffer (in, or out) */ +}; + +#define IOCTL_USBFS_DISCONNECT _IO('U', 22) +#define IOCTL_USBFS_CONNECT _IO('U', 23) + +int usbfs_is_kernel_driver_alive(int fd, int ifnum) +{ + struct usbfs_getdriver getdrv; + getdrv.interface = ifnum; + if (ioctl(fd, USBDEVFS_GETDRIVER, &getdrv) < 0) { + dbg_time("%s ioctl USBDEVFS_GETDRIVER failed, kernel driver may be inactive", __func__); + return 0; + } + dbg_time("%s find interface %d has match the driver %s", __func__, ifnum, getdrv.driver); + return 1; +} + +void usbfs_detach_kernel_driver(int fd, int ifnum) +{ + struct usbfs_ioctl operate; + operate.data = NULL; + operate.ifno = ifnum; + operate.ioctl_code = IOCTL_USBFS_DISCONNECT; + if (ioctl(fd, USBDEVFS_IOCTL, &operate) < 0) { + dbg_time("%s detach kernel driver failed", __func__); + } else { + dbg_time("%s detach kernel driver success", __func__); + } +} + +void usbfs_attach_kernel_driver(int fd, int ifnum) +{ + struct usbfs_ioctl operate; + operate.data = NULL; + operate.ifno = ifnum; + operate.ioctl_code = IOCTL_USBFS_CONNECT; + if (ioctl(fd, USBDEVFS_IOCTL, &operate) < 0) { + dbg_time("%s detach kernel driver failed", __func__); + } else { + dbg_time("%s detach kernel driver success", __func__); + } +} + +int reattach_driver(PROFILE_T *profile) +{ + int ifnum = 4; + int fd; + char devpath[128] = {'\0'}; + snprintf(devpath, sizeof(devpath), "/dev/bus/usb/%03d/%03d", profile->busnum, profile->devnum); + fd = open(devpath, O_RDWR | O_NOCTTY); + if (fd < 0) + { + dbg_time("%s fail to open %s", __func__, devpath); + return -1; + } + usbfs_detach_kernel_driver(fd, ifnum); + usbfs_attach_kernel_driver(fd, ifnum); + close(fd); + return 0; +} + +#define SIOCETHTOOL 0x8946 +int ql_get_netcard_driver_info(const char *devname) +{ + int fd = -1; + struct ethtool_drvinfo drvinfo; + struct ifreq ifr; /* ifreq suitable for ethtool ioctl */ + + memset(&ifr, 0, sizeof(ifr)); + strcpy(ifr.ifr_name, devname); + + fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd < 0) { + dbg_time("Cannot get control socket: errno(%d)(%s)", errno, strerror(errno)); + return -1; + } + + drvinfo.cmd = ETHTOOL_GDRVINFO; + ifr.ifr_data = (void *)&drvinfo; + + if (ioctl(fd, SIOCETHTOOL, &ifr) < 0) { + dbg_time("ioctl() error: errno(%d)(%s)", errno, strerror(errno)); + return -1; + } + + dbg_time("netcard driver = %s, driver version = %s", drvinfo.driver, drvinfo.version); + + close(fd); + + return 0; +} + +static void *catch_log(void *arg) +{ + PROFILE_T *profile = (PROFILE_T *)arg; + int nreads = 0; + char tbuff[256+32]; + time_t t; + struct tm *tm; + char filter[10]; + size_t tsize = strlen("2020/06/22_22:50:07 "); + + sprintf(filter, ":%d:%03d:", profile->busnum, profile->devnum); + + while(1) { + nreads = read(profile->usbmon_fd, tbuff + tsize, sizeof(tbuff) - tsize - 1); + if (nreads <= 0) { + break; + } + + tbuff[tsize+nreads] = '\0'; // printf("%s", buff); + + if (!strstr(tbuff+tsize, filter)) + continue; + + time(&t); + tm = localtime(&t); + + sprintf(tbuff, "%04d/%02d/%02d_%02d:%02d:%02d", + tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); + tbuff[tsize-1] = ' '; + + write(profile->usbmon_logfile_fd, tbuff, strlen(tbuff)); + } + + return NULL; +} + +int ql_capture_usbmon_log(PROFILE_T *profile, const char *log_path) +{ + char usbmon_path[256]; + pthread_t pt; + pthread_attr_t attr; + + if (access("/sys/module/usbmon", F_OK)) { + dbg_time("usbmon is not load, please execute \"modprobe usbmon\" or \"insmod usbmon.ko\""); + return -1; + } + + if (access("/sys/kernel/debug/usb", F_OK)) { + dbg_time("debugfs is not mount, please execute \"mount -t debugfs none_debugs /sys/kernel/debug\""); + return -1; + } + + sprintf(usbmon_path, "/sys/kernel/debug/usb/usbmon/%du", profile->busnum); + profile->usbmon_fd = open(usbmon_path, O_RDONLY); + if (profile->usbmon_fd < 0) { + dbg_time("open %s error(%d) (%s)", usbmon_path, errno, strerror(errno)); + return -1; + } + + sprintf(usbmon_path, "cat /sys/kernel/debug/usb/devices >> %s", log_path); + system(usbmon_path); + + profile->usbmon_logfile_fd = open(log_path, O_WRONLY | O_CREAT | O_APPEND, 0644); + if (profile->usbmon_logfile_fd < 0) { + dbg_time("open %s error(%d) (%s)", log_path, errno, strerror(errno)); + close(profile->usbmon_fd); + profile->usbmon_fd = -1; + return -1; + } + + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + + pthread_create(&pt, &attr, catch_log, (void *)profile); + + return 0; +} + +void ql_stop_usbmon_log(PROFILE_T *profile) { + if (profile->usbmon_fd > 0) + close(profile->usbmon_fd); + if (profile->usbmon_logfile_fd > 0) + close(profile->usbmon_logfile_fd); +} diff --git a/root/package/link4all/quectel-CM/src/device.new.c b/root/package/link4all/quectel-CM/src/device.new.c new file mode 100644 index 00000000..5824ceaf --- /dev/null +++ b/root/package/link4all/quectel-CM/src/device.new.c @@ -0,0 +1,569 @@ +/****************************************************************************** + @file device.c + @brief QMI device dirver. + + DESCRIPTION + Connectivity Management Tool for USB network adapter of Quectel wireless cellular modules. + + INITIALIZATION AND SEQUENCING REQUIREMENTS + None. + + --------------------------------------------------------------------------- + Copyright (c) 2016 - 2020 Quectel Wireless Solution, Co., Ltd. All Rights Reserved. + Quectel Wireless Solution Proprietary and Confidential. + --------------------------------------------------------------------------- +******************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "QMIThread.h" +#include "ethtool-copy.h" + +#define USB_CLASS_COMM 2 +#define USB_CLASS_VENDOR_SPEC 0xff +#define USB_CDC_SUBCLASS_MBIM 0x0e + +#define USB_INTERFACE_IS_QMI(_Class, _SubClass) (_Class == 0xff && _SubClass == 0xff) +#define USB_INTERFACE_IS_MBIM(_Class, _SubClass) (_Class == USB_CLASS_COMM && _SubClass == USB_CDC_SUBCLASS_MBIM) + +#define CM_MAX_PATHLEN 256 + +#define CM_INVALID_VAL (~((int)0)) +/* get first line from file 'fname' + * And convert the content into a hex number, then return this number */ +static int file_get_value(const char *fname, int base) +{ + FILE *fp = NULL; + long num; + char buff[32 + 1] = {'\0'}; + char *endptr = NULL; + + fp = fopen(fname, "r"); + if (!fp) goto error; + if (fgets(buff, sizeof(buff), fp) == NULL) + goto error; + fclose(fp); + + num = (int)strtol(buff, &endptr, base); + if (errno == ERANGE && (num == LONG_MAX || num == LONG_MIN)) + goto error; + /* if there is no digit in buff */ + if (endptr == buff) + goto error; + + if (debug_qmi) + dbg_time("(%s) = %lx", fname, num); + return (int)num; + +error: + if (fp) fclose(fp); + return CM_INVALID_VAL; +} + +/* + * This function will search the directory 'dirname' and return the first child. + * '.' and '..' is ignored by default + */ +static int dir_get_child(const char *dirname, char *buff, unsigned bufsize) +{ + struct dirent *entptr = NULL; + DIR *dirptr = opendir(dirname); + if (!dirptr) + goto error; + while ((entptr = readdir(dirptr))) { + if (entptr->d_name[0] == '.') + continue; + snprintf(buff, bufsize, "%s", entptr->d_name); + break; + } + + closedir(dirptr); + return 0; +error: + buff[0] = '\0'; + if (dirptr) closedir(dirptr); + return -1; +} + +static int conf_get_val(const char *fname, const char *key) +{ + char buff[CM_MAX_BUFF] = {'\0'}; + FILE *fp = fopen(fname, "r"); + if (!fp) + goto error; + + while (fgets(buff, CM_MAX_BUFF, fp)) { + char prefix[CM_MAX_BUFF] = {'\0'}; + char tail[CM_MAX_BUFF] = {'\0'}; + /* To eliminate cppcheck warnning: Assume string length is no more than 15 */ + sscanf(buff, "%15[^=]=%15s", prefix, tail); + if (!strncasecmp(prefix, key, strlen(key))) { + fclose(fp); + return atoi(tail); + } + } + +error: + fclose(fp); + return CM_INVALID_VAL; +} + +static int detect_path_cdc_wdm_or_qcqmi(char *path, size_t len) +{ + (void)len; + size_t offset = strlen(path); + + if (!access(path, R_OK)) + { + // int bNumEndpoints; + int bInterfaceClass, bInterfaceSubClass; + + path[offset] = '\0'; + // should we check endpint??? why bNumEndpoints == 3???, for unisoc mbim bNumEndpoints = 1 + // strcat(path, "/bNumEndpoints"); + // bNumEndpoints = file_get_value(path, 16); + // if (bNumEndpoints != 3) + // return -1; + + path[offset] = '\0'; + strcat(path, "/bInterfaceClass"); + bInterfaceClass = file_get_value(path, 16); + + path[offset] = '\0'; + strcat(path, "/bInterfaceSubClass"); + bInterfaceSubClass = file_get_value(path, 16); + + if (USB_INTERFACE_IS_QMI(bInterfaceClass, bInterfaceSubClass)) + ; //qmi_wwan / GobiNet + else if (USB_INTERFACE_IS_MBIM(bInterfaceClass, bInterfaceSubClass)) + ; //cdc_mbim + else + return -1; + + path[offset] = '\0'; + strcat(path, "/GobiQMI"); + if (!access(path, R_OK)) + return 0; + + path[offset] = '\0'; + strcat(path, "/usbmisc"); + if (!access(path, R_OK)) + return 0; + + path[offset] = '\0'; + strcat(path, "/usb"); + if (!access(path, R_OK)) + return 0; + } + + return -1; +} + +/* To detect the device info of the modem. + * return: + * FALSE -> fail + * TRUE -> ok + */ +BOOL qmidevice_detect(char *qmichannel, char *usbnet_adapter, unsigned bufsize, int *pbusnum, int *pdevnum) { + struct dirent* ent = NULL; + DIR *pDir; + const char *rootdir = "/sys/bus/usb/devices"; + struct { + char path[255*2]; + char uevent[255*3]; + } *pl; + pl = (typeof(pl)) malloc(sizeof(*pl)); + memset(pl, 0x00, sizeof(*pl)); + + pDir = opendir(rootdir); + if (!pDir) { + dbg_time("opendir %s failed: %s", rootdir, strerror(errno)); + goto error; + } + + while ((ent = readdir(pDir)) != NULL) { + int idVendor; + int idProduct; + char netcard[32+1] = {'\0'}; + char device[32+1] = {'\0'}; + char devname[32+1+6] = {'\0'}; + int busnum, devnum; + + snprintf(pl->path, sizeof(pl->path), "%s/%s/idVendor", rootdir, ent->d_name); + idVendor = file_get_value(pl->path, 16); + + snprintf(pl->path, sizeof(pl->path), "%s/%s/idProduct", rootdir, ent->d_name); + idProduct = file_get_value(pl->path, 16); + + if (idVendor != 0x05c6 && idVendor != 0x2c7c && idVendor != 0x1e0e && idVendor != 0x2df3) + continue; + + snprintf(pl->path, sizeof(pl->path), "%s/%s/busnum", rootdir, ent->d_name); + busnum = file_get_value(pl->path, 10); + snprintf(pl->path, sizeof(pl->path), "%s/%s/devnum", rootdir, ent->d_name); + devnum = file_get_value(pl->path, 10); + dbg_time("Find %s/%s idVendor=0x%x idProduct=0x%x, bus=0x%03x, dev=0x%03x", + rootdir, ent->d_name, idVendor, idProduct, busnum, devnum); + + /* get network interface */ + snprintf(pl->path, sizeof(pl->path), "%s/%s:1.3/net", rootdir, ent->d_name); + dir_get_child(pl->path, netcard, sizeof(netcard)); + if (netcard[0] == '\0') { + if (idVendor == 0x05c6) + { + if(idProduct == 0x90db){ + snprintf(pl->path, sizeof(pl->path), "%s/%s:1.2/net", rootdir, ent->d_name); //for 90db + dir_get_child(pl->path, netcard, sizeof(netcard)); + } + if(idProduct == 0xf601){ + snprintf(pl->path, sizeof(pl->path), "%s/%s:1.5/net", rootdir, ent->d_name); //for slm730 + dir_get_child(pl->path, netcard, sizeof(netcard)); + } + } + else if(idVendor == 0x1e0e) + { + if (idProduct == 0x901e) + { + snprintf(pl->path, sizeof(pl->path), "%s/%s:1.2/net", rootdir, ent->d_name); //for 901e + dir_get_child(pl->path, netcard, sizeof(netcard)); + } + else + { + snprintf(pl->path, sizeof(pl->path), "%s/%s:1.5/net", rootdir, ent->d_name); //for 9001 + dir_get_child(pl->path, netcard, sizeof(netcard)); + } + } + else if(idVendor == 0x2c7c){ + if(idProduct == 0x0125||idProduct == 0x0121||idProduct == 0x00306 ||idProduct == 0x030a ){ + snprintf(pl->path, sizeof(pl->path), "%s/%s:1.4/net", rootdir, ent->d_name); //for EC20 EC21/EC25 EP06 + printf("ec20 em05 usb path is :%s\n",pl->path); + dir_get_child(pl->path, netcard, sizeof(netcard)); + } + } + else + { + snprintf(pl->path, sizeof(pl->path), "%s/%s:1.4/net", rootdir, ent->d_name); //for 0800 + dir_get_child(pl->path, netcard, sizeof(netcard)); + } + } + + if (netcard[0] == '\0') { //for centos 2.6.x + const char *n= "usb0"; + const char *c = "qcqmi0"; + + snprintf(pl->path, sizeof(pl->path), "%s/%s:1.4/net:%s", rootdir, ent->d_name, n); + if (!access(pl->path, F_OK)) { + snprintf(pl->path, sizeof(pl->path), "%s/%s:1.4/GobiQMI:%s", rootdir, ent->d_name, c); + if (!access(pl->path, F_OK)) { + snprintf(qmichannel, bufsize, "/dev/%s", c); + snprintf(usbnet_adapter, bufsize, "%s", n); + break; + } + } + } + + if (netcard[0] == '\0') + continue; + + if (usbnet_adapter[0] && strcmp(usbnet_adapter, netcard)) + continue; + + pl->path[strlen(pl->path)-strlen("/net")] = '\0'; + if (detect_path_cdc_wdm_or_qcqmi(pl->path, sizeof(pl->path))) + continue; + + /* get device */ + dir_get_child(pl->path, device, sizeof(device)); + if (device[0] == '\0') + continue; + + /* There is a chance that, no device(qcqmiX|cdc-wdmX) is generated. We should warn user about that! */ + snprintf(devname, sizeof(devname), "/dev/%s", device); + if (access(devname, R_OK | F_OK) && errno == ENOENT) { + int major; + int minor; + int ret; + + dbg_time("%s access failed, errno: %d (%s)", devname, errno, strerror(errno)); + snprintf(pl->uevent, sizeof(pl->uevent), "%s/%s/uevent", pl->path, device); + major = conf_get_val(pl->uevent, "MAJOR"); + minor = conf_get_val(pl->uevent, "MINOR"); + if(major == CM_INVALID_VAL || minor == CM_INVALID_VAL) + dbg_time("get major and minor failed"); + + ret = mknod(devname, S_IFCHR|0666, (((major & 0xfff) << 8) | (minor & 0xff) | ((minor & 0xfff00) << 12))); + if (ret) + dbg_time("please mknod %s c %d %d", devname, major, minor); + } + + if (netcard[0] && device[0]) { + snprintf(qmichannel, bufsize, "/dev/%s", device); + snprintf(usbnet_adapter, bufsize, "%s", netcard); + dbg_time("Auto find qmichannel = %s", qmichannel); + dbg_time("Auto find usbnet_adapter = %s", usbnet_adapter); + *pbusnum = busnum; + *pdevnum = devnum; + break; + } + } + closedir(pDir); + + if (qmichannel[0] == '\0' || usbnet_adapter[0] == '\0') { + dbg_time("network interface '%s' or qmidev '%s' is not exist", usbnet_adapter, qmichannel); + goto error; + } + free(pl); + return TRUE; +error: + free(pl); + return FALSE; +} + +int mhidevice_detect(char *qmichannel, char *usbnet_adapter, PROFILE_T *profile) { + if (!access("/sys/class/net/pcie_mhi0", F_OK)) + strcpy(usbnet_adapter, "pcie_mhi0"); + else if (!access("/sys/class/net/rmnet_mhi0", F_OK)) + strcpy(usbnet_adapter, "rmnet_mhi0"); + else { + dbg_time("qmidevice_detect failed"); + goto error; + } + + if (!access("/dev/mhi_MBIM", F_OK)) { + strcpy(qmichannel, "/dev/mhi_MBIM"); + profile->software_interface = SOFTWARE_MBIM; + } + else if (!access("/dev/mhi_QMI0", F_OK)) { + strcpy(qmichannel, "/dev/mhi_QMI0"); + profile->software_interface = SOFTWARE_QMI; + } + else { + goto error; + } + + return 1; +error: + return 0; +} + +int get_driver_type(PROFILE_T *profile) +{ + char path[CM_MAX_PATHLEN+1] = {'\0'}; + int bInterfaceClass; + int type = DRV_INVALID; + + snprintf(path, sizeof(path), "/sys/class/net/%s/device/bInterfaceClass", profile->usbnet_adapter); + bInterfaceClass = file_get_value(path, 16); + + /* QMI_WWAN */ + if (bInterfaceClass == USB_CLASS_VENDOR_SPEC) + type = SOFTWARE_QMI; + + /* CDC_MBIM */ + if (bInterfaceClass == USB_CLASS_COMM) + type = SOFTWARE_MBIM; + + return type; +} + +struct usbfs_getdriver +{ + unsigned int interface; + char driver[255 + 1]; +}; + +struct usbfs_ioctl +{ + int ifno; /* interface 0..N ; negative numbers reserved */ + int ioctl_code; /* MUST encode size + direction of data so the + * macros in give correct values */ + void *data; /* param buffer (in, or out) */ +}; + +#define IOCTL_USBFS_DISCONNECT _IO('U', 22) +#define IOCTL_USBFS_CONNECT _IO('U', 23) + +int usbfs_is_kernel_driver_alive(int fd, int ifnum) +{ + struct usbfs_getdriver getdrv; + getdrv.interface = ifnum; + if (ioctl(fd, USBDEVFS_GETDRIVER, &getdrv) < 0) { + dbg_time("%s ioctl USBDEVFS_GETDRIVER failed, kernel driver may be inactive", __func__); + return 0; + } + dbg_time("%s find interface %d has match the driver %s", __func__, ifnum, getdrv.driver); + return 1; +} + +void usbfs_detach_kernel_driver(int fd, int ifnum) +{ + struct usbfs_ioctl operate; + operate.data = NULL; + operate.ifno = ifnum; + operate.ioctl_code = IOCTL_USBFS_DISCONNECT; + if (ioctl(fd, USBDEVFS_IOCTL, &operate) < 0) { + dbg_time("%s detach kernel driver failed", __func__); + } else { + dbg_time("%s detach kernel driver success", __func__); + } +} + +void usbfs_attach_kernel_driver(int fd, int ifnum) +{ + struct usbfs_ioctl operate; + operate.data = NULL; + operate.ifno = ifnum; + operate.ioctl_code = IOCTL_USBFS_CONNECT; + if (ioctl(fd, USBDEVFS_IOCTL, &operate) < 0) { + dbg_time("%s detach kernel driver failed", __func__); + } else { + dbg_time("%s detach kernel driver success", __func__); + } +} + +int reattach_driver(PROFILE_T *profile) +{ + int ifnum = 4; + int fd; + char devpath[128] = {'\0'}; + snprintf(devpath, sizeof(devpath), "/dev/bus/usb/%03d/%03d", profile->busnum, profile->devnum); + fd = open(devpath, O_RDWR | O_NOCTTY); + if (fd < 0) + { + dbg_time("%s fail to open %s", __func__, devpath); + return -1; + } + usbfs_detach_kernel_driver(fd, ifnum); + usbfs_attach_kernel_driver(fd, ifnum); + close(fd); + return 0; +} + +#define SIOCETHTOOL 0x8946 +int ql_get_netcard_driver_info(const char *devname) +{ + int fd = -1; + struct ethtool_drvinfo drvinfo; + struct ifreq ifr; /* ifreq suitable for ethtool ioctl */ + + memset(&ifr, 0, sizeof(ifr)); + strcpy(ifr.ifr_name, devname); + + fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd < 0) { + dbg_time("Cannot get control socket: errno(%d)(%s)", errno, strerror(errno)); + return -1; + } + + drvinfo.cmd = ETHTOOL_GDRVINFO; + ifr.ifr_data = (void *)&drvinfo; + + if (ioctl(fd, SIOCETHTOOL, &ifr) < 0) { + dbg_time("ioctl() error: errno(%d)(%s)", errno, strerror(errno)); + return -1; + } + + dbg_time("netcard driver = %s, driver version = %s", drvinfo.driver, drvinfo.version); + + close(fd); + + return 0; +} + +static void *catch_log(void *arg) +{ + PROFILE_T *profile = (PROFILE_T *)arg; + int nreads = 0; + char tbuff[256+32]; + time_t t; + struct tm *tm; + char filter[10]; + size_t tsize = strlen("2020/06/22_22:50:07 "); + + sprintf(filter, ":%d:%03d:", profile->busnum, profile->devnum); + + while(1) { + nreads = read(profile->usbmon_fd, tbuff + tsize, sizeof(tbuff) - tsize - 1); + if (nreads <= 0) { + break; + } + + tbuff[tsize+nreads] = '\0'; // printf("%s", buff); + + if (!strstr(tbuff+tsize, filter)) + continue; + + time(&t); + tm = localtime(&t); + + sprintf(tbuff, "%04d/%02d/%02d_%02d:%02d:%02d", + tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); + tbuff[tsize-1] = ' '; + + write(profile->usbmon_logfile_fd, tbuff, strlen(tbuff)); + } + + return NULL; +} + +int ql_capture_usbmon_log(PROFILE_T *profile, const char *log_path) +{ + char usbmon_path[256]; + pthread_t pt; + pthread_attr_t attr; + + if (access("/sys/module/usbmon", F_OK)) { + dbg_time("usbmon is not load, please execute \"modprobe usbmon\" or \"insmod usbmon.ko\""); + return -1; + } + + if (access("/sys/kernel/debug/usb", F_OK)) { + dbg_time("debugfs is not mount, please execute \"mount -t debugfs none_debugs /sys/kernel/debug\""); + return -1; + } + + sprintf(usbmon_path, "/sys/kernel/debug/usb/usbmon/%du", profile->busnum); + profile->usbmon_fd = open(usbmon_path, O_RDONLY); + if (profile->usbmon_fd < 0) { + dbg_time("open %s error(%d) (%s)", usbmon_path, errno, strerror(errno)); + return -1; + } + + sprintf(usbmon_path, "cat /sys/kernel/debug/usb/devices >> %s", log_path); + system(usbmon_path); + + profile->usbmon_logfile_fd = open(log_path, O_WRONLY | O_CREAT | O_APPEND, 0644); + if (profile->usbmon_logfile_fd < 0) { + dbg_time("open %s error(%d) (%s)", log_path, errno, strerror(errno)); + close(profile->usbmon_fd); + profile->usbmon_fd = -1; + return -1; + } + + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + + pthread_create(&pt, &attr, catch_log, (void *)profile); + + return 0; +} + +void ql_stop_usbmon_log(PROFILE_T *profile) { + if (profile->usbmon_fd > 0) + close(profile->usbmon_fd); + if (profile->usbmon_logfile_fd > 0) + close(profile->usbmon_logfile_fd); +} diff --git a/root/package/link4all/quectel-CM/src/dhcpclient.c b/root/package/link4all/quectel-CM/src/dhcpclient.c new file mode 100644 index 00000000..e5aafd22 --- /dev/null +++ b/root/package/link4all/quectel-CM/src/dhcpclient.c @@ -0,0 +1,90 @@ +#ifdef ANDROID +/* + * Copyright 2008, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "QMIThread.h" +#ifdef USE_NDK +extern int (*ifc_init)(void); +extern void (*ifc_close)(void); +extern int (*do_dhcp)(const char *iname); +extern void (*get_dhcp_info)(uint32_t *ipaddr, uint32_t *gateway, uint32_t *prefixLength, + uint32_t *dns1, uint32_t *dns2, uint32_t *server, + uint32_t *lease); +extern int (*property_set)(const char *key, const char *value); +#else +#include +#include +extern int do_dhcp(const char *iname); +extern void get_dhcp_info(uint32_t *ipaddr, uint32_t *gateway, uint32_t *prefixLength, + uint32_t *dns1, uint32_t *dns2, uint32_t *server, + uint32_t *lease); +#endif + +static const char *ipaddr_to_string(in_addr_t addr) +{ + struct in_addr in_addr; + + in_addr.s_addr = addr; + return inet_ntoa(in_addr); +} + +void do_dhcp_request(PROFILE_T *profile) { +#ifdef USE_NDK + if (!ifc_init ||!ifc_close ||!do_dhcp || !get_dhcp_info || !property_set) { + return; + } +#endif + + char *ifname = profile->usbnet_adapter; + uint32_t ipaddr, gateway, prefixLength, dns1, dns2, server, lease; + char propKey[128]; + +#if 0 + if (profile->rawIP && ((profile->IPType==0x04 && profile->ipv4.Address))) + { + snprintf(propKey, sizeof(propKey), "net.%s.dns1", ifname); + property_set(propKey, profile->ipv4.DnsPrimary ? ipaddr_to_string(ql_swap32(profile->ipv4.DnsPrimary)) : "8.8.8.8"); + snprintf(propKey, sizeof(propKey), "net.%s.dns2", ifname); + property_set(propKey, profile->ipv4.DnsSecondary ? ipaddr_to_string(ql_swap32(profile->ipv4.DnsSecondary)) : "8.8.8.8"); + snprintf(propKey, sizeof(propKey), "net.%s.gw", ifname); + property_set(propKey, profile->ipv4.Gateway ? ipaddr_to_string(ql_swap32(profile->ipv4.Gateway)) : "0.0.0.0"); + return; + } +#endif + + if(ifc_init()) { + dbg_time("failed to ifc_init(%s): %s\n", ifname, strerror(errno)); + } + + if (do_dhcp(ifname) < 0) { + dbg_time("failed to do_dhcp(%s): %s\n", ifname, strerror(errno)); + } + + ifc_close(); + + get_dhcp_info(&ipaddr, &gateway, &prefixLength, &dns1, &dns2, &server, &lease); + snprintf(propKey, sizeof(propKey), "net.%s.gw", ifname); + property_set(propKey, gateway ? ipaddr_to_string(gateway) : "0.0.0.0"); +} +#endif diff --git a/root/package/link4all/quectel-CM/src/ethtool-copy.h b/root/package/link4all/quectel-CM/src/ethtool-copy.h new file mode 100644 index 00000000..b5515c2f --- /dev/null +++ b/root/package/link4all/quectel-CM/src/ethtool-copy.h @@ -0,0 +1,1100 @@ +/* + * ethtool.h: Defines for Linux ethtool. + * + * Copyright (C) 1998 David S. Miller (davem@redhat.com) + * Copyright 2001 Jeff Garzik + * Portions Copyright 2001 Sun Microsystems (thockin@sun.com) + * Portions Copyright 2002 Intel (eli.kupermann@intel.com, + * christopher.leech@intel.com, + * scott.feldman@intel.com) + * Portions Copyright (C) Sun Microsystems 2008 + */ + +#ifndef _LINUX_ETHTOOL_H +#define _LINUX_ETHTOOL_H + +#include +#include + +/* This should work for both 32 and 64 bit userland. */ +struct ethtool_cmd { + __u32 cmd; + __u32 supported; /* Features this interface supports */ + __u32 advertising; /* Features this interface advertises */ + __u16 speed; /* The forced speed (lower bits) in + * Mbps. Please use + * ethtool_cmd_speed()/_set() to + * access it */ + __u8 duplex; /* Duplex, half or full */ + __u8 port; /* Which connector port */ + __u8 phy_address; /* MDIO PHY address (PRTAD for clause 45). + * May be read-only or read-write + * depending on the driver. + */ + __u8 transceiver; /* Which transceiver to use */ + __u8 autoneg; /* Enable or disable autonegotiation */ + __u8 mdio_support; /* MDIO protocols supported. Read-only. + * Not set by all drivers. + */ + __u32 maxtxpkt; /* Tx pkts before generating tx int */ + __u32 maxrxpkt; /* Rx pkts before generating rx int */ + __u16 speed_hi; /* The forced speed (upper + * bits) in Mbps. Please use + * ethtool_cmd_speed()/_set() to + * access it */ + __u8 eth_tp_mdix; /* twisted pair MDI-X status */ + __u8 eth_tp_mdix_ctrl; /* twisted pair MDI-X control, when set, + * link should be renegotiated if necessary + */ + __u32 lp_advertising; /* Features the link partner advertises */ + __u32 reserved[2]; +}; + +static __inline__ void ethtool_cmd_speed_set(struct ethtool_cmd *ep, + __u32 speed) +{ + + ep->speed = (__u16)speed; + ep->speed_hi = (__u16)(speed >> 16); +} + +static __inline__ __u32 ethtool_cmd_speed(const struct ethtool_cmd *ep) +{ + return (ep->speed_hi << 16) | ep->speed; +} + +/* Device supports clause 22 register access to PHY or peripherals + * using the interface defined in . This should not be + * set if there are known to be no such peripherals present or if + * the driver only emulates clause 22 registers for compatibility. + */ +#define ETH_MDIO_SUPPORTS_C22 1 + +/* Device supports clause 45 register access to PHY or peripherals + * using the interface defined in and . + * This should not be set if there are known to be no such peripherals + * present. + */ +#define ETH_MDIO_SUPPORTS_C45 2 + +#define ETHTOOL_FWVERS_LEN 32 +#define ETHTOOL_BUSINFO_LEN 32 +/* these strings are set to whatever the driver author decides... */ +struct ethtool_drvinfo { + __u32 cmd; + char driver[32]; /* driver short name, "tulip", "eepro100" */ + char version[32]; /* driver version string */ + char fw_version[ETHTOOL_FWVERS_LEN]; /* firmware version string */ + char bus_info[ETHTOOL_BUSINFO_LEN]; /* Bus info for this IF. */ + /* For PCI devices, use pci_name(pci_dev). */ + char reserved1[32]; + char reserved2[12]; + /* + * Some struct members below are filled in + * using ops->get_sset_count(). Obtaining + * this info from ethtool_drvinfo is now + * deprecated; Use ETHTOOL_GSSET_INFO + * instead. + */ + __u32 n_priv_flags; /* number of flags valid in ETHTOOL_GPFLAGS */ + __u32 n_stats; /* number of u64's from ETHTOOL_GSTATS */ + __u32 testinfo_len; + __u32 eedump_len; /* Size of data from ETHTOOL_GEEPROM (bytes) */ + __u32 regdump_len; /* Size of data from ETHTOOL_GREGS (bytes) */ +}; + +#define SOPASS_MAX 6 +/* wake-on-lan settings */ +struct ethtool_wolinfo { + __u32 cmd; + __u32 supported; + __u32 wolopts; + __u8 sopass[SOPASS_MAX]; /* SecureOn(tm) password */ +}; + +/* for passing single values */ +struct ethtool_value { + __u32 cmd; + __u32 data; +}; + +/* for passing big chunks of data */ +struct ethtool_regs { + __u32 cmd; + __u32 version; /* driver-specific, indicates different chips/revs */ + __u32 len; /* bytes */ + __u8 data[0]; +}; + +/* for passing EEPROM chunks */ +struct ethtool_eeprom { + __u32 cmd; + __u32 magic; + __u32 offset; /* in bytes */ + __u32 len; /* in bytes */ + __u8 data[0]; +}; + +/** + * struct ethtool_eee - Energy Efficient Ethernet information + * @cmd: ETHTOOL_{G,S}EEE + * @supported: Mask of %SUPPORTED_* flags for the speed/duplex combinations + * for which there is EEE support. + * @advertised: Mask of %ADVERTISED_* flags for the speed/duplex combinations + * advertised as eee capable. + * @lp_advertised: Mask of %ADVERTISED_* flags for the speed/duplex + * combinations advertised by the link partner as eee capable. + * @eee_active: Result of the eee auto negotiation. + * @eee_enabled: EEE configured mode (enabled/disabled). + * @tx_lpi_enabled: Whether the interface should assert its tx lpi, given + * that eee was negotiated. + * @tx_lpi_timer: Time in microseconds the interface delays prior to asserting + * its tx lpi (after reaching 'idle' state). Effective only when eee + * was negotiated and tx_lpi_enabled was set. + */ +struct ethtool_eee { + __u32 cmd; + __u32 supported; + __u32 advertised; + __u32 lp_advertised; + __u32 eee_active; + __u32 eee_enabled; + __u32 tx_lpi_enabled; + __u32 tx_lpi_timer; + __u32 reserved[2]; +}; + +/** + * struct ethtool_modinfo - plugin module eeprom information + * @cmd: %ETHTOOL_GMODULEINFO + * @type: Standard the module information conforms to %ETH_MODULE_SFF_xxxx + * @eeprom_len: Length of the eeprom + * + * This structure is used to return the information to + * properly size memory for a subsequent call to %ETHTOOL_GMODULEEEPROM. + * The type code indicates the eeprom data format + */ +struct ethtool_modinfo { + __u32 cmd; + __u32 type; + __u32 eeprom_len; + __u32 reserved[8]; +}; + +/** + * struct ethtool_coalesce - coalescing parameters for IRQs and stats updates + * @cmd: ETHTOOL_{G,S}COALESCE + * @rx_coalesce_usecs: How many usecs to delay an RX interrupt after + * a packet arrives. + * @rx_max_coalesced_frames: Maximum number of packets to receive + * before an RX interrupt. + * @rx_coalesce_usecs_irq: Same as @rx_coalesce_usecs, except that + * this value applies while an IRQ is being serviced by the host. + * @rx_max_coalesced_frames_irq: Same as @rx_max_coalesced_frames, + * except that this value applies while an IRQ is being serviced + * by the host. + * @tx_coalesce_usecs: How many usecs to delay a TX interrupt after + * a packet is sent. + * @tx_max_coalesced_frames: Maximum number of packets to be sent + * before a TX interrupt. + * @tx_coalesce_usecs_irq: Same as @tx_coalesce_usecs, except that + * this value applies while an IRQ is being serviced by the host. + * @tx_max_coalesced_frames_irq: Same as @tx_max_coalesced_frames, + * except that this value applies while an IRQ is being serviced + * by the host. + * @stats_block_coalesce_usecs: How many usecs to delay in-memory + * statistics block updates. Some drivers do not have an + * in-memory statistic block, and in such cases this value is + * ignored. This value must not be zero. + * @use_adaptive_rx_coalesce: Enable adaptive RX coalescing. + * @use_adaptive_tx_coalesce: Enable adaptive TX coalescing. + * @pkt_rate_low: Threshold for low packet rate (packets per second). + * @rx_coalesce_usecs_low: How many usecs to delay an RX interrupt after + * a packet arrives, when the packet rate is below @pkt_rate_low. + * @rx_max_coalesced_frames_low: Maximum number of packets to be received + * before an RX interrupt, when the packet rate is below @pkt_rate_low. + * @tx_coalesce_usecs_low: How many usecs to delay a TX interrupt after + * a packet is sent, when the packet rate is below @pkt_rate_low. + * @tx_max_coalesced_frames_low: Maximum nuumber of packets to be sent before + * a TX interrupt, when the packet rate is below @pkt_rate_low. + * @pkt_rate_high: Threshold for high packet rate (packets per second). + * @rx_coalesce_usecs_high: How many usecs to delay an RX interrupt after + * a packet arrives, when the packet rate is above @pkt_rate_high. + * @rx_max_coalesced_frames_high: Maximum number of packets to be received + * before an RX interrupt, when the packet rate is above @pkt_rate_high. + * @tx_coalesce_usecs_high: How many usecs to delay a TX interrupt after + * a packet is sent, when the packet rate is above @pkt_rate_high. + * @tx_max_coalesced_frames_high: Maximum number of packets to be sent before + * a TX interrupt, when the packet rate is above @pkt_rate_high. + * @rate_sample_interval: How often to do adaptive coalescing packet rate + * sampling, measured in seconds. Must not be zero. + * + * Each pair of (usecs, max_frames) fields specifies this exit + * condition for interrupt coalescing: + * (usecs > 0 && time_since_first_completion >= usecs) || + * (max_frames > 0 && completed_frames >= max_frames) + * It is illegal to set both usecs and max_frames to zero as this + * would cause interrupts to never be generated. To disable + * coalescing, set usecs = 0 and max_frames = 1. + * + * Some implementations ignore the value of max_frames and use the + * condition: + * time_since_first_completion >= usecs + * This is deprecated. Drivers for hardware that does not support + * counting completions should validate that max_frames == !rx_usecs. + * + * Adaptive RX/TX coalescing is an algorithm implemented by some + * drivers to improve latency under low packet rates and improve + * throughput under high packet rates. Some drivers only implement + * one of RX or TX adaptive coalescing. Anything not implemented by + * the driver causes these values to be silently ignored. + * + * When the packet rate is below @pkt_rate_high but above + * @pkt_rate_low (both measured in packets per second) the + * normal {rx,tx}_* coalescing parameters are used. + */ +struct ethtool_coalesce { + __u32 cmd; + __u32 rx_coalesce_usecs; + __u32 rx_max_coalesced_frames; + __u32 rx_coalesce_usecs_irq; + __u32 rx_max_coalesced_frames_irq; + __u32 tx_coalesce_usecs; + __u32 tx_max_coalesced_frames; + __u32 tx_coalesce_usecs_irq; + __u32 tx_max_coalesced_frames_irq; + __u32 stats_block_coalesce_usecs; + __u32 use_adaptive_rx_coalesce; + __u32 use_adaptive_tx_coalesce; + __u32 pkt_rate_low; + __u32 rx_coalesce_usecs_low; + __u32 rx_max_coalesced_frames_low; + __u32 tx_coalesce_usecs_low; + __u32 tx_max_coalesced_frames_low; + __u32 pkt_rate_high; + __u32 rx_coalesce_usecs_high; + __u32 rx_max_coalesced_frames_high; + __u32 tx_coalesce_usecs_high; + __u32 tx_max_coalesced_frames_high; + __u32 rate_sample_interval; +}; + +/* for configuring RX/TX ring parameters */ +struct ethtool_ringparam { + __u32 cmd; /* ETHTOOL_{G,S}RINGPARAM */ + + /* Read only attributes. These indicate the maximum number + * of pending RX/TX ring entries the driver will allow the + * user to set. + */ + __u32 rx_max_pending; + __u32 rx_mini_max_pending; + __u32 rx_jumbo_max_pending; + __u32 tx_max_pending; + + /* Values changeable by the user. The valid values are + * in the range 1 to the "*_max_pending" counterpart above. + */ + __u32 rx_pending; + __u32 rx_mini_pending; + __u32 rx_jumbo_pending; + __u32 tx_pending; +}; + +/** + * struct ethtool_channels - configuring number of network channel + * @cmd: ETHTOOL_{G,S}CHANNELS + * @max_rx: Read only. Maximum number of receive channel the driver support. + * @max_tx: Read only. Maximum number of transmit channel the driver support. + * @max_other: Read only. Maximum number of other channel the driver support. + * @max_combined: Read only. Maximum number of combined channel the driver + * support. Set of queues RX, TX or other. + * @rx_count: Valid values are in the range 1 to the max_rx. + * @tx_count: Valid values are in the range 1 to the max_tx. + * @other_count: Valid values are in the range 1 to the max_other. + * @combined_count: Valid values are in the range 1 to the max_combined. + * + * This can be used to configure RX, TX and other channels. + */ + +struct ethtool_channels { + __u32 cmd; + __u32 max_rx; + __u32 max_tx; + __u32 max_other; + __u32 max_combined; + __u32 rx_count; + __u32 tx_count; + __u32 other_count; + __u32 combined_count; +}; + +/* for configuring link flow control parameters */ +struct ethtool_pauseparam { + __u32 cmd; /* ETHTOOL_{G,S}PAUSEPARAM */ + + /* If the link is being auto-negotiated (via ethtool_cmd.autoneg + * being true) the user may set 'autoneg' here non-zero to have the + * pause parameters be auto-negotiated too. In such a case, the + * {rx,tx}_pause values below determine what capabilities are + * advertised. + * + * If 'autoneg' is zero or the link is not being auto-negotiated, + * then {rx,tx}_pause force the driver to use/not-use pause + * flow control. + */ + __u32 autoneg; + __u32 rx_pause; + __u32 tx_pause; +}; + +#define ETH_GSTRING_LEN 32 +enum ethtool_stringset { + ETH_SS_TEST = 0, + ETH_SS_STATS, + ETH_SS_PRIV_FLAGS, + ETH_SS_NTUPLE_FILTERS, /* Do not use, GRXNTUPLE is now deprecated */ + ETH_SS_FEATURES, +}; + +/* for passing string sets for data tagging */ +struct ethtool_gstrings { + __u32 cmd; /* ETHTOOL_GSTRINGS */ + __u32 string_set; /* string set id e.c. ETH_SS_TEST, etc*/ + __u32 len; /* number of strings in the string set */ + __u8 data[0]; +}; + +struct ethtool_sset_info { + __u32 cmd; /* ETHTOOL_GSSET_INFO */ + __u32 reserved; + __u64 sset_mask; /* input: each bit selects an sset to query */ + /* output: each bit a returned sset */ + __u32 data[0]; /* ETH_SS_xxx count, in order, based on bits + in sset_mask. One bit implies one + __u32, two bits implies two + __u32's, etc. */ +}; + +/** + * enum ethtool_test_flags - flags definition of ethtool_test + * @ETH_TEST_FL_OFFLINE: if set perform online and offline tests, otherwise + * only online tests. + * @ETH_TEST_FL_FAILED: Driver set this flag if test fails. + * @ETH_TEST_FL_EXTERNAL_LB: Application request to perform external loopback + * test. + * @ETH_TEST_FL_EXTERNAL_LB_DONE: Driver performed the external loopback test + */ + +enum ethtool_test_flags { + ETH_TEST_FL_OFFLINE = (1 << 0), + ETH_TEST_FL_FAILED = (1 << 1), + ETH_TEST_FL_EXTERNAL_LB = (1 << 2), + ETH_TEST_FL_EXTERNAL_LB_DONE = (1 << 3), +}; + +/* for requesting NIC test and getting results*/ +struct ethtool_test { + __u32 cmd; /* ETHTOOL_TEST */ + __u32 flags; /* ETH_TEST_FL_xxx */ + __u32 reserved; + __u32 len; /* result length, in number of u64 elements */ + __u64 data[0]; +}; + +/* for dumping NIC-specific statistics */ +struct ethtool_stats { + __u32 cmd; /* ETHTOOL_GSTATS */ + __u32 n_stats; /* number of u64's being returned */ + __u64 data[0]; +}; + +struct ethtool_perm_addr { + __u32 cmd; /* ETHTOOL_GPERMADDR */ + __u32 size; + __u8 data[0]; +}; + +/* boolean flags controlling per-interface behavior characteristics. + * When reading, the flag indicates whether or not a certain behavior + * is enabled/present. When writing, the flag indicates whether + * or not the driver should turn on (set) or off (clear) a behavior. + * + * Some behaviors may read-only (unconditionally absent or present). + * If such is the case, return EINVAL in the set-flags operation if the + * flag differs from the read-only value. + */ +enum ethtool_flags { + ETH_FLAG_TXVLAN = (1 << 7), /* TX VLAN offload enabled */ + ETH_FLAG_RXVLAN = (1 << 8), /* RX VLAN offload enabled */ + ETH_FLAG_LRO = (1 << 15), /* LRO is enabled */ + ETH_FLAG_NTUPLE = (1 << 27), /* N-tuple filters enabled */ + ETH_FLAG_RXHASH = (1 << 28), +}; + +/* The following structures are for supporting RX network flow + * classification and RX n-tuple configuration. Note, all multibyte + * fields, e.g., ip4src, ip4dst, psrc, pdst, spi, etc. are expected to + * be in network byte order. + */ + +/** + * struct ethtool_tcpip4_spec - flow specification for TCP/IPv4 etc. + * @ip4src: Source host + * @ip4dst: Destination host + * @psrc: Source port + * @pdst: Destination port + * @tos: Type-of-service + * + * This can be used to specify a TCP/IPv4, UDP/IPv4 or SCTP/IPv4 flow. + */ +struct ethtool_tcpip4_spec { + __be32 ip4src; + __be32 ip4dst; + __be16 psrc; + __be16 pdst; + __u8 tos; +}; + +/** + * struct ethtool_ah_espip4_spec - flow specification for IPsec/IPv4 + * @ip4src: Source host + * @ip4dst: Destination host + * @spi: Security parameters index + * @tos: Type-of-service + * + * This can be used to specify an IPsec transport or tunnel over IPv4. + */ +struct ethtool_ah_espip4_spec { + __be32 ip4src; + __be32 ip4dst; + __be32 spi; + __u8 tos; +}; + +#define ETH_RX_NFC_IP4 1 + +/** + * struct ethtool_usrip4_spec - general flow specification for IPv4 + * @ip4src: Source host + * @ip4dst: Destination host + * @l4_4_bytes: First 4 bytes of transport (layer 4) header + * @tos: Type-of-service + * @ip_ver: Value must be %ETH_RX_NFC_IP4; mask must be 0 + * @proto: Transport protocol number; mask must be 0 + */ +struct ethtool_usrip4_spec { + __be32 ip4src; + __be32 ip4dst; + __be32 l4_4_bytes; + __u8 tos; + __u8 ip_ver; + __u8 proto; +}; + +union ethtool_flow_union { + struct ethtool_tcpip4_spec tcp_ip4_spec; + struct ethtool_tcpip4_spec udp_ip4_spec; + struct ethtool_tcpip4_spec sctp_ip4_spec; + struct ethtool_ah_espip4_spec ah_ip4_spec; + struct ethtool_ah_espip4_spec esp_ip4_spec; + struct ethtool_usrip4_spec usr_ip4_spec; + struct ethhdr ether_spec; + __u8 hdata[52]; +}; + +/** + * struct ethtool_flow_ext - additional RX flow fields + * @h_dest: destination MAC address + * @vlan_etype: VLAN EtherType + * @vlan_tci: VLAN tag control information + * @data: user defined data + * + * Note, @vlan_etype, @vlan_tci, and @data are only valid if %FLOW_EXT + * is set in &struct ethtool_rx_flow_spec @flow_type. + * @h_dest is valid if %FLOW_MAC_EXT is set. + */ +struct ethtool_flow_ext { + __u8 padding[2]; + unsigned char h_dest[ETH_ALEN]; + __be16 vlan_etype; + __be16 vlan_tci; + __be32 data[2]; +}; + +/** + * struct ethtool_rx_flow_spec - classification rule for RX flows + * @flow_type: Type of match to perform, e.g. %TCP_V4_FLOW + * @h_u: Flow fields to match (dependent on @flow_type) + * @h_ext: Additional fields to match + * @m_u: Masks for flow field bits to be matched + * @m_ext: Masks for additional field bits to be matched + * Note, all additional fields must be ignored unless @flow_type + * includes the %FLOW_EXT or %FLOW_MAC_EXT flag + * (see &struct ethtool_flow_ext description). + * @ring_cookie: RX ring/queue index to deliver to, or %RX_CLS_FLOW_DISC + * if packets should be discarded + * @location: Location of rule in the table. Locations must be + * numbered such that a flow matching multiple rules will be + * classified according to the first (lowest numbered) rule. + */ +struct ethtool_rx_flow_spec { + __u32 flow_type; + union ethtool_flow_union h_u; + struct ethtool_flow_ext h_ext; + union ethtool_flow_union m_u; + struct ethtool_flow_ext m_ext; + __u64 ring_cookie; + __u32 location; +}; + +/** + * struct ethtool_rxnfc - command to get or set RX flow classification rules + * @cmd: Specific command number - %ETHTOOL_GRXFH, %ETHTOOL_SRXFH, + * %ETHTOOL_GRXRINGS, %ETHTOOL_GRXCLSRLCNT, %ETHTOOL_GRXCLSRULE, + * %ETHTOOL_GRXCLSRLALL, %ETHTOOL_SRXCLSRLDEL or %ETHTOOL_SRXCLSRLINS + * @flow_type: Type of flow to be affected, e.g. %TCP_V4_FLOW + * @data: Command-dependent value + * @fs: Flow classification rule + * @rule_cnt: Number of rules to be affected + * @rule_locs: Array of used rule locations + * + * For %ETHTOOL_GRXFH and %ETHTOOL_SRXFH, @data is a bitmask indicating + * the fields included in the flow hash, e.g. %RXH_IP_SRC. The following + * structure fields must not be used. + * + * For %ETHTOOL_GRXRINGS, @data is set to the number of RX rings/queues + * on return. + * + * For %ETHTOOL_GRXCLSRLCNT, @rule_cnt is set to the number of defined + * rules on return. If @data is non-zero on return then it is the + * size of the rule table, plus the flag %RX_CLS_LOC_SPECIAL if the + * driver supports any special location values. If that flag is not + * set in @data then special location values should not be used. + * + * For %ETHTOOL_GRXCLSRULE, @fs.@location specifies the location of an + * existing rule on entry and @fs contains the rule on return. + * + * For %ETHTOOL_GRXCLSRLALL, @rule_cnt specifies the array size of the + * user buffer for @rule_locs on entry. On return, @data is the size + * of the rule table, @rule_cnt is the number of defined rules, and + * @rule_locs contains the locations of the defined rules. Drivers + * must use the second parameter to get_rxnfc() instead of @rule_locs. + * + * For %ETHTOOL_SRXCLSRLINS, @fs specifies the rule to add or update. + * @fs.@location either specifies the location to use or is a special + * location value with %RX_CLS_LOC_SPECIAL flag set. On return, + * @fs.@location is the actual rule location. + * + * For %ETHTOOL_SRXCLSRLDEL, @fs.@location specifies the location of an + * existing rule on entry. + * + * A driver supporting the special location values for + * %ETHTOOL_SRXCLSRLINS may add the rule at any suitable unused + * location, and may remove a rule at a later location (lower + * priority) that matches exactly the same set of flows. The special + * values are: %RX_CLS_LOC_ANY, selecting any location; + * %RX_CLS_LOC_FIRST, selecting the first suitable location (maximum + * priority); and %RX_CLS_LOC_LAST, selecting the last suitable + * location (minimum priority). Additional special values may be + * defined in future and drivers must return -%EINVAL for any + * unrecognised value. + */ +struct ethtool_rxnfc { + __u32 cmd; + __u32 flow_type; + __u64 data; + struct ethtool_rx_flow_spec fs; + __u32 rule_cnt; + __u32 rule_locs[0]; +}; + + +/** + * struct ethtool_rxfh_indir - command to get or set RX flow hash indirection + * @cmd: Specific command number - %ETHTOOL_GRXFHINDIR or %ETHTOOL_SRXFHINDIR + * @size: On entry, the array size of the user buffer, which may be zero. + * On return from %ETHTOOL_GRXFHINDIR, the array size of the hardware + * indirection table. + * @ring_index: RX ring/queue index for each hash value + * + * For %ETHTOOL_GRXFHINDIR, a @size of zero means that only the size + * should be returned. For %ETHTOOL_SRXFHINDIR, a @size of zero means + * the table should be reset to default values. This last feature + * is not supported by the original implementations. + */ +struct ethtool_rxfh_indir { + __u32 cmd; + __u32 size; + __u32 ring_index[0]; +}; + +/** + * struct ethtool_rx_ntuple_flow_spec - specification for RX flow filter + * @flow_type: Type of match to perform, e.g. %TCP_V4_FLOW + * @h_u: Flow field values to match (dependent on @flow_type) + * @m_u: Masks for flow field value bits to be ignored + * @vlan_tag: VLAN tag to match + * @vlan_tag_mask: Mask for VLAN tag bits to be ignored + * @data: Driver-dependent data to match + * @data_mask: Mask for driver-dependent data bits to be ignored + * @action: RX ring/queue index to deliver to (non-negative) or other action + * (negative, e.g. %ETHTOOL_RXNTUPLE_ACTION_DROP) + * + * For flow types %TCP_V4_FLOW, %UDP_V4_FLOW and %SCTP_V4_FLOW, where + * a field value and mask are both zero this is treated as if all mask + * bits are set i.e. the field is ignored. + */ +struct ethtool_rx_ntuple_flow_spec { + __u32 flow_type; + union { + struct ethtool_tcpip4_spec tcp_ip4_spec; + struct ethtool_tcpip4_spec udp_ip4_spec; + struct ethtool_tcpip4_spec sctp_ip4_spec; + struct ethtool_ah_espip4_spec ah_ip4_spec; + struct ethtool_ah_espip4_spec esp_ip4_spec; + struct ethtool_usrip4_spec usr_ip4_spec; + struct ethhdr ether_spec; + __u8 hdata[72]; + } h_u, m_u; + + __u16 vlan_tag; + __u16 vlan_tag_mask; + __u64 data; + __u64 data_mask; + + __s32 action; +#define ETHTOOL_RXNTUPLE_ACTION_DROP (-1) /* drop packet */ +#define ETHTOOL_RXNTUPLE_ACTION_CLEAR (-2) /* clear filter */ +}; + +/** + * struct ethtool_rx_ntuple - command to set or clear RX flow filter + * @cmd: Command number - %ETHTOOL_SRXNTUPLE + * @fs: Flow filter specification + */ +struct ethtool_rx_ntuple { + __u32 cmd; + struct ethtool_rx_ntuple_flow_spec fs; +}; + +#define ETHTOOL_FLASH_MAX_FILENAME 128 +enum ethtool_flash_op_type { + ETHTOOL_FLASH_ALL_REGIONS = 0, +}; + +/* for passing firmware flashing related parameters */ +struct ethtool_flash { + __u32 cmd; + __u32 region; + char data[ETHTOOL_FLASH_MAX_FILENAME]; +}; + +/** + * struct ethtool_dump - used for retrieving, setting device dump + * @cmd: Command number - %ETHTOOL_GET_DUMP_FLAG, %ETHTOOL_GET_DUMP_DATA, or + * %ETHTOOL_SET_DUMP + * @version: FW version of the dump, filled in by driver + * @flag: driver dependent flag for dump setting, filled in by driver during + * get and filled in by ethtool for set operation. + * flag must be initialized by macro ETH_FW_DUMP_DISABLE value when + * firmware dump is disabled. + * @len: length of dump data, used as the length of the user buffer on entry to + * %ETHTOOL_GET_DUMP_DATA and this is returned as dump length by driver + * for %ETHTOOL_GET_DUMP_FLAG command + * @data: data collected for get dump data operation + */ + +#define ETH_FW_DUMP_DISABLE 0 + +struct ethtool_dump { + __u32 cmd; + __u32 version; + __u32 flag; + __u32 len; + __u8 data[0]; +}; + +/* for returning and changing feature sets */ + +/** + * struct ethtool_get_features_block - block with state of 32 features + * @available: mask of changeable features + * @requested: mask of features requested to be enabled if possible + * @active: mask of currently enabled features + * @never_changed: mask of features not changeable for any device + */ +struct ethtool_get_features_block { + __u32 available; + __u32 requested; + __u32 active; + __u32 never_changed; +}; + +/** + * struct ethtool_gfeatures - command to get state of device's features + * @cmd: command number = %ETHTOOL_GFEATURES + * @size: in: number of elements in the features[] array; + * out: number of elements in features[] needed to hold all features + * @features: state of features + */ +struct ethtool_gfeatures { + __u32 cmd; + __u32 size; + struct ethtool_get_features_block features[0]; +}; + +/** + * struct ethtool_set_features_block - block with request for 32 features + * @valid: mask of features to be changed + * @requested: values of features to be changed + */ +struct ethtool_set_features_block { + __u32 valid; + __u32 requested; +}; + +/** + * struct ethtool_sfeatures - command to request change in device's features + * @cmd: command number = %ETHTOOL_SFEATURES + * @size: array size of the features[] array + * @features: feature change masks + */ +struct ethtool_sfeatures { + __u32 cmd; + __u32 size; + struct ethtool_set_features_block features[0]; +}; + +/** + * struct ethtool_ts_info - holds a device's timestamping and PHC association + * @cmd: command number = %ETHTOOL_GET_TS_INFO + * @so_timestamping: bit mask of the sum of the supported SO_TIMESTAMPING flags + * @phc_index: device index of the associated PHC, or -1 if there is none + * @tx_types: bit mask of the supported hwtstamp_tx_types enumeration values + * @rx_filters: bit mask of the supported hwtstamp_rx_filters enumeration values + * + * The bits in the 'tx_types' and 'rx_filters' fields correspond to + * the 'hwtstamp_tx_types' and 'hwtstamp_rx_filters' enumeration values, + * respectively. For example, if the device supports HWTSTAMP_TX_ON, + * then (1 << HWTSTAMP_TX_ON) in 'tx_types' will be set. + */ +struct ethtool_ts_info { + __u32 cmd; + __u32 so_timestamping; + __s32 phc_index; + __u32 tx_types; + __u32 tx_reserved[3]; + __u32 rx_filters; + __u32 rx_reserved[3]; +}; + +/* + * %ETHTOOL_SFEATURES changes features present in features[].valid to the + * values of corresponding bits in features[].requested. Bits in .requested + * not set in .valid or not changeable are ignored. + * + * Returns %EINVAL when .valid contains undefined or never-changeable bits + * or size is not equal to required number of features words (32-bit blocks). + * Returns >= 0 if request was completed; bits set in the value mean: + * %ETHTOOL_F_UNSUPPORTED - there were bits set in .valid that are not + * changeable (not present in %ETHTOOL_GFEATURES' features[].available) + * those bits were ignored. + * %ETHTOOL_F_WISH - some or all changes requested were recorded but the + * resulting state of bits masked by .valid is not equal to .requested. + * Probably there are other device-specific constraints on some features + * in the set. When %ETHTOOL_F_UNSUPPORTED is set, .valid is considered + * here as though ignored bits were cleared. + * %ETHTOOL_F_COMPAT - some or all changes requested were made by calling + * compatibility functions. Requested offload state cannot be properly + * managed by kernel. + * + * Meaning of bits in the masks are obtained by %ETHTOOL_GSSET_INFO (number of + * bits in the arrays - always multiple of 32) and %ETHTOOL_GSTRINGS commands + * for ETH_SS_FEATURES string set. First entry in the table corresponds to least + * significant bit in features[0] fields. Empty strings mark undefined features. + */ +enum ethtool_sfeatures_retval_bits { + ETHTOOL_F_UNSUPPORTED__BIT, + ETHTOOL_F_WISH__BIT, + ETHTOOL_F_COMPAT__BIT, +}; + +#define ETHTOOL_F_UNSUPPORTED (1 << ETHTOOL_F_UNSUPPORTED__BIT) +#define ETHTOOL_F_WISH (1 << ETHTOOL_F_WISH__BIT) +#define ETHTOOL_F_COMPAT (1 << ETHTOOL_F_COMPAT__BIT) + + +/* CMDs currently supported */ +#define ETHTOOL_GSET 0x00000001 /* Get settings. */ +#define ETHTOOL_SSET 0x00000002 /* Set settings. */ +#define ETHTOOL_GDRVINFO 0x00000003 /* Get driver info. */ +#define ETHTOOL_GREGS 0x00000004 /* Get NIC registers. */ +#define ETHTOOL_GWOL 0x00000005 /* Get wake-on-lan options. */ +#define ETHTOOL_SWOL 0x00000006 /* Set wake-on-lan options. */ +#define ETHTOOL_GMSGLVL 0x00000007 /* Get driver message level */ +#define ETHTOOL_SMSGLVL 0x00000008 /* Set driver msg level. */ +#define ETHTOOL_NWAY_RST 0x00000009 /* Restart autonegotiation. */ +/* Get link status for host, i.e. whether the interface *and* the + * physical port (if there is one) are up (ethtool_value). */ +#define ETHTOOL_GLINK 0x0000000a +#define ETHTOOL_GEEPROM 0x0000000b /* Get EEPROM data */ +#define ETHTOOL_SEEPROM 0x0000000c /* Set EEPROM data. */ +#define ETHTOOL_GCOALESCE 0x0000000e /* Get coalesce config */ +#define ETHTOOL_SCOALESCE 0x0000000f /* Set coalesce config. */ +#define ETHTOOL_GRINGPARAM 0x00000010 /* Get ring parameters */ +#define ETHTOOL_SRINGPARAM 0x00000011 /* Set ring parameters. */ +#define ETHTOOL_GPAUSEPARAM 0x00000012 /* Get pause parameters */ +#define ETHTOOL_SPAUSEPARAM 0x00000013 /* Set pause parameters. */ +#define ETHTOOL_GRXCSUM 0x00000014 /* Get RX hw csum enable (ethtool_value) */ +#define ETHTOOL_SRXCSUM 0x00000015 /* Set RX hw csum enable (ethtool_value) */ +#define ETHTOOL_GTXCSUM 0x00000016 /* Get TX hw csum enable (ethtool_value) */ +#define ETHTOOL_STXCSUM 0x00000017 /* Set TX hw csum enable (ethtool_value) */ +#define ETHTOOL_GSG 0x00000018 /* Get scatter-gather enable + * (ethtool_value) */ +#define ETHTOOL_SSG 0x00000019 /* Set scatter-gather enable + * (ethtool_value). */ +#define ETHTOOL_TEST 0x0000001a /* execute NIC self-test. */ +#define ETHTOOL_GSTRINGS 0x0000001b /* get specified string set */ +#define ETHTOOL_PHYS_ID 0x0000001c /* identify the NIC */ +#define ETHTOOL_GSTATS 0x0000001d /* get NIC-specific statistics */ +#define ETHTOOL_GTSO 0x0000001e /* Get TSO enable (ethtool_value) */ +#define ETHTOOL_STSO 0x0000001f /* Set TSO enable (ethtool_value) */ +#define ETHTOOL_GPERMADDR 0x00000020 /* Get permanent hardware address */ +#define ETHTOOL_GUFO 0x00000021 /* Get UFO enable (ethtool_value) */ +#define ETHTOOL_SUFO 0x00000022 /* Set UFO enable (ethtool_value) */ +#define ETHTOOL_GGSO 0x00000023 /* Get GSO enable (ethtool_value) */ +#define ETHTOOL_SGSO 0x00000024 /* Set GSO enable (ethtool_value) */ +#define ETHTOOL_GFLAGS 0x00000025 /* Get flags bitmap(ethtool_value) */ +#define ETHTOOL_SFLAGS 0x00000026 /* Set flags bitmap(ethtool_value) */ +#define ETHTOOL_GPFLAGS 0x00000027 /* Get driver-private flags bitmap */ +#define ETHTOOL_SPFLAGS 0x00000028 /* Set driver-private flags bitmap */ + +#define ETHTOOL_GRXFH 0x00000029 /* Get RX flow hash configuration */ +#define ETHTOOL_SRXFH 0x0000002a /* Set RX flow hash configuration */ +#define ETHTOOL_GGRO 0x0000002b /* Get GRO enable (ethtool_value) */ +#define ETHTOOL_SGRO 0x0000002c /* Set GRO enable (ethtool_value) */ +#define ETHTOOL_GRXRINGS 0x0000002d /* Get RX rings available for LB */ +#define ETHTOOL_GRXCLSRLCNT 0x0000002e /* Get RX class rule count */ +#define ETHTOOL_GRXCLSRULE 0x0000002f /* Get RX classification rule */ +#define ETHTOOL_GRXCLSRLALL 0x00000030 /* Get all RX classification rule */ +#define ETHTOOL_SRXCLSRLDEL 0x00000031 /* Delete RX classification rule */ +#define ETHTOOL_SRXCLSRLINS 0x00000032 /* Insert RX classification rule */ +#define ETHTOOL_FLASHDEV 0x00000033 /* Flash firmware to device */ +#define ETHTOOL_RESET 0x00000034 /* Reset hardware */ +#define ETHTOOL_SRXNTUPLE 0x00000035 /* Add an n-tuple filter to device */ +#define ETHTOOL_GRXNTUPLE 0x00000036 /* deprecated */ +#define ETHTOOL_GSSET_INFO 0x00000037 /* Get string set info */ +#define ETHTOOL_GRXFHINDIR 0x00000038 /* Get RX flow hash indir'n table */ +#define ETHTOOL_SRXFHINDIR 0x00000039 /* Set RX flow hash indir'n table */ + +#define ETHTOOL_GFEATURES 0x0000003a /* Get device offload settings */ +#define ETHTOOL_SFEATURES 0x0000003b /* Change device offload settings */ +#define ETHTOOL_GCHANNELS 0x0000003c /* Get no of channels */ +#define ETHTOOL_SCHANNELS 0x0000003d /* Set no of channels */ +#define ETHTOOL_SET_DUMP 0x0000003e /* Set dump settings */ +#define ETHTOOL_GET_DUMP_FLAG 0x0000003f /* Get dump settings */ +#define ETHTOOL_GET_DUMP_DATA 0x00000040 /* Get dump data */ +#define ETHTOOL_GET_TS_INFO 0x00000041 /* Get time stamping and PHC info */ +#define ETHTOOL_GMODULEINFO 0x00000042 /* Get plug-in module information */ +#define ETHTOOL_GMODULEEEPROM 0x00000043 /* Get plug-in module eeprom */ +#define ETHTOOL_GEEE 0x00000044 /* Get EEE settings */ +#define ETHTOOL_SEEE 0x00000045 /* Set EEE settings */ + +/* compatibility with older code */ +#define SPARC_ETH_GSET ETHTOOL_GSET +#define SPARC_ETH_SSET ETHTOOL_SSET + +/* Indicates what features are supported by the interface. */ +#define SUPPORTED_10baseT_Half (1 << 0) +#define SUPPORTED_10baseT_Full (1 << 1) +#define SUPPORTED_100baseT_Half (1 << 2) +#define SUPPORTED_100baseT_Full (1 << 3) +#define SUPPORTED_1000baseT_Half (1 << 4) +#define SUPPORTED_1000baseT_Full (1 << 5) +#define SUPPORTED_Autoneg (1 << 6) +#define SUPPORTED_TP (1 << 7) +#define SUPPORTED_AUI (1 << 8) +#define SUPPORTED_MII (1 << 9) +#define SUPPORTED_FIBRE (1 << 10) +#define SUPPORTED_BNC (1 << 11) +#define SUPPORTED_10000baseT_Full (1 << 12) +#define SUPPORTED_Pause (1 << 13) +#define SUPPORTED_Asym_Pause (1 << 14) +#define SUPPORTED_2500baseX_Full (1 << 15) +#define SUPPORTED_Backplane (1 << 16) +#define SUPPORTED_1000baseKX_Full (1 << 17) +#define SUPPORTED_10000baseKX4_Full (1 << 18) +#define SUPPORTED_10000baseKR_Full (1 << 19) +#define SUPPORTED_10000baseR_FEC (1 << 20) +#define SUPPORTED_20000baseMLD2_Full (1 << 21) +#define SUPPORTED_20000baseKR2_Full (1 << 22) +#define SUPPORTED_40000baseKR4_Full (1 << 23) +#define SUPPORTED_40000baseCR4_Full (1 << 24) +#define SUPPORTED_40000baseSR4_Full (1 << 25) +#define SUPPORTED_40000baseLR4_Full (1 << 26) + +/* Indicates what features are advertised by the interface. */ +#define ADVERTISED_10baseT_Half (1 << 0) +#define ADVERTISED_10baseT_Full (1 << 1) +#define ADVERTISED_100baseT_Half (1 << 2) +#define ADVERTISED_100baseT_Full (1 << 3) +#define ADVERTISED_1000baseT_Half (1 << 4) +#define ADVERTISED_1000baseT_Full (1 << 5) +#define ADVERTISED_Autoneg (1 << 6) +#define ADVERTISED_TP (1 << 7) +#define ADVERTISED_AUI (1 << 8) +#define ADVERTISED_MII (1 << 9) +#define ADVERTISED_FIBRE (1 << 10) +#define ADVERTISED_BNC (1 << 11) +#define ADVERTISED_10000baseT_Full (1 << 12) +#define ADVERTISED_Pause (1 << 13) +#define ADVERTISED_Asym_Pause (1 << 14) +#define ADVERTISED_2500baseX_Full (1 << 15) +#define ADVERTISED_Backplane (1 << 16) +#define ADVERTISED_1000baseKX_Full (1 << 17) +#define ADVERTISED_10000baseKX4_Full (1 << 18) +#define ADVERTISED_10000baseKR_Full (1 << 19) +#define ADVERTISED_10000baseR_FEC (1 << 20) +#define ADVERTISED_20000baseMLD2_Full (1 << 21) +#define ADVERTISED_20000baseKR2_Full (1 << 22) +#define ADVERTISED_40000baseKR4_Full (1 << 23) +#define ADVERTISED_40000baseCR4_Full (1 << 24) +#define ADVERTISED_40000baseSR4_Full (1 << 25) +#define ADVERTISED_40000baseLR4_Full (1 << 26) + +/* The following are all involved in forcing a particular link + * mode for the device for setting things. When getting the + * devices settings, these indicate the current mode and whether + * it was forced up into this mode or autonegotiated. + */ + +/* The forced speed, 10Mb, 100Mb, gigabit, 2.5Gb, 10GbE. */ +#define SPEED_10 10 +#define SPEED_100 100 +#define SPEED_1000 1000 +#define SPEED_2500 2500 +#define SPEED_10000 10000 +#define SPEED_UNKNOWN -1 + +/* Duplex, half or full. */ +#define DUPLEX_HALF 0x00 +#define DUPLEX_FULL 0x01 +#define DUPLEX_UNKNOWN 0xff + +/* Which connector port. */ +#define PORT_TP 0x00 +#define PORT_AUI 0x01 +#define PORT_MII 0x02 +#define PORT_FIBRE 0x03 +#define PORT_BNC 0x04 +#define PORT_DA 0x05 +#define PORT_NONE 0xef +#define PORT_OTHER 0xff + +/* Which transceiver to use. */ +#define XCVR_INTERNAL 0x00 +#define XCVR_EXTERNAL 0x01 +#define XCVR_DUMMY1 0x02 +#define XCVR_DUMMY2 0x03 +#define XCVR_DUMMY3 0x04 + +/* Enable or disable autonegotiation. If this is set to enable, + * the forced link modes above are completely ignored. + */ +#define AUTONEG_DISABLE 0x00 +#define AUTONEG_ENABLE 0x01 + +/* MDI or MDI-X status/control - if MDI/MDI_X/AUTO is set then + * the driver is required to renegotiate link + */ +#define ETH_TP_MDI_INVALID 0x00 /* status: unknown; control: unsupported */ +#define ETH_TP_MDI 0x01 /* status: MDI; control: force MDI */ +#define ETH_TP_MDI_X 0x02 /* status: MDI-X; control: force MDI-X */ +#define ETH_TP_MDI_AUTO 0x03 /* control: auto-select */ + +/* Wake-On-Lan options. */ +#define WAKE_PHY (1 << 0) +#define WAKE_UCAST (1 << 1) +#define WAKE_MCAST (1 << 2) +#define WAKE_BCAST (1 << 3) +#define WAKE_ARP (1 << 4) +#define WAKE_MAGIC (1 << 5) +#define WAKE_MAGICSECURE (1 << 6) /* only meaningful if WAKE_MAGIC */ + +/* L2-L4 network traffic flow types */ +#define TCP_V4_FLOW 0x01 /* hash or spec (tcp_ip4_spec) */ +#define UDP_V4_FLOW 0x02 /* hash or spec (udp_ip4_spec) */ +#define SCTP_V4_FLOW 0x03 /* hash or spec (sctp_ip4_spec) */ +#define AH_ESP_V4_FLOW 0x04 /* hash only */ +#define TCP_V6_FLOW 0x05 /* hash only */ +#define UDP_V6_FLOW 0x06 /* hash only */ +#define SCTP_V6_FLOW 0x07 /* hash only */ +#define AH_ESP_V6_FLOW 0x08 /* hash only */ +#define AH_V4_FLOW 0x09 /* hash or spec (ah_ip4_spec) */ +#define ESP_V4_FLOW 0x0a /* hash or spec (esp_ip4_spec) */ +#define AH_V6_FLOW 0x0b /* hash only */ +#define ESP_V6_FLOW 0x0c /* hash only */ +#define IP_USER_FLOW 0x0d /* spec only (usr_ip4_spec) */ +#define IPV4_FLOW 0x10 /* hash only */ +#define IPV6_FLOW 0x11 /* hash only */ +#define ETHER_FLOW 0x12 /* spec only (ether_spec) */ +/* Flag to enable additional fields in struct ethtool_rx_flow_spec */ +#define FLOW_EXT 0x80000000 +#define FLOW_MAC_EXT 0x40000000 + +/* L3-L4 network traffic flow hash options */ +#define RXH_L2DA (1 << 1) +#define RXH_VLAN (1 << 2) +#define RXH_L3_PROTO (1 << 3) +#define RXH_IP_SRC (1 << 4) +#define RXH_IP_DST (1 << 5) +#define RXH_L4_B_0_1 (1 << 6) /* src port in case of TCP/UDP/SCTP */ +#define RXH_L4_B_2_3 (1 << 7) /* dst port in case of TCP/UDP/SCTP */ +#define RXH_DISCARD (1 << 31) + +#define RX_CLS_FLOW_DISC 0xffffffffffffffffULL + +/* Special RX classification rule insert location values */ +#define RX_CLS_LOC_SPECIAL 0x80000000 /* flag */ +#define RX_CLS_LOC_ANY 0xffffffff +#define RX_CLS_LOC_FIRST 0xfffffffe +#define RX_CLS_LOC_LAST 0xfffffffd + +/* EEPROM Standards for plug in modules */ +#define ETH_MODULE_SFF_8079 0x1 +#define ETH_MODULE_SFF_8079_LEN 256 +#define ETH_MODULE_SFF_8472 0x2 +#define ETH_MODULE_SFF_8472_LEN 512 + +/* Reset flags */ +/* The reset() operation must clear the flags for the components which + * were actually reset. On successful return, the flags indicate the + * components which were not reset, either because they do not exist + * in the hardware or because they cannot be reset independently. The + * driver must never reset any components that were not requested. + */ +enum ethtool_reset_flags { + /* These flags represent components dedicated to the interface + * the command is addressed to. Shift any flag left by + * ETH_RESET_SHARED_SHIFT to reset a shared component of the + * same type. + */ + ETH_RESET_MGMT = 1 << 0, /* Management processor */ + ETH_RESET_IRQ = 1 << 1, /* Interrupt requester */ + ETH_RESET_DMA = 1 << 2, /* DMA engine */ + ETH_RESET_FILTER = 1 << 3, /* Filtering/flow direction */ + ETH_RESET_OFFLOAD = 1 << 4, /* Protocol offload */ + ETH_RESET_MAC = 1 << 5, /* Media access controller */ + ETH_RESET_PHY = 1 << 6, /* Transceiver/PHY */ + ETH_RESET_RAM = 1 << 7, /* RAM shared between + * multiple components */ + + ETH_RESET_DEDICATED = 0x0000ffff, /* All components dedicated to + * this interface */ + ETH_RESET_ALL = 0xffffffff, /* All components used by this + * interface, even if shared */ +}; +#define ETH_RESET_SHARED_SHIFT 16 + +#endif /* _LINUX_ETHTOOL_H */ diff --git a/root/package/link4all/quectel-CM/src/libmnl/README b/root/package/link4all/quectel-CM/src/libmnl/README new file mode 100644 index 00000000..fbac9d2a --- /dev/null +++ b/root/package/link4all/quectel-CM/src/libmnl/README @@ -0,0 +1,28 @@ += What is libmnl? = + +libmnl is a minimalistic user-space library oriented to Netlink developers. +There are a lot of common tasks in parsing, validating, constructing of +both the Netlink header and TLVs that are repetitive and easy to get wrong. +This library aims to provide simple helpers that allows you to re-use code +and to avoid re-inventing the wheel. The main features of this library are: + +* Small: the shared library requires around 30KB for an x86-based computer. +* Simple: this library avoids complexity and elaborated abstractions that +tend to hide Netlink details. +* Easy to use: the library simplifies the work for Netlink-wise developers. +It provides functions to make socket handling, message building, validating, +parsing and sequence tracking, easier. +* Easy to re-use: you can use the library to build your own abstraction layer +on top of this library. +* Decoupling: the interdependency of the main bricks that compose the library +is reduced, i.e. the library provides many helpers, but the programmer is not +forced to use them. + += Example files = + +You can find several example files under examples/ that you can compile by +invoking `make check'. + +-- +08/sep/2010 +Pablo Neira Ayuso diff --git a/root/package/link4all/quectel-CM/src/libmnl/attr.c b/root/package/link4all/quectel-CM/src/libmnl/attr.c new file mode 100644 index 00000000..30eb537b --- /dev/null +++ b/root/package/link4all/quectel-CM/src/libmnl/attr.c @@ -0,0 +1,722 @@ +/* + * (C) 2008-2012 by Pablo Neira Ayuso + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + */ +#include /* for INT_MAX */ +#include +#include + +#include "libmnl.h" + +/** + * \defgroup attr Netlink attribute helpers + * + * Netlink Type-Length-Value (TLV) attribute: + * \verbatim + |<-- 2 bytes -->|<-- 2 bytes -->|<-- variable -->| + ------------------------------------------------- + | length | type | value | + ------------------------------------------------- + |<--------- header ------------>|<-- payload --->| +\endverbatim + * The payload of the Netlink message contains sequences of attributes that are + * expressed in TLV format. + * + * @{ + */ + +/** + * mnl_attr_get_type - get type of netlink attribute + * \param attr pointer to netlink attribute + * + * This function returns the attribute type. + */ +uint16_t mnl_attr_get_type(const struct nlattr *attr) +{ + return attr->nla_type & NLA_TYPE_MASK; +} + +/** + * mnl_attr_get_len - get length of netlink attribute + * \param attr pointer to netlink attribute + * + * This function returns the attribute length that is the attribute header + * plus the attribute payload. + */ +uint16_t mnl_attr_get_len(const struct nlattr *attr) +{ + return attr->nla_len; +} + +/** + * mnl_attr_get_payload_len - get the attribute payload-value length + * \param attr pointer to netlink attribute + * + * This function returns the attribute payload-value length. + */ +uint16_t mnl_attr_get_payload_len(const struct nlattr *attr) +{ + return attr->nla_len - MNL_ATTR_HDRLEN; +} + +/** + * mnl_attr_get_payload - get pointer to the attribute payload + * \param attr pointer to netlink attribute + * + * This function return a pointer to the attribute payload. + */ +void *mnl_attr_get_payload(const struct nlattr *attr) +{ + return (void *)attr + MNL_ATTR_HDRLEN; +} + +/** + * mnl_attr_ok - check if there is room for an attribute in a buffer + * \param attr attribute that we want to check if there is room for + * \param len remaining bytes in a buffer that contains the attribute + * + * This function is used to check that a buffer, which is supposed to contain + * an attribute, has enough room for the attribute that it stores, i.e. this + * function can be used to verify that an attribute is neither malformed nor + * truncated. + * + * This function does not set errno in case of error since it is intended + * for iterations. Thus, it returns true on success and false on error. + * + * The len parameter may be negative in the case of malformed messages during + * attribute iteration, that is why we use a signed integer. + */ +bool mnl_attr_ok(const struct nlattr *attr, int len) +{ + return len >= (int)sizeof(struct nlattr) && + attr->nla_len >= sizeof(struct nlattr) && + (int)attr->nla_len <= len; +} + +/** + * mnl_attr_next - get the next attribute in the payload of a netlink message + * \param attr pointer to the current attribute + * + * This function returns a pointer to the next attribute after the one passed + * as parameter. You have to use mnl_attr_ok() to ensure that the next + * attribute is valid. + */ +struct nlattr *mnl_attr_next(const struct nlattr *attr) +{ + return (struct nlattr *)((void *)attr + MNL_ALIGN(attr->nla_len)); +} + +/** + * mnl_attr_type_valid - check if the attribute type is valid + * \param attr pointer to attribute to be checked + * \param max maximum attribute type + * + * This function allows to check if the attribute type is higher than the + * maximum supported type. If the attribute type is invalid, this function + * returns -1 and errno is explicitly set. On success, this function returns 1. + * + * Strict attribute checking in user-space is not a good idea since you may + * run an old application with a newer kernel that supports new attributes. + * This leads to backward compatibility breakages in user-space. Better check + * if you support an attribute, if not, skip it. + */ +int mnl_attr_type_valid(const struct nlattr *attr, uint16_t max) +{ + if (mnl_attr_get_type(attr) > max) { + errno = EOPNOTSUPP; + return -1; + } + return 1; +} + +static int __mnl_attr_validate(const struct nlattr *attr, + enum mnl_attr_data_type type, size_t exp_len) +{ + uint16_t attr_len = mnl_attr_get_payload_len(attr); + const char *attr_data = mnl_attr_get_payload(attr); + + if (attr_len < exp_len) { + errno = ERANGE; + return -1; + } + switch(type) { + case MNL_TYPE_FLAG: + if (attr_len > 0) { + errno = ERANGE; + return -1; + } + break; + case MNL_TYPE_NUL_STRING: + if (attr_len == 0) { + errno = ERANGE; + return -1; + } + if (attr_data[attr_len-1] != '\0') { + errno = EINVAL; + return -1; + } + break; + case MNL_TYPE_STRING: + if (attr_len == 0) { + errno = ERANGE; + return -1; + } + break; + case MNL_TYPE_NESTED: + /* empty nested attributes are OK. */ + if (attr_len == 0) + break; + /* if not empty, they must contain one header, eg. flag */ + if (attr_len < MNL_ATTR_HDRLEN) { + errno = ERANGE; + return -1; + } + break; + default: + /* make gcc happy. */ + break; + } + if (exp_len && attr_len > exp_len) { + errno = ERANGE; + return -1; + } + return 0; +} + +static const size_t mnl_attr_data_type_len[MNL_TYPE_MAX] = { + [MNL_TYPE_U8] = sizeof(uint8_t), + [MNL_TYPE_U16] = sizeof(uint16_t), + [MNL_TYPE_U32] = sizeof(uint32_t), + [MNL_TYPE_U64] = sizeof(uint64_t), + [MNL_TYPE_MSECS] = sizeof(uint64_t), +}; + +/** + * mnl_attr_validate - validate netlink attribute (simplified version) + * \param attr pointer to netlink attribute that we want to validate + * \param type data type (see enum mnl_attr_data_type) + * + * The validation is based on the data type. Specifically, it checks that + * integers (u8, u16, u32 and u64) have enough room for them. This function + * returns -1 in case of error, and errno is explicitly set. + */ +int mnl_attr_validate(const struct nlattr *attr, enum mnl_attr_data_type type) +{ + int exp_len; + + if (type >= MNL_TYPE_MAX) { + errno = EINVAL; + return -1; + } + exp_len = mnl_attr_data_type_len[type]; + return __mnl_attr_validate(attr, type, exp_len); +} + +/** + * mnl_attr_validate2 - validate netlink attribute (extended version) + * \param attr pointer to netlink attribute that we want to validate + * \param type attribute type (see enum mnl_attr_data_type) + * \param exp_len expected attribute data size + * + * This function allows to perform a more accurate validation for attributes + * whose size is variable. If the size of the attribute is not what we expect, + * this functions returns -1 and errno is explicitly set. + */ +int mnl_attr_validate2(const struct nlattr *attr, + enum mnl_attr_data_type type, + size_t exp_len) +{ + if (type >= MNL_TYPE_MAX) { + errno = EINVAL; + return -1; + } + return __mnl_attr_validate(attr, type, exp_len); +} + +/** + * mnl_attr_parse - parse attributes + * \param nlh pointer to netlink message + * \param offset offset to start parsing from (if payload is after any header) + * \param cb callback function that is called for each attribute + * \param data pointer to data that is passed to the callback function + * + * This function allows to iterate over the sequence of attributes that compose + * the Netlink message. You can then put the attribute in an array as it + * usually happens at this stage or you can use any other data structure (such + * as lists or trees). + * + * This function propagates the return value of the callback, which can be + * MNL_CB_ERROR, MNL_CB_OK or MNL_CB_STOP. + */ +int mnl_attr_parse(const struct nlmsghdr *nlh, + unsigned int offset, mnl_attr_cb_t cb, + void *data) +{ + int ret = MNL_CB_OK; + const struct nlattr *attr; + + mnl_attr_for_each(attr, nlh, offset) + if ((ret = cb(attr, data)) <= MNL_CB_STOP) + return ret; + return ret; +} + +/** + * mnl_attr_parse_nested - parse attributes inside a nest + * \param nested pointer to netlink attribute that contains a nest + * \param cb callback function that is called for each attribute in the nest + * \param data pointer to data passed to the callback function + * + * This function allows to iterate over the sequence of attributes that compose + * the Netlink message. You can then put the attribute in an array as it + * usually happens at this stage or you can use any other data structure (such + * as lists or trees). + * + * This function propagates the return value of the callback, which can be + * MNL_CB_ERROR, MNL_CB_OK or MNL_CB_STOP. + */ +int mnl_attr_parse_nested(const struct nlattr *nested, + mnl_attr_cb_t cb, void *data) +{ + int ret = MNL_CB_OK; + const struct nlattr *attr; + + mnl_attr_for_each_nested(attr, nested) + if ((ret = cb(attr, data)) <= MNL_CB_STOP) + return ret; + return ret; +} + +/** + * mnl_attr_parse_payload - parse attributes in payload of Netlink message + * \param payload pointer to payload of the Netlink message + * \param payload_len payload length that contains the attributes + * \param cb callback function that is called for each attribute + * \param data pointer to data that is passed to the callback function + * + * This function takes a pointer to the area that contains the attributes, + * commonly known as the payload of the Netlink message. Thus, you have to + * pass a pointer to the Netlink message payload, instead of the entire + * message. + * + * This function allows you to iterate over the sequence of attributes that are + * located at some payload offset. You can then put the attributes in one array + * as usual, or you can use any other data structure (such as lists or trees). + * + * This function propagates the return value of the callback, which can be + * MNL_CB_ERROR, MNL_CB_OK or MNL_CB_STOP. + */ +int mnl_attr_parse_payload(const void *payload, + size_t payload_len, + mnl_attr_cb_t cb, void *data) +{ + int ret = MNL_CB_OK; + const struct nlattr *attr; + + mnl_attr_for_each_payload(payload, payload_len) + if ((ret = cb(attr, data)) <= MNL_CB_STOP) + return ret; + return ret; +} + +/** + * mnl_attr_get_u8 - returns 8-bit unsigned integer attribute payload + * \param attr pointer to netlink attribute + * + * This function returns the 8-bit value of the attribute payload. + */ +uint8_t mnl_attr_get_u8(const struct nlattr *attr) +{ + return *((uint8_t *)mnl_attr_get_payload(attr)); +} + +/** + * mnl_attr_get_u16 - returns 16-bit unsigned integer attribute payload + * \param attr pointer to netlink attribute + * + * This function returns the 16-bit value of the attribute payload. + */ +uint16_t mnl_attr_get_u16(const struct nlattr *attr) +{ + return *((uint16_t *)mnl_attr_get_payload(attr)); +} + +/** + * mnl_attr_get_u32 - returns 32-bit unsigned integer attribute payload + * \param attr pointer to netlink attribute + * + * This function returns the 32-bit value of the attribute payload. + */ +uint32_t mnl_attr_get_u32(const struct nlattr *attr) +{ + return *((uint32_t *)mnl_attr_get_payload(attr)); +} + +/** + * mnl_attr_get_u64 - returns 64-bit unsigned integer attribute. + * \param attr pointer to netlink attribute + * + * This function returns the 64-bit value of the attribute payload. This + * function is align-safe, since accessing 64-bit Netlink attributes is a + * common source of alignment issues. + */ +uint64_t mnl_attr_get_u64(const struct nlattr *attr) +{ + uint64_t tmp; + memcpy(&tmp, mnl_attr_get_payload(attr), sizeof(tmp)); + return tmp; +} + +/** + * mnl_attr_get_str - returns pointer to string attribute. + * \param attr pointer to netlink attribute + * + * This function returns the payload of string attribute value. + */ +const char *mnl_attr_get_str(const struct nlattr *attr) +{ + return mnl_attr_get_payload(attr); +} + +/** + * mnl_attr_put - add an attribute to netlink message + * \param nlh pointer to the netlink message + * \param type netlink attribute type that you want to add + * \param len netlink attribute payload length + * \param data pointer to the data that will be stored by the new attribute + * + * This function updates the length field of the Netlink message (nlmsg_len) + * by adding the size (header + payload) of the new attribute. + */ +void mnl_attr_put(struct nlmsghdr *nlh, uint16_t type, + size_t len, const void *data) +{ + struct nlattr *attr = mnl_nlmsg_get_payload_tail(nlh); + uint16_t payload_len = MNL_ALIGN(sizeof(struct nlattr)) + len; + int pad; + + attr->nla_type = type; + attr->nla_len = payload_len; + memcpy(mnl_attr_get_payload(attr), data, len); + pad = MNL_ALIGN(len) - len; + if (pad > 0) + memset(mnl_attr_get_payload(attr) + len, 0, pad); + + nlh->nlmsg_len += MNL_ALIGN(payload_len); +} + +/** + * mnl_attr_put_u8 - add 8-bit unsigned integer attribute to netlink message + * \param nlh pointer to the netlink message + * \param type netlink attribute type + * \param data 8-bit unsigned integer data that is stored by the new attribute + * + * This function updates the length field of the Netlink message (nlmsg_len) + * by adding the size (header + payload) of the new attribute. + */ +void mnl_attr_put_u8(struct nlmsghdr *nlh, uint16_t type, + uint8_t data) +{ + mnl_attr_put(nlh, type, sizeof(uint8_t), &data); +} + +/** + * mnl_attr_put_u16 - add 16-bit unsigned integer attribute to netlink message + * \param nlh pointer to the netlink message + * \param type netlink attribute type + * \param data 16-bit unsigned integer data that is stored by the new attribute + * + * This function updates the length field of the Netlink message (nlmsg_len) + * by adding the size (header + payload) of the new attribute. + */ +void mnl_attr_put_u16(struct nlmsghdr *nlh, uint16_t type, + uint16_t data) +{ + mnl_attr_put(nlh, type, sizeof(uint16_t), &data); +} + +/** + * mnl_attr_put_u32 - add 32-bit unsigned integer attribute to netlink message + * \param nlh pointer to the netlink message + * \param type netlink attribute type + * \param data 32-bit unsigned integer data that is stored by the new attribute + * + * This function updates the length field of the Netlink message (nlmsg_len) + * by adding the size (header + payload) of the new attribute. + */ +void mnl_attr_put_u32(struct nlmsghdr *nlh, uint16_t type, + uint32_t data) +{ + mnl_attr_put(nlh, type, sizeof(uint32_t), &data); +} + +/** + * mnl_attr_put_u64 - add 64-bit unsigned integer attribute to netlink message + * \param nlh pointer to the netlink message + * \param type netlink attribute type + * \param data 64-bit unsigned integer data that is stored by the new attribute + * + * This function updates the length field of the Netlink message (nlmsg_len) + * by adding the size (header + payload) of the new attribute. + */ +void mnl_attr_put_u64(struct nlmsghdr *nlh, uint16_t type, + uint64_t data) +{ + mnl_attr_put(nlh, type, sizeof(uint64_t), &data); +} + +/** + * mnl_attr_put_str - add string attribute to netlink message + * \param nlh pointer to the netlink message + * \param type netlink attribute type + * \param data pointer to string data that is stored by the new attribute + * + * This function updates the length field of the Netlink message (nlmsg_len) + * by adding the size (header + payload) of the new attribute. + */ +void mnl_attr_put_str(struct nlmsghdr *nlh, uint16_t type, + const char *data) +{ + mnl_attr_put(nlh, type, strlen(data), data); +} + +/** + * mnl_attr_put_strz - add string attribute to netlink message + * \param nlh pointer to the netlink message + * \param type netlink attribute type + * \param data pointer to string data that is stored by the new attribute + * + * This function is similar to mnl_attr_put_str, but it includes the + * NUL/zero ('\0') terminator at the end of the string. + * + * This function updates the length field of the Netlink message (nlmsg_len) + * by adding the size (header + payload) of the new attribute. + */ +void mnl_attr_put_strz(struct nlmsghdr *nlh, uint16_t type, + const char *data) +{ + mnl_attr_put(nlh, type, strlen(data)+1, data); +} + +/** + * mnl_attr_nest_start - start an attribute nest + * \param nlh pointer to the netlink message + * \param type netlink attribute type + * + * This function adds the attribute header that identifies the beginning of + * an attribute nest. This function always returns a valid pointer to the + * beginning of the nest. + */ +struct nlattr *mnl_attr_nest_start(struct nlmsghdr *nlh, + uint16_t type) +{ + struct nlattr *start = mnl_nlmsg_get_payload_tail(nlh); + + /* set start->nla_len in mnl_attr_nest_end() */ + start->nla_type = NLA_F_NESTED | type; + nlh->nlmsg_len += MNL_ALIGN(sizeof(struct nlattr)); + + return start; +} + +/** + * mnl_attr_put_check - add an attribute to netlink message + * \param nlh pointer to the netlink message + * \param buflen size of buffer which stores the message + * \param type netlink attribute type that you want to add + * \param len netlink attribute payload length + * \param data pointer to the data that will be stored by the new attribute + * + * This function first checks that the data can be added to the message + * (fits into the buffer) and then updates the length field of the Netlink + * message (nlmsg_len) by adding the size (header + payload) of the new + * attribute. The function returns true if the attribute could be added + * to the message, otherwise false is returned. + */ +bool mnl_attr_put_check(struct nlmsghdr *nlh, size_t buflen, + uint16_t type, size_t len, + const void *data) +{ + if (nlh->nlmsg_len + MNL_ATTR_HDRLEN + MNL_ALIGN(len) > buflen) + return false; + mnl_attr_put(nlh, type, len, data); + return true; +} + +/** + * mnl_attr_put_u8_check - add 8-bit unsigned int attribute to netlink message + * \param nlh pointer to the netlink message + * \param buflen size of buffer which stores the message + * \param type netlink attribute type + * \param data 8-bit unsigned integer data that is stored by the new attribute + * + * This function first checks that the data can be added to the message + * (fits into the buffer) and then updates the length field of the Netlink + * message (nlmsg_len) by adding the size (header + payload) of the new + * attribute. The function returns true if the attribute could be added + * to the message, otherwise false is returned. + */ +bool mnl_attr_put_u8_check(struct nlmsghdr *nlh, size_t buflen, + uint16_t type, uint8_t data) +{ + return mnl_attr_put_check(nlh, buflen, type, sizeof(uint8_t), &data); +} + +/** + * mnl_attr_put_u16_check - add 16-bit unsigned int attribute to netlink message + * \param nlh pointer to the netlink message + * \param buflen size of buffer which stores the message + * \param type netlink attribute type + * \param data 16-bit unsigned integer data that is stored by the new attribute + * + * This function first checks that the data can be added to the message + * (fits into the buffer) and then updates the length field of the Netlink + * message (nlmsg_len) by adding the size (header + payload) of the new + * attribute. The function returns true if the attribute could be added + * to the message, otherwise false is returned. + * This function updates the length field of the Netlink message (nlmsg_len) + * by adding the size (header + payload) of the new attribute. + */ +bool mnl_attr_put_u16_check(struct nlmsghdr *nlh, size_t buflen, + uint16_t type, uint16_t data) +{ + return mnl_attr_put_check(nlh, buflen, type, sizeof(uint16_t), &data); +} + +/** + * mnl_attr_put_u32_check - add 32-bit unsigned int attribute to netlink message + * \param nlh pointer to the netlink message + * \param buflen size of buffer which stores the message + * \param type netlink attribute type + * \param data 32-bit unsigned integer data that is stored by the new attribute + * + * This function first checks that the data can be added to the message + * (fits into the buffer) and then updates the length field of the Netlink + * message (nlmsg_len) by adding the size (header + payload) of the new + * attribute. The function returns true if the attribute could be added + * to the message, otherwise false is returned. + * This function updates the length field of the Netlink message (nlmsg_len) + * by adding the size (header + payload) of the new attribute. + */ +bool mnl_attr_put_u32_check(struct nlmsghdr *nlh, size_t buflen, + uint16_t type, uint32_t data) +{ + return mnl_attr_put_check(nlh, buflen, type, sizeof(uint32_t), &data); +} + +/** + * mnl_attr_put_u64_check - add 64-bit unsigned int attribute to netlink message + * \param nlh pointer to the netlink message + * \param buflen size of buffer which stores the message + * \param type netlink attribute type + * \param data 64-bit unsigned integer data that is stored by the new attribute + * + * This function first checks that the data can be added to the message + * (fits into the buffer) and then updates the length field of the Netlink + * message (nlmsg_len) by adding the size (header + payload) of the new + * attribute. The function returns true if the attribute could be added + * to the message, otherwise false is returned. + * This function updates the length field of the Netlink message (nlmsg_len) + * by adding the size (header + payload) of the new attribute. + */ +bool mnl_attr_put_u64_check(struct nlmsghdr *nlh, size_t buflen, + uint16_t type, uint64_t data) +{ + return mnl_attr_put_check(nlh, buflen, type, sizeof(uint64_t), &data); +} + +/** + * mnl_attr_put_str_check - add string attribute to netlink message + * \param nlh pointer to the netlink message + * \param buflen size of buffer which stores the message + * \param type netlink attribute type + * \param data pointer to string data that is stored by the new attribute + * + * This function first checks that the data can be added to the message + * (fits into the buffer) and then updates the length field of the Netlink + * message (nlmsg_len) by adding the size (header + payload) of the new + * attribute. The function returns true if the attribute could be added + * to the message, otherwise false is returned. + * This function updates the length field of the Netlink message (nlmsg_len) + * by adding the size (header + payload) of the new attribute. + */ +bool mnl_attr_put_str_check(struct nlmsghdr *nlh, size_t buflen, + uint16_t type, const char *data) +{ + return mnl_attr_put_check(nlh, buflen, type, strlen(data), data); +} + +/** + * mnl_attr_put_strz_check - add string attribute to netlink message + * \param nlh pointer to the netlink message + * \param buflen size of buffer which stores the message + * \param type netlink attribute type + * \param data pointer to string data that is stored by the new attribute + * + * This function is similar to mnl_attr_put_str, but it includes the + * NUL/zero ('\0') terminator at the end of the string. + * + * This function first checks that the data can be added to the message + * (fits into the buffer) and then updates the length field of the Netlink + * message (nlmsg_len) by adding the size (header + payload) of the new + * attribute. The function returns true if the attribute could be added + * to the message, otherwise false is returned. + */ +bool mnl_attr_put_strz_check(struct nlmsghdr *nlh, size_t buflen, + uint16_t type, const char *data) +{ + return mnl_attr_put_check(nlh, buflen, type, strlen(data)+1, data); +} + +/** + * mnl_attr_nest_start_check - start an attribute nest + * \param buflen size of buffer which stores the message + * \param nlh pointer to the netlink message + * \param type netlink attribute type + * + * This function adds the attribute header that identifies the beginning of + * an attribute nest. If the nested attribute cannot be added then NULL, + * otherwise valid pointer to the beginning of the nest is returned. + */ +struct nlattr *mnl_attr_nest_start_check(struct nlmsghdr *nlh, + size_t buflen, + uint16_t type) +{ + if (nlh->nlmsg_len + MNL_ATTR_HDRLEN > buflen) + return NULL; + return mnl_attr_nest_start(nlh, type); +} + +/** + * mnl_attr_nest_end - end an attribute nest + * \param nlh pointer to the netlink message + * \param start pointer to the attribute nest returned by mnl_attr_nest_start() + * + * This function updates the attribute header that identifies the nest. + */ +void mnl_attr_nest_end(struct nlmsghdr *nlh, + struct nlattr *start) +{ + start->nla_len = mnl_nlmsg_get_payload_tail(nlh) - (void *)start; +} + +/** + * mnl_attr_nest_cancel - cancel an attribute nest + * \param nlh pointer to the netlink message + * \param start pointer to the attribute nest returned by mnl_attr_nest_start() + * + * This function updates the attribute header that identifies the nest. + */ +void mnl_attr_nest_cancel(struct nlmsghdr *nlh, + struct nlattr *start) +{ + nlh->nlmsg_len -= mnl_nlmsg_get_payload_tail(nlh) - (void *)start; +} + +/** + * @} + */ diff --git a/root/package/link4all/quectel-CM/src/libmnl/callback.c b/root/package/link4all/quectel-CM/src/libmnl/callback.c new file mode 100644 index 00000000..d6c42ba6 --- /dev/null +++ b/root/package/link4all/quectel-CM/src/libmnl/callback.c @@ -0,0 +1,171 @@ +/* + * (C) 2008-2010 by Pablo Neira Ayuso + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + */ + +#include + +#include "libmnl.h" + +static int mnl_cb_noop(const struct nlmsghdr *nlh, void *data) +{ + (void)nlh; + (void)data; + return MNL_CB_OK; +} + +static int mnl_cb_error(const struct nlmsghdr *nlh, void *data) +{ + const struct nlmsgerr *err = mnl_nlmsg_get_payload(nlh); + (void)data; + if (nlh->nlmsg_len < mnl_nlmsg_size(sizeof(struct nlmsgerr))) { + errno = EBADMSG; + return MNL_CB_ERROR; + } + /* Netlink subsystems returns the errno value with different signess */ + if (err->error < 0) + errno = -err->error; + else + errno = err->error; + + return err->error == 0 ? MNL_CB_STOP : MNL_CB_ERROR; +} + +static int mnl_cb_stop(const struct nlmsghdr *nlh, void *data) +{ + (void)nlh; + (void)data; + return MNL_CB_STOP; +} + +static const mnl_cb_t default_cb_array[NLMSG_MIN_TYPE] = { + [NLMSG_NOOP] = mnl_cb_noop, + [NLMSG_ERROR] = mnl_cb_error, + [NLMSG_DONE] = mnl_cb_stop, + [NLMSG_OVERRUN] = mnl_cb_noop, +}; + +static inline int __mnl_cb_run(const void *buf, size_t numbytes, + unsigned int seq, unsigned int portid, + mnl_cb_t cb_data, void *data, + const mnl_cb_t *cb_ctl_array, + unsigned int cb_ctl_array_len) +{ + int ret = MNL_CB_OK, len = numbytes; + const struct nlmsghdr *nlh = buf; + + while (mnl_nlmsg_ok(nlh, len)) { + /* check message source */ + if (!mnl_nlmsg_portid_ok(nlh, portid)) { + errno = ESRCH; + return -1; + } + /* perform sequence tracking */ + if (!mnl_nlmsg_seq_ok(nlh, seq)) { + errno = EPROTO; + return -1; + } + + /* dump was interrupted */ + if (nlh->nlmsg_flags & NLM_F_DUMP_INTR) { + errno = EINTR; + return -1; + } + + /* netlink data message handling */ + if (nlh->nlmsg_type >= NLMSG_MIN_TYPE) { + if (cb_data){ + ret = cb_data(nlh, data); + if (ret <= MNL_CB_STOP) + goto out; + } + } else if (nlh->nlmsg_type < cb_ctl_array_len) { + if (cb_ctl_array && cb_ctl_array[nlh->nlmsg_type]) { + ret = cb_ctl_array[nlh->nlmsg_type](nlh, data); + if (ret <= MNL_CB_STOP) + goto out; + } + } else if (default_cb_array[nlh->nlmsg_type]) { + ret = default_cb_array[nlh->nlmsg_type](nlh, data); + if (ret <= MNL_CB_STOP) + goto out; + } + nlh = mnl_nlmsg_next(nlh, &len); + } +out: + return ret; +} + +/** + * \defgroup callback Callback helpers + * @{ + */ + +/** + * mnl_cb_run2 - callback runqueue for netlink messages + * \param buf buffer that contains the netlink messages + * \param numbytes number of bytes stored in the buffer + * \param seq sequence number that we expect to receive + * \param portid Netlink PortID that we expect to receive + * \param cb_data callback handler for data messages + * \param data pointer to data that will be passed to the data callback handler + * \param cb_ctl_array array of custom callback handlers from control messages + * \param cb_ctl_array_len array length of custom control callback handlers + * + * You can set the cb_ctl_array to NULL if you want to use the default control + * callback handlers, in that case, the parameter cb_ctl_array_len is not + * checked. + * + * Your callback may return three possible values: + * - MNL_CB_ERROR (<=-1): an error has occurred. Stop callback runqueue. + * - MNL_CB_STOP (=0): stop callback runqueue. + * - MNL_CB_OK (>=1): no problem has occurred. + * + * This function propagates the callback return value. On error, it returns + * -1 and errno is explicitly set. If the portID is not the expected, errno + * is set to ESRCH. If the sequence number is not the expected, errno is set + * to EPROTO. If the dump was interrupted, errno is set to EINTR and you should + * request a new fresh dump again. + */ +int mnl_cb_run2(const void *buf, size_t numbytes, + unsigned int seq, unsigned int portid, + mnl_cb_t cb_data, void *data, + const mnl_cb_t *cb_ctl_array, + unsigned int cb_ctl_array_len) +{ + return __mnl_cb_run(buf, numbytes, seq, portid, cb_data, data, + cb_ctl_array, cb_ctl_array_len); +} + +/** + * mnl_cb_run - callback runqueue for netlink messages (simplified version) + * \param buf buffer that contains the netlink messages + * \param numbytes number of bytes stored in the buffer + * \param seq sequence number that we expect to receive + * \param portid Netlink PortID that we expect to receive + * \param cb_data callback handler for data messages + * \param data pointer to data that will be passed to the data callback handler + * + * This function is like mnl_cb_run2() but it does not allow you to set + * the control callback handlers. + * + * Your callback may return three possible values: + * - MNL_CB_ERROR (<=-1): an error has occurred. Stop callback runqueue. + * - MNL_CB_STOP (=0): stop callback runqueue. + * - MNL_CB_OK (>=1): no problems has occurred. + * + * This function propagates the callback return value. + */ +int mnl_cb_run(const void *buf, size_t numbytes, unsigned int seq, + unsigned int portid, mnl_cb_t cb_data, void *data) +{ + return __mnl_cb_run(buf, numbytes, seq, portid, cb_data, data, NULL, 0); +} + +/** + * @} + */ diff --git a/root/package/link4all/quectel-CM/src/libmnl/dhcp/dhcp.h b/root/package/link4all/quectel-CM/src/libmnl/dhcp/dhcp.h new file mode 100644 index 00000000..f4802855 --- /dev/null +++ b/root/package/link4all/quectel-CM/src/libmnl/dhcp/dhcp.h @@ -0,0 +1,5 @@ +#ifndef __DHCP_H__ +#define __DHCP_H__ + +int do_dhcp(char *iname); +#endif //__DHCP_H__ \ No newline at end of file diff --git a/root/package/link4all/quectel-CM/src/libmnl/dhcp/dhcpclient.c b/root/package/link4all/quectel-CM/src/libmnl/dhcp/dhcpclient.c new file mode 100644 index 00000000..ccb71b5d --- /dev/null +++ b/root/package/link4all/quectel-CM/src/libmnl/dhcp/dhcpclient.c @@ -0,0 +1,515 @@ +/* + * Copyright 2008, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../ifutils.h" +#include "dhcpmsg.h" +#include "packet.h" + +#define VERBOSE 2 + +static int verbose = 1; +static char errmsg[2048]; + +typedef unsigned long long msecs_t; +#if VERBOSE +void dump_dhcp_msg(); +#endif + +msecs_t get_msecs(void) +{ + struct timespec ts; + + if (clock_gettime(CLOCK_MONOTONIC, &ts)) { + return 0; + } else { + return (((msecs_t) ts.tv_sec) * ((msecs_t) 1000)) + + (((msecs_t) ts.tv_nsec) / ((msecs_t) 1000000)); + } +} + +void printerr(char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vsnprintf(errmsg, sizeof(errmsg), fmt, ap); + va_end(ap); + + printf("%s\n", errmsg); +} + +const char *dhcp_lasterror() +{ + return errmsg; +} + +int fatal(const char *reason) +{ + printerr("%s: %s\n", reason, strerror(errno)); + return -1; +// exit(1); +} + +typedef struct dhcp_info dhcp_info; + +struct dhcp_info { + uint32_t type; + + uint32_t ipaddr; + uint32_t gateway; + uint32_t prefixLength; + + uint32_t dns1; + uint32_t dns2; + + uint32_t serveraddr; + uint32_t lease; +}; + +dhcp_info last_good_info; + +void get_dhcp_info(uint32_t *ipaddr, uint32_t *gateway, uint32_t *prefixLength, + uint32_t *dns1, uint32_t *dns2, uint32_t *server, + uint32_t *lease) +{ + *ipaddr = last_good_info.ipaddr; + *gateway = last_good_info.gateway; + *prefixLength = last_good_info.prefixLength; + *dns1 = last_good_info.dns1; + *dns2 = last_good_info.dns2; + *server = last_good_info.serveraddr; + *lease = last_good_info.lease; +} + +static int dhcp_configure(const char *ifname, dhcp_info *info) +{ + last_good_info = *info; + return if_set_network_v4(ifname, info->ipaddr, info->prefixLength, info->gateway, + info->dns1, info->dns2); +} + +static const char *dhcp_type_to_name(uint32_t type) +{ + switch(type) { + case DHCPDISCOVER: return "discover"; + case DHCPOFFER: return "offer"; + case DHCPREQUEST: return "request"; + case DHCPDECLINE: return "decline"; + case DHCPACK: return "ack"; + case DHCPNAK: return "nak"; + case DHCPRELEASE: return "release"; + case DHCPINFORM: return "inform"; + default: return "???"; + } +} + +void dump_dhcp_info(dhcp_info *info) +{ + char addr[20], gway[20]; + printf("--- dhcp %s (%d) ---\n", + dhcp_type_to_name(info->type), info->type); + strcpy(addr, ipaddr_to_string_v4(info->ipaddr)); + strcpy(gway, ipaddr_to_string_v4(info->gateway)); + printf("ip %s gw %s prefixLength %d\n", addr, gway, info->prefixLength); + if (info->dns1) printf("dns1: %s\n", ipaddr_to_string_v4(info->dns1)); + if (info->dns2) printf("dns2: %s\n", ipaddr_to_string_v4(info->dns2)); + printf("server %s, lease %d seconds\n", + ipaddr_to_string_v4(info->serveraddr), info->lease); +} + + +int decode_dhcp_msg(dhcp_msg *msg, int len, dhcp_info *info) +{ + uint8_t *x; + unsigned int opt; + int optlen; + + memset(info, 0, sizeof(dhcp_info)); + if (len < (DHCP_MSG_FIXED_SIZE + 4)) return -1; + + len -= (DHCP_MSG_FIXED_SIZE + 4); + + if (msg->options[0] != OPT_COOKIE1) return -1; + if (msg->options[1] != OPT_COOKIE2) return -1; + if (msg->options[2] != OPT_COOKIE3) return -1; + if (msg->options[3] != OPT_COOKIE4) return -1; + + x = msg->options + 4; + + while (len > 2) { + opt = *x++; + if (opt == OPT_PAD) { + len--; + continue; + } + if (opt == OPT_END) { + break; + } + optlen = *x++; + len -= 2; + if (optlen > len) { + break; + } + switch(opt) { + case OPT_SUBNET_MASK: + if (optlen >= 4) { + in_addr_t mask; + memcpy(&mask, x, 4); + info->prefixLength = mask_to_prefix_v4(mask); + } + break; + case OPT_GATEWAY: + if (optlen >= 4) memcpy(&info->gateway, x, 4); + break; + case OPT_DNS: + if (optlen >= 4) memcpy(&info->dns1, x + 0, 4); + if (optlen >= 8) memcpy(&info->dns2, x + 4, 4); + break; + case OPT_LEASE_TIME: + if (optlen >= 4) { + memcpy(&info->lease, x, 4); + info->lease = ntohl(info->lease); + } + break; + case OPT_SERVER_ID: + if (optlen >= 4) memcpy(&info->serveraddr, x, 4); + break; + case OPT_MESSAGE_TYPE: + info->type = *x; + break; + default: + break; + } + x += optlen; + len -= optlen; + } + + info->ipaddr = msg->yiaddr; + + return 0; +} + +#if VERBOSE + +static void hex2str(char *buf, size_t buf_size, const unsigned char *array, int len) +{ + int i; + char *cp = buf; + char *buf_end = buf + buf_size; + for (i = 0; i < len; i++) { + cp += snprintf(cp, buf_end - cp, " %02x ", array[i]); + } +} + +void dump_dhcp_msg(dhcp_msg *msg, int len) +{ + unsigned char *x; + unsigned int n,c; + int optsz; + const char *name; + char buf[2048]; + + if (len < DHCP_MSG_FIXED_SIZE) { + printf("Invalid length %d, should be %d", len, DHCP_MSG_FIXED_SIZE); + return; + } + + len -= DHCP_MSG_FIXED_SIZE; + + if (msg->op == OP_BOOTREQUEST) + name = "BOOTREQUEST"; + else if (msg->op == OP_BOOTREPLY) + name = "BOOTREPLY"; + else + name = "????"; + + c = msg->hlen > 16 ? 16 : msg->hlen; + hex2str(buf, sizeof(buf), msg->chaddr, c); + + for (n = 0; n < 64; n++) { + unsigned char x = msg->sname[n]; + if ((x < ' ') || (x > 127)) { + if (x == 0) break; + msg->sname[n] = '.'; + } + } + msg->sname[63] = 0; + + for (n = 0; n < 128; n++) { + unsigned char x = msg->file[n]; + if ((x < ' ') || (x > 127)) { + if (x == 0) break; + msg->file[n] = '.'; + } + } + msg->file[127] = 0; + + if (len < 4) return; + len -= 4; + x = msg->options + 4; + + while (len > 2) { + if (*x == 0) { + x++; + len--; + continue; + } + if (*x == OPT_END) { + break; + } + len -= 2; + optsz = x[1]; + if (optsz > len) break; + if (x[0] == OPT_DOMAIN_NAME || x[0] == OPT_MESSAGE) { + if ((unsigned int)optsz < sizeof(buf) - 1) { + n = optsz; + } else { + n = sizeof(buf) - 1; + } + memcpy(buf, &x[2], n); + buf[n] = '\0'; + } else { + hex2str(buf, sizeof(buf), &x[2], optsz); + } + if (x[0] == OPT_MESSAGE_TYPE) + name = dhcp_type_to_name(x[2]); + else + name = NULL; + len -= optsz; + x = x + optsz + 2; + } +} + +#endif + +static int send_message(int sock, int if_index, dhcp_msg *msg, int size) +{ +#if VERBOSE > 1 + dump_dhcp_msg(msg, size); +#endif + return send_packet(sock, if_index, msg, size, INADDR_ANY, INADDR_BROADCAST, + PORT_BOOTP_CLIENT, PORT_BOOTP_SERVER); +} + +static int is_valid_reply(dhcp_msg *msg, dhcp_msg *reply, int sz) +{ + if (sz < DHCP_MSG_FIXED_SIZE) { + if (verbose) printf("Wrong size %d != %d\n", sz, DHCP_MSG_FIXED_SIZE); + return 0; + } + if (reply->op != OP_BOOTREPLY) { + if (verbose) printf("Wrong Op %d != %d\n", reply->op, OP_BOOTREPLY); + return 0; + } + if (reply->xid != msg->xid) { + if (verbose) printf("Wrong Xid 0x%x != 0x%x\n", ntohl(reply->xid), + ntohl(msg->xid)); + return 0; + } + if (reply->htype != msg->htype) { + if (verbose) printf("Wrong Htype %d != %d\n", reply->htype, msg->htype); + return 0; + } + if (reply->hlen != msg->hlen) { + if (verbose) printf("Wrong Hlen %d != %d\n", reply->hlen, msg->hlen); + return 0; + } + if (memcmp(msg->chaddr, reply->chaddr, msg->hlen)) { + if (verbose) printf("Wrong chaddr %x != %x\n", *(reply->chaddr),*(msg->chaddr)); + return 0; + } + return 1; +} + +#define STATE_SELECTING 1 +#define STATE_REQUESTING 2 + +#define TIMEOUT_INITIAL 4000 +#define TIMEOUT_MAX 32000 + +int dhcp_init_ifc(const char *ifname) +{ + dhcp_msg discover_msg; + dhcp_msg request_msg; + dhcp_msg reply; + dhcp_msg *msg; + dhcp_info info; + int s, r, size; + int valid_reply; + uint32_t xid; + unsigned char hwaddr[6]; + struct pollfd pfd; + unsigned int state; + unsigned int timeout; + int if_index; + + xid = (uint32_t) get_msecs(); + + if (if_get_hwaddr(ifname, hwaddr)) { + return fatal("cannot obtain interface address"); + } + if ((if_index = if_nametoindex(ifname)) == 0) { + return fatal("cannot obtain interface index"); + } + + s = open_raw_socket(ifname, hwaddr, if_index); + + timeout = TIMEOUT_INITIAL; + state = STATE_SELECTING; + info.type = 0; + goto transmit; + + for (;;) { + pfd.fd = s; + pfd.events = POLLIN; + pfd.revents = 0; + r = poll(&pfd, 1, timeout); + + if (r == 0) { +#if VERBOSE + printerr("TIMEOUT\n"); +#endif + if (timeout >= TIMEOUT_MAX) { + printerr("timed out\n"); + if ( info.type == DHCPOFFER ) { + printerr("no acknowledgement from DHCP server\nconfiguring %s with offered parameters\n", ifname); + return dhcp_configure(ifname, &info); + } + errno = ETIME; + close(s); + return -1; + } + timeout = timeout * 2; + + transmit: + size = 0; + msg = NULL; + switch(state) { + case STATE_SELECTING: + msg = &discover_msg; + size = init_dhcp_discover_msg(msg, hwaddr, xid); + break; + case STATE_REQUESTING: + msg = &request_msg; + size = init_dhcp_request_msg(msg, hwaddr, xid, info.ipaddr, info.serveraddr); + break; + default: + r = 0; + } + if (size != 0) { + r = send_message(s, if_index, msg, size); + if (r < 0) { + printerr("error sending dhcp msg: %s\n", strerror(errno)); + } + } + continue; + } + + if (r < 0) { + if ((errno == EAGAIN) || (errno == EINTR)) { + continue; + } + return fatal("poll failed"); + } + + errno = 0; + r = receive_packet(s, &reply); + if (r < 0) { + if (errno != 0) { + printf("receive_packet failed (%d): %s", r, strerror(errno)); + if (errno == ENETDOWN || errno == ENXIO) { + return -1; + } + } + continue; + } + +#if VERBOSE > 1 + dump_dhcp_msg(&reply, r); +#endif + decode_dhcp_msg(&reply, r, &info); + + if (state == STATE_SELECTING) { + valid_reply = is_valid_reply(&discover_msg, &reply, r); + } else { + valid_reply = is_valid_reply(&request_msg, &reply, r); + } + if (!valid_reply) { + printerr("invalid reply\n"); + continue; + } + + if (verbose) dump_dhcp_info(&info); + + switch(state) { + case STATE_SELECTING: + if (info.type == DHCPOFFER) { + state = STATE_REQUESTING; + timeout = TIMEOUT_INITIAL; + xid++; + goto transmit; + } + break; + case STATE_REQUESTING: + if (info.type == DHCPACK) { + printerr("configuring %s\n", ifname); + close(s); + return dhcp_configure(ifname, &info); + } else if (info.type == DHCPNAK) { + printerr("configuration request denied\n"); + close(s); + return -1; + } else { + printerr("ignoring %s message in state %d\n", + dhcp_type_to_name(info.type), state); + } + break; + } + } + close(s); + return 0; +} + +int do_dhcp(char *iname) +{ + if (if_set_addr_v4(iname, 0, 32)) { + printerr("failed to set ip addr for %s to 0.0.0.0: %s\n", iname, strerror(errno)); + return -1; + } + + if (if_link_up(iname)) { + printerr("failed to bring up interface %s: %s\n", iname, strerror(errno)); + return -1; + } + + return dhcp_init_ifc(iname); +} diff --git a/root/package/link4all/quectel-CM/src/libmnl/dhcp/dhcpmsg.c b/root/package/link4all/quectel-CM/src/libmnl/dhcp/dhcpmsg.c new file mode 100644 index 00000000..1e0a233f --- /dev/null +++ b/root/package/link4all/quectel-CM/src/libmnl/dhcp/dhcpmsg.c @@ -0,0 +1,100 @@ +/* + * Copyright 2008, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include + +#include "dhcpmsg.h" + +static void *init_dhcp_msg(dhcp_msg *msg, int type, void *hwaddr, uint32_t xid) +{ + uint8_t *x; + + memset(msg, 0, sizeof(dhcp_msg)); + + msg->op = OP_BOOTREQUEST; + msg->htype = HTYPE_ETHER; + msg->hlen = 6; + msg->hops = 0; + + msg->flags = htons(FLAGS_BROADCAST); + + msg->xid = xid; + + memcpy(msg->chaddr, hwaddr, 6); + + x = msg->options; + + *x++ = OPT_COOKIE1; + *x++ = OPT_COOKIE2; + *x++ = OPT_COOKIE3; + *x++ = OPT_COOKIE4; + + *x++ = OPT_MESSAGE_TYPE; + *x++ = 1; + *x++ = type; + + return x; +} + +int init_dhcp_discover_msg(dhcp_msg *msg, void *hwaddr, uint32_t xid) +{ + uint8_t *x; + + x = init_dhcp_msg(msg, DHCPDISCOVER, hwaddr, xid); + + *x++ = OPT_PARAMETER_LIST; + *x++ = 4; + *x++ = OPT_SUBNET_MASK; + *x++ = OPT_GATEWAY; + *x++ = OPT_DNS; + *x++ = OPT_BROADCAST_ADDR; + + *x++ = OPT_END; + + return DHCP_MSG_FIXED_SIZE + (x - msg->options); +} + +int init_dhcp_request_msg(dhcp_msg *msg, void *hwaddr, uint32_t xid, + uint32_t ipaddr, uint32_t serveraddr) +{ + uint8_t *x; + + x = init_dhcp_msg(msg, DHCPREQUEST, hwaddr, xid); + + *x++ = OPT_PARAMETER_LIST; + *x++ = 4; + *x++ = OPT_SUBNET_MASK; + *x++ = OPT_GATEWAY; + *x++ = OPT_DNS; + *x++ = OPT_BROADCAST_ADDR; + + *x++ = OPT_REQUESTED_IP; + *x++ = 4; + memcpy(x, &ipaddr, 4); + x += 4; + + *x++ = OPT_SERVER_ID; + *x++ = 4; + memcpy(x, &serveraddr, 4); + x += 4; + + *x++ = OPT_END; + + return DHCP_MSG_FIXED_SIZE + (x - msg->options); +} diff --git a/root/package/link4all/quectel-CM/src/libmnl/dhcp/dhcpmsg.h b/root/package/link4all/quectel-CM/src/libmnl/dhcp/dhcpmsg.h new file mode 100644 index 00000000..fb99490a --- /dev/null +++ b/root/package/link4all/quectel-CM/src/libmnl/dhcp/dhcpmsg.h @@ -0,0 +1,106 @@ +/* + * Copyright 2008, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _WIFI_DHCP_H_ +#define _WIFI_DHCP_H_ + +#include + +#define PORT_BOOTP_SERVER 67 +#define PORT_BOOTP_CLIENT 68 + +/* RFC 2131 p 9 */ +typedef struct dhcp_msg dhcp_msg; + +#define OP_BOOTREQUEST 1 +#define OP_BOOTREPLY 2 + +#define FLAGS_BROADCAST 0x8000 + +#define HTYPE_ETHER 1 + +struct dhcp_msg +{ + uint8_t op; /* BOOTREQUEST / BOOTREPLY */ + uint8_t htype; /* hw addr type */ + uint8_t hlen; /* hw addr len */ + uint8_t hops; /* client set to 0 */ + + uint32_t xid; /* transaction id */ + + uint16_t secs; /* seconds since start of acq */ + uint16_t flags; + + uint32_t ciaddr; /* client IP addr */ + uint32_t yiaddr; /* your (client) IP addr */ + uint32_t siaddr; /* ip addr of next server */ + /* (DHCPOFFER and DHCPACK) */ + uint32_t giaddr; /* relay agent IP addr */ + + uint8_t chaddr[16]; /* client hw addr */ + char sname[64]; /* asciiz server hostname */ + char file[128]; /* asciiz boot file name */ + + uint8_t options[1024]; /* optional parameters */ +}; + +#define DHCP_MSG_FIXED_SIZE 236 + +/* first four bytes of options are a cookie to indicate that +** the payload are DHCP options as opposed to some other BOOTP +** extension. +*/ +#define OPT_COOKIE1 0x63 +#define OPT_COOKIE2 0x82 +#define OPT_COOKIE3 0x53 +#define OPT_COOKIE4 0x63 + +/* BOOTP/DHCP options - see RFC 2132 */ +#define OPT_PAD 0 + +#define OPT_SUBNET_MASK 1 /* 4 */ +#define OPT_TIME_OFFSET 2 /* 4 */ +#define OPT_GATEWAY 3 /* 4*n * n */ +#define OPT_DNS 6 /* 4*n * n */ +#define OPT_DOMAIN_NAME 15 /* n */ +#define OPT_BROADCAST_ADDR 28 /* 4 */ + +#define OPT_REQUESTED_IP 50 /* 4 */ +#define OPT_LEASE_TIME 51 /* 4 */ +#define OPT_MESSAGE_TYPE 53 /* 1 */ +#define OPT_SERVER_ID 54 /* 4 */ +#define OPT_PARAMETER_LIST 55 /* n * n */ +#define OPT_MESSAGE 56 /* n */ +#define OPT_CLASS_ID 60 /* n */ +#define OPT_CLIENT_ID 61 /* n */ +#define OPT_END 255 + +/* DHCP message types */ +#define DHCPDISCOVER 1 +#define DHCPOFFER 2 +#define DHCPREQUEST 3 +#define DHCPDECLINE 4 +#define DHCPACK 5 +#define DHCPNAK 6 +#define DHCPRELEASE 7 +#define DHCPINFORM 8 + +int init_dhcp_discover_msg(dhcp_msg *msg, void *hwaddr, uint32_t xid); + +int init_dhcp_request_msg(dhcp_msg *msg, void *hwaddr, uint32_t xid, + uint32_t ipaddr, uint32_t serveraddr); + +#endif diff --git a/root/package/link4all/quectel-CM/src/libmnl/dhcp/packet.c b/root/package/link4all/quectel-CM/src/libmnl/dhcp/packet.c new file mode 100644 index 00000000..9515dd1e --- /dev/null +++ b/root/package/link4all/quectel-CM/src/libmnl/dhcp/packet.c @@ -0,0 +1,247 @@ +/* + * Copyright 2008, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dhcpmsg.h" + +int fatal(); + +int open_raw_socket(const char *ifname __attribute__((unused)), uint8_t *hwaddr, int if_index) +{ + int s; + struct sockaddr_ll bindaddr; + + if((s = socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_IP))) < 0) { + return fatal("socket(PF_PACKET)"); + } + + memset(&bindaddr, 0, sizeof(bindaddr)); + bindaddr.sll_family = AF_PACKET; + bindaddr.sll_protocol = htons(ETH_P_IP); + bindaddr.sll_halen = ETH_ALEN; + memcpy(bindaddr.sll_addr, hwaddr, ETH_ALEN); + bindaddr.sll_ifindex = if_index; + + if (bind(s, (struct sockaddr *)&bindaddr, sizeof(bindaddr)) < 0) { + return fatal("Cannot bind raw socket to interface"); + } + + return s; +} + +static uint32_t checksum(void *buffer, unsigned int count, uint32_t startsum) +{ + uint16_t *up = (uint16_t *)buffer; + uint32_t sum = startsum; + uint32_t upper16; + + while (count > 1) { + sum += *up++; + count -= 2; + } + if (count > 0) { + sum += (uint16_t) *(uint8_t *)up; + } + while ((upper16 = (sum >> 16)) != 0) { + sum = (sum & 0xffff) + upper16; + } + return sum; +} + +static uint32_t finish_sum(uint32_t sum) +{ + return ~sum & 0xffff; +} + +int send_packet(int s, int if_index, struct dhcp_msg *msg, int size, + uint32_t saddr, uint32_t daddr, uint32_t sport, uint32_t dport) +{ + struct iphdr ip; + struct udphdr udp; + struct iovec iov[3]; + uint32_t udpsum; + uint16_t temp; + struct msghdr msghdr; + struct sockaddr_ll destaddr; + + ip.version = IPVERSION; + ip.ihl = sizeof(ip) >> 2; + ip.tos = 0; + ip.tot_len = htons(sizeof(ip) + sizeof(udp) + size); + ip.id = 0; + ip.frag_off = 0; + ip.ttl = IPDEFTTL; + ip.protocol = IPPROTO_UDP; + ip.check = 0; + ip.saddr = saddr; + ip.daddr = daddr; + ip.check = finish_sum(checksum(&ip, sizeof(ip), 0)); + + udp.source = htons(sport); + udp.dest = htons(dport); + udp.len = htons(sizeof(udp) + size); + udp.check = 0; + + /* Calculate checksum for pseudo header */ + udpsum = checksum(&ip.saddr, sizeof(ip.saddr), 0); + udpsum = checksum(&ip.daddr, sizeof(ip.daddr), udpsum); + temp = htons(IPPROTO_UDP); + udpsum = checksum(&temp, sizeof(temp), udpsum); + temp = udp.len; + udpsum = checksum(&temp, sizeof(temp), udpsum); + + /* Add in the checksum for the udp header */ + udpsum = checksum(&udp, sizeof(udp), udpsum); + + /* Add in the checksum for the data */ + udpsum = checksum(msg, size, udpsum); + udp.check = finish_sum(udpsum); + + iov[0].iov_base = (char *)&ip; + iov[0].iov_len = sizeof(ip); + iov[1].iov_base = (char *)&udp; + iov[1].iov_len = sizeof(udp); + iov[2].iov_base = (char *)msg; + iov[2].iov_len = size; + memset(&destaddr, 0, sizeof(destaddr)); + destaddr.sll_family = AF_PACKET; + destaddr.sll_protocol = htons(ETH_P_IP); + destaddr.sll_ifindex = if_index; + destaddr.sll_halen = ETH_ALEN; + memcpy(destaddr.sll_addr, "\xff\xff\xff\xff\xff\xff", ETH_ALEN); + + msghdr.msg_name = &destaddr; + msghdr.msg_namelen = sizeof(destaddr); + msghdr.msg_iov = iov; + msghdr.msg_iovlen = sizeof(iov) / sizeof(struct iovec); + msghdr.msg_flags = 0; + msghdr.msg_control = 0; + msghdr.msg_controllen = 0; + return sendmsg(s, &msghdr, 0); +} + +int receive_packet(int s, struct dhcp_msg *msg) +{ + int nread; + int is_valid; + struct dhcp_packet { + struct iphdr ip; + struct udphdr udp; + struct dhcp_msg dhcp; + } packet; + int dhcp_size; + uint32_t sum; + uint16_t temp; + uint32_t saddr, daddr; + + nread = read(s, &packet, sizeof(packet)); + if (nread < 0) { + return -1; + } + /* + * The raw packet interface gives us all packets received by the + * network interface. We need to filter out all packets that are + * not meant for us. + */ + is_valid = 0; + if (nread < (int)(sizeof(struct iphdr) + sizeof(struct udphdr))) { +#if VERBOSE + ALOGD("Packet is too small (%d) to be a UDP datagram", nread); +#endif + } else if (packet.ip.version != IPVERSION || packet.ip.ihl != (sizeof(packet.ip) >> 2)) { +#if VERBOSE + ALOGD("Not a valid IP packet"); +#endif + } else if (nread < ntohs(packet.ip.tot_len)) { +#if VERBOSE + ALOGD("Packet was truncated (read %d, needed %d)", nread, ntohs(packet.ip.tot_len)); +#endif + } else if (packet.ip.protocol != IPPROTO_UDP) { +#if VERBOSE + ALOGD("IP protocol (%d) is not UDP", packet.ip.protocol); +#endif + } else if (packet.udp.dest != htons(PORT_BOOTP_CLIENT)) { +#if VERBOSE + ALOGD("UDP dest port (%d) is not DHCP client", ntohs(packet.udp.dest)); +#endif + } else { + is_valid = 1; + } + + if (!is_valid) { + return -1; + } + + /* Seems like it's probably a valid DHCP packet */ + /* validate IP header checksum */ + sum = finish_sum(checksum(&packet.ip, sizeof(packet.ip), 0)); + if (sum != 0) { + printf("IP header checksum failure (0x%x)\n", packet.ip.check); + return -1; + } + /* + * Validate the UDP checksum. + * Since we don't need the IP header anymore, we "borrow" it + * to construct the pseudo header used in the checksum calculation. + */ + dhcp_size = ntohs(packet.udp.len) - sizeof(packet.udp); + /* + * check validity of dhcp_size. + * 1) cannot be negative or zero. + * 2) src buffer contains enough bytes to copy + * 3) cannot exceed destination buffer + */ + if ((dhcp_size <= 0) || + ((int)(nread - sizeof(struct iphdr) - sizeof(struct udphdr)) < dhcp_size) || + ((int)sizeof(struct dhcp_msg) < dhcp_size)) { +#if VERBOSE + printf("Malformed Packet\n"); +#endif + return -1; + } + saddr = packet.ip.saddr; + daddr = packet.ip.daddr; + nread = ntohs(packet.ip.tot_len); + memset(&packet.ip, 0, sizeof(packet.ip)); + packet.ip.saddr = saddr; + packet.ip.daddr = daddr; + packet.ip.protocol = IPPROTO_UDP; + packet.ip.tot_len = packet.udp.len; + temp = packet.udp.check; + packet.udp.check = 0; + sum = finish_sum(checksum(&packet, nread, 0)); + packet.udp.check = temp; + if (!sum) + sum = finish_sum(sum); + if (temp != sum) { + printf("UDP header checksum failure (0x%x should be 0x%x)\n", sum, temp); + return -1; + } + memcpy(msg, &packet.dhcp, dhcp_size); + return dhcp_size; +} diff --git a/root/package/link4all/quectel-CM/src/libmnl/dhcp/packet.h b/root/package/link4all/quectel-CM/src/libmnl/dhcp/packet.h new file mode 100644 index 00000000..aade392d --- /dev/null +++ b/root/package/link4all/quectel-CM/src/libmnl/dhcp/packet.h @@ -0,0 +1,25 @@ +/* + * Copyright 2008, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _WIFI_PACKET_H_ +#define _WIFI_PACKET_H_ + +int open_raw_socket(const char *ifname, uint8_t *hwaddr, int if_index); +int send_packet(int s, int if_index, struct dhcp_msg *msg, int size, + uint32_t saddr, uint32_t daddr, uint32_t sport, uint32_t dport); +int receive_packet(int s, struct dhcp_msg *msg); + +#endif diff --git a/root/package/link4all/quectel-CM/src/libmnl/ifutils.c b/root/package/link4all/quectel-CM/src/libmnl/ifutils.c new file mode 100644 index 00000000..fb0d9364 --- /dev/null +++ b/root/package/link4all/quectel-CM/src/libmnl/ifutils.c @@ -0,0 +1,744 @@ +/* This example is placed in the public domain. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "libmnl.h" +#include "ifutils.h" + +#define ERRMSG(v...) printf("%s-%d: error=%s %s\n", __func__, __LINE__, strerror(errno), ##v) + +int mask_to_prefix_v4(uint32_t mask) +{ + int ret = 0; + while (mask) + { + mask = mask & (mask - 1); + ret++; + } + return ret; +} + +const char *ipaddr_to_string_v4(in_addr_t ipaddr) +{ + static char buf[INET6_ADDRSTRLEN] = {'\0'}; + buf[0] = '\0'; + uint32_t addr = ipaddr; + return inet_ntop(AF_INET, &addr, buf, sizeof(buf)); +} + +const char *ipaddr_to_string_v6(uint8_t *ipaddr) +{ + static char buf[INET6_ADDRSTRLEN] = {'\0'}; + buf[0] = '\0'; + return inet_ntop(AF_INET6, ipaddr, buf, sizeof(buf)); +} + +static void ifc_init_ifr(const char *name, struct ifreq *ifr) +{ + memset(ifr, 0, sizeof(struct ifreq)); + strncpy(ifr->ifr_name, name, IFNAMSIZ); + ifr->ifr_name[IFNAMSIZ - 1] = 0; +} + +int if_get_hwaddr(const char *name, void *ptr) +{ + int r; + struct ifreq ifr; + ifc_init_ifr(name, &ifr); + + int ifc_ctl_sock = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0); + if (ifc_ctl_sock < 0) + { + return -1; + } + r = ioctl(ifc_ctl_sock, SIOCGIFHWADDR, &ifr); + if (r < 0) + return -1; + + memcpy(ptr, &ifr.ifr_hwaddr.sa_data, ETH_ALEN); + return 0; +} + +static int if_act_on_link(const char *ifname, int state) +{ + struct mnl_socket *nl; + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + struct ifinfomsg *ifm; + int ret; + unsigned int seq, portid, change = 0, flags = 0; + static int oldstate = -1; + + if (state == oldstate) + return 0; + oldstate = state; + + if (state) + { + change |= IFF_UP; + flags |= IFF_UP; + } + else + { + change |= IFF_UP; + flags &= ~IFF_UP; + } + + nlh = mnl_nlmsg_put_header(buf); + nlh->nlmsg_type = RTM_NEWLINK; + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + nlh->nlmsg_seq = seq = time(NULL); + ifm = mnl_nlmsg_put_extra_header(nlh, sizeof(*ifm)); + ifm->ifi_family = AF_UNSPEC; + ifm->ifi_change = change; + ifm->ifi_flags = flags; + + mnl_attr_put_str(nlh, IFLA_IFNAME, ifname); + + nl = mnl_socket_open(NETLINK_ROUTE); + if (nl == NULL) + { + ERRMSG("mnl_socket_open"); + return -1; + } + + if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) + { + ERRMSG(" mnl_socket_bind"); + return -1; + } + portid = mnl_socket_get_portid(nl); + + if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) + { + ERRMSG(" mnl_socket_sendto"); + return -1; + } + + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + if (ret == -1) + { + ERRMSG(" mnl_socket_recvfrom"); + return -1; + } + + ret = mnl_cb_run(buf, ret, seq, portid, NULL, NULL); + if (ret == -1) + { + ERRMSG(" mnl_cb_run"); + return -1; + } + + mnl_socket_close(nl); + + return 0; +} + +int if_link_up(const char *ifname) +{ + return if_act_on_link(ifname, 1); +} + +int if_link_down(const char *ifname) +{ + return if_act_on_link(ifname, 0); +} + +int if_set_mtu(const char *ifname, uint32_t mtu) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + unsigned int seq, portid; + struct mnl_socket *nl; + struct nlmsghdr *nlh; + struct ifinfomsg *ifm; + int ret; + int iface; + static uint32_t oldmtu = 1500; + + if (mtu == oldmtu) + return 0; + oldmtu = mtu; + + iface = if_nametoindex(ifname); + if (iface == 0) + { + ERRMSG(" if_nametoindex"); + return -1; + } + + nlh = mnl_nlmsg_put_header(buf); + nlh->nlmsg_type = RTM_NEWLINK; + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + nlh->nlmsg_seq = seq = time(NULL); + ifm = mnl_nlmsg_put_extra_header(nlh, sizeof(struct ifinfomsg)); + ifm->ifi_family = AF_UNSPEC; + ifm->ifi_index = iface; + ifm->ifi_change = 0xFFFFFFFF; + ifm->ifi_type = ifm->ifi_flags = 0; + + mnl_attr_put_u32(nlh, IFLA_MTU, mtu); + + nl = mnl_socket_open(NETLINK_ROUTE); + if (nl == NULL) + { + ERRMSG(" mnl_socket_open"); + return -1; + } + + if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) + { + ERRMSG(" mnl_socket_bind"); + return -1; + } + portid = mnl_socket_get_portid(nl); + + if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) + { + ERRMSG(" mnl_socket_sendto"); + return -1; + } + + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + if (ret == -1) + { + ERRMSG(" mnl_socket_recvfrom"); + return -1; + } + + ret = mnl_cb_run(buf, ret, seq, portid, NULL, NULL); + if (ret == -1) + { + ERRMSG(" mnl_cb_run"); + return -1; + } + + mnl_socket_close(nl); + + return 0; +} + +/** + * @brief Set the ip addr object + * + * @param operate + * 0 -> add address on interface + * 1 -> delete address on interface + * @param ifname + * @param ipaddr + * @param prefix + * @return int + */ +static int if_act_on_addr(bool operate, int proto, const char *ifname, addr_t *ipaddr, uint32_t prefix) +{ + struct mnl_socket *nl; + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + struct ifaddrmsg *ifm; + uint32_t seq, portid; + int ret, family = proto; + + int iface; + + iface = if_nametoindex(ifname); + if (iface == 0) + { + ERRMSG(" if_nametoindex"); + return -1; + } + + nlh = mnl_nlmsg_put_header(buf); + if (operate) + nlh->nlmsg_type = RTM_NEWADDR; + else + nlh->nlmsg_type = RTM_DELADDR; + + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_REPLACE | NLM_F_ACK; + nlh->nlmsg_seq = seq = time(NULL); + + ifm = mnl_nlmsg_put_extra_header(nlh, sizeof(struct ifaddrmsg)); + + ifm->ifa_family = family; + ifm->ifa_prefixlen = prefix; + ifm->ifa_flags = IFA_F_PERMANENT; + + ifm->ifa_scope = RT_SCOPE_UNIVERSE; + ifm->ifa_index = iface; + + /* + * The exact meaning of IFA_LOCAL and IFA_ADDRESS depend + * on the address family being used and the device type. + * For broadcast devices (like the interfaces we use), + * for IPv4 we specify both and they are used interchangeably. + * For IPv6, only IFA_ADDRESS needs to be set. + */ + if (family == AF_INET) + { + mnl_attr_put_u32(nlh, IFA_LOCAL, ipaddr->ip); + mnl_attr_put_u32(nlh, IFA_ADDRESS, ipaddr->ip); + } + else + { + mnl_attr_put(nlh, IFA_ADDRESS, sizeof(struct in6_addr), ipaddr); + } + + nl = mnl_socket_open(NETLINK_ROUTE); + if (nl == NULL) + { + ERRMSG(" mnl_socket_open"); + return -1; + } + + if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) + { + ERRMSG(" mnl_socket_bind"); + return -1; + } + portid = mnl_socket_get_portid(nl); + + if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) + { + ERRMSG(" mnl_socket_sendto"); + return -1; + } + + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + if (ret < 0) + { + ERRMSG(" mnl_socket_recvfrom"); + return -1; + } + + ret = mnl_cb_run(buf, ret, seq, portid, NULL, NULL); + if (ret < 0) + { + ERRMSG(" mnl_cb_run"); + return -1; + } + + mnl_socket_close(nl); + + return 0; +} + +int if_set_addr_v4(const char *ifname, in_addr_t ipaddr, uint32_t prefix) +{ + addr_t addr; + addr.ip = ipaddr; + return if_act_on_addr(1, AF_INET, ifname, &addr, prefix); +} + +int if_del_addr_v4(const char *ifname, in_addr_t ipaddr, uint32_t prefix) +{ + addr_t addr; + addr.ip = ipaddr; + return if_act_on_addr(0, AF_INET, ifname, &addr, prefix); +} + +int if_set_addr_v6(const char *ifname, uint8_t *ipaddr, uint32_t prefix) +{ + addr_t addr; + memcpy(&addr.ip6, ipaddr, 16); + return if_act_on_addr(1, AF_INET6, ifname, &addr, prefix); +} + +int if_del_addr_v6(const char *ifname, uint8_t *ipaddr, uint32_t prefix) +{ + addr_t addr; + memcpy(&addr.ip6, ipaddr, 16); + return if_act_on_addr(0, AF_INET6, ifname, &addr, prefix); +} + +static int data_attr_cb(const struct nlattr *attr, void *data) +{ + const struct nlattr **tb = data; + int type = mnl_attr_get_type(attr); + + /* skip unsupported attribute in user-space */ + if (mnl_attr_type_valid(attr, IFA_MAX) < 0) + return MNL_CB_OK; + + switch (type) + { + case IFA_ADDRESS: + if (mnl_attr_validate(attr, MNL_TYPE_BINARY) < 0) + { + ERRMSG(" mnl_attr_validate"); + return MNL_CB_ERROR; + } + break; + } + tb[type] = attr; + return MNL_CB_OK; +} + +static int data_cb(const struct nlmsghdr *nlh, void *data) +{ + struct nlattr *tb[IFA_MAX + 1] = {}; + struct ifaddrmsg *ifa = mnl_nlmsg_get_payload(nlh); + struct addrinfo_t *addrinfo = (struct addrinfo_t *)data; + void *addr = NULL; + + mnl_attr_parse(nlh, sizeof(*ifa), data_attr_cb, tb); + if (tb[IFA_ADDRESS]) + { + char out[INET6_ADDRSTRLEN]; + + addr = mnl_attr_get_payload(tb[IFLA_ADDRESS]); + addr = mnl_attr_get_payload(tb[IFA_ADDRESS]); + if (!inet_ntop(ifa->ifa_family, addr, out, sizeof(out))) + ERRMSG("inet_ntop"); + // printf("%d %d-> %d %s\n", addrinfo->iface, ifa->ifa_index, ifa->ifa_scope, out); + + addrinfo->addrs[addrinfo->num].prefix = ifa->ifa_prefixlen; + if (ifa->ifa_index == (unsigned int)addrinfo->iface) + { + if (ifa->ifa_family == AF_INET6) + memcpy(addrinfo->addrs[addrinfo->num].address.ip6.s6_addr, addr, 16); + if (ifa->ifa_family == AF_INET) + memcpy(&(addrinfo->addrs[addrinfo->num].address.ip), addr, 4); + addrinfo->num++; + } + } + + // ifa->ifa_scope + // 0: global + // 200: site + // 253: link + // 254: host + // 255: nowhere + + return MNL_CB_OK; +} + +/** + * @brief + * + * @param ifname + * @param proto + * AF_INET -> for IPv4 + * AF_INET6 -> for IPv6 + * @return int + */ +static int if_get_addr(const char *ifname, int proto, struct addrinfo_t *addrinfo) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + unsigned int seq, portid; + struct mnl_socket *nl; + struct nlmsghdr *nlh; + struct rtgenmsg *rt; + int ret; + + addrinfo->iface = if_nametoindex(ifname); + if (addrinfo->iface == 0) + { + ERRMSG(" if_nametoindex"); + return -1; + } + + nlh = mnl_nlmsg_put_header(buf); + nlh->nlmsg_type = RTM_GETADDR; + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; + nlh->nlmsg_seq = seq = time(NULL); + rt = mnl_nlmsg_put_extra_header(nlh, sizeof(struct rtgenmsg)); + if (proto == AF_INET) + rt->rtgen_family = AF_INET; + else if (proto == AF_INET6) + rt->rtgen_family = AF_INET6; + + nl = mnl_socket_open(NETLINK_ROUTE); + if (nl == NULL) + { + ERRMSG(" mnl_socket_open"); + return -1; + } + + if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) + { + ERRMSG(" mnl_socket_bind"); + return -1; + } + portid = mnl_socket_get_portid(nl); + + if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) + { + ERRMSG(" mnl_socket_sendto"); + return -1; + } + + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + while (ret > 0) + { + ret = mnl_cb_run(buf, ret, seq, portid, data_cb, addrinfo); + if (ret <= MNL_CB_STOP) + break; + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + } + if (ret == -1) + { + ERRMSG(" error"); + return -1; + } + mnl_socket_close(nl); + + return 0; +} + +int if_flush_v4_addr(const char *ifname) +{ + struct addrinfo_t addrinfo; + int i = 0; + + memset(&addrinfo, 0, sizeof(struct addrinfo_t)); + if_get_addr(ifname, AF_INET, &addrinfo); + for (; i < addrinfo.num; i++) + { + // printf("remove address: %s\n", ipaddr_to_string_v4(addrinfo.addrs[i].address.ip)); + if_del_addr_v4(ifname, addrinfo.addrs[i].address.ip, addrinfo.addrs[i].prefix); + } + return 0; +} + +int if_flush_v6_addr(const char *ifname) +{ + struct addrinfo_t addrinfo; + int i = 0; + + memset(&addrinfo, 0, sizeof(struct addrinfo_t)); + if_get_addr(ifname, AF_INET6, &addrinfo); + for (; i < addrinfo.num; i++) + { + // printf("remove address: %s\n", ipaddr_to_string_v6(addrinfo.addrs[i].address.ip6.s6_addr)); + if_del_addr_v6(ifname, addrinfo.addrs[i].address.ip6.s6_addr, addrinfo.addrs[i].prefix); + } + return 0; +} + +/** + * @brief Set the route addr object + * Usage: + * iface destination cidr [gateway] + * Example: + * eth0 10.0.1.12 32 10.0.1.11 + * eth0 ffff::10.0.1.12 128 fdff::1 + * @param operate + * add or del + * @param ifname + * @param dstaddr + * @param prefix + * @param gwaddr + * @return int + */ +int if_act_on_route(bool operate, int proto, const char *ifname, addr_t *dstaddr, uint32_t prefix, addr_t *gwaddr) +{ + struct mnl_socket *nl; + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + struct rtmsg *rtm; + uint32_t seq, portid; + int iface, ret, family = proto; + + iface = if_nametoindex(ifname); + if (iface == 0) + { + ERRMSG(" if_nametoindex"); + return -1; + } + + nlh = mnl_nlmsg_put_header(buf); + if (operate) + nlh->nlmsg_type = RTM_NEWROUTE; + else + nlh->nlmsg_type = RTM_DELROUTE; + + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_ACK; + nlh->nlmsg_seq = seq = time(NULL); + + rtm = mnl_nlmsg_put_extra_header(nlh, sizeof(struct rtmsg)); + rtm->rtm_family = family; + rtm->rtm_dst_len = prefix; + rtm->rtm_src_len = 0; + rtm->rtm_tos = 0; + rtm->rtm_protocol = RTPROT_STATIC; + rtm->rtm_table = RT_TABLE_MAIN; + rtm->rtm_type = RTN_UNICAST; + /* is there any gateway? */ + rtm->rtm_scope = gwaddr ? RT_SCOPE_UNIVERSE : RT_SCOPE_LINK; + rtm->rtm_flags = 0; + + if (family == AF_INET) + mnl_attr_put_u32(nlh, RTA_DST, dstaddr->ip); + else + mnl_attr_put(nlh, RTA_DST, sizeof(struct in6_addr), dstaddr); + + mnl_attr_put_u32(nlh, RTA_OIF, iface); + if (gwaddr) + { + if (family == AF_INET) + mnl_attr_put_u32(nlh, RTA_GATEWAY, gwaddr->ip); + else + { + mnl_attr_put(nlh, RTA_GATEWAY, sizeof(struct in6_addr), gwaddr); + } + } + + nl = mnl_socket_open(NETLINK_ROUTE); + if (nl == NULL) + { + ERRMSG(" mnl_socket_open"); + return -1; + } + + if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) + { + ERRMSG(" mnl_socket_bind"); + return -1; + } + portid = mnl_socket_get_portid(nl); + + if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) + { + ERRMSG(" mnl_socket_sendto"); + return -1; + } + + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + if (ret < 0) + { + ERRMSG(" mnl_socket_recvfrom"); + return -1; + } + + ret = mnl_cb_run(buf, ret, seq, portid, NULL, NULL); + if (ret < 0) + { + ERRMSG(" mnl_cb_run"); + return -1; + } + + mnl_socket_close(nl); + + return 0; +} + +int if_set_default_route_v4(const char *ifname) +{ + return if_act_on_route(1, AF_INET, ifname, (addr_t *)&in6addr_any, 0, NULL); +} + +int if_del_default_route_v4(const char *ifname) +{ + return if_act_on_route(0, AF_INET, ifname, (addr_t *)&in6addr_any, 0, NULL); +} + +int if_set_default_route_v6(const char *ifname) +{ + return if_act_on_route(1, AF_INET6, ifname, (addr_t *)&in6addr_any, 0, NULL); +} + +int if_del_default_route_v6(const char *ifname) +{ + return if_act_on_route(0, AF_INET6, ifname, (addr_t *)&in6addr_any, 0, NULL); +} + +/** + * @brief Set the default gwaddr object + * set default gw + * @param operate + * @param ifname + * @param gwaddr + * gateway ip + * @return int + */ +int if_set_route_gw_v4(const char *ifname, in_addr_t gwaddr) +{ + addr_t addr; + memset(&addr, 0, sizeof(addr_t)); + addr.ip = gwaddr; + return if_act_on_route(1, AF_INET, ifname, (addr_t *)&in6addr_any, 0, &addr); +} + +int if_del_route_gw_v4(const char *ifname, in_addr_t gwaddr) +{ + addr_t addr; + memset(&addr, 0, sizeof(addr_t)); + addr.ip = gwaddr; + return if_act_on_route(0, AF_INET, ifname, (addr_t *)&in6addr_any, 0, &addr); +} + +int if_set_route_gw_v6(const char *ifname, uint8_t *gwaddr) +{ + addr_t addr; + memset(&addr, 0, sizeof(addr_t)); + memcpy(&addr.ip6, gwaddr, 16); + return if_act_on_route(1, AF_INET6, ifname, (addr_t *)&in6addr_any, 0, &addr); +} + +int if_del_route_gw_v6(const char *ifname, uint8_t *gwaddr) +{ + addr_t addr; + memset(&addr, 0, sizeof(addr_t)); + memcpy(&addr.ip6, gwaddr, 16); + return if_act_on_route(0, AF_INET6, ifname, (addr_t *)&in6addr_any, 0, &addr); +} + +int if_set_dns(const char *dns1, const char *dns2) +{ + int ret = 0; + char buf[128] = {'\0'}; + int fd = open("/etc/resolv.conf", O_CREAT | O_WRONLY | O_TRUNC); + if (fd < 0) + { + ERRMSG(" fail to open /etc/resolv.conf"); + return -1; + } + + if (dns1) + snprintf(buf, sizeof(buf), "nameserver %s\n", dns1); + if (dns2) + snprintf(buf, sizeof(buf), "nameserver %s\n", dns2); + ret = write(fd, buf, strlen(buf)); + if (ret < 0) + { + ERRMSG(" write dns"); + } + close(fd); + return ret > 0 ? 0 : -1; +} + +int if_set_network_v4(const char *ifname, in_addr_t ipaddr, uint32_t prefix, + in_addr_t gwaddr, in_addr_t dns1, in_addr_t dns2) +{ + (void)gwaddr; + if_link_up(ifname); + if_set_addr_v4(ifname, ipaddr, prefix); + if_set_default_route_v4(ifname); + if_set_dns(ipaddr_to_string_v4(dns1), ipaddr_to_string_v4(dns2)); + return 0; +} + +int if_set_network_v6(const char *ifname, uint8_t *ipaddr, uint32_t prefix, + uint8_t *gwaddr, uint8_t *dns1, uint8_t *dns2) +{ + (void)gwaddr; + if_link_up(ifname); + if_set_addr_v6(ifname, ipaddr, prefix); + if_set_default_route_v6(ifname); + if_set_dns(ipaddr_to_string_v6(dns1), ipaddr_to_string_v6(dns2)); + return 0; +} \ No newline at end of file diff --git a/root/package/link4all/quectel-CM/src/libmnl/ifutils.h b/root/package/link4all/quectel-CM/src/libmnl/ifutils.h new file mode 100644 index 00000000..4c2b5650 --- /dev/null +++ b/root/package/link4all/quectel-CM/src/libmnl/ifutils.h @@ -0,0 +1,53 @@ +#ifndef __IFUTILS_H__ +#define __IFUTILS_H__ + +typedef union { + in_addr_t ip; + struct in6_addr ip6; +} addr_t; + +#define MAX_IP_NUM 32 +struct addrinfo_t +{ + int iface; + int num; + struct + { + int prefix; + addr_t address; + } addrs[MAX_IP_NUM]; +}; + +const char *ipaddr_to_string_v4(in_addr_t ipaddr); +const char *ipaddr_to_string_v6(uint8_t *ipaddr); +int mask_to_prefix_v4(in_addr_t mask); + +int if_get_hwaddr(const char *name, void *ptr); + +int if_link_down(const char *ifname); +int if_link_up(const char *ifname); +int if_set_mtu(const char *ifname, uint32_t mtu); + +int if_set_addr_v4(const char *name, in_addr_t address, uint32_t prefixlen); +int if_del_addr_v4(const char *name, in_addr_t address, uint32_t prefixlen); +int if_set_addr_v6(const char *name, uint8_t *address, uint32_t prefixlen); +int if_del_addr_v6(const char *name, uint8_t *address, uint32_t prefixlen); +int if_flush_v4_addr(const char *ifname); +int if_flush_v6_addr(const char *ifname); + +int if_set_route_gw_v4(const char *ifname, in_addr_t gwaddr); +int if_del_route_gw_v4(const char *ifname, in_addr_t gwaddr); +int if_set_default_route_v4(const char *ifname); +int if_del_default_route_v4(const char *ifname); + +int if_set_route_gw_v6(const char *ifname, uint8_t *gwaddr); +int if_del_route_gw_v6(const char *ifname, uint8_t *gwaddr); +int if_set_default_route_v6(const char *ifname); +int if_del_default_route_v6(const char *ifname); + +int if_set_network_v4(const char *ifname, in_addr_t ipaddr, uint32_t prefix, + in_addr_t gwaddr, in_addr_t dns1, in_addr_t dns2); +int if_set_network_v6(const char *ifname, uint8_t *ipaddr, uint32_t prefix, + uint8_t *gwaddr, uint8_t *dns1, uint8_t *dns2); + +#endif //__IFUTILS_H__ \ No newline at end of file diff --git a/root/package/link4all/quectel-CM/src/libmnl/libmnl.h b/root/package/link4all/quectel-CM/src/libmnl/libmnl.h new file mode 100644 index 00000000..4bd0b92e --- /dev/null +++ b/root/package/link4all/quectel-CM/src/libmnl/libmnl.h @@ -0,0 +1,202 @@ +#ifndef _LIBMNL_H_ +#define _LIBMNL_H_ + +#include +#include +#include +#include +#include /* for sa_family_t */ +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Netlink socket API + */ + +#define MNL_SOCKET_AUTOPID 0 +#define MNL_SOCKET_BUFFER_SIZE (sysconf(_SC_PAGESIZE) < 8192L ? sysconf(_SC_PAGESIZE) : 8192L) +#define MNL_SOCKET_DUMP_SIZE 32768 + +struct mnl_socket; + +extern struct mnl_socket *mnl_socket_open(int bus); +extern struct mnl_socket *mnl_socket_open2(int bus, int flags); +extern struct mnl_socket *mnl_socket_fdopen(int fd); +extern int mnl_socket_bind(struct mnl_socket *nl, unsigned int groups, pid_t pid); +extern int mnl_socket_close(struct mnl_socket *nl); +extern int mnl_socket_get_fd(const struct mnl_socket *nl); +extern unsigned int mnl_socket_get_portid(const struct mnl_socket *nl); +extern ssize_t mnl_socket_sendto(const struct mnl_socket *nl, const void *req, size_t siz); +extern ssize_t mnl_socket_recvfrom(const struct mnl_socket *nl, void *buf, size_t siz); +extern int mnl_socket_setsockopt(const struct mnl_socket *nl, int type, void *buf, socklen_t len); +extern int mnl_socket_getsockopt(const struct mnl_socket *nl, int type, void *buf, socklen_t *len); + +/* + * Netlink message API + */ + +#define MNL_ALIGNTO 4 +#define MNL_ALIGN(len) (((len)+MNL_ALIGNTO-1) & ~(MNL_ALIGNTO-1)) +#define MNL_NLMSG_HDRLEN MNL_ALIGN(sizeof(struct nlmsghdr)) + +extern size_t mnl_nlmsg_size(size_t len); +extern size_t mnl_nlmsg_get_payload_len(const struct nlmsghdr *nlh); + +/* Netlink message header builder */ +extern struct nlmsghdr *mnl_nlmsg_put_header(void *buf); +extern void *mnl_nlmsg_put_extra_header(struct nlmsghdr *nlh, size_t size); + +/* Netlink message iterators */ +extern bool mnl_nlmsg_ok(const struct nlmsghdr *nlh, int len); +extern struct nlmsghdr *mnl_nlmsg_next(const struct nlmsghdr *nlh, int *len); + +/* Netlink sequence tracking */ +extern bool mnl_nlmsg_seq_ok(const struct nlmsghdr *nlh, unsigned int seq); + +/* Netlink portID checking */ +extern bool mnl_nlmsg_portid_ok(const struct nlmsghdr *nlh, unsigned int portid); + +/* Netlink message getters */ +extern void *mnl_nlmsg_get_payload(const struct nlmsghdr *nlh); +extern void *mnl_nlmsg_get_payload_offset(const struct nlmsghdr *nlh, size_t offset); +extern void *mnl_nlmsg_get_payload_tail(const struct nlmsghdr *nlh); + +/* Netlink message printer */ +extern void mnl_nlmsg_fprintf(FILE *fd, const void *data, size_t datalen, size_t extra_header_size); + +/* Message batch helpers */ +struct mnl_nlmsg_batch; +extern struct mnl_nlmsg_batch *mnl_nlmsg_batch_start(void *buf, size_t bufsiz); +extern bool mnl_nlmsg_batch_next(struct mnl_nlmsg_batch *b); +extern void mnl_nlmsg_batch_stop(struct mnl_nlmsg_batch *b); +extern size_t mnl_nlmsg_batch_size(struct mnl_nlmsg_batch *b); +extern void mnl_nlmsg_batch_reset(struct mnl_nlmsg_batch *b); +extern void *mnl_nlmsg_batch_head(struct mnl_nlmsg_batch *b); +extern void *mnl_nlmsg_batch_current(struct mnl_nlmsg_batch *b); +extern bool mnl_nlmsg_batch_is_empty(struct mnl_nlmsg_batch *b); + +/* + * Netlink attributes API + */ +#define MNL_ATTR_HDRLEN MNL_ALIGN(sizeof(struct nlattr)) + +/* TLV attribute getters */ +extern uint16_t mnl_attr_get_type(const struct nlattr *attr); +extern uint16_t mnl_attr_get_len(const struct nlattr *attr); +extern uint16_t mnl_attr_get_payload_len(const struct nlattr *attr); +extern void *mnl_attr_get_payload(const struct nlattr *attr); +extern uint8_t mnl_attr_get_u8(const struct nlattr *attr); +extern uint16_t mnl_attr_get_u16(const struct nlattr *attr); +extern uint32_t mnl_attr_get_u32(const struct nlattr *attr); +extern uint64_t mnl_attr_get_u64(const struct nlattr *attr); +extern const char *mnl_attr_get_str(const struct nlattr *attr); + +/* TLV attribute putters */ +extern void mnl_attr_put(struct nlmsghdr *nlh, uint16_t type, size_t len, const void *data); +extern void mnl_attr_put_u8(struct nlmsghdr *nlh, uint16_t type, uint8_t data); +extern void mnl_attr_put_u16(struct nlmsghdr *nlh, uint16_t type, uint16_t data); +extern void mnl_attr_put_u32(struct nlmsghdr *nlh, uint16_t type, uint32_t data); +extern void mnl_attr_put_u64(struct nlmsghdr *nlh, uint16_t type, uint64_t data); +extern void mnl_attr_put_str(struct nlmsghdr *nlh, uint16_t type, const char *data); +extern void mnl_attr_put_strz(struct nlmsghdr *nlh, uint16_t type, const char *data); + +/* TLV attribute putters with buffer boundary checkings */ +extern bool mnl_attr_put_check(struct nlmsghdr *nlh, size_t buflen, uint16_t type, size_t len, const void *data); +extern bool mnl_attr_put_u8_check(struct nlmsghdr *nlh, size_t buflen, uint16_t type, uint8_t data); +extern bool mnl_attr_put_u16_check(struct nlmsghdr *nlh, size_t buflen, uint16_t type, uint16_t data); +extern bool mnl_attr_put_u32_check(struct nlmsghdr *nlh, size_t buflen, uint16_t type, uint32_t data); +extern bool mnl_attr_put_u64_check(struct nlmsghdr *nlh, size_t buflen, uint16_t type, uint64_t data); +extern bool mnl_attr_put_str_check(struct nlmsghdr *nlh, size_t buflen, uint16_t type, const char *data); +extern bool mnl_attr_put_strz_check(struct nlmsghdr *nlh, size_t buflen, uint16_t type, const char *data); + +/* TLV attribute nesting */ +extern struct nlattr *mnl_attr_nest_start(struct nlmsghdr *nlh, uint16_t type); +extern struct nlattr *mnl_attr_nest_start_check(struct nlmsghdr *nlh, size_t buflen, uint16_t type); +extern void mnl_attr_nest_end(struct nlmsghdr *nlh, struct nlattr *start); +extern void mnl_attr_nest_cancel(struct nlmsghdr *nlh, struct nlattr *start); + +/* TLV validation */ +extern int mnl_attr_type_valid(const struct nlattr *attr, uint16_t maxtype); + +enum mnl_attr_data_type { + MNL_TYPE_UNSPEC, + MNL_TYPE_U8, + MNL_TYPE_U16, + MNL_TYPE_U32, + MNL_TYPE_U64, + MNL_TYPE_STRING, + MNL_TYPE_FLAG, + MNL_TYPE_MSECS, + MNL_TYPE_NESTED, + MNL_TYPE_NESTED_COMPAT, + MNL_TYPE_NUL_STRING, + MNL_TYPE_BINARY, + MNL_TYPE_MAX, +}; + +extern int mnl_attr_validate(const struct nlattr *attr, enum mnl_attr_data_type type); +extern int mnl_attr_validate2(const struct nlattr *attr, enum mnl_attr_data_type type, size_t len); + +/* TLV iterators */ +extern bool mnl_attr_ok(const struct nlattr *attr, int len); +extern struct nlattr *mnl_attr_next(const struct nlattr *attr); + +#define mnl_attr_for_each(attr, nlh, offset) \ + for ((attr) = mnl_nlmsg_get_payload_offset((nlh), (offset)); \ + mnl_attr_ok((attr), (char *)mnl_nlmsg_get_payload_tail(nlh) - (char *)(attr)); \ + (attr) = mnl_attr_next(attr)) + +#define mnl_attr_for_each_nested(attr, nest) \ + for ((attr) = mnl_attr_get_payload(nest); \ + mnl_attr_ok((attr), (char *)mnl_attr_get_payload(nest) + mnl_attr_get_payload_len(nest) - (char *)(attr)); \ + (attr) = mnl_attr_next(attr)) + +#define mnl_attr_for_each_payload(payload, payload_size) \ + for ((attr) = (payload); \ + mnl_attr_ok((attr), (char *)(payload) + payload_size - (char *)(attr)); \ + (attr) = mnl_attr_next(attr)) + +/* TLV callback-based attribute parsers */ +typedef int (*mnl_attr_cb_t)(const struct nlattr *attr, void *data); + +extern int mnl_attr_parse(const struct nlmsghdr *nlh, unsigned int offset, mnl_attr_cb_t cb, void *data); +extern int mnl_attr_parse_nested(const struct nlattr *attr, mnl_attr_cb_t cb, void *data); +extern int mnl_attr_parse_payload(const void *payload, size_t payload_len, mnl_attr_cb_t cb, void *data); + +/* + * callback API + */ +#define MNL_CB_ERROR -1 +#define MNL_CB_STOP 0 +#define MNL_CB_OK 1 + +typedef int (*mnl_cb_t)(const struct nlmsghdr *nlh, void *data); + +extern int mnl_cb_run(const void *buf, size_t numbytes, unsigned int seq, + unsigned int portid, mnl_cb_t cb_data, void *data); + +extern int mnl_cb_run2(const void *buf, size_t numbytes, unsigned int seq, + unsigned int portid, mnl_cb_t cb_data, void *data, + const mnl_cb_t *cb_ctl_array, + unsigned int cb_ctl_array_len); + +/* + * other declarations + */ + +#ifndef SOL_NETLINK +#define SOL_NETLINK 270 +#endif + +#ifndef MNL_ARRAY_SIZE +#define MNL_ARRAY_SIZE(a) (sizeof(a)/sizeof((a)[0])) +#endif + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/root/package/link4all/quectel-CM/src/libmnl/nlmsg.c b/root/package/link4all/quectel-CM/src/libmnl/nlmsg.c new file mode 100644 index 00000000..d960cf33 --- /dev/null +++ b/root/package/link4all/quectel-CM/src/libmnl/nlmsg.c @@ -0,0 +1,556 @@ +/* + * (C) 2008-2010 by Pablo Neira Ayuso + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + */ +#include +#include +#include +#include +#include +#include + +#include "libmnl.h" + +/** + * \defgroup nlmsg Netlink message helpers + * + * Netlink message: + * \verbatim + |<----------------- 4 bytes ------------------->| + |<----- 2 bytes ------>|<------- 2 bytes ------>| + |-----------------------------------------------| + | Message length (including header) | + |-----------------------------------------------| + | Message type | Message flags | + |-----------------------------------------------| + | Message sequence number | + |-----------------------------------------------| + | Netlink PortID | + |-----------------------------------------------| + | | + . Payload . + |_______________________________________________| +\endverbatim + * + * There is usually an extra header after the the Netlink header (at the + * beginning of the payload). This extra header is specific of the Netlink + * subsystem. After this extra header, it comes the sequence of attributes + * that are expressed in Type-Length-Value (TLV) format. + * + * @{ + */ + +/** + * mnl_nlmsg_size - calculate the size of Netlink message (without alignment) + * \param len length of the Netlink payload + * + * This function returns the size of a netlink message (header plus payload) + * without alignment. + */ +size_t mnl_nlmsg_size(size_t len) +{ + return len + MNL_NLMSG_HDRLEN; +} + +/** + * mnl_nlmsg_get_payload_len - get the length of the Netlink payload + * \param nlh pointer to the header of the Netlink message + * + * This function returns the Length of the netlink payload, ie. the length + * of the full message minus the size of the Netlink header. + */ +size_t mnl_nlmsg_get_payload_len(const struct nlmsghdr *nlh) +{ + return nlh->nlmsg_len - MNL_NLMSG_HDRLEN; +} + +/** + * mnl_nlmsg_put_header - reserve and prepare room for Netlink header + * \param buf memory already allocated to store the Netlink header + * + * This function sets to zero the room that is required to put the Netlink + * header in the memory buffer passed as parameter. This function also + * initializes the nlmsg_len field to the size of the Netlink header. This + * function returns a pointer to the Netlink header structure. + */ +struct nlmsghdr *mnl_nlmsg_put_header(void *buf) +{ + int len = MNL_ALIGN(sizeof(struct nlmsghdr)); + struct nlmsghdr *nlh = buf; + + memset(buf, 0, len); + nlh->nlmsg_len = len; + return nlh; +} + +/** + * mnl_nlmsg_put_extra_header - reserve and prepare room for an extra header + * \param nlh pointer to Netlink header + * \param size size of the extra header that we want to put + * + * This function sets to zero the room that is required to put the extra + * header after the initial Netlink header. This function also increases + * the nlmsg_len field. You have to invoke mnl_nlmsg_put_header() before + * you call this function. This function returns a pointer to the extra + * header. + */ +void *mnl_nlmsg_put_extra_header(struct nlmsghdr *nlh, + size_t size) +{ + char *ptr = (char *)nlh + nlh->nlmsg_len; + size_t len = MNL_ALIGN(size); + nlh->nlmsg_len += len; + memset(ptr, 0, len); + return ptr; +} + +/** + * mnl_nlmsg_get_payload - get a pointer to the payload of the netlink message + * \param nlh pointer to a netlink header + * + * This function returns a pointer to the payload of the netlink message. + */ +void *mnl_nlmsg_get_payload(const struct nlmsghdr *nlh) +{ + return (void *)nlh + MNL_NLMSG_HDRLEN; +} + +/** + * mnl_nlmsg_get_payload_offset - get a pointer to the payload of the message + * \param nlh pointer to a netlink header + * \param offset offset to the payload of the attributes TLV set + * + * This function returns a pointer to the payload of the netlink message plus + * a given offset. + */ +void *mnl_nlmsg_get_payload_offset(const struct nlmsghdr *nlh, + size_t offset) +{ + return (void *)nlh + MNL_NLMSG_HDRLEN + MNL_ALIGN(offset); +} + +/** + * mnl_nlmsg_ok - check a there is room for netlink message + * \param nlh netlink message that we want to check + * \param len remaining bytes in a buffer that contains the netlink message + * + * This function is used to check that a buffer that contains a netlink + * message has enough room for the netlink message that it stores, ie. this + * function can be used to verify that a netlink message is not malformed nor + * truncated. + * + * This function does not set errno in case of error since it is intended + * for iterations. Thus, it returns true on success and false on error. + * + * The len parameter may become negative in malformed messages during message + * iteration, that is why we use a signed integer. + */ +bool mnl_nlmsg_ok(const struct nlmsghdr *nlh, int len) +{ + return len >= (int)sizeof(struct nlmsghdr) && + nlh->nlmsg_len >= sizeof(struct nlmsghdr) && + (int)nlh->nlmsg_len <= len; +} + +/** + * mnl_nlmsg_next - get the next netlink message in a multipart message + * \param nlh current netlink message that we are handling + * \param len length of the remaining bytes in the buffer (passed by reference). + * + * This function returns a pointer to the next netlink message that is part + * of a multi-part netlink message. Netlink can batch several messages into + * one buffer so that the receiver has to iterate over the whole set of + * Netlink messages. + * + * You have to use mnl_nlmsg_ok() to check if the next Netlink message is + * valid. + */ +struct nlmsghdr *mnl_nlmsg_next(const struct nlmsghdr *nlh, + int *len) +{ + *len -= MNL_ALIGN(nlh->nlmsg_len); + return (struct nlmsghdr *)((void *)nlh + MNL_ALIGN(nlh->nlmsg_len)); +} + +/** + * mnl_nlmsg_get_payload_tail - get the ending of the netlink message + * \param nlh pointer to netlink message + * + * This function returns a pointer to the netlink message tail. This is useful + * to build a message since we continue adding attributes at the end of the + * message. + */ +void *mnl_nlmsg_get_payload_tail(const struct nlmsghdr *nlh) +{ + return (void *)nlh + MNL_ALIGN(nlh->nlmsg_len); +} + +/** + * mnl_nlmsg_seq_ok - perform sequence tracking + * \param nlh current netlink message that we are handling + * \param seq last sequence number used to send a message + * + * This functions returns true if the sequence tracking is fulfilled, otherwise + * false is returned. We skip the tracking for netlink messages whose sequence + * number is zero since it is usually reserved for event-based kernel + * notifications. On the other hand, if seq is set but the message sequence + * number is not set (i.e. this is an event message coming from kernel-space), + * then we also skip the tracking. This approach is good if we use the same + * socket to send commands to kernel-space (that we want to track) and to + * listen to events (that we do not track). + */ +bool mnl_nlmsg_seq_ok(const struct nlmsghdr *nlh, + unsigned int seq) +{ + return nlh->nlmsg_seq && seq ? nlh->nlmsg_seq == seq : true; +} + +/** + * mnl_nlmsg_portid_ok - perform portID origin check + * \param nlh current netlink message that we are handling + * \param portid netlink portid that we want to check + * + * This functions returns true if the origin is fulfilled, otherwise + * false is returned. We skip the tracking for netlink message whose portID + * is zero since it is reserved for event-based kernel notifications. On the + * other hand, if portid is set but the message PortID is not (i.e. this + * is an event message coming from kernel-space), then we also skip the + * tracking. This approach is good if we use the same socket to send commands + * to kernel-space (that we want to track) and to listen to events (that we + * do not track). + */ +bool mnl_nlmsg_portid_ok(const struct nlmsghdr *nlh, + unsigned int portid) +{ + return nlh->nlmsg_pid && portid ? nlh->nlmsg_pid == portid : true; +} + +static void mnl_nlmsg_fprintf_header(FILE *fd, const struct nlmsghdr *nlh) +{ + fprintf(fd, "----------------\t------------------\n"); + fprintf(fd, "| %.010u |\t| message length |\n", nlh->nlmsg_len); + fprintf(fd, "| %.05u | %c%c%c%c |\t| type | flags |\n", + nlh->nlmsg_type, + nlh->nlmsg_flags & NLM_F_REQUEST ? 'R' : '-', + nlh->nlmsg_flags & NLM_F_MULTI ? 'M' : '-', + nlh->nlmsg_flags & NLM_F_ACK ? 'A' : '-', + nlh->nlmsg_flags & NLM_F_ECHO ? 'E' : '-'); + fprintf(fd, "| %.010u |\t| sequence number|\n", nlh->nlmsg_seq); + fprintf(fd, "| %.010u |\t| port ID |\n", nlh->nlmsg_pid); + fprintf(fd, "----------------\t------------------\n"); +} + +static void mnl_nlmsg_fprintf_payload(FILE *fd, const struct nlmsghdr *nlh, + size_t extra_header_size) +{ + int rem = 0; + unsigned int i; + + for (i=sizeof(struct nlmsghdr); inlmsg_len; i+=4) { + char *b = (char *) nlh; + struct nlattr *attr = (struct nlattr *) (b+i); + + /* netlink control message. */ + if (nlh->nlmsg_type < NLMSG_MIN_TYPE) { + fprintf(fd, "| %.2x %.2x %.2x %.2x |\t", + 0xff & b[i], 0xff & b[i+1], + 0xff & b[i+2], 0xff & b[i+3]); + fprintf(fd, "| |\n"); + /* special handling for the extra header. */ + } else if (extra_header_size > 0) { + extra_header_size -= 4; + fprintf(fd, "| %.2x %.2x %.2x %.2x |\t", + 0xff & b[i], 0xff & b[i+1], + 0xff & b[i+2], 0xff & b[i+3]); + fprintf(fd, "| extra header |\n"); + /* this seems like an attribute header. */ + } else if (rem == 0 && (attr->nla_type & NLA_TYPE_MASK) != 0) { + fprintf(fd, "|%c[%d;%dm" + "%.5u" + "%c[%dm" + "|" + "%c[%d;%dm" + "%c%c" + "%c[%dm" + "|" + "%c[%d;%dm" + "%.5u" + "%c[%dm|\t", + 27, 1, 31, + attr->nla_len, + 27, 0, + 27, 1, 32, + attr->nla_type & NLA_F_NESTED ? 'N' : '-', + attr->nla_type & + NLA_F_NET_BYTEORDER ? 'B' : '-', + 27, 0, + 27, 1, 34, + attr->nla_type & NLA_TYPE_MASK, + 27, 0); + fprintf(fd, "|len |flags| type|\n"); + + if (!(attr->nla_type & NLA_F_NESTED)) { + rem = NLA_ALIGN(attr->nla_len) - + sizeof(struct nlattr); + } + /* this is the attribute payload. */ + } else if (rem > 0) { + rem -= 4; + fprintf(fd, "| %.2x %.2x %.2x %.2x |\t", + 0xff & b[i], 0xff & b[i+1], + 0xff & b[i+2], 0xff & b[i+3]); + fprintf(fd, "| data |"); + fprintf(fd, "\t %c %c %c %c\n", + isprint(b[i]) ? b[i] : ' ', + isprint(b[i+1]) ? b[i+1] : ' ', + isprint(b[i+2]) ? b[i+2] : ' ', + isprint(b[i+3]) ? b[i+3] : ' '); + } + } + fprintf(fd, "----------------\t------------------\n"); +} + +/** + * mnl_nlmsg_fprintf - print netlink message to file + * \param fd pointer to file type + * \param data pointer to the buffer that contains messages to be printed + * \param datalen length of data stored in the buffer + * \param extra_header_size size of the extra header (if any) + * + * This function prints the netlink header to a file handle. + * It may be useful for debugging purposes. One example of the output + * is the following: + * + *\verbatim +---------------- ------------------ +| 0000000040 | | message length | +| 00016 | R-A- | | type | flags | +| 1289148991 | | sequence number| +| 0000000000 | | port ID | +---------------- ------------------ +| 00 00 00 00 | | extra header | +| 00 00 00 00 | | extra header | +| 01 00 00 00 | | extra header | +| 01 00 00 00 | | extra header | +|00008|--|00003| |len |flags| type| +| 65 74 68 30 | | data | e t h 0 +---------------- ------------------ +\endverbatim + * + * This example above shows the netlink message that is send to kernel-space + * to set up the link interface eth0. The netlink and attribute header data + * are displayed in base 10 whereas the extra header and the attribute payload + * are expressed in base 16. The possible flags in the netlink header are: + * + * - R, that indicates that NLM_F_REQUEST is set. + * - M, that indicates that NLM_F_MULTI is set. + * - A, that indicates that NLM_F_ACK is set. + * - E, that indicates that NLM_F_ECHO is set. + * + * The lack of one flag is displayed with '-'. On the other hand, the possible + * attribute flags available are: + * + * - N, that indicates that NLA_F_NESTED is set. + * - B, that indicates that NLA_F_NET_BYTEORDER is set. + */ +void mnl_nlmsg_fprintf(FILE *fd, const void *data, size_t datalen, + size_t extra_header_size) +{ + const struct nlmsghdr *nlh = data; + int len = datalen; + + while (mnl_nlmsg_ok(nlh, len)) { + mnl_nlmsg_fprintf_header(fd, nlh); + mnl_nlmsg_fprintf_payload(fd, nlh, extra_header_size); + nlh = mnl_nlmsg_next(nlh, &len); + } +} + +/** + * @} + */ + +/** + * \defgroup batch Netlink message batch helpers + * + * This library provides helpers to batch several messages into one single + * datagram. These helpers do not perform strict memory boundary checkings. + * + * The following figure represents a Netlink message batch: + * + * |<-------------- MNL_SOCKET_BUFFER_SIZE ------------->| + * |<-------------------- batch ------------------>| | + * |-----------|-----------|-----------|-----------|-----------| + * |<- nlmsg ->|<- nlmsg ->|<- nlmsg ->|<- nlmsg ->|<- nlmsg ->| + * |-----------|-----------|-----------|-----------|-----------| + * ^ ^ + * | | + * message N message N+1 + * + * To start the batch, you have to call mnl_nlmsg_batch_start() and you can + * use mnl_nlmsg_batch_stop() to release it. + * + * You have to invoke mnl_nlmsg_batch_next() to get room for a new message + * in the batch. If this function returns NULL, it means that the last + * message that was added (message N+1 in the figure above) does not fit the + * batch. Thus, you have to send the batch (which includes until message N) + * and, then, you have to call mnl_nlmsg_batch_reset() to re-initialize + * the batch (this moves message N+1 to the head of the buffer). For that + * reason, the buffer that you have to use to store the batch must be double + * of MNL_SOCKET_BUFFER_SIZE to ensure that the last message (message N+1) + * that did not fit into the batch is written inside valid memory boundaries. + * + * @{ + */ + +struct mnl_nlmsg_batch { + /* the buffer that is used to store the batch. */ + void *buf; + size_t limit; + size_t buflen; + /* the current netlink message in the batch. */ + void *cur; + bool overflow; +}; + +/** + * mnl_nlmsg_batch_start - initialize a batch + * \param buf pointer to the buffer that will store this batch + * \param limit maximum size of the batch (should be MNL_SOCKET_BUFFER_SIZE). + * + * The buffer that you pass must be double of MNL_SOCKET_BUFFER_SIZE. The + * limit must be half of the buffer size, otherwise expect funny memory + * corruptions 8-). + * + * You can allocate the buffer that you use to store the batch in the stack or + * the heap, no restrictions in this regard. This function returns NULL on + * error. + */ +struct mnl_nlmsg_batch *mnl_nlmsg_batch_start(void *buf, + size_t limit) +{ + struct mnl_nlmsg_batch *b; + + b = malloc(sizeof(struct mnl_nlmsg_batch)); + if (b == NULL) + return NULL; + + b->buf = buf; + b->limit = limit; + b->buflen = 0; + b->cur = buf; + b->overflow = false; + + return b; +} + +/** + * mnl_nlmsg_batch_stop - release a batch + * \param b pointer to batch + * + * This function releases the batch allocated by mnl_nlmsg_batch_start(). + */ +void mnl_nlmsg_batch_stop(struct mnl_nlmsg_batch *b) +{ + free(b); +} + +/** + * mnl_nlmsg_batch_next - get room for the next message in the batch + * \param b pointer to batch + * + * This function returns false if the last message did not fit into the + * batch. Otherwise, it prepares the batch to provide room for the new + * Netlink message in the batch and returns true. + * + * You have to put at least one message in the batch before calling this + * function, otherwise your application is likely to crash. + */ +bool mnl_nlmsg_batch_next(struct mnl_nlmsg_batch *b) +{ + struct nlmsghdr *nlh = b->cur; + + if (b->buflen + nlh->nlmsg_len > b->limit) { + b->overflow = true; + return false; + } + b->cur = b->buf + b->buflen + nlh->nlmsg_len; + b->buflen += nlh->nlmsg_len; + return true; +} + +/** + * mnl_nlmsg_batch_reset - reset the batch + * \param b pointer to batch + * + * This function allows to reset a batch, so you can reuse it to create a + * new one. This function moves the last message which does not fit the + * batch to the head of the buffer, if any. + */ +void mnl_nlmsg_batch_reset(struct mnl_nlmsg_batch *b) +{ + if (b->overflow) { + struct nlmsghdr *nlh = b->cur; + memcpy(b->buf, b->cur, nlh->nlmsg_len); + b->buflen = nlh->nlmsg_len; + b->cur = b->buf + b->buflen; + b->overflow = false; + } else { + b->buflen = 0; + b->cur = b->buf; + } +} + +/** + * mnl_nlmsg_batch_size - get current size of the batch + * \param b pointer to batch + * + * This function returns the current size of the batch. + */ +size_t mnl_nlmsg_batch_size(struct mnl_nlmsg_batch *b) +{ + return b->buflen; +} + +/** + * mnl_nlmsg_batch_head - get head of this batch + * \param b pointer to batch + * + * This function returns a pointer to the head of the batch, which is the + * beginning of the buffer that is used. + */ +void *mnl_nlmsg_batch_head(struct mnl_nlmsg_batch *b) +{ + return b->buf; +} + +/** + * mnl_nlmsg_batch_current - returns current position in the batch + * \param b pointer to batch + * + * This function returns a pointer to the current position in the buffer + * that is used to store the batch. + */ +void *mnl_nlmsg_batch_current(struct mnl_nlmsg_batch *b) +{ + return b->cur; +} + +/** + * mnl_nlmsg_batch_is_empty - check if there is any message in the batch + * \param b pointer to batch + * + * This function returns true if the batch is empty. + */ +bool mnl_nlmsg_batch_is_empty(struct mnl_nlmsg_batch *b) +{ + return b->buflen == 0; +} + +/** + * @} + */ diff --git a/root/package/link4all/quectel-CM/src/libmnl/socket.c b/root/package/link4all/quectel-CM/src/libmnl/socket.c new file mode 100644 index 00000000..dd5ab664 --- /dev/null +++ b/root/package/link4all/quectel-CM/src/libmnl/socket.c @@ -0,0 +1,351 @@ +/* + * (C) 2008-2010 by Pablo Neira Ayuso + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include + +#include "libmnl.h" + +/** + * \mainpage + * + * libmnl is a minimalistic user-space library oriented to Netlink developers. + * There are a lot of common tasks in parsing, validating, constructing of + * both the Netlink header and TLVs that are repetitive and easy to get wrong. + * This library aims to provide simple helpers that allows you to avoid + * re-inventing the wheel in common Netlink tasks. + * + * \verbatim +"Simplify, simplify" -- Henry David Thoureau. Walden (1854) +\endverbatim + * + * The acronym libmnl stands for LIBrary Minimalistic NetLink. + * + * libmnl homepage is: + * http://www.netfilter.org/projects/libmnl/ + * + * \section features Main Features + * - Small: the shared library requires around 30KB for an x86-based computer. + * - Simple: this library avoids complex abstractions that tend to hide Netlink + * details. It avoids elaborated object-oriented infrastructure and complex + * callback-based workflow. + * - Easy to use: the library simplifies the work for Netlink-wise developers. + * It provides functions to make socket handling, message building, + * validating, parsing and sequence tracking, easier. + * - Easy to re-use: you can use this library to build your own abstraction + * layer upon this library, if you want to provide another library that + * hides Netlink details to your users. + * - Decoupling: the interdependency of the main bricks that compose this + * library is reduced, i.e. the library provides many helpers, but the + * programmer is not forced to use them. + * + * \section licensing Licensing terms + * This library is released under the LGPLv2.1 or any later (at your option). + * + * \section Dependencies + * You have to install the Linux kernel headers that you want to use to develop + * your application. Moreover, this library requires that you have some basics + * on Netlink. + * + * \section scm Git Tree + * The current development version of libmnl can be accessed at: + * http://git.netfilter.org/cgi-bin/gitweb.cgi?p=libmnl.git;a=summary + * + * \section using Using libmnl + * You can access several example files under examples/ in the libmnl source + * code tree. + */ + +struct mnl_socket { + int fd; + struct sockaddr_nl addr; +}; + +/** + * \defgroup socket Netlink socket helpers + * @{ + */ + +/** + * mnl_socket_get_fd - obtain file descriptor from netlink socket + * \param nl netlink socket obtained via mnl_socket_open() + * + * This function returns the file descriptor of a given netlink socket. + */ +int mnl_socket_get_fd(const struct mnl_socket *nl) +{ + return nl->fd; +} + +/** + * mnl_socket_get_portid - obtain Netlink PortID from netlink socket + * \param nl netlink socket obtained via mnl_socket_open() + * + * This function returns the Netlink PortID of a given netlink socket. + * It's a common mistake to assume that this PortID equals the process ID + * which is not always true. This is the case if you open more than one + * socket that is binded to the same Netlink subsystem from the same process. + */ +unsigned int mnl_socket_get_portid(const struct mnl_socket *nl) +{ + return nl->addr.nl_pid; +} + +static struct mnl_socket *__mnl_socket_open(int bus, int flags) +{ + struct mnl_socket *nl; + + nl = calloc(1, sizeof(struct mnl_socket)); + if (nl == NULL) + return NULL; + + nl->fd = socket(AF_NETLINK, SOCK_RAW | flags, bus); + if (nl->fd == -1) { + free(nl); + return NULL; + } + + return nl; +} + +/** + * mnl_socket_open - open a netlink socket + * \param bus the netlink socket bus ID (see NETLINK_* constants) + * + * On error, it returns NULL and errno is appropriately set. Otherwise, it + * returns a valid pointer to the mnl_socket structure. + */ +struct mnl_socket *mnl_socket_open(int bus) +{ + return __mnl_socket_open(bus, 0); +} + +/** + * mnl_socket_open2 - open a netlink socket with appropriate flags + * \param bus the netlink socket bus ID (see NETLINK_* constants) + * \param flags the netlink socket flags (see SOCK_* constants in socket(2)) + * + * This is similar to mnl_socket_open(), but allows to set flags like + * SOCK_CLOEXEC at socket creation time (useful for multi-threaded programs + * performing exec calls). + * + * On error, it returns NULL and errno is appropriately set. Otherwise, it + * returns a valid pointer to the mnl_socket structure. + */ +struct mnl_socket *mnl_socket_open2(int bus, int flags) +{ + return __mnl_socket_open(bus, flags); +} + +/** + * mnl_socket_fdopen - associates a mnl_socket object with pre-existing socket. + * \param fd pre-existing socket descriptor. + * + * On error, it returns NULL and errno is appropriately set. Otherwise, it + * returns a valid pointer to the mnl_socket structure. It also sets the portID + * if the socket fd is already bound and it is AF_NETLINK. + * + * Note that mnl_socket_get_portid() returns 0 if this function is used with + * non-netlink socket. + */ +struct mnl_socket *mnl_socket_fdopen(int fd) +{ + int ret; + struct mnl_socket *nl; + struct sockaddr_nl addr; + socklen_t addr_len = sizeof(struct sockaddr_nl); + + ret = getsockname(fd, (struct sockaddr *) &addr, &addr_len); + if (ret == -1) + return NULL; + + nl = calloc(1, sizeof(struct mnl_socket)); + if (nl == NULL) + return NULL; + + nl->fd = fd; + if (addr.nl_family == AF_NETLINK) + nl->addr = addr; + + return nl; +} + +/** + * mnl_socket_bind - bind netlink socket + * \param nl netlink socket obtained via mnl_socket_open() + * \param groups the group of message you're interested in + * \param pid the port ID you want to use (use zero for automatic selection) + * + * On error, this function returns -1 and errno is appropriately set. On + * success, 0 is returned. You can use MNL_SOCKET_AUTOPID which is 0 for + * automatic port ID selection. + */ +int mnl_socket_bind(struct mnl_socket *nl, unsigned int groups, + pid_t pid) +{ + int ret; + socklen_t addr_len; + + nl->addr.nl_family = AF_NETLINK; + nl->addr.nl_groups = groups; + nl->addr.nl_pid = pid; + + ret = bind(nl->fd, (struct sockaddr *) &nl->addr, sizeof (nl->addr)); + if (ret < 0) + return ret; + + addr_len = sizeof(nl->addr); + ret = getsockname(nl->fd, (struct sockaddr *) &nl->addr, &addr_len); + if (ret < 0) + return ret; + + if (addr_len != sizeof(nl->addr)) { + errno = EINVAL; + return -1; + } + if (nl->addr.nl_family != AF_NETLINK) { + errno = EINVAL; + return -1; + } + return 0; +} + +/** + * mnl_socket_sendto - send a netlink message of a certain size + * \param nl netlink socket obtained via mnl_socket_open() + * \param buf buffer containing the netlink message to be sent + * \param len number of bytes in the buffer that you want to send + * + * On error, it returns -1 and errno is appropriately set. Otherwise, it + * returns the number of bytes sent. + */ +ssize_t mnl_socket_sendto(const struct mnl_socket *nl, + const void *buf, size_t len) +{ + static const struct sockaddr_nl snl = { + .nl_family = AF_NETLINK + }; + return sendto(nl->fd, buf, len, 0, + (struct sockaddr *) &snl, sizeof(snl)); +} + +/** + * mnl_socket_recvfrom - receive a netlink message + * \param nl netlink socket obtained via mnl_socket_open() + * \param buf buffer that you want to use to store the netlink message + * \param bufsiz size of the buffer passed to store the netlink message + * + * On error, it returns -1 and errno is appropriately set. If errno is set + * to ENOSPC, it means that the buffer that you have passed to store the + * netlink message is too small, so you have received a truncated message. + * To avoid this, you have to allocate a buffer of MNL_SOCKET_BUFFER_SIZE + * (which is 8KB, see linux/netlink.h for more information). Using this + * buffer size ensures that your buffer is big enough to store the netlink + * message without truncating it. + */ +ssize_t mnl_socket_recvfrom(const struct mnl_socket *nl, + void *buf, size_t bufsiz) +{ + ssize_t ret; + struct sockaddr_nl addr; + struct iovec iov = { + .iov_base = buf, + .iov_len = bufsiz, + }; + struct msghdr msg = { + .msg_name = &addr, + .msg_namelen = sizeof(struct sockaddr_nl), + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = NULL, + .msg_controllen = 0, + .msg_flags = 0, + }; + ret = recvmsg(nl->fd, &msg, 0); + if (ret == -1) + return ret; + + if (msg.msg_flags & MSG_TRUNC) { + errno = ENOSPC; + return -1; + } + if (msg.msg_namelen != sizeof(struct sockaddr_nl)) { + errno = EINVAL; + return -1; + } + return ret; +} + +/** + * mnl_socket_close - close a given netlink socket + * \param nl netlink socket obtained via mnl_socket_open() + * + * On error, this function returns -1 and errno is appropriately set. + * On success, it returns 0. + */ +int mnl_socket_close(struct mnl_socket *nl) +{ + int ret = close(nl->fd); + free(nl); + return ret; +} + +/** + * mnl_socket_setsockopt - set Netlink socket option + * \param nl netlink socket obtained via mnl_socket_open() + * \param type type of Netlink socket options + * \param buf the buffer that contains the data about this option + * \param len the size of the buffer passed + * + * This function allows you to set some Netlink socket option. As of writing + * this (see linux/netlink.h), the existing options are: + * + * - \#define NETLINK_ADD_MEMBERSHIP 1 + * - \#define NETLINK_DROP_MEMBERSHIP 2 + * - \#define NETLINK_PKTINFO 3 + * - \#define NETLINK_BROADCAST_ERROR 4 + * - \#define NETLINK_NO_ENOBUFS 5 + * + * In the early days, Netlink only supported 32 groups expressed in a + * 32-bits mask. However, since 2.6.14, Netlink may have up to 2^32 multicast + * groups but you have to use setsockopt() with NETLINK_ADD_MEMBERSHIP to + * join a given multicast group. This function internally calls setsockopt() + * to join a given netlink multicast group. You can still use mnl_bind() + * and the 32-bit mask to join a set of Netlink multicast groups. + * + * On error, this function returns -1 and errno is appropriately set. + */ +int mnl_socket_setsockopt(const struct mnl_socket *nl, int type, + void *buf, socklen_t len) +{ + return setsockopt(nl->fd, SOL_NETLINK, type, buf, len); +} + +/** + * mnl_socket_getsockopt - get a Netlink socket option + * \param nl netlink socket obtained via mnl_socket_open() + * \param type type of Netlink socket options + * \param buf pointer to the buffer to store the value of this option + * \param len size of the information written in the buffer + * + * On error, this function returns -1 and errno is appropriately set. + */ +int mnl_socket_getsockopt(const struct mnl_socket *nl, int type, + void *buf, socklen_t *len) +{ + return getsockopt(nl->fd, SOL_NETLINK, type, buf, len); +} + +/** + * @} + */ diff --git a/root/package/link4all/quectel-CM/src/main.c b/root/package/link4all/quectel-CM/src/main.c new file mode 100644 index 00000000..61719535 --- /dev/null +++ b/root/package/link4all/quectel-CM/src/main.c @@ -0,0 +1,953 @@ +/****************************************************************************** + @file main.c + @brief The entry program. + + DESCRIPTION + Connectivity Management Tool for USB network adapter of Quectel wireless cellular modules. + + INITIALIZATION AND SEQUENCING REQUIREMENTS + None. + + --------------------------------------------------------------------------- + Copyright (c) 2016 -2020 Quectel Wireless Solution, Co., Ltd. All Rights Reserved. + Quectel Wireless Solution Proprietary and Confidential. + --------------------------------------------------------------------------- +******************************************************************************/ + +#include "QMIThread.h" +#include +#include +#include +#include + +#include "util.h" +//#define CONFIG_EXIT_WHEN_DIAL_FAILED +//#define CONFIG_BACKGROUND_WHEN_GET_IP +//#define CONFIG_PID_FILE_FORMAT "/var/run/quectel-CM-%s.pid" //for example /var/run/quectel-CM-wwan0.pid + +int debug_qmi = 0; +int main_loop = 0; +int qmidevice_control_fd[2]; +static int signal_control_fd[2]; + +extern const struct qmi_device_ops gobi_qmidev_ops; +extern const struct qmi_device_ops qmiwwan_qmidev_ops; +extern int ql_ifconfig(int argc, char *argv[]); +extern int ql_get_netcard_driver_info(const char*); +extern int ql_capture_usbmon_log(PROFILE_T *profile, const char *log_path); +extern void ql_stop_usbmon_log(PROFILE_T *profile); + +#ifdef CONFIG_BACKGROUND_WHEN_GET_IP +static int daemon_pipe_fd[2]; + +static void ql_prepare_daemon(void) { + pid_t daemon_child_pid; + + if (pipe(daemon_pipe_fd) < 0) { + dbg_time("%s Faild to create daemon_pipe_fd: %d (%s)", __func__, errno, strerror(errno)); + return; + } + + daemon_child_pid = fork(); + if (daemon_child_pid > 0) { + struct pollfd pollfds[] = {{daemon_pipe_fd[0], POLLIN, 0}, {0, POLLIN, 0}}; + int ne, ret, nevents = sizeof(pollfds)/sizeof(pollfds[0]); + int signo; + + //dbg_time("father"); + + close(daemon_pipe_fd[1]); + + if (socketpair( AF_LOCAL, SOCK_STREAM, 0, signal_control_fd) < 0 ) { + dbg_time("%s Faild to create main_control_fd: %d (%s)", __func__, errno, strerror(errno)); + return; + } + + pollfds[1].fd = signal_control_fd[1]; + + while (1) { + do { + ret = poll(pollfds, nevents, -1); + } while ((ret < 0) && (errno == EINTR)); + + if (ret < 0) { + dbg_time("%s poll=%d, errno: %d (%s)", __func__, ret, errno, strerror(errno)); + goto __daemon_quit; + } + + for (ne = 0; ne < nevents; ne++) { + int fd = pollfds[ne].fd; + short revents = pollfds[ne].revents; + + if (revents & (POLLERR | POLLHUP | POLLNVAL)) { + //dbg_time("%s poll err/hup", __func__); + //dbg_time("poll fd = %d, events = 0x%04x", fd, revents); + if (revents & POLLHUP) + goto __daemon_quit; + } + + if ((revents & POLLIN) && read(fd, &signo, sizeof(signo)) == sizeof(signo)) { + if (signal_control_fd[1] == fd) { + if (signo == SIGCHLD) { + int status; + int pid = waitpid(daemon_child_pid, &status, 0); + dbg_time("waitpid pid=%d, status=%x", pid, status); + goto __daemon_quit; + } else { + kill(daemon_child_pid, signo); + } + } else if (daemon_pipe_fd[0] == fd) { + //dbg_time("daemon_pipe_signo = %d", signo); + goto __daemon_quit; + } + } + } + } +__daemon_quit: + //dbg_time("father exit"); + _exit(0); + } else if (daemon_child_pid == 0) { + close(daemon_pipe_fd[0]); + //dbg_time("child", getpid()); + } else { + close(daemon_pipe_fd[0]); + close(daemon_pipe_fd[1]); + dbg_time("%s Faild to create daemon_child_pid: %d (%s)", __func__, errno, strerror(errno)); + } +} + +static void ql_enter_daemon(int signo) { + if (daemon_pipe_fd[1] > 0) + if (signo) { + write(daemon_pipe_fd[1], &signo, sizeof(signo)); + sleep(1); + } + close(daemon_pipe_fd[1]); + daemon_pipe_fd[1] = -1; + setsid(); + } +#endif + +//UINT ifc_get_addr(const char *ifname); +static int s_link = -1; +static void usbnet_link_state(int state) +{ + s_link = state ? 1 : 0; +} +static void usbnet_link_change(int link, PROFILE_T *profile) { + if (s_link == link) + return; + + s_link = link; + + if (link) { + udhcpc_start(profile); + } else { + udhcpc_stop(profile); + } + +#ifdef CONFIG_BACKGROUND_WHEN_GET_IP + if (link && daemon_pipe_fd[1] > 0) { + int timeout = 6; + while (timeout-- /*&& ifc_get_addr(profile->usbnet_adapter) == 0*/) { + sleep(1); + } + ql_enter_daemon(SIG_EVENT_START); + } +#endif +} + +static int check_ipv4_address(PROFILE_T *now_profile) { + PROFILE_T new_profile_v; + PROFILE_T *new_profile = &new_profile_v; + + memcpy(new_profile, now_profile, sizeof(PROFILE_T)); + if (requestGetIPAddress(new_profile, IpFamilyV4) == 0) { + if (new_profile->ipv4.Address != now_profile->ipv4.Address || debug_qmi) { + unsigned char *l = (unsigned char *)&now_profile->ipv4.Address; + unsigned char *r = (unsigned char *)&new_profile->ipv4.Address; + dbg_time("localIP: %d.%d.%d.%d VS remoteIP: %d.%d.%d.%d", + l[3], l[2], l[1], l[0], r[3], r[2], r[1], r[0]); + } + return (new_profile->ipv4.Address == now_profile->ipv4.Address); + } + return 0; +} + +static void main_send_event_to_qmidevice(int triger_event) { + write(qmidevice_control_fd[0], &triger_event, sizeof(triger_event)); +} + +static void send_signo_to_main(int signo) { + write(signal_control_fd[0], &signo, sizeof(signo)); +} + +void qmidevice_send_event_to_main(int triger_event) { + write(qmidevice_control_fd[1], &triger_event, sizeof(triger_event)); +} + +void qmidevice_send_event_to_main_ext(int triger_event, void *data, unsigned len) { + write(qmidevice_control_fd[1], &triger_event, sizeof(triger_event)); + write(qmidevice_control_fd[1], data, len); +} + +#define MAX_PATH 256 + +static int ls_dir(const char *dir, int (*match)(const char *dir, const char *file, void *argv[]), void *argv[]) +{ + DIR *pDir; + struct dirent* ent = NULL; + int match_times = 0; + + pDir = opendir(dir); + if (pDir == NULL) { + dbg_time("Cannot open directory: %s, errno: %d (%s)", dir, errno, strerror(errno)); + return 0; + } + + while ((ent = readdir(pDir)) != NULL) { + match_times += match(dir, ent->d_name, argv); + } + closedir(pDir); + + return match_times; +} + +static int is_same_linkfile(const char *dir, const char *file, void *argv[]) +{ + const char *qmichannel = (const char *)argv[1]; + char linkname[MAX_PATH]; + char filename[MAX_PATH]; + int linksize; + + snprintf(linkname, MAX_PATH, "%s/%s", dir, file); + linksize = readlink(linkname, filename, MAX_PATH); + if (linksize <= 0) + return 0; + + filename[linksize] = 0; + if (strcmp(filename, qmichannel)) + return 0; + + dbg_time("%s -> %s", linkname, filename); + return 1; +} + +static int is_brother_process(const char *dir, const char *file, void *argv[]) +{ + //const char *myself = (const char *)argv[0]; + char linkname[MAX_PATH]; + char filename[MAX_PATH]; + int linksize; + int i = 0, kill_timeout = 15; + pid_t pid; + + //dbg_time("%s", file); + while (file[i]) { + if (!isdigit(file[i])) + break; + i++; + } + + if (file[i]) { + //dbg_time("%s not digit", file); + return 0; + } + + snprintf(linkname, MAX_PATH, "%s/%s/exe", dir, file); + linksize = readlink(linkname, filename, MAX_PATH); + if (linksize <= 0) + return 0; + + filename[linksize] = 0; + + pid = atoi(file); + if (pid >= getpid()) + return 0; + + snprintf(linkname, MAX_PATH, "%s/%s/fd", dir, file); + if (!ls_dir(linkname, is_same_linkfile, argv)) + return 0; + + dbg_time("%s/%s/exe -> %s", dir, file, filename); + while (kill_timeout-- && !kill(pid, 0)) + { + kill(pid, SIGTERM); + sleep(1); + } + if (!kill(pid, 0)) + { + dbg_time("force kill %s/%s/exe -> %s", dir, file, filename); + kill(pid, SIGKILL); + sleep(1); + } + + return 1; +} + +static int kill_brothers(const char *qmichannel) +{ + char myself[MAX_PATH]; + int filenamesize; + void *argv[2] = {myself, (void *)qmichannel}; + + filenamesize = readlink("/proc/self/exe", myself, MAX_PATH); + if (filenamesize <= 0) + return 0; + myself[filenamesize] = 0; + + if (ls_dir("/proc", is_brother_process, argv)) + sleep(1); + + return 0; +} + +static int kill_data_call_pdp(int pdp, char *self) { + int pid; + char *p = NULL; + + p = self; + while (*self) { + if (*self == '/') + p = self+1; + self++; + } + + pid = getpid_by_pdp(pdp, p); + if (pid > 0) { + dbg_time("send SIGINT to process %d", pid); + return kill(pid, SIGINT); + } + + return -1; +} + +static void ql_sigaction(int signo) { + if (SIGALRM == signo) + send_signo_to_main(SIG_EVENT_START); + else + { + main_loop = 0; + send_signo_to_main(SIG_EVENT_STOP); + main_send_event_to_qmidevice(SIG_EVENT_STOP); //main may be wating qmi response + } +} + +pthread_t gQmiThreadID; + +static int usage(const char *progname) { + dbg_time("Usage: %s [options]", progname); + dbg_time("-s [apn [user password auth]] Set apn/user/password/auth get from your network provider"); + dbg_time("-p pincode Verify sim card pin if sim card is locked"); + dbg_time("-f logfilename Save log message of this program to file"); + dbg_time("-u usbmonlog filename Save usbmon log of this program to file"); + dbg_time("-i interface Specify network interface(default auto-detect)"); + dbg_time("-4 IPv4 protocol"); + dbg_time("-6 IPv6 protocol"); + dbg_time("-m muxID Specify muxid when set multi-pdn data connection."); + dbg_time("-n channelID Specify channelID when set multi-pdn data connection(default 1)."); + dbg_time("-k channelID Send SIGINT to quectel-CM which multi-pdn is channelID."); + dbg_time("-r Detach and attach kernel driver before open device."); + dbg_time("-b enable network interface bridge function(default 0)."); + dbg_time("[Examples]"); + dbg_time("Example 1: %s ", progname); + dbg_time("Example 2: %s -s 3gnet ", progname); + dbg_time("Example 3: %s -s 3gnet carl 1234 0 -p 1234 -f gobinet_log.txt", progname); + return 0; +} + +int qmi_main(PROFILE_T *profile) +{ + int triger_event = 0; + int signo; +#ifdef CONFIG_SIM + SIM_Status SIMStatus; +#endif + UCHAR PSAttachedState; + UCHAR IPv4ConnectionStatus = 0xff; //unknow state + UCHAR IPv6ConnectionStatus = 0xff; //unknow state + unsigned SetupCallFail = 0; + unsigned long SetupCallAllowTime = clock_msec(); + int qmierr = 0; + char * save_usbnet_adapter = NULL; + + /* signal trigger quit event */ + signal(SIGINT, ql_sigaction); + signal(SIGTERM, ql_sigaction); + /* timer routine */ + signal(SIGALRM, ql_sigaction); + +#ifdef CONFIG_BACKGROUND_WHEN_GET_IP + ql_prepare_daemon(); +#endif + +//sudo apt-get install udhcpc +//sudo apt-get remove ModemManager +__main_loop: + if (profile->reattach_flag) { + if (!reattach_driver(profile)) + sleep(2); + } + + /* try to recreate FDs*/ + if (socketpair( AF_LOCAL, SOCK_STREAM, 0, signal_control_fd) < 0 ) { + dbg_time("%s Faild to create main_control_fd: %d (%s)", __func__, errno, strerror(errno)); + return -1; + } + + if ( socketpair( AF_LOCAL, SOCK_STREAM, 0, qmidevice_control_fd ) < 0 ) { + dbg_time("%s Failed to create thread control socket pair: %d (%s)", __func__, errno, strerror(errno)); + return 0; + } + + while (!profile->qmichannel) { + char qmichannel[32+1] = {'\0'}; + char usbnet_adapter[32+1] = {'\0'}; + + if (!qmidevice_detect(qmichannel, usbnet_adapter, sizeof(qmichannel), &profile->busnum, &profile->devnum)) { + dbg_time("qmidevice_detect failed"); + continue; + } else { + if (!(profile->qmichannel)) + strset(profile->qmichannel, qmichannel); + if (!(profile->usbnet_adapter)) + strset(profile->usbnet_adapter, usbnet_adapter); + break; + } + if (main_loop) { + int wait_for_device = 3000; + dbg_time("Wait for Quectel modules connect"); + while (wait_for_device && main_loop) { + wait_for_device -= 100; + usleep(100*1000); + } + continue; + } + dbg_time("Cannot find qmichannel(%s) usbnet_adapter(%s) for Quectel modules", profile->qmichannel, profile->usbnet_adapter); + return -ENODEV; + } + + if (qmidev_is_gobinet(profile->qmichannel)) { + profile->qmi_ops = &gobi_qmidev_ops; + } + else { + profile->qmi_ops = &qmiwwan_qmidev_ops; + } + qmidev_send = profile->qmi_ops->send; + + if (profile->qmap_mode == 0 || profile->qmap_mode == 1) + kill_brothers(profile->qmichannel); + + if (pthread_create( &gQmiThreadID, 0, profile->qmi_ops->read, (void *)profile) != 0) { + dbg_time("%s Failed to create QMIThread: %d (%s)", __func__, errno, strerror(errno)); + return 0; + } + + if ((read(qmidevice_control_fd[0], &triger_event, sizeof(triger_event)) != sizeof(triger_event)) + || (triger_event != RIL_INDICATE_DEVICE_CONNECTED)) { + dbg_time("%s Failed to init QMIThread: %d (%s)", __func__, errno, strerror(errno)); + return 0; + } + + if (profile->qmi_ops->init && profile->qmi_ops->init(profile)) { + dbg_time("%s Failed to qmi init: %d (%s)", __func__, errno, strerror(errno)); + return 0; + } + +#ifdef CONFIG_VERSION + requestBaseBandVersion(NULL); +#endif + requestSetEthMode(profile); + if (profile->loopback_state) { + requestSetLoopBackState(profile->loopback_state, profile->replication_factor); + profile->loopback_state = 0; + } +#ifdef CONFIG_SIM + qmierr = requestGetSIMStatus(&SIMStatus); + while (qmierr == QMI_ERR_OP_DEVICE_UNSUPPORTED) { + sleep(1); + qmierr = requestGetSIMStatus(&SIMStatus); + } + if ((SIMStatus == SIM_PIN) && profile->pincode) { + requestEnterSimPin(profile->pincode); + } +#ifdef CONFIG_IMSI_ICCID + if (SIMStatus == SIM_READY) { + requestGetICCID(); + requestGetIMSI(); + } +#endif +#endif +#ifdef CONFIG_APN + if (profile->apn || profile->user || profile->password) { + requestSetProfile(profile); + } + requestGetProfile(profile); +#endif + requestRegistrationState(&PSAttachedState); + + send_signo_to_main(SIG_EVENT_CHECK); + +#ifdef CONFIG_PID_FILE_FORMAT + { + char cmd[255]; + sprintf(cmd, "echo %d > " CONFIG_PID_FILE_FORMAT, getpid(), profile->usbnet_adapter); + system(cmd); + } +#endif + + while (1) + { + struct pollfd pollfds[] = {{signal_control_fd[1], POLLIN, 0}, {qmidevice_control_fd[0], POLLIN, 0}}; + int ne, ret, nevents = sizeof(pollfds)/sizeof(pollfds[0]); + + do { + ret = poll(pollfds, nevents, 15*1000); + } while ((ret < 0) && (errno == EINTR)); + + if (ret == 0) + { + send_signo_to_main(SIG_EVENT_CHECK); + continue; + } + + if (ret <= 0) { + dbg_time("%s poll=%d, errno: %d (%s)", __func__, ret, errno, strerror(errno)); + goto __main_quit; + } + + for (ne = 0; ne < nevents; ne++) { + int fd = pollfds[ne].fd; + short revents = pollfds[ne].revents; + + if (revents & (POLLERR | POLLHUP | POLLNVAL)) { + dbg_time("%s poll err/hup", __func__); + dbg_time("epoll fd = %d, events = 0x%04x", fd, revents); + main_send_event_to_qmidevice(RIL_REQUEST_QUIT); + if (revents & POLLHUP) + goto __main_quit; + } + + if ((revents & POLLIN) == 0) + continue; + + if (fd == signal_control_fd[1]) + { + if (read(fd, &signo, sizeof(signo)) == sizeof(signo)) + { + alarm(0); + switch (signo) + { + case SIG_EVENT_START: + if (PSAttachedState != 1 && profile->loopback_state == 0) + break; + + if (SetupCallAllowTime > clock_msec()) { + alarm((SetupCallAllowTime - clock_msec()+999)/1000); + break; + } + + if (profile->enable_ipv4 && IPv4ConnectionStatus != QWDS_PKT_DATA_CONNECTED) { + qmierr = requestSetupDataCall(profile, IpFamilyV4); + + if ((qmierr > 0) && profile->user && profile->user[0] && profile->password && profile->password[0]) { + int old_auto = profile->auth; + + //may be fail because wrong auth mode, try pap->chap, or chap->pap + profile->auth = (profile->auth == 1) ? 2 : 1; + qmierr = requestSetupDataCall(profile, IpFamilyV4); + + if (qmierr) + profile->auth = old_auto; //still fail, restore old auth moe + } + + if (!qmierr) { + qmierr = requestGetIPAddress(profile, IpFamilyV4); + if (!qmierr) + IPv4ConnectionStatus = QWDS_PKT_DATA_CONNECTED; + } + + } + + if (profile->enable_ipv6 && IPv6ConnectionStatus != QWDS_PKT_DATA_CONNECTED) { + qmierr = requestSetupDataCall(profile, IpFamilyV6); + + if (!qmierr) { + qmierr = requestGetIPAddress(profile, IpFamilyV6); + if (!qmierr) + IPv6ConnectionStatus = QWDS_PKT_DATA_CONNECTED; + } + } + + if ((profile->enable_ipv4 && IPv4ConnectionStatus == QWDS_PKT_DATA_DISCONNECTED) + || (profile->enable_ipv6 && IPv6ConnectionStatus == QWDS_PKT_DATA_DISCONNECTED)) { + const unsigned allow_time[] = {5, 10, 20, 40, 60}; + + if (SetupCallFail < (sizeof(allow_time)/sizeof(unsigned))) + SetupCallAllowTime = allow_time[SetupCallFail]; + else + SetupCallAllowTime = 60; + SetupCallFail++; + dbg_time("try to requestSetupDataCall %ld second later", SetupCallAllowTime); + alarm(SetupCallAllowTime); + SetupCallAllowTime = SetupCallAllowTime*1000 + clock_msec(); +#ifdef CONFIG_EXIT_WHEN_DIAL_FAILED + send_signo_to_main(SIG_EVENT_STOP); +#endif + } + else if (IPv4ConnectionStatus == QWDS_PKT_DATA_CONNECTED || IPv6ConnectionStatus == QWDS_PKT_DATA_CONNECTED) { + SetupCallFail = 0; + SetupCallAllowTime = clock_msec(); + } + break; + + case SIG_EVENT_CHECK: + #ifdef CONFIG_SIGNALINFO + requestGetSignalInfo(); + #endif + if (profile->enable_ipv4) { + requestQueryDataCall(&IPv4ConnectionStatus, IpFamilyV4); + + //local ip is different with remote ip + if (QWDS_PKT_DATA_CONNECTED == IPv4ConnectionStatus && check_ipv4_address(profile) == 0) { + requestDeactivateDefaultPDP(profile, IpFamilyV4); + IPv4ConnectionStatus = QWDS_PKT_DATA_DISCONNECTED; + } + } + else { + IPv4ConnectionStatus = QWDS_PKT_DATA_DISCONNECTED; + } + + if (profile->enable_ipv6) { + requestQueryDataCall(&IPv6ConnectionStatus, IpFamilyV6); + } + else { + IPv6ConnectionStatus = QWDS_PKT_DATA_DISCONNECTED; + } + + if (IPv4ConnectionStatus == QWDS_PKT_DATA_DISCONNECTED && IPv6ConnectionStatus == QWDS_PKT_DATA_DISCONNECTED) { + usbnet_link_change(0, profile); + } + else if (IPv4ConnectionStatus == QWDS_PKT_DATA_CONNECTED || IPv6ConnectionStatus == QWDS_PKT_DATA_CONNECTED) { + int link = 0; + if (IPv4ConnectionStatus == QWDS_PKT_DATA_CONNECTED) + link |= (1<enable_ipv4 && IPv4ConnectionStatus == QWDS_PKT_DATA_DISCONNECTED) + || (profile->enable_ipv6 && IPv6ConnectionStatus == QWDS_PKT_DATA_DISCONNECTED)) { + send_signo_to_main(SIG_EVENT_START); + } + break; + + case SIG_EVENT_STOP: + if (profile->enable_ipv4 && IPv4ConnectionStatus == QWDS_PKT_DATA_CONNECTED) { + requestDeactivateDefaultPDP(profile, IpFamilyV4); + } + if (profile->enable_ipv6 && IPv6ConnectionStatus == QWDS_PKT_DATA_CONNECTED) { + requestDeactivateDefaultPDP(profile, IpFamilyV6); + } + usbnet_link_change(0, profile); + if (profile->qmi_ops->deinit) + profile->qmi_ops->deinit(); + main_send_event_to_qmidevice(RIL_REQUEST_QUIT); + goto __main_quit; + break; + + default: + break; + } + } + } + + if (fd == qmidevice_control_fd[0]) { + if (read(fd, &triger_event, sizeof(triger_event)) == sizeof(triger_event)) { + switch (triger_event) { + case RIL_INDICATE_DEVICE_DISCONNECTED: + usbnet_link_change(0, profile); + if (main_loop) + { + if (pthread_join(gQmiThreadID, NULL)) { + dbg_time("%s Error joining to listener thread (%s)", __func__, strerror(errno)); + } + profile->qmichannel = NULL; + profile->usbnet_adapter = save_usbnet_adapter; + goto __main_loop; + } + goto __main_quit; + break; + + case RIL_UNSOL_RESPONSE_VOICE_NETWORK_STATE_CHANGED: + requestRegistrationState(&PSAttachedState); + if (PSAttachedState == 1) { + if ((profile->enable_ipv4 && IPv4ConnectionStatus == QWDS_PKT_DATA_DISCONNECTED) + || (profile->enable_ipv6 && IPv6ConnectionStatus == QWDS_PKT_DATA_DISCONNECTED)) { + send_signo_to_main(SIG_EVENT_START); + } + } else { + SetupCallAllowTime = clock_msec(); + } + break; + + case RIL_UNSOL_DATA_CALL_LIST_CHANGED: + if (IPv4ConnectionStatus == QWDS_PKT_DATA_CONNECTED || IPv6ConnectionStatus == QWDS_PKT_DATA_CONNECTED) + SetupCallAllowTime = clock_msec() + 1000; //from connect -> disconnect, do not re-dail immediately, wait network stable + send_signo_to_main(SIG_EVENT_CHECK); + break; + + case MODEM_REPORT_RESET_EVENT: + { + unsigned int time_to_wait = 20; + unsigned int time_expired = 0; + dbg_time("main recv MODEM RESET SIGNAL"); + dbg_time("quit QMI thread and wait %ds and try to restart", time_to_wait); + main_send_event_to_qmidevice(RIL_REQUEST_QUIT); + /** NOTICE + * DO NOT CALL usbnet_link_change(0, profile) DIRECTLLY + * for, the modem may go into wrong state(only ttyUSB0 left) and wont go back + */ + usbnet_link_state(0); + /* close FDs, for we want restart. */ + close(signal_control_fd[0]); + close(signal_control_fd[1]); + close(qmidevice_control_fd[0]); + close(qmidevice_control_fd[1]); + while (time_expired++ < time_to_wait) { + sleep(1); + char qmidev[64] = {'\0'}; + snprintf(qmidev, sizeof(qmidev), "/dev/bus/usb/%03d/%03d", profile->busnum, profile->devnum); + if (access(qmidev, F_OK)) { + dbg_time("whoo, fatal error info, qmi device node disappeared!!! cannot continue!\n"); + goto __main_quit; + } + } + dbg_time("main try do restart"); + goto __main_loop; + } + case RIL_UNSOL_LOOPBACK_CONFIG_IND: + { + QMI_WDA_SET_LOOPBACK_CONFIG_IND_MSG SetLoopBackInd; + if (read(fd, &SetLoopBackInd, sizeof(SetLoopBackInd)) == sizeof(SetLoopBackInd)) { + profile->loopback_state = SetLoopBackInd.loopback_state.TLVVaule; + profile->replication_factor = le32_to_cpu(SetLoopBackInd.replication_factor.TLVVaule); + dbg_time("SetLoopBackInd: loopback_state=%d, replication_factor=%u", + profile->loopback_state, profile->replication_factor); + if (profile->loopback_state) + send_signo_to_main(SIG_EVENT_START); + } + } + break; + default: + break; + } + } + } + } + } + +__main_quit: + usbnet_link_change(0, profile); + if (pthread_join(gQmiThreadID, NULL)) { + dbg_time("%s Error joining to listener thread (%s)", __func__, strerror(errno)); + } + close(signal_control_fd[0]); + close(signal_control_fd[1]); + close(qmidevice_control_fd[0]); + close(qmidevice_control_fd[1]); + dbg_time("%s exit", __func__); + +#ifdef CONFIG_PID_FILE_FORMAT + { + char cmd[255]; + sprintf(cmd, "rm " CONFIG_PID_FILE_FORMAT, profile.usbnet_adapter); + system(cmd); + } +#endif + + return 0; +} + +#define has_more_argv() ((opt < argc) && (argv[opt][0] != '-')) +int main(int argc, char *argv[]) +{ + int opt = 1; + char * save_usbnet_adapter = NULL; + const char *usbmon_logfile = NULL; + PROFILE_T profile; + int ret = -1; + + dbg_time("Quectel_QConnectManager_Linux_V1.6.0.19"); + memset(&profile, 0x00, sizeof(profile)); + profile.pdp = CONFIG_DEFAULT_PDP; + + if (!strcmp(argv[argc-1], "&")) + argc--; + + opt = 1; + while (opt < argc) { + if (argv[opt][0] != '-') + return usage(argv[0]); + + switch (argv[opt++][1]) + { + case 's': + profile.apn = profile.user = profile.password = ""; + if (has_more_argv()) + profile.apn = argv[opt++]; + if (has_more_argv()) + profile.user = argv[opt++]; + if (has_more_argv()) + { + profile.password = argv[opt++]; + if (profile.password && profile.password[0]) + profile.auth = 2; //default chap, customers may miss auth + } + if (has_more_argv()) + profile.auth = argv[opt++][0] - '0'; + break; + + case 'm': + if (has_more_argv()) + profile.muxid = argv[opt++][0] - '0'; + break; + + case 'p': + if (has_more_argv()) + profile.pincode = argv[opt++]; + break; + + case 'n': + if (has_more_argv()) + profile.pdp = argv[opt++][0] - '0'; + break; + + case 'f': + if (has_more_argv()) + { + const char * filename = argv[opt++]; + logfilefp = fopen(filename, "a+"); + if (!logfilefp) { + dbg_time("Fail to open %s, errno: %d(%s)", filename, errno, strerror(errno)); + } + } + break; + + case 'i': + if (has_more_argv()) + profile.usbnet_adapter = save_usbnet_adapter = argv[opt++]; + break; + + case 'v': + debug_qmi = 1; + break; + + case 'l': + if (has_more_argv()) { + profile.replication_factor = atoi(argv[opt++]); + if (profile.replication_factor > 0) + profile.loopback_state = 1; + } + else + main_loop = 1; + break; + + case '4': + profile.enable_ipv4 = 1; + break; + + case '6': + profile.enable_ipv6 = 1; + break; + + case 'd': + if (has_more_argv()) { + profile.qmichannel = argv[opt++]; + if (qmidev_is_pciemhi(profile.qmichannel)) + profile.usbnet_adapter = "pcie_mhi0"; + } + break; + + case 'r': + profile.reattach_flag = 1; + break; + + case 'u': + if (has_more_argv()) { + usbmon_logfile = argv[opt++]; + } + break; + + case 'b': + profile.enable_bridge = 1; + break; + + case 'k': + if (has_more_argv()) { + return kill_data_call_pdp(argv[opt++][0] - '0', argv[0]); + } + break; + + default: + return usage(argv[0]); + break; + } + } + + if (profile.enable_ipv4 != 1 && profile.enable_ipv6 != 1) { // default enable IPv4 + profile.enable_ipv4 = 1; + } + + if (!(profile.qmichannel) || !(profile.usbnet_adapter)) { + char qmichannel[32+1] = {'\0'}; + char usbnet_adapter[32+1] = {'\0'}; + + if (profile.usbnet_adapter) + strcpy(usbnet_adapter, profile.usbnet_adapter); + + if (qmidevice_detect(qmichannel, usbnet_adapter, sizeof(qmichannel), &profile.busnum, &profile.devnum)) { + profile.hardware_interface = HARDWARE_USB; + } + else if (mhidevice_detect(qmichannel, usbnet_adapter, &profile)) { + profile.hardware_interface = HARDWARE_PCIE; + } + else { + dbg_time("qmidevice_detect failed"); + goto error; + } + + if (!(profile.qmichannel)) + strset(profile.qmichannel, qmichannel); + if (!(profile.usbnet_adapter)) + strset(profile.usbnet_adapter, usbnet_adapter); + + ql_get_netcard_driver_info(profile.usbnet_adapter); + + if ((profile.hardware_interface == HARDWARE_USB) && usbmon_logfile) + ql_capture_usbmon_log(&profile, usbmon_logfile); + + if (profile.hardware_interface == HARDWARE_USB) { + profile.software_interface = get_driver_type(&profile); + } + } + + ql_qmap_mode_detect(&profile); + + if (profile.software_interface == SOFTWARE_MBIM) { + dbg_time("Modem works in MBIM mode"); + ret = mbim_main(&profile); + } else if (profile.software_interface == SOFTWARE_QMI) { + dbg_time("Modem works in QMI mode"); + ret = qmi_main(&profile); + } + + ql_stop_usbmon_log(&profile); + if (logfilefp) + fclose(logfilefp); + +error: + + return ret; +} diff --git a/root/package/link4all/quectel-CM/src/mbim-cm.c b/root/package/link4all/quectel-CM/src/mbim-cm.c new file mode 100644 index 00000000..f733d47c --- /dev/null +++ b/root/package/link4all/quectel-CM/src/mbim-cm.c @@ -0,0 +1,2351 @@ +/****************************************************************************** + @file mbim-cm.c + @brief MIBIM drivers. + + DESCRIPTION + Connectivity Management Tool for USB network adapter of Quectel wireless cellular modules. + + INITIALIZATION AND SEQUENCING REQUIREMENTS + None. + + --------------------------------------------------------------------------- + Copyright (c) 2016 - 2020 Quectel Wireless Solution, Co., Ltd. All Rights Reserved. + Quectel Wireless Solution Proprietary and Confidential. + --------------------------------------------------------------------------- +******************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +typedef unsigned short sa_family_t; +#include +#include "QMIThread.h" + +//#define QUECTEL_MBIM_PROXY "quectel-mbim-proxy" + +#ifndef htole32 +#if __BYTE_ORDER == __LITTLE_ENDIAN +#define htole16(x) (uint16_t)(x) +#define le16toh(x) (uint16_t)(x) +#define letoh16(x) (uint16_t)(x) +#define htole32(x) (uint32_t)(x) +#define le32toh(x) (uint32_t)(x) +#define letoh32(x) (uint32_t)(x) +#define htole64(x) (uint64_t)(x) +#define le64toh(x) (uint64_t)(x) +#define letoh64(x) (uint64_t)(x) +#else +static __inline uint16_t __bswap16(uint16_t __x) { + return (__x<<8) | (__x>>8); +} + +static __inline uint32_t __bswap32(uint32_t __x) { + return (__x>>24) | (__x>>8&0xff00) | (__x<<8&0xff0000) | (__x<<24); +} + +static __inline uint64_t __bswap64(uint64_t __x) { + return (__bswap32(__x)+0ULL<<32) | (__bswap32(__x>>32)); +} + +#define htole16(x) __bswap16(x) +#define le16toh(x) __bswap16(x) +#define letoh16(x) __bswap16(x) +#define htole32(x) __bswap32(x) +#define le32toh(x) __bswap32(x) +#define letoh32(x) __bswap32(x) +#define htole64(x) __bswap64(x) +#define le64toh(x) __bswap64(x) +#define letoh64(x) __bswap64(x) +#endif +#endif + +#define mbim_debug dbg_time + +#define UUID_BASIC_CONNECT "a289cc33-bcbb-8b4f-b6b0-133ec2aae6df" +//https://docs.microsoft.com/en-us/windows-hardware/drivers/network/mb-5g-data-class-support +#define UUID_BASIC_CONNECT_EXT "3d01dcc5-fef5-4d05-0d3a-bef7058e9aaf" +#define UUID_SMS "533fbeeb-14fe-4467-9f90-33a223e56c3f" +#define UUID_USSD "e550a0c8-5e82-479e-82f7-10abf4c3351f" +#define UUID_PHONEBOOK "4bf38476-1e6a-41db-b1d8-bed289c25bdb" +#define UUID_STK "d8f20131-fcb5-4e17-8602-d6ed3816164c" +#define UUID_AUTH "1d2b5ff7-0aa1-48b2-aa52-50f15767174e" +#define UUID_DSS "c08a26dd-7718-4382-8482-6e0d583c4d0e" +#define uuid_ext_qmux "d1a30bc2-f97a-6e43-bf65-c7e24fb0f0d3" +#define uuid_mshsd "883b7c26-985f-43fa-9804-27d7fb80959c" +#define uuid_qmbe "2d0c12c9-0e6a-495a-915c-8d174fe5d63c" +#define UUID_MSFWID "e9f7dea2-feaf-4009-93ce-90a3694103b6" +#define uuid_atds "5967bdcc-7fd2-49a2-9f5c-b2e70e527db3" +#define uuid_qdu "6427015f-579d-48f5-8c54-f43ed1e76f83" +#define UUID_MS_UICC_LOW_LEVEL "c2f6588e-f037-4bc9-8665-f4d44bd09367" +#define UUID_MS_SARControl "68223D04-9F6C-4E0F-822D-28441FB72340" +#define UUID_VOICEEXTENSIONS "8d8b9eba-37be-449b-8f1e-61cb034a702e" + +#define UUID_MBIMContextTypeInternet "7E5E2A7E-4E6F-7272-736B-656E7E5E2A7E" + +typedef unsigned char UINT8; +typedef unsigned short UINT16; +typedef unsigned int UINT32; +typedef unsigned long long UINT64; + +#define STRINGFY(v) #v +/* The function name will be _ENUM_NAMEStr */ +#define enumstrfunc(_ENUM_NAME, _ENUM_MEMS) \ +static const char *_ENUM_NAME##Str(int _val) { \ + struct { int val;char *name;} _enumstr[] = { _ENUM_MEMS }; \ + int idx; for (idx = 0; idx < (int)(sizeof(_enumstr)/sizeof(_enumstr[0])); idx++) { \ + if (_val == _enumstr[idx].val) return _enumstr[idx].name;} \ + return STRINGFY(_ENUM_NAME##Unknow); \ +} + +#pragma pack(4) +typedef enum { + MBIM_CID_CMD_TYPE_QUERY = 0, + MBIM_CID_CMD_TYPE_SET = 1, +} MBIM_CID_CMD_TYPE_E; + +//Set Query Notification +#define UUID_BASIC_CONNECT_CIDs \ + MBIM_ENUM_HELPER(MBIM_CID_DEVICE_CAPS, 1) \ + MBIM_ENUM_HELPER(MBIM_CID_SUBSCRIBER_READY_STATUS, 2) \ + MBIM_ENUM_HELPER(MBIM_CID_RADIO_STATE, 3) \ + MBIM_ENUM_HELPER(MBIM_CID_PIN, 4) \ + MBIM_ENUM_HELPER(MBIM_CID_PIN_LIS, 5) \ + MBIM_ENUM_HELPER(MBIM_CID_HOME_PROVIDER, 6) \ + MBIM_ENUM_HELPER(MBIM_CID_PREFERRED_PROVIDERS, 7) \ + MBIM_ENUM_HELPER(MBIM_CID_VISIBLE_PROVIDERS, 8) \ + MBIM_ENUM_HELPER(MBIM_CID_REGISTER_STATE, 9) \ + MBIM_ENUM_HELPER(MBIM_CID_PACKET_SERVICE, 10) \ + MBIM_ENUM_HELPER(MBIM_CID_SIGNAL_STATE, 11) \ + MBIM_ENUM_HELPER(MBIM_CID_CONNECT, 12) \ + MBIM_ENUM_HELPER(MBIM_CID_PROVISIONED_CONTEXTS, 13) \ + MBIM_ENUM_HELPER(MBIM_CID_SERVICE_ACTIVATION, 14) \ + MBIM_ENUM_HELPER(MBIM_CID_IP_CONFIGURATION, 15) \ + MBIM_ENUM_HELPER(MBIM_CID_DEVICE_SERVICES, 16) \ + MBIM_ENUM_HELPER(MBIM_CID_DEVICE_SERVICE_SUBSCRIBE_LIST, 19) \ + MBIM_ENUM_HELPER(MBIM_CID_PACKET_STATISTICS, 20) \ + MBIM_ENUM_HELPER(MBIM_CID_NETWORK_IDLE_HINT, 21) \ + MBIM_ENUM_HELPER(MBIM_CID_EMERGENCY_MODE, 22) \ + MBIM_ENUM_HELPER(MBIM_CID_IP_PACKET_FILTERS, 23) \ + MBIM_ENUM_HELPER(MBIM_CID_MULTICARRIER_PROVIDERS, 24) + +#define MBIM_ENUM_HELPER(k, v) k = v, +typedef enum{ + UUID_BASIC_CONNECT_CIDs +} UUID_BASIC_CONNECT_CID_E; +#undef MBIM_ENUM_HELPER +#define MBIM_ENUM_HELPER(k, v) {k, #k}, +enumstrfunc(CID2, UUID_BASIC_CONNECT_CIDs); +#undef MBIM_ENUM_HELPER + +static int mbim_ms_version = 1; + +#define UUID_BASIC_CONNECT_EXT_CIDs \ + MBIM_ENUM_HELPER(MBIM_CID_MS_PROVISIONED_CONTEXT_V2, 1) \ + MBIM_ENUM_HELPER(MBIM_CID_MS_NETWORK_BLACKLIST, 2) \ + MBIM_ENUM_HELPER(MBIM_CID_MS_LTE_ATTACH_CONFIG, 3) \ + MBIM_ENUM_HELPER(MBIM_CID_MS_LTE_ATTACH_STATUS , 4) \ + MBIM_ENUM_HELPER(MBIM_CID_MS_SYS_CAPS , 5) \ + MBIM_ENUM_HELPER(MBIM_CID_MS_DEVICE_CAPS_V2, 6) \ + MBIM_ENUM_HELPER(MBIM_CID_MS_DEVICE_SLOT_MAPPING, 7) \ + MBIM_ENUM_HELPER(MBIM_CID_MS_SLOT_INFO_STATUS, 8) \ + MBIM_ENUM_HELPER(MBIM_CID_MS_PCO, 9) \ + MBIM_ENUM_HELPER(MBIM_CID_MS_DEVICE_RESET, 10) \ + MBIM_ENUM_HELPER(MBIM_CID_MS_BASE_STATIONS_INFO, 11) \ + MBIM_ENUM_HELPER(MBIM_CID_MS_LOCATION_INFO_STATUS, 12) \ + MBIM_ENUM_HELPER(MBIM_CID_NOT_DEFINED, 13) \ + MBIM_ENUM_HELPER(MBIM_CID_MS_PIN_EX, 14) \ + MBIM_ENUM_HELPER(MBIM_CID_MS_VERSION , 15) + +#define MBIM_ENUM_HELPER(k, v) k = v, +typedef enum{ + UUID_BASIC_CONNECT_EXT_CIDs +} UUID_BASIC_CONNECT_EXT_CID_E; +#undef MBIM_ENUM_HELPER +#define MBIM_ENUM_HELPER(k, v) {k, #k}, +enumstrfunc(MS_CID2, UUID_BASIC_CONNECT_EXT_CIDs); +#undef MBIM_ENUM_HELPER + +typedef enum { + MBIM_CID_SMS_CONFIGURATION = 1, // Y Y Y + MBIM_CID_SMS_READ = 2, // N Y Y + MBIM_CID_SMS_SEND = 3, // Y N N + MBIM_CID_SMS_DELETE = 4, // Y N N + MBIM_CID_SMS_MESSAGE_STORE_STATUS = 5, // N Y Y +} UUID_SMS_CID_E; + +typedef enum { + MBIM_CID_DSS_CONNECT = 1, // Y N N +} UUID_DSS_CID_E; + +#define MBIM_MSGS \ + MBIM_ENUM_HELPER(MBIM_OPEN_MSG, 1) \ + MBIM_ENUM_HELPER(MBIM_CLOSE_MSG, 2) \ + MBIM_ENUM_HELPER(MBIM_COMMAND_MSG, 3) \ + MBIM_ENUM_HELPER(MBIM_HOST_ERROR_MSG, 4) \ + \ + MBIM_ENUM_HELPER(MBIM_OPEN_DONE, 0x80000001) \ + MBIM_ENUM_HELPER(MBIM_CLOSE_DONE, 0x80000002) \ + MBIM_ENUM_HELPER(MBIM_COMMAND_DONE, 0x80000003) \ + MBIM_ENUM_HELPER(MBIM_FUNCTION_ERROR_MSG, 0x80000004) \ + MBIM_ENUM_HELPER(MBIM_INDICATE_STATUS_MSG, 0x80000007) + +#define MBIM_ENUM_HELPER(k, v) k = v, +typedef enum{ + MBIM_MSGS +} MBIM_MSG_Type_E; +#undef MBIM_ENUM_HELPER +#define MBIM_ENUM_HELPER(k, v) {k, #k}, +enumstrfunc(MBIMMSGType, MBIM_MSGS); +#undef MBIM_ENUM_HELPER + +typedef enum { + MBIM_ERROR_TIMEOUT_FRAGMENT = 1, + MBIM_ERROR_FRAGMENT_OUT_OF_SEQUENCE = 2, + MBIM_ERROR_LENGTH_MISMATCH = 3, + MBIM_ERROR_DUPLICATED_TID = 4, + MBIM_ERROR_NOT_OPENED = 5, + MBIM_ERROR_UNKNOWN = 6, + MBIM_ERROR_CANCEL = 7, + MBIM_ERROR_MAX_TRANSFER = 8, +} MBIM_ERROR_E; + +typedef enum { + MBIM_STATUS_SUCCESS = 0, + MBIM_STATUS_BUSY = 1, + MBIM_STATUS_FAILURE = 2, + MBIM_STATUS_SIM_NOT_INSERTED = 3, + MBIM_STATUS_BAD_SIM = 4, + MBIM_STATUS_PIN_REQUIRED = 5, + MBIM_STATUS_PIN_DISABLED = 6, + MBIM_STATUS_NOT_REGISTERED = 7, + MBIM_STATUS_PROVIDERS_NOT_FOUND = 8, + MBIM_STATUS_NO_DEVICE_SUPPORT = 9, + MBIM_STATUS_PROVIDER_NOT_VISIBLE = 10, + MBIM_STATUS_DATA_CLASS_NOT_AVAILABL = 11, + MBIM_STATUS_PACKET_SERVICE_DETACHED = 12, +} MBIM_STATUS_CODES_E; + +typedef enum { + MBIMPacketServiceActionAttach = 0, + MBIMPacketServiceActionDetach = 1, +} MBIM_PACKET_SERVICE_ACTION_E; + +typedef enum { + MBIMPacketServiceStateUnknown = 0, + MBIMPacketServiceStateAttaching = 1, + MBIMPacketServiceStateAttached = 2, + MBIMPacketServiceStateDetaching = 3, + MBIMPacketServiceStateDetached = 4, +} MBIM_PACKET_SERVICE_STATE_E; + +static const char *MBIMPacketServiceStateStr(int _val) { + struct { int val;char *name;} _enumstr[] = { + {MBIMPacketServiceStateUnknown, "Unknown"}, + {MBIMPacketServiceStateAttaching, "Attaching"}, + {MBIMPacketServiceStateAttached, "Attached"}, + {MBIMPacketServiceStateDetaching, "Detaching"}, + {MBIMPacketServiceStateDetached, "Detached"}, + }; + int idx; + + for (idx = 0; idx < (int)(sizeof(_enumstr)/sizeof(_enumstr[0])); idx++) { + if (_val == _enumstr[idx].val) + return _enumstr[idx].name; + } + + return "Undefined"; +}; + +typedef enum { + MBIMDataClassNone = 0x0, + MBIMDataClassGPRS = 0x1, + MBIMDataClassEDGE = 0x2, + MBIMDataClassUMTS = 0x4, + MBIMDataClassHSDPA = 0x8, + MBIMDataClassHSUPA = 0x10, + MBIMDataClassLTE = 0x20, + MBIMDataClass5G_NSA = 0x40, + MBIMDataClass5G_SA = 0x80, + MBIMDataClass1XRTT = 0x10000, + MBIMDataClass1XEVDO = 0x20000, + MBIMDataClass1XEVDORevA = 0x40000, + MBIMDataClass1XEVDV = 0x80000, + MBIMDataClass3XRTT = 0x100000, + MBIMDataClass1XEVDORevB = 0x200000, + MBIMDataClassUMB = 0x400000, + MBIMDataClassCustom = 0x80000000, +} MBIM_DATA_CLASS_E; + +static const char *MBIMDataClassStr(int _val) { + struct { int val;char *name;} _enumstr[] = { + {MBIMDataClassNone, "None"}, + {MBIMDataClassGPRS, "GPRS"}, + {MBIMDataClassEDGE, "EDGE"}, + {MBIMDataClassUMTS, "UMTS"}, + {MBIMDataClassHSDPA, "HSDPA"}, + {MBIMDataClassHSUPA, "HSUPA"}, + {MBIMDataClassLTE, "LTE"}, + {MBIMDataClass5G_NSA, "5G_NSA"}, + {MBIMDataClass5G_SA, "5G_SA"}, + {MBIMDataClass1XRTT, "1XRTT"}, + {MBIMDataClass1XEVDO, "1XEVDO"}, + {MBIMDataClass1XEVDORevA, "1XEVDORevA"}, + {MBIMDataClass1XEVDV, "1XEVDV"}, + {MBIMDataClass3XRTT, "3XRTT"}, + {MBIMDataClass1XEVDORevB, "1XEVDORevB"}, + {MBIMDataClassUMB, "UMB"}, + {MBIMDataClassCustom, "Custom"}, + }; + int idx; + + for (idx = 0; idx < (int)(sizeof(_enumstr)/sizeof(_enumstr[0])); idx++) { + if (_val == _enumstr[idx].val) + return _enumstr[idx].name; + } + + return "Unknow"; +}; + +typedef struct { + UINT32 NwError; + UINT32 PacketServiceState; //MBIM_PACKET_SERVICE_STATE_E + UINT32 HighestAvailableDataClass; //MBIM_DATA_CLASS_E + UINT64 UplinkSpeed; + UINT64 DownlinkSpeed; +} MBIM_PACKET_SERVICE_INFO_T; + +typedef struct { + UINT32 NwError; + UINT32 PacketServiceState; //MBIM_PACKET_SERVICE_STATE_E + UINT32 CurrentDataClass; //MBIM_DATA_CLASS_E + UINT64 UplinkSpeed; + UINT64 DownlinkSpeed; + UINT32 FrequencyRange; +} MBIM_PACKET_SERVICE_INFO_V2_T; + +typedef enum { + MBIMSubscriberReadyStateNotInitialized = 0, + MBIMSubscriberReadyStateInitialized = 1, + MBIMSubscriberReadyStateSimNotInserted = 2, + MBIMSubscriberReadyStateBadSim = 3, + MBIMSubscriberReadyStateFailure = 4, + MBIMSubscriberReadyStateNotActivated = 5, + MBIMSubscriberReadyStateDeviceLocked = 6, +}MBIM_SUBSCRIBER_READY_STATE_E; + +static const char *MBIMSubscriberReadyStateStr(int _val) { + struct { int val;char *name;} _enumstr[] = { + {MBIMSubscriberReadyStateNotInitialized, "NotInitialized"}, + {MBIMSubscriberReadyStateInitialized, "Initialized"}, + {MBIMSubscriberReadyStateSimNotInserted, "NotInserted"}, + {MBIMSubscriberReadyStateBadSim, "BadSim"}, + {MBIMSubscriberReadyStateFailure, "Failure"}, + {MBIMSubscriberReadyStateNotActivated, "NotActivated"}, + {MBIMSubscriberReadyStateDeviceLocked, "DeviceLocked"}, + }; + int idx; + + for (idx = 0; idx < (int)(sizeof(_enumstr)/sizeof(_enumstr[0])); idx++) { + if (_val == _enumstr[idx].val) + return _enumstr[idx].name; + } + + return "Undefined"; +}; + +typedef struct { + UINT32 DeviceType; //MBIM_DEVICE_TYPE + UINT32 CellularClass; //MBIM_CELLULAR_CLASS + UINT32 VoiceClass; //MBIM_VOICE_CLASS + UINT32 SimClass; //MBIM_SIM_CLASS + UINT32 DataClass; //MBIM_DATA_CLASS + UINT32 SmsCaps; //MBIM_SMS_CAPS + UINT32 ControlCaps; //MBIM_CTRL_CAPS + UINT32 MaxSessions; + UINT32 CustomDataClassOffset; + UINT32 CustomDataClassSize; + UINT32 DeviceIdOffset; + UINT32 DeviceIdSize; + UINT32 FirmwareInfoOffset; + UINT32 FirmwareInfoSize; + UINT32 HardwareInfoOffset; + UINT32 HardwareInfoSize; + UINT8 DataBuffer[0]; //DeviceId FirmwareInfo HardwareInfo +} MBIM_DEVICE_CAPS_INFO_T; + +typedef enum { + MBIMRadioOff = 0, + MBIMRadioOn = 1, +} MBIM_RADIO_SWITCH_STATE_E; + +typedef struct { + MBIM_RADIO_SWITCH_STATE_E RadioState; +} MBIM_SET_RADIO_STATE_T; + +typedef struct { + MBIM_RADIO_SWITCH_STATE_E HwRadioState; + MBIM_RADIO_SWITCH_STATE_E SwRadioState; +} MBIM_RADIO_STATE_INFO_T; + +typedef enum { + MBIMReadyInfoFlagsNone, + MBIMReadyInfoFlagsProtectUniqueID, +}MBIM_UNIQUE_ID_FLAGS; + +typedef struct { + UINT32 ReadyState; + UINT32 SubscriberIdOffset; + UINT32 SubscriberIdSize; + UINT32 SimIccIdOffset; + UINT32 SimIccIdSize; + UINT32 ReadyInfo; + UINT32 ElementCount; + UINT8 *TelephoneNumbersRefList; + UINT8 *DataBuffer; +} MBIM_SUBSCRIBER_READY_STATUS_T; + +typedef enum { + MBIMRegisterActionAutomatic, + MBIMRegisterActionManual, +}MBIM_REGISTER_ACTION_E; + +typedef enum { + MBIMRegisterStateUnknown = 0, + MBIMRegisterStateDeregistered = 1, + MBIMRegisterStateSearching = 2, + MBIMRegisterStateHome = 3, + MBIMRegisterStateRoaming = 4, + MBIMRegisterStatePartner = 5, + MBIMRegisterStateDenied = 6, +}MBIM_REGISTER_STATE_E; + +typedef enum { + MBIMRegisterModeUnknown = 0, + MBIMRegisterModeAutomatic = 1, + MBIMRegisterModeManual = 2, +}MBIM_REGISTER_MODE_E; + +static const char *MBIMRegisterStateStr(int _val) { + struct { int val;char *name;} _enumstr[] ={ + {MBIMRegisterStateUnknown, "Unknown"}, + {MBIMRegisterStateDeregistered, "Deregistered"}, + {MBIMRegisterStateSearching, "Searching"}, + {MBIMRegisterStateHome, "Home"}, + {MBIMRegisterStateRoaming, "Roaming"}, + {MBIMRegisterStatePartner, "Partner"}, + {MBIMRegisterStateDenied, "Denied"}, + }; + int idx; + + for (idx = 0; idx < (int)(sizeof(_enumstr)/sizeof(_enumstr[0])); idx++) { + if (_val == _enumstr[idx].val) + return _enumstr[idx].name; + } + + return "Undefined"; +}; + +static const char *MBIMRegisterModeStr(int _val) { + struct { int val;char *name;} _enumstr[] = { + {MBIMRegisterModeUnknown, "Unknown"}, + {MBIMRegisterModeAutomatic, "Automatic"}, + {MBIMRegisterModeManual, "Manual"}, + }; + int idx; + + for (idx = 0; (int)(sizeof(_enumstr)/sizeof(_enumstr[0])); idx++) { + if (_val == _enumstr[idx].val) + return _enumstr[idx].name; + } + + return "Undefined"; +}; + +typedef enum { + MBIM_REGISTRATION_NONE, + MBIM_REGISTRATION_MANUAL_SELECTION_NOT_AVAILABLE, + MBIM_REGISTRATION_PACKET_SERVICE_AUTOMATIC_ATTACH, +}MBIM_REGISTRATION_FLAGS_E; + +typedef struct { + UINT32 NwError; + UINT32 RegisterState; //MBIM_REGISTER_STATE_E + UINT32 RegisterMode; + UINT32 AvailableDataClasses; + UINT32 CurrentCellularClass; + UINT32 ProviderIdOffset; + UINT32 ProviderIdSize; + UINT32 ProviderNameOffset; + UINT32 ProviderNameSize; + UINT32 RoamingTextOffset; + UINT32 RoamingTextSize; + UINT32 RegistrationFlag; + UINT8 *DataBuffer; +} MBIM_REGISTRATION_STATE_INFO_T; + +typedef struct { + UINT32 NwError; + UINT32 RegisterState; //MBIM_REGISTER_STATE_E + UINT32 RegisterMode; + UINT32 AvailableDataClasses; + UINT32 CurrentCellularClass; + UINT32 ProviderIdOffset; + UINT32 ProviderIdSize; + UINT32 ProviderNameOffset; + UINT32 ProviderNameSize; + UINT32 RoamingTextOffset; + UINT32 RoamingTextSize; + UINT32 RegistrationFlag; + UINT32 PreferredDataClass; + UINT8 *DataBuffer; +} MBIM_REGISTRATION_STATE_INFO_V2_T; + +typedef struct { + UINT32 MessageType; //Specifies the MBIM message type. + UINT32 MessageLength; //Specifies the total length of this MBIM message in bytes. + /* Specifies the MBIM message id value. This value is used to match host sent messages with function responses. + This value must be unique among all outstanding transactions. + For notifications, the TransactionId must be set to 0 by the function */ + UINT32 TransactionId; +} MBIM_MESSAGE_HEADER; + +typedef struct { + UINT32 TotalFragments; //this field indicates how many fragments there are intotal. + UINT32 CurrentFragment; //This field indicates which fragment this message is. Values are 0 to TotalFragments?\1 +} MBIM_FRAGMENT_HEADER; + +typedef struct { + MBIM_MESSAGE_HEADER MessageHeader; + UINT32 MaxControlTransfer; +} MBIM_OPEN_MSG_T; + +typedef struct { + MBIM_MESSAGE_HEADER MessageHeader; + UINT32 Status; +} MBIM_OPEN_DONE_T; + +typedef struct { + MBIM_MESSAGE_HEADER MessageHeader; +} MBIM_CLOSE_MSG_T; + +typedef struct { + MBIM_MESSAGE_HEADER MessageHeader; + UINT32 Status; +} MBIM_CLOSE_DONE_T; + +typedef struct { + UINT8 uuid[16]; +} UUID_T; + +typedef struct { + MBIM_MESSAGE_HEADER MessageHeader; + MBIM_FRAGMENT_HEADER FragmentHeader; + UUID_T DeviceServiceId; //A 16 byte UUID that identifies the device service the following CID value applies. + UINT32 CID; //Specifies the CID that identifies the parameter being queried for + UINT32 CommandType; //0 for a query operation, 1 for a Set operation + UINT32 InformationBufferLength; //Size of the Total InformationBuffer, may be larger than current message if fragmented. + UINT8 InformationBuffer[0]; //Data supplied to device specific to the CID +} MBIM_COMMAND_MSG_T; + +typedef struct { + MBIM_MESSAGE_HEADER MessageHeader; + MBIM_FRAGMENT_HEADER FragmentHeader; + UUID_T DeviceServiceId; //A 16 byte UUID that identifies the device service the following CID value applies. + UINT32 CID; //Specifies the CID that identifies the parameter being queried for + UINT32 Status; + UINT32 InformationBufferLength; //Size of the Total InformationBuffer, may be larger than current message if fragmented. + UINT8 InformationBuffer[0]; //Data supplied to device specific to the CID +} MBIM_COMMAND_DONE_T; + +typedef struct { + MBIM_MESSAGE_HEADER MessageHeader; + UINT32 ErrorStatusCode; +} MBIM_HOST_ERROR_MSG_T; + +typedef struct { + MBIM_MESSAGE_HEADER MessageHeader; + UINT32 ErrorStatusCode; +} MBIM_FUNCTION_ERROR_MSG_T; + +typedef struct { + MBIM_MESSAGE_HEADER MessageHeader; + MBIM_FRAGMENT_HEADER FragmentHeader; + UUID_T DeviceServiceId; //A 16 byte UUID that identifies the device service the following CID value applies. + UINT32 CID; //Specifies the CID that identifies the parameter being queried for + UINT32 InformationBufferLength; //Size of the Total InformationBuffer, may be larger than current message if fragmented. + UINT8 InformationBuffer[0]; //Data supplied to device specific to the CID +} MBIM_INDICATE_STATUS_MSG_T; + +typedef struct { + UINT32 offset; + UINT32 size; +} OL_PAIR_LIST; + +typedef struct { + UUID_T DeviceServiceId; + UINT32 DssPayload; + UINT32 MaxDssInstances; + UINT32 CidCount; + UINT32 CidList[]; +} MBIM_DEVICE_SERVICE_ELEMENT_T; + +typedef struct { + UINT32 DeviceServicesCount; + UINT32 MaxDssSessions; + OL_PAIR_LIST DeviceServicesRefList[]; +} MBIM_DEVICE_SERVICES_INFO_T; + +typedef enum { + MBIMActivationCommandDeactivate = 0, + MBIMActivationCommandActivate = 1, +} MBIM_ACTIVATION_COMMAND_E; + +typedef enum { + MBIMCompressionNone = 0, + MBIMCompressionEnable = 1, +} MBIM_COMPRESSION_E; + +typedef enum { + MBIMAuthProtocolNone = 0, + MBIMAuthProtocolPap = 1, + MBIMAuthProtocolChap = 2, + MBIMAuthProtocolMsChapV2 = 3, +} MBIM_AUTH_PROTOCOL_E; + +#define MBIMContextIPTypes \ + MBIM_ENUM_HELPER(MBIMContextIPTypeDefault, 0) \ + MBIM_ENUM_HELPER(MBIMContextIPTypeIPv4, 1) \ + MBIM_ENUM_HELPER(MBIMContextIPTypeIPv6, 2) \ + MBIM_ENUM_HELPER(MBIMContextIPTypeIPv4v6, 3) \ + MBIM_ENUM_HELPER(MBIMContextIPTypeIPv4AndIPv6, 4) + +#define MBIM_ENUM_HELPER(k, v) k = v, +typedef enum { + MBIMContextIPTypes +} MBIM_CONTEXT_IP_TYPE_E; +#undef MBIM_ENUM_HELPER +#define MBIM_ENUM_HELPER(k, v) {k, #k}, +enumstrfunc(MBIMContextIPType, MBIMContextIPTypes); +#undef MBIM_ENUM_HELPER + +typedef enum { + MBIMActivationStateUnknown = 0, + MBIMActivationStateActivated = 1, + MBIMActivationStateActivating = 2, + MBIMActivationStateDeactivated = 3, + MBIMActivationStateDeactivating = 4, +} MBIM_ACTIVATION_STATE_E; + +typedef enum { + MBIMVoiceCallStateNone = 0, + MBIMVoiceCallStateInProgress = 1, + MBIMVoiceCallStateHangUp = 2, +} MBIM_VOICECALL_STATE_E; + +static const char *MBIMActivationStateStr(int _val) { + struct { int val;char *name;} _enumstr[] = { + {MBIMActivationStateUnknown, "Unknown"}, + {MBIMActivationStateActivated, "Activated"}, + {MBIMActivationStateActivating, "Activating"}, + {MBIMActivationStateDeactivated, "Deactivated"}, + {MBIMActivationStateDeactivating, "Deactivating"}, + }; + int idx; + + for (idx = 0; (int)(sizeof(_enumstr)/sizeof(_enumstr[0])); idx++) { + if (_val == _enumstr[idx].val) + return _enumstr[idx].name; + } + + return "Undefined"; +}; + +static const char *MBIMVoiceCallStateStr(int _val) { + struct { int val;char *name;} _enumstr[] = { + {MBIMVoiceCallStateNone, "None"}, + {MBIMVoiceCallStateInProgress, "InProgress"}, + {MBIMVoiceCallStateHangUp, "HangUp"}, + }; + int idx; + + for (idx = 0; (int)(sizeof(_enumstr)/sizeof(_enumstr[0])); idx++) { + if (_val == _enumstr[idx].val) + return _enumstr[idx].name; + } + + return "Undefined"; +}; + +typedef struct { + UINT32 SessionId; + UINT32 ActivationCommand; //MBIM_ACTIVATION_COMMAND_E + UINT32 AccessStringOffset; + UINT32 AccessStringSize; + UINT32 UserNameOffset; + UINT32 UserNameSize; + UINT32 PasswordOffset; + UINT32 PasswordSize; + UINT32 Compression; //MBIM_COMPRESSION_E + UINT32 AuthProtocol; //MBIM_AUTH_PROTOCOL_E + UINT32 IPType; //MBIM_CONTEXT_IP_TYPE_E + UUID_T ContextType; + UINT8 DataBuffer[0]; /* apn, username, password */ +} MBIM_SET_CONNECT_T; + +typedef struct { + UINT32 SessionId; + UINT32 ActivationState; //MBIM_ACTIVATION_STATE_E + UINT32 VoiceCallState; + UINT32 IPType; //MBIM_CONTEXT_IP_TYPE_E + UUID_T ContextType; + UINT32 NwError; +} MBIM_CONNECT_T; + +typedef struct { + UINT32 OnLinkPrefixLength; + UINT8 IPv4Address[4]; +} MBIM_IPV4_ELEMENT_T; + +typedef struct { + UINT32 OnLinkPrefixLength; + UINT8 IPv6Address[16]; +} MBIM_IPV6_ELEMENT_T; + +typedef struct { + UINT32 SessionId; + UINT32 IPv4ConfigurationAvailable; //bit0~Address, bit1~gateway, bit2~DNS, bit3~MTU + UINT32 IPv6ConfigurationAvailable; //bit0~Address, bit1~gateway, bit2~DNS, bit3~MTU + UINT32 IPv4AddressCount; + UINT32 IPv4AddressOffset; + UINT32 IPv6AddressCount; + UINT32 IPv6AddressOffset; + UINT32 IPv4GatewayOffset; + UINT32 IPv6GatewayOffset; + UINT32 IPv4DnsServerCount; + UINT32 IPv4DnsServerOffset; + UINT32 IPv6DnsServerCount; + UINT32 IPv6DnsServerOffset; + UINT32 IPv4Mtu; + UINT32 IPv6Mtu; + UINT8 DataBuffer[]; +} MBIM_IP_CONFIGURATION_INFO_T; + +typedef struct { + UINT32 RSRP; + UINT32 SNR; + UINT32 RSRPThreshold; + UINT32 SNRThreshold; + UINT32 SystemType; +} MBIM_RSRP_SNR_INFO_T; + +typedef struct { + UINT32 Elementcount; + MBIM_RSRP_SNR_INFO_T RsrpSnr[0]; +} MBIM_RSRP_SNR_T; + +typedef struct { + UINT32 Rssi; + UINT32 ErrorRate; + UINT32 SignalStrengthInterval; + UINT32 RssiThreshold; + UINT32 ErrorRateThreshold; +} MBIM_SIGNAL_STATE_INFO_T; + +typedef struct { + UINT32 Rssi; + UINT32 ErrorRate; + UINT32 SignalStrengthInterval; + UINT32 RssiThreshold; + UINT32 ErrorRateThreshold; + UINT32 RsrpSnrOffset; + UINT32 RsrpSnrSize; + UINT8 DataBuffer[]; +} MBIM_SIGNAL_STATE_INFO_V2_T; + +typedef struct { + UINT32 SignalStrengthInterval; + UINT32 RssiThreshold; + UINT32 ErrorRateThreshold; +} MBIM_SET_SIGNAL_STATE_T; + +#pragma pack() + +static pthread_t read_tid = 0; +static int mbim_verbose = 0; +static UINT32 TransactionId = 1; +static unsigned mbim_default_timeout = 30000; +static const char *mbim_netcard = "wwan0"; +static const char *real_netcard = NULL; +static const char *mbim_dev = "/dev/cdc-wdm0"; +static const char *mbim_apn = NULL; +static const char *mbim_user = NULL; +static const char *mbim_passwd = NULL; +static int mbim_iptype = MBIMContextIPTypeDefault; +static int mbim_auth = MBIMAuthProtocolNone; +static int mbim_sessionID = 0; +static int mbim_fd = -1; +static pthread_mutex_t mbim_command_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t mbim_command_cond = PTHREAD_COND_INITIALIZER; +static pthread_mutex_t mbim_state_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t mbim_state_cond = PTHREAD_COND_INITIALIZER; +static MBIM_MESSAGE_HEADER *mbim_pRequest; +static MBIM_MESSAGE_HEADER *mbim_pResponse; +static int bridge_mode; +extern int ql_ifconfig(int argc, char *argv[]); + +static int mysystem(const char *cmd) +{ + int status = system(cmd); + mbim_debug("system(%s)=%d", cmd, status); + + return status; +} + +static void prefix_to_addr(int prefix, char *buf) +{ + UINT32 _addr = 0xffffffff - (1 << (32 - prefix)) + 1; + sprintf(buf, "%d.%d.%d.%d", + ((unsigned char*)&_addr)[3], ((unsigned char*)&_addr)[2], + ((unsigned char*)&_addr)[1], ((unsigned char*)&_addr)[0]); +} + +static int file_get_value(const char *fname) +{ + FILE *fp = NULL; + long hexnum; + char buff[32 + 1] = {'\0'}; + char *endptr = NULL; + + fp = fopen(fname, "r"); + if (!fp) goto error; + if (fgets(buff, sizeof(buff), fp) == NULL) + goto error; + fclose(fp); + + hexnum = strtol(buff, &endptr, 16); + if (errno == ERANGE && (hexnum == LONG_MAX || hexnum == LONG_MIN)) + goto error; + /* if there is no digit in buff */ + if (endptr == buff) + goto error; + return (int)hexnum; + +error: + if (fp) fclose(fp); + return 0; +} + +static int bridge_mode_detect() +{ + char path[128] = {'\0'}; + int val; + + snprintf(path, sizeof(path), "/sys/class/net/%s/mbim/bridge_mode", mbim_netcard); + val = file_get_value(path); + if (val) { + mbim_debug("mbim interface %s works in bridge mode", mbim_netcard); + } + + return !!val; +} + +static void bridge_set_kernel_attr(const unsigned char *ipaddr, const unsigned char *gw, const unsigned char *dns, UINT32 prefix) +{ + char cmd[256] = {'\0'}; + char ipstr[32] = {'\0'}; + char maskstr[32] = {'\0'}; + char gwstr[32] = {'\0'}; + char dnsstr[32] = {'\0'}; + + if (ipaddr) + snprintf(ipstr, sizeof(ipstr), "%d.%d.%d.%d", ipaddr[0], ipaddr[1], ipaddr[2], ipaddr[3]); + if (gw) + snprintf(gwstr, sizeof(gwstr), "%d.%d.%d.%d", gw[0], gw[1], gw[2], gw[3]); + if (dns) + snprintf(dnsstr, sizeof(dnsstr), "%d.%d.%d.%d", dns[0], dns[1], dns[2], dns[3]); + prefix_to_addr(prefix, maskstr); + + /* srv ip mask router dns */ + snprintf(cmd, sizeof(cmd), "echo \"%s %s %s %s\" > /sys/class/net/%s/mbim/bridge_dhcp_info", + ipstr, maskstr, gwstr, dnsstr, mbim_netcard); + mysystem(cmd); +} + +static const char *ipv4Str(const unsigned char *d) { + static char str[] = {"255.225.255.255"}; + + snprintf(str, sizeof(str), "%d.%d.%d.%d", d[0], d[1], d[2], d[3]); + return str; +} + +static const char *ipv6Str(const unsigned char *d) { + static char str[64]; + + snprintf(str, sizeof(str), + "%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x", + d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7], d[8], d[9], d[10], d[11], d[12], d[13], d[14], d[15]); + + return str; +} + +static void mbim_ifconfig(int iptype, const char *ifname, const unsigned char *ipaddr, const unsigned char *gw, + const unsigned char *dns1, const unsigned char *dns2, UINT32 prefix, UINT32 mtu) { + char shell_cmd[256] = {'\0'}; + char *d1, *d2; + + bridge_mode = bridge_mode_detect(); + if (ipaddr) { + snprintf(shell_cmd, sizeof(shell_cmd), "echo 1 > /sys/class/net/%s/mbim/link_state", ifname); + mysystem(shell_cmd); + if (bridge_mode) + bridge_set_kernel_attr(ipaddr, gw, dns1, prefix); + + if(real_netcard) { + snprintf(shell_cmd, sizeof(shell_cmd), "ip link set dev %s up", real_netcard); + mysystem(shell_cmd); + } + snprintf(shell_cmd, sizeof(shell_cmd), "ip link set dev %s up", ifname); + mysystem(shell_cmd); + if (bridge_mode) + return; + + if (iptype == 4) { + d1 = strdup(ipv4Str(ipaddr)); + d2 = strdup(ipv4Str(gw)); + + update_ipv4_address(ifname, d1, d2, prefix); + free(d1); free(d2); + + if (dns1) { + d1 = strdup(ipv4Str(dns1)); + d2 = strdup(ipv4Str(dns2 ? dns2 : dns1)); + update_resolv_conf(4, ifname, d1, d2); + free(d1); free(d2); + } + + if (mtu) { + snprintf(shell_cmd, sizeof(shell_cmd), "ip -%d link set dev %s mtu %u", iptype, ifname, mtu); + mysystem(shell_cmd); + } + } + else if (iptype == 6) { + d1 = strdup(ipv6Str(ipaddr)); + d2 = strdup(ipv6Str(gw)); + + update_ipv6_address(ifname, d1, d2, prefix); + free(d1); free(d2); + + if (dns1) { + d1 = strdup(ipv6Str(dns1)); + d2 = strdup(ipv6Str(dns2 ? dns2 : dns1)); + update_resolv_conf(6, ifname, d1, d2); + free(d1); free(d2); + } + } + } + else { + snprintf(shell_cmd, sizeof(shell_cmd), "echo 0 > /sys/class/net/%s/mbim/link_state", ifname); + mysystem(shell_cmd); + /* remove all address */ + snprintf(shell_cmd, sizeof(shell_cmd), "ip address flush dev %s", ifname); + mysystem(shell_cmd); + snprintf(shell_cmd, sizeof(shell_cmd), "ip link set dev %s down", ifname); + mysystem(shell_cmd); + + if(real_netcard) { + snprintf(shell_cmd, sizeof(shell_cmd), "ip link set dev %s down", real_netcard); + mysystem(shell_cmd); + } + update_resolv_conf(4, ifname, NULL, NULL); + update_resolv_conf(6, ifname, NULL, NULL); + } +} + +static const UUID_T * str2uuid(const char *str) { + static UUID_T uuid; + UINT32 d[16]; + char tmp[16*2+4+1]; + unsigned i = 0; + + while (str[i]) { + tmp[i] = tolower(str[i]); + i++; + } + tmp[i] = '\0'; + + sscanf(tmp, "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", + &d[0], &d[1], &d[2], &d[3], &d[4], &d[5], &d[6], &d[7], + &d[8], &d[9], &d[10], &d[11], &d[12], &d[13], &d[14], &d[15]); + + for (i = 0; i < 16; i++) { + uuid.uuid[i] = d[i]&0xFF; + } + + return &uuid; +} + +#define mbim_alloc( _size) malloc(_size) +#define mbim_free(_mem) do { if (_mem) { free(_mem); _mem = NULL;}} while(0) + +static int mbim_quit = 0; +static int mbim_open_state = 0; +static MBIM_SUBSCRIBER_READY_STATE_E ReadyState = MBIMSubscriberReadyStateNotInitialized; +static MBIM_REGISTER_STATE_E RegisterState = MBIMRegisterStateUnknown; +static MBIM_PACKET_SERVICE_STATE_E PacketServiceState = MBIMPacketServiceStateUnknown; +static MBIM_ACTIVATION_STATE_E ActivationState = MBIMActivationStateUnknown; +static MBIM_SUBSCRIBER_READY_STATE_E oldReadyState = MBIMSubscriberReadyStateNotInitialized; +static MBIM_REGISTER_STATE_E oldRegisterState = MBIMRegisterStateUnknown; +static MBIM_PACKET_SERVICE_STATE_E oldPacketServiceState = MBIMPacketServiceStateUnknown; +static MBIM_ACTIVATION_STATE_E oldActivationState = MBIMActivationStateUnknown; + +static void _notify_state_chage(void) { + pthread_mutex_lock(&mbim_state_mutex); + pthread_cond_signal(&mbim_state_cond); + pthread_mutex_unlock(&mbim_state_mutex); +} + +#define notify_state_chage(_var, _new) do {if (_var != _new) {_var = _new; _notify_state_chage();}} while (0) + +static int wait_state_change(uint32_t seconds) { + int retval = 0; + + pthread_mutex_lock(&mbim_state_mutex); + retval = pthread_cond_timeout_np(&mbim_state_cond, &mbim_state_mutex, seconds*1000); + pthread_mutex_unlock(&mbim_state_mutex); + + if (retval !=0 && retval != ETIMEDOUT) mbim_debug("seconds=%u, retval=%d", seconds, retval); + return retval; +} + +static MBIM_MESSAGE_HEADER *compose_open_command(UINT32 MaxControlTransfer) +{ + MBIM_OPEN_MSG_T *pRequest = (MBIM_OPEN_MSG_T *)mbim_alloc(sizeof(MBIM_OPEN_MSG_T)); + + if(!pRequest) + return NULL; + + pRequest->MessageHeader.MessageType = htole32(MBIM_OPEN_MSG); + pRequest->MessageHeader.MessageLength = htole32(sizeof(MBIM_COMMAND_MSG_T)); + pRequest->MessageHeader.TransactionId = htole32(TransactionId++); + pRequest->MaxControlTransfer = htole32(MaxControlTransfer); + + return &pRequest->MessageHeader; +} + +static MBIM_MESSAGE_HEADER *compose_close_command(void) +{ + MBIM_CLOSE_MSG_T *pRequest = (MBIM_CLOSE_MSG_T *)mbim_alloc(sizeof(MBIM_CLOSE_MSG_T)); + + if(!pRequest) + return NULL; + + pRequest->MessageHeader.MessageType = htole32(MBIM_CLOSE_MSG); + pRequest->MessageHeader.MessageLength = htole32(sizeof(MBIM_CLOSE_MSG_T)); + pRequest->MessageHeader.TransactionId = htole32(TransactionId++); + + return &pRequest->MessageHeader; +} + +static MBIM_MESSAGE_HEADER *compose_basic_connect_command(UINT32 CID, UINT32 CommandType, void *pInformationBuffer, UINT32 InformationBufferLength) +{ + MBIM_COMMAND_MSG_T *pRequest = (MBIM_COMMAND_MSG_T *)mbim_alloc(sizeof(MBIM_COMMAND_MSG_T) + InformationBufferLength); + + if (!pRequest) + return NULL; + + pRequest->MessageHeader.MessageType = htole32(MBIM_COMMAND_MSG); + pRequest->MessageHeader.MessageLength = htole32((sizeof(MBIM_COMMAND_MSG_T) + InformationBufferLength)); + pRequest->MessageHeader.TransactionId = htole32(TransactionId++); + + pRequest->FragmentHeader.TotalFragments = htole32(1); + pRequest->FragmentHeader.CurrentFragment= htole32(0); + + memcpy(pRequest->DeviceServiceId.uuid, str2uuid(UUID_BASIC_CONNECT), 16); + + pRequest->CID = htole32(CID); + pRequest->CommandType = htole32(CommandType); + if (InformationBufferLength && pInformationBuffer) { + pRequest->InformationBufferLength = htole32(InformationBufferLength); + memcpy(pRequest->InformationBuffer, pInformationBuffer, InformationBufferLength); + } else { + pRequest->InformationBufferLength = htole32(0); + } + + return &pRequest->MessageHeader; +} + +static MBIM_MESSAGE_HEADER *compose_basic_connect_ext_command(UINT32 CID, UINT32 CommandType, void *pInformationBuffer, UINT32 InformationBufferLength) +{ + MBIM_COMMAND_MSG_T *pRequest = (MBIM_COMMAND_MSG_T *)compose_basic_connect_command(CID, CommandType, pInformationBuffer, InformationBufferLength); + + if (!pRequest) + return NULL; + + memcpy(pRequest->DeviceServiceId.uuid, str2uuid(UUID_BASIC_CONNECT_EXT), 16); + + return &pRequest->MessageHeader; +} + +static const char * uuid2str(const UUID_T *pUUID) { + static char str[16*2+4+1]; + const UINT8 *d = pUUID->uuid; + + snprintf(str, sizeof(str), "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", + d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7], + d[8], d[9], d[10], d[11], d[12], d[13], d[14], d[15]); + + return str; +} + +static const char *DeviceServiceId2str(const UUID_T *pUUID) { + const char *str = uuid2str(pUUID); + + struct { char *val;char *name;} _enumstr[] = { + {UUID_BASIC_CONNECT, "UUID_BASIC_CONNECT"}, + {UUID_BASIC_CONNECT_EXT, "UUID_BASIC_CONNECT_EXT"}, + {UUID_SMS, "UUID_SMS"}, + {UUID_USSD, "UUID_USSD"}, + {UUID_PHONEBOOK, "UUID_PHONEBOOK"}, + {UUID_STK, "UUID_STK"}, + {UUID_AUTH, "UUID_AUTH"}, + {UUID_DSS, "UUID_DSS"}, + {uuid_ext_qmux, "uuid_ext_qmux"}, + {uuid_mshsd, "uuid_mshsd"}, + {uuid_qmbe, "uuid_qmbe"}, + {UUID_MSFWID, "UUID_MSFWID"}, + {uuid_atds, "uuid_atds"}, + {uuid_qdu, "uuid_qdu"}, + {UUID_MS_UICC_LOW_LEVEL, "UUID_MS_UICC_LOW_LEVEL"}, + {UUID_MS_SARControl, "UUID_MS_SARControl"}, + {UUID_VOICEEXTENSIONS, "UUID_VOICEEXTENSIONS"}, + }; + int idx; + + for (idx = 0; (int)(sizeof(_enumstr)/sizeof(_enumstr[0])); idx++) { + if (!strcasecmp(str, _enumstr[idx].val)) + return _enumstr[idx].name; + } + + return str; +} + +static const char *mbim_get_segment(void *_pMsg, UINT32 offset, UINT32 len) +{ + int idx; + static char buff[256] = {'\0'}; + UINT8 *pMsg = (UINT8*)_pMsg; + + for (idx = 0; idx < (int)(len/2); idx++) + buff[idx] = pMsg[offset+idx*2]; + buff[idx] = '\0'; + return buff; +} + +static void mbim_dump_header(MBIM_MESSAGE_HEADER *pMsg, const char *direction) { + mbim_debug("%s Header:", direction); + mbim_debug("%s MessageLength = %u", direction, le32toh(pMsg->MessageLength)); + mbim_debug("%s MessageType = %s (0x%08x)", direction, MBIMMSGTypeStr(le32toh(pMsg->MessageType)), le32toh(pMsg->MessageType)); + mbim_debug("%s TransactionId = %u", direction, le32toh(pMsg->TransactionId)); + mbim_debug("%s Contents:", direction); +} + +static void mbim_dump_command_msg(MBIM_COMMAND_MSG_T *pCmdMsg, const char *direction) { + mbim_debug("%s DeviceServiceId = %s (%s)", direction, DeviceServiceId2str(&pCmdMsg->DeviceServiceId), uuid2str(&pCmdMsg->DeviceServiceId)); + mbim_debug("%s CID = %s (%u)", direction, CID2Str(le32toh(pCmdMsg->CID)), le32toh(pCmdMsg->CID)); + mbim_debug("%s CommandType = %s (%u)", direction, le32toh(pCmdMsg->CommandType) ? "set" : "query", le32toh(pCmdMsg->CommandType)); + mbim_debug("%s InformationBufferLength = %u", direction, le32toh(pCmdMsg->InformationBufferLength)); +} + +static void mbim_dump_command_done(MBIM_COMMAND_DONE_T *pCmdDone, const char *direction) { + mbim_debug("%s DeviceServiceId = %s (%s)", direction, DeviceServiceId2str(&pCmdDone->DeviceServiceId), uuid2str(&pCmdDone->DeviceServiceId)); + mbim_debug("%s CID = %s (%u)", direction, CID2Str(le32toh(pCmdDone->CID)), le32toh(pCmdDone->CID)); + mbim_debug("%s Status = %u", direction, le32toh(pCmdDone->Status)); + mbim_debug("%s InformationBufferLength = %u", direction, le32toh(pCmdDone->InformationBufferLength)); +} + +static void mbim_dump_indicate_msg(MBIM_INDICATE_STATUS_MSG_T *pIndMsg, const char *direction) { + mbim_debug("%s DeviceServiceId = %s (%s)", direction, DeviceServiceId2str(&pIndMsg->DeviceServiceId), uuid2str(&pIndMsg->DeviceServiceId)); + if (!memcmp(pIndMsg->DeviceServiceId.uuid, str2uuid(UUID_BASIC_CONNECT_EXT), 16)) + mbim_debug("%s CID = %s (%u)", direction, MS_CID2Str(le32toh(pIndMsg->CID)), le32toh(pIndMsg->CID)); + else + mbim_debug("%s CID = %s (%u)", direction, CID2Str(le32toh(pIndMsg->CID)), le32toh(pIndMsg->CID)); + mbim_debug("%s InformationBufferLength = %u", direction, le32toh(pIndMsg->InformationBufferLength)); +} + +static void mbim_dump_connect(MBIM_CONNECT_T *pInfo, const char *direction) { + mbim_debug("%s SessionId = %u", direction, le32toh(pInfo->SessionId)); + mbim_debug("%s ActivationState = %s (%u)", direction, MBIMActivationStateStr(le32toh(pInfo->ActivationState)), le32toh(pInfo->ActivationState)); + mbim_debug("%s IPType = %s", direction, MBIMContextIPTypeStr(le32toh(pInfo->IPType))); + mbim_debug("%s VoiceCallState = %s", direction, MBIMVoiceCallStateStr(le32toh(pInfo->VoiceCallState))); + mbim_debug("%s ContextType = %s", direction, uuid2str(&pInfo->ContextType)); + mbim_debug("%s NwError = %u", direction, le32toh(pInfo->NwError)); +} + +static void mbim_dump_signal_state(MBIM_SIGNAL_STATE_INFO_T *pInfo, const char *direction) +{ + mbim_debug("%s Rssi = %u", direction, le32toh(pInfo->Rssi)); + mbim_debug("%s ErrorRate = %u", direction, le32toh(pInfo->ErrorRate)); + mbim_debug("%s SignalStrengthInterval = %u", direction, le32toh(pInfo->SignalStrengthInterval)); + mbim_debug("%s RssiThreshold = %u", direction, le32toh(pInfo->RssiThreshold)); + mbim_debug("%s ErrorRateThreshold = %u", direction, le32toh(pInfo->ErrorRateThreshold)); +} + +static void mbim_dump_packet_service(MBIM_PACKET_SERVICE_INFO_T *pInfo, const char *direction) +{ + mbim_debug("%s NwError = %u", direction, le32toh(pInfo->NwError)); + mbim_debug("%s PacketServiceState = %s", direction, MBIMPacketServiceStateStr(le32toh(pInfo->PacketServiceState))); + mbim_debug("%s HighestAvailableDataClass = %s", direction, MBIMDataClassStr(le32toh(pInfo->HighestAvailableDataClass))); + mbim_debug("%s UplinkSpeed = %ld", direction, (long)le64toh(pInfo->UplinkSpeed)); + mbim_debug("%s DownlinkSpeed = %ld", direction, (long)le64toh(pInfo->DownlinkSpeed)); +} + +static void mbim_dump_subscriber_status(MBIM_SUBSCRIBER_READY_STATUS_T *pInfo, const char *direction) +{ + mbim_debug("%s ReadyState = %s", direction, MBIMSubscriberReadyStateStr(le32toh(pInfo->ReadyState))); + mbim_debug("%s SIMICCID = %s", direction, mbim_get_segment(pInfo, le32toh(pInfo->SimIccIdOffset), le32toh(pInfo->SimIccIdSize))); + mbim_debug("%s SubscriberID = %s", direction, mbim_get_segment(pInfo, le32toh(pInfo->SubscriberIdOffset), le32toh(pInfo->SubscriberIdSize))); + /* maybe more than one number */ + uint32_t idx; + for (idx = 0; idx < le32toh(pInfo->ElementCount); idx++) { + UINT32 offset = ((UINT32*)((UINT8*)pInfo+offsetof(MBIM_SUBSCRIBER_READY_STATUS_T, TelephoneNumbersRefList)))[0]; + UINT32 length = ((UINT32*)((UINT8*)pInfo+offsetof(MBIM_SUBSCRIBER_READY_STATUS_T, TelephoneNumbersRefList)))[1]; + mbim_debug("%s Number = %s", direction, mbim_get_segment(pInfo, le32toh(offset), le32toh(length))); + } +} + +static void mbim_dump_regiester_status(MBIM_REGISTRATION_STATE_INFO_T *pInfo, const char *direction) +{ + mbim_debug("%s NwError = %u", direction, le32toh(pInfo->NwError)); + mbim_debug("%s RegisterState = %s", direction, MBIMRegisterStateStr(le32toh(pInfo->RegisterState))); + mbim_debug("%s RegisterMode = %s", direction, MBIMRegisterModeStr(le32toh(pInfo->RegisterMode))); +} + +static void mbim_dump_ipconfig(MBIM_IP_CONFIGURATION_INFO_T *pInfo, const char *direction) +{ + UINT8 prefix = 0, *ipv4=NULL, *ipv6=NULL, *gw=NULL, *dns1=NULL, *dns2=NULL; + + mbim_debug("%s SessionId = %u", direction, le32toh(pInfo->SessionId)); + mbim_debug("%s IPv4ConfigurationAvailable = 0x%x", direction, le32toh(pInfo->IPv4ConfigurationAvailable)); + mbim_debug("%s IPv6ConfigurationAvailable = 0x%x", direction, le32toh(pInfo->IPv6ConfigurationAvailable)); + mbim_debug("%s IPv4AddressCount = 0x%x", direction, le32toh(pInfo->IPv4AddressCount)); + mbim_debug("%s IPv4AddressOffset = 0x%x", direction, le32toh(pInfo->IPv4AddressOffset)); + mbim_debug("%s IPv6AddressCount = 0x%x", direction, le32toh(pInfo->IPv6AddressCount)); + mbim_debug("%s IPv6AddressOffset = 0x%x", direction, le32toh(pInfo->IPv6AddressOffset)); + + /* IPv4 */ + if (le32toh(pInfo->IPv4ConfigurationAvailable)&0x1) { + MBIM_IPV4_ELEMENT_T *pAddress = (MBIM_IPV4_ELEMENT_T *)(&pInfo->DataBuffer[le32toh(pInfo->IPv4AddressOffset)-sizeof(MBIM_IP_CONFIGURATION_INFO_T)]); + prefix = le32toh(pAddress->OnLinkPrefixLength); + ipv4 = pAddress->IPv4Address; + mbim_debug("%s IPv4 = %u.%u.%u.%u/%u", direction, ipv4[0], ipv4[1], ipv4[2], ipv4[3], prefix); + } + if (le32toh(pInfo->IPv4ConfigurationAvailable)&0x2) { + gw = (UINT8 *)(&pInfo->DataBuffer[le32toh(pInfo->IPv4GatewayOffset)-sizeof(MBIM_IP_CONFIGURATION_INFO_T)]); + mbim_debug("%s gw = %u.%u.%u.%u", direction, gw[0], gw[1], gw[2], gw[3]); + } + if (le32toh(pInfo->IPv4ConfigurationAvailable)&0x3) { + dns1 = (UINT8 *)(&pInfo->DataBuffer[le32toh(pInfo->IPv4DnsServerOffset) -sizeof(MBIM_IP_CONFIGURATION_INFO_T)]); + mbim_debug("%s dns1 = %u.%u.%u.%u", direction, dns1[0], dns1[1], dns1[2], dns1[3]); + if (le32toh(pInfo->IPv4DnsServerCount) == 2) { + dns2 = dns1 + 4; + mbim_debug("%s dns2 = %u.%u.%u.%u", direction, dns2[0], dns2[1], dns2[2], dns2[3]); + } + } + if (le32toh(pInfo->IPv4Mtu)) mbim_debug("%s ipv4 mtu = %u", direction, le32toh(pInfo->IPv4Mtu)); + + /* IPv6 */ + if (le32toh(pInfo->IPv6ConfigurationAvailable)&0x1) { + MBIM_IPV6_ELEMENT_T *pAddress = (MBIM_IPV6_ELEMENT_T *)(&pInfo->DataBuffer[le32toh(pInfo->IPv6AddressOffset)-sizeof(MBIM_IP_CONFIGURATION_INFO_T)]); + prefix = le32toh(pAddress->OnLinkPrefixLength); + ipv6 = pAddress->IPv6Address; + mbim_debug("%s IPv6 = %02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x/%d", \ + direction, ipv6[0], ipv6[1], ipv6[2], ipv6[3], ipv6[4], ipv6[5], ipv6[6], ipv6[7], \ + ipv6[8], ipv6[9], ipv6[10], ipv6[11], ipv6[12], ipv6[13], ipv6[14], ipv6[15], prefix); + } + if (le32toh(pInfo->IPv6ConfigurationAvailable)&0x2) { + gw = (UINT8 *)(&pInfo->DataBuffer[le32toh(pInfo->IPv6GatewayOffset)-sizeof(MBIM_IP_CONFIGURATION_INFO_T)]); + mbim_debug("%s gw = %02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x", \ + direction, gw[0], gw[1], gw[2], gw[3], gw[4], gw[5], gw[6], gw[7], \ + gw[8], gw[9], gw[10], gw[11], gw[12], gw[13], gw[14], gw[15]); + } + if (le32toh(pInfo->IPv6ConfigurationAvailable)&0x3) { + dns1 = (UINT8 *)(&pInfo->DataBuffer[le32toh(pInfo->IPv6DnsServerOffset)-sizeof(MBIM_IP_CONFIGURATION_INFO_T)]); + mbim_debug("%s dns1 = %02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x", \ + direction, dns1[0], dns1[1], dns1[2], dns1[3], dns1[4], dns1[5], dns1[6], dns1[7], \ + dns1[8], dns1[9], dns1[10], dns1[11], dns1[12], dns1[13], dns1[14], dns1[15]); + if (le32toh(pInfo->IPv6DnsServerCount) == 2) { + dns2 = dns1 + 16; + mbim_debug("%s dns2 = %02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x", \ + direction, dns2[0], dns2[1], dns2[2], dns2[3], dns1[4], dns1[5], dns1[6], dns1[7], + dns2[8], dns2[9], dns2[10], dns2[11], dns2[12], dns2[13], dns2[14], dns2[15]); + } + } + if (le32toh(pInfo->IPv6Mtu)) mbim_debug("%s ipv6 mtu = %u", direction, le32toh(pInfo->IPv6Mtu)); +} + +static void mbim_dump(MBIM_MESSAGE_HEADER *pMsg, int mbim_verbose) { + unsigned char *data = (unsigned char *)pMsg; + const char *direction = (pMsg->MessageType & 0x80000000) ? "<" : ">"; + + if (!mbim_verbose) + return; + + if (mbim_verbose) { + unsigned i; + static char _tmp[4096] = {'\0'}; + _tmp[0] = (le32toh(pMsg->MessageType) & 0x80000000) ? '<' : '>'; + _tmp[1] = '\0'; + for (i = 0; i < le32toh(pMsg->MessageLength) && i < 4096; i++) + snprintf(_tmp + strlen(_tmp), 4096 - strlen(_tmp), "%02X:", data[i]); + mbim_debug("%s", _tmp); + } + + mbim_dump_header(pMsg, direction); + + switch (le32toh(pMsg->MessageType)) { + case MBIM_OPEN_MSG: { + MBIM_OPEN_MSG_T *pOpenMsg = (MBIM_OPEN_MSG_T *)pMsg; + mbim_debug("%s MaxControlTransfer = %u", direction, le32toh(pOpenMsg->MaxControlTransfer)); + } + break; + case MBIM_OPEN_DONE: { + MBIM_OPEN_DONE_T *pOpenDone = (MBIM_OPEN_DONE_T *)pMsg; + mbim_debug("%s Status = %u", direction, le32toh(pOpenDone->Status)); + } + break; + case MBIM_CLOSE_MSG: { + + } + break; + case MBIM_CLOSE_DONE: { + MBIM_CLOSE_DONE_T *pCloseDone = (MBIM_CLOSE_DONE_T *)pMsg; + mbim_debug("%s Status = %u", direction, le32toh(pCloseDone->Status)); + } + break; + case MBIM_COMMAND_MSG: { + MBIM_COMMAND_MSG_T *pCmdMsg = (MBIM_COMMAND_MSG_T *)pMsg; + + mbim_dump_command_msg(pCmdMsg, direction); + if (!memcmp(pCmdMsg->DeviceServiceId.uuid, str2uuid(UUID_BASIC_CONNECT), 16)) { + switch (le32toh(pCmdMsg->CID)) { + case MBIM_CID_CONNECT: { + MBIM_SET_CONNECT_T *pInfo = (MBIM_SET_CONNECT_T *)pCmdMsg->InformationBuffer; + mbim_debug("%s SessionId = %u", direction, le32toh(pInfo->SessionId)); + } + break; + case MBIM_CID_IP_CONFIGURATION: { + MBIM_IP_CONFIGURATION_INFO_T *pInfo = (MBIM_IP_CONFIGURATION_INFO_T *)pCmdMsg->InformationBuffer; + mbim_debug("%s SessionId = %u", direction, le32toh(pInfo->SessionId)); + } + break; + default: + break; + } + } + } + break; + case MBIM_COMMAND_DONE: { + MBIM_COMMAND_DONE_T *pCmdDone = (MBIM_COMMAND_DONE_T *)pMsg; + + mbim_dump_command_done(pCmdDone, direction); + if (le32toh(pCmdDone->InformationBufferLength) == 0) + return; + + if (!memcmp(pCmdDone->DeviceServiceId.uuid, str2uuid(UUID_BASIC_CONNECT), 16)) { + switch (le32toh(pCmdDone->CID)) { + case MBIM_CID_CONNECT: { + MBIM_CONNECT_T *pInfo = (MBIM_CONNECT_T *)pCmdDone->InformationBuffer; + mbim_dump_connect(pInfo, direction); + } + break; + case MBIM_CID_IP_CONFIGURATION: { + MBIM_IP_CONFIGURATION_INFO_T *pInfo = (MBIM_IP_CONFIGURATION_INFO_T *)pCmdDone->InformationBuffer; + mbim_dump_ipconfig(pInfo, direction); + } + break; + case MBIM_CID_PACKET_SERVICE: { + MBIM_PACKET_SERVICE_INFO_T *pInfo = (MBIM_PACKET_SERVICE_INFO_T *)pCmdDone->InformationBuffer; + mbim_dump_packet_service(pInfo, direction); + } + break; + case MBIM_CID_SUBSCRIBER_READY_STATUS: { + MBIM_SUBSCRIBER_READY_STATUS_T *pInfo = (MBIM_SUBSCRIBER_READY_STATUS_T *)pCmdDone->InformationBuffer; + mbim_dump_subscriber_status(pInfo, direction); + } + break; + case MBIM_CID_REGISTER_STATE: { + MBIM_REGISTRATION_STATE_INFO_T *pInfo = (MBIM_REGISTRATION_STATE_INFO_T *)pCmdDone->InformationBuffer; + mbim_dump_regiester_status(pInfo, direction); + } + break; + default: + break; + } + } + } + break; + case MBIM_INDICATE_STATUS_MSG: { + MBIM_INDICATE_STATUS_MSG_T *pIndMsg = (MBIM_INDICATE_STATUS_MSG_T *)pMsg; + + mbim_dump_indicate_msg(pIndMsg, direction); + if (le32toh(pIndMsg->InformationBufferLength) == 0) + return; + + if (!memcmp(pIndMsg->DeviceServiceId.uuid, str2uuid(UUID_BASIC_CONNECT), 16)) { + switch (le32toh(pIndMsg->CID)) { + case MBIM_CID_CONNECT: { + MBIM_CONNECT_T *pInfo = (MBIM_CONNECT_T *)pIndMsg->InformationBuffer; + mbim_dump_connect(pInfo, direction); + } + break; + case MBIM_CID_SIGNAL_STATE: { + MBIM_SIGNAL_STATE_INFO_T *pInfo = (MBIM_SIGNAL_STATE_INFO_T *)pIndMsg->InformationBuffer; + mbim_dump_signal_state(pInfo, direction); + } + break; + case MBIM_CID_SUBSCRIBER_READY_STATUS: { + MBIM_SUBSCRIBER_READY_STATUS_T *pInfo = (MBIM_SUBSCRIBER_READY_STATUS_T *)pIndMsg->InformationBuffer; + mbim_dump_subscriber_status(pInfo, direction); + } + break; + case MBIM_CID_REGISTER_STATE: { + MBIM_REGISTRATION_STATE_INFO_T *pInfo = (MBIM_REGISTRATION_STATE_INFO_T *)pIndMsg->InformationBuffer; + mbim_dump_regiester_status(pInfo, direction); + } + break; + case MBIM_CID_PACKET_SERVICE: { + MBIM_PACKET_SERVICE_INFO_T *pInfo = (MBIM_PACKET_SERVICE_INFO_T *)pIndMsg->InformationBuffer; + mbim_dump_packet_service(pInfo, direction); + } + break; + default: + break; + } + } + else if (!memcmp(pIndMsg->DeviceServiceId.uuid, str2uuid(UUID_BASIC_CONNECT_EXT), 16)) { + } + } + break; + case MBIM_FUNCTION_ERROR_MSG: { + MBIM_FUNCTION_ERROR_MSG_T *pErrMsg = (MBIM_FUNCTION_ERROR_MSG_T*)pMsg; + mbim_debug("%s ErrorStatusCode = %u", direction, le32toh(pErrMsg->ErrorStatusCode)); + } + break; + default: + break; + } +} + +static void mbim_recv_command(MBIM_MESSAGE_HEADER *pResponse, unsigned size) +{ + (void)size; + pthread_mutex_lock(&mbim_command_mutex); + + if (pResponse) + mbim_dump(pResponse, mbim_verbose); + + if (pResponse == NULL) { + pthread_cond_signal(&mbim_command_cond); + } + else if (mbim_pRequest && le32toh(mbim_pRequest->TransactionId) == le32toh(pResponse->TransactionId)) { + mbim_pResponse = mbim_alloc(le32toh(pResponse->MessageLength)); + if (mbim_pResponse) + memcpy(mbim_pResponse, pResponse, le32toh(pResponse->MessageLength)); + pthread_cond_signal(&mbim_command_cond); + } + else if (le32toh(pResponse->MessageType) == MBIM_INDICATE_STATUS_MSG) { + MBIM_INDICATE_STATUS_MSG_T *pIndMsg = (MBIM_INDICATE_STATUS_MSG_T *)pResponse; + + if (!memcmp(pIndMsg->DeviceServiceId.uuid, str2uuid(UUID_BASIC_CONNECT), 16)) + { + switch (le32toh(pIndMsg->CID)) { + case MBIM_CID_SUBSCRIBER_READY_STATUS: { + MBIM_SUBSCRIBER_READY_STATUS_T *pInfo = (MBIM_SUBSCRIBER_READY_STATUS_T *)pIndMsg->InformationBuffer; + notify_state_chage(ReadyState, le32toh(pInfo->ReadyState)); + } + break; + case MBIM_CID_REGISTER_STATE: { + MBIM_REGISTRATION_STATE_INFO_T *pInfo = (MBIM_REGISTRATION_STATE_INFO_T *)pIndMsg->InformationBuffer; + notify_state_chage(RegisterState, le32toh(pInfo->RegisterState)); + } + break; + case MBIM_CID_PACKET_SERVICE: { + MBIM_PACKET_SERVICE_INFO_T *pInfo = (MBIM_PACKET_SERVICE_INFO_T *)pIndMsg->InformationBuffer; + notify_state_chage(PacketServiceState, le32toh(pInfo->PacketServiceState)); + } + break; + case MBIM_CID_CONNECT: { + MBIM_CONNECT_T *pInfo = (MBIM_CONNECT_T *)pIndMsg->InformationBuffer; + if (pInfo->SessionId == (uint32_t)mbim_sessionID) { + if (le32toh(pInfo->ActivationState) == MBIMActivationStateDeactivated || le32toh(pInfo->ActivationState) == MBIMActivationStateDeactivating) + mbim_ifconfig(4, mbim_netcard, NULL, NULL, NULL, NULL, 0, 0); + notify_state_chage(ActivationState, le32toh(pInfo->ActivationState)); + } + } + break; + default: + break; + } + } + } + + pthread_mutex_unlock(&mbim_command_mutex); +} + +static int mbim_send_command(MBIM_MESSAGE_HEADER *pRequest, MBIM_COMMAND_DONE_T **ppCmdDone, unsigned msecs) { + int ret; + + if (ppCmdDone) + *ppCmdDone = NULL; + + if (mbim_fd <= 0) + return -ENODEV; + + if (read_tid == 0) + return -EINVAL; + + if (!pRequest) + return -ENOMEM; + + pthread_mutex_lock(&mbim_command_mutex); + + if (pRequest) { + #ifdef QUECTEL_MBIM_PROXY + if (pRequest->TransactionId == (0xFFFFFF + 1)) { + TransactionId = 1; + pRequest->TransactionId = htole32(TransactionId++); + } + #else + if (pRequest->TransactionId == 0) { + TransactionId = 1; + pRequest->TransactionId = htole32(TransactionId++); + } + #endif + mbim_dump(pRequest, mbim_verbose); + } + + mbim_pRequest = pRequest; + mbim_pResponse = NULL; + + ret = write(mbim_fd, pRequest, le32toh(pRequest->MessageLength)); + + if (ret > 0 && (uint32_t)ret == le32toh(pRequest->MessageLength)) { + ret = pthread_cond_timeout_np(&mbim_command_cond, &mbim_command_mutex, msecs); + if (!ret) { + if (mbim_pResponse && ppCmdDone) { + *ppCmdDone = (MBIM_COMMAND_DONE_T *)mbim_pResponse; + } + } + } else { + mbim_debug("%s pthread_cond_timeout_np=%d", __func__, ret); + } + + mbim_pRequest = mbim_pResponse = NULL; + + pthread_mutex_unlock(&mbim_command_mutex); + + return ret; +} + +static UINT32 mbim_recv_buf[1024]; +static void * mbim_read_thread(void *param) { + mbim_debug("%s is created", __func__); + (void)param; + while (mbim_fd > 0) { + struct pollfd pollfds[] = {{mbim_fd, POLLIN, 0}}; + int ne, ret, nevents = 1; + + ret = poll(pollfds, nevents, -1); + + if (ret <= 0) { + if (mbim_quit == 0) mbim_debug("%s poll=%d, errno: %d (%s)", __func__, ret, errno, strerror(errno)); + break; + } + + for (ne = 0; ne < nevents; ne++) { + int fd = pollfds[ne].fd; + short revents = pollfds[ne].revents; + + if (revents & (POLLERR | POLLHUP | POLLNVAL)) { + mbim_debug("%s poll err/hup/inval", __func__); + mbim_debug("epoll fd = %d, events = 0x%04x", fd, revents); + if (revents & (POLLERR | POLLHUP | POLLNVAL)) + goto __quit; + } + + if ((revents & POLLIN) == 0) + continue; + + if (mbim_fd == fd) { + ssize_t nreads; + MBIM_MESSAGE_HEADER *pResponse = (MBIM_MESSAGE_HEADER *) mbim_recv_buf; + + nreads = read(fd, pResponse, sizeof(mbim_recv_buf)); + if (nreads <= 0) { + mbim_debug("%s read=%d errno: %d (%s)", __func__, (int)nreads, errno, strerror(errno)); + break; + } + + mbim_recv_command(pResponse, nreads); + } + } + } + +__quit: + mbim_quit++; + mbim_recv_command(NULL, 0); + mbim_debug("%s exit", __func__); + _notify_state_chage(); + read_tid = 0; + + return NULL; +} + +static int mbim_status_code(MBIM_MESSAGE_HEADER *pMsgHdr) { + int status = 0; + + if (!pMsgHdr) + return 0; + + switch (pMsgHdr->MessageType) { + case MBIM_OPEN_DONE: { + MBIM_OPEN_DONE_T *pOpenDone = (MBIM_OPEN_DONE_T *)pMsgHdr; + status = le32toh(pOpenDone->Status); + } + break; + case MBIM_CLOSE_DONE: { + MBIM_CLOSE_DONE_T *pCloseDone = (MBIM_CLOSE_DONE_T *)pMsgHdr; + status = le32toh(pCloseDone->Status); + } + break; + case MBIM_COMMAND_DONE: { + MBIM_COMMAND_DONE_T *pCmdDone = (MBIM_COMMAND_DONE_T *)pMsgHdr; + status = le32toh(pCmdDone->Status); + } + break; + case MBIM_FUNCTION_ERROR_MSG: { + MBIM_FUNCTION_ERROR_MSG_T *pErrMsg = (MBIM_FUNCTION_ERROR_MSG_T *)pMsgHdr; + status = le32toh(pErrMsg->ErrorStatusCode); + if (status == MBIM_ERROR_NOT_OPENED) + mbim_open_state = 0; //EM06ELAR03A05M4G when suspend/resume, may get this error + } + break; + default: + break; + } + + return status; +} + +#define mbim_check_err(err, pRequest, pCmdDone) do { \ + int _status = mbim_status_code(&pCmdDone->MessageHeader); \ + if ((err || _status) && pCmdDone) { mbim_dump(&pCmdDone->MessageHeader, (mbim_verbose == 0)); } \ + if (err || _status) {mbim_debug("%s:%d err=%d, Status=%d", __func__, __LINE__, err, _status); } \ + if (err || pCmdDone == NULL) { mbim_free(pRequest); mbim_free(pCmdDone); return (err ? err : 8888);} \ +} while(0) + +/* + * MBIM device can be open repeatly without error + * So, we can call the function, no matter it have been opened or not + */ +static int mbim_open_device(uint32_t MaxControlTransfer) { + MBIM_MESSAGE_HEADER *pRequest = NULL; + MBIM_OPEN_DONE_T *pOpenDone = NULL; + int err = 0; + +#ifdef QUECTEL_MBIM_PROXY + return 0; +#endif + + mbim_debug("%s()", __func__); + pRequest = compose_open_command(MaxControlTransfer); + err = mbim_send_command(pRequest, (MBIM_COMMAND_DONE_T **)&pOpenDone, mbim_default_timeout); + mbim_check_err(err, pRequest, pOpenDone); + + err = le32toh(pOpenDone->Status); + mbim_free(pRequest); mbim_free(pOpenDone); + + return err; +} + +static int mbim_close_device(void) { + MBIM_MESSAGE_HEADER *pRequest = NULL; + MBIM_CLOSE_DONE_T *pCloseDone = NULL; + int err = 0; + +#ifdef QUECTEL_MBIM_PROXY + return 0; +#endif + + mbim_debug("%s()", __func__); + pRequest = compose_close_command(); + err = mbim_send_command(pRequest, (MBIM_COMMAND_DONE_T **)&pCloseDone, mbim_default_timeout); + mbim_check_err(err, pRequest, pCloseDone); + + err = le32toh(pCloseDone->Status); + mbim_free(pRequest); mbim_free(pCloseDone); + + return err; +} + +static int mbim_query_connect(int sessionID) { + MBIM_MESSAGE_HEADER *pRequest = NULL; + MBIM_COMMAND_DONE_T *pCmdDone = NULL; + MBIM_SET_CONNECT_T set_connect; + int err; + + mbim_debug("%s(sessionID=%d)", __func__, sessionID); + set_connect.SessionId = htole32(sessionID); + pRequest = compose_basic_connect_command(MBIM_CID_CONNECT, MBIM_CID_CMD_TYPE_QUERY, &set_connect, sizeof(set_connect)); + err = mbim_send_command(pRequest, &pCmdDone, mbim_default_timeout); + mbim_check_err(err, pRequest, pCmdDone); + + if (le32toh(pCmdDone->InformationBufferLength)) + { + MBIM_CONNECT_T *pInfo = (MBIM_CONNECT_T *)pCmdDone->InformationBuffer; + ActivationState = le32toh(pInfo->ActivationState); + } + mbim_free(pRequest); mbim_free(pCmdDone); + return err; +} + +static int mbim_ms_version_query(void) { + MBIM_MESSAGE_HEADER *pRequest = NULL; + MBIM_COMMAND_DONE_T *pCmdDone = NULL; + int err; + + struct _bc_ext_version { + UINT8 ver_minor; + UINT8 ver_major; + UINT8 ext_ver_minor; + UINT8 ext_ver_major; + } __attribute__ ((packed)) bc_ext_version; + + bc_ext_version.ver_major = 1; + bc_ext_version.ver_minor = 0; + bc_ext_version.ext_ver_major = 2; + bc_ext_version.ext_ver_minor = 0; + + pRequest = compose_basic_connect_ext_command(MBIM_CID_MS_VERSION, MBIM_CID_CMD_TYPE_QUERY, &bc_ext_version, sizeof(bc_ext_version)); + err = mbim_send_command(pRequest, &pCmdDone, mbim_default_timeout); + mbim_check_err(err, pRequest, pCmdDone); + + if (le32toh(pCmdDone->InformationBufferLength)) { + struct _bc_ext_version *pInfo = (struct _bc_ext_version *)pCmdDone->InformationBuffer; + //mbim_debug("%s ext_rel_ver major=%d, minor=%d", __func__, pInfo->ext_ver_major, pInfo->ext_ver_minor); + mbim_ms_version = pInfo->ext_ver_major; + } + + mbim_free(pRequest); mbim_free(pCmdDone); + return err; +} + +static int mbim_device_services_query(void) { + MBIM_MESSAGE_HEADER *pRequest = NULL; + MBIM_COMMAND_DONE_T *pCmdDone = NULL; + int err; + int mbim_v2_support = 0; + + mbim_debug("%s()", __func__); + pRequest = compose_basic_connect_command(MBIM_CID_DEVICE_SERVICES, MBIM_CID_CMD_TYPE_QUERY, NULL, 0); + err = mbim_send_command(pRequest, &pCmdDone, mbim_default_timeout); + mbim_check_err(err, pRequest, pCmdDone); + + if (pCmdDone->InformationBufferLength) { + MBIM_DEVICE_SERVICES_INFO_T *pInfo = (MBIM_DEVICE_SERVICES_INFO_T *)pCmdDone->InformationBuffer; + UINT32 i; + + for (i = 0; i < le32toh(pInfo->DeviceServicesCount) ; i++) { + //UINT32 size = pInfo->DeviceServicesRefList[i].size; + UINT32 offset = le32toh(pInfo->DeviceServicesRefList[i].offset); + MBIM_DEVICE_SERVICE_ELEMENT_T *pSrvEle = (MBIM_DEVICE_SERVICE_ELEMENT_T *)((void *)pInfo + offset); + + if (!strcasecmp(UUID_BASIC_CONNECT_EXT, uuid2str(&pSrvEle->DeviceServiceId))) { + UINT32 cid = 0; + + for (cid = 0; cid < le32toh(pSrvEle->CidCount); cid++) { + if (MBIM_CID_MS_VERSION == le32toh(pSrvEle->CidList[cid])) { + mbim_v2_support = 1; + } + } + } + } + } + mbim_free(pRequest); mbim_free(pCmdDone); + + if (mbim_v2_support) { + mbim_ms_version_query(); + } + + return err; +} + +static int mbim_device_caps_query(void) { + MBIM_MESSAGE_HEADER *pRequest = NULL; + MBIM_COMMAND_DONE_T *pCmdDone = NULL; + int err; + + mbim_debug("%s()", __func__); + pRequest = compose_basic_connect_command(MBIM_CID_DEVICE_CAPS, MBIM_CID_CMD_TYPE_QUERY, NULL, 0); + err = mbim_send_command(pRequest, &pCmdDone, mbim_default_timeout); + mbim_check_err(err, pRequest, pCmdDone); + + if (le32toh(pCmdDone->InformationBufferLength)) { + MBIM_DEVICE_CAPS_INFO_T *pInfo = (MBIM_DEVICE_CAPS_INFO_T *)pCmdDone->InformationBuffer; + char tmp[32]; + UINT32 i; + + if (le32toh(pInfo->DeviceIdOffset) && le32toh(pInfo->DeviceIdSize)) { + for (i = 0; i < 32 && i < (le32toh(pInfo->DeviceIdSize)/2); i++) + tmp[i] = pInfo->DataBuffer[le32toh(pInfo->DeviceIdOffset) - sizeof(MBIM_DEVICE_CAPS_INFO_T) + i*2]; + tmp[i] = '\0'; + mbim_debug("DeviceId: %s", tmp); + } + if (le32toh(pInfo->FirmwareInfoOffset) && le32toh(pInfo->FirmwareInfoSize)) { + for (i = 0; i < 32 && i < (le32toh(pInfo->FirmwareInfoSize)/2); i++) + tmp[i] = pInfo->DataBuffer[le32toh(pInfo->FirmwareInfoOffset) - sizeof(MBIM_DEVICE_CAPS_INFO_T) + i*2]; + tmp[i] = '\0'; + mbim_debug("FirmwareInfo: %s", tmp); + } + if (le32toh(pInfo->HardwareInfoOffset) && le32toh(pInfo->HardwareInfoSize)) { + for (i = 0; i < 32 && i < (le32toh(pInfo->HardwareInfoSize)/2); i++) + tmp[i] = pInfo->DataBuffer[le32toh(pInfo->HardwareInfoOffset) - sizeof(MBIM_DEVICE_CAPS_INFO_T) + i*2]; + tmp[i] = '\0'; + mbim_debug("HardwareInfo: %s", tmp); + } + } + mbim_free(pRequest); mbim_free(pCmdDone); + return err; +} + +#if 0 +static int mbim_radio_state_query(void) { + MBIM_MESSAGE_HEADER *pRequest = NULL; + MBIM_COMMAND_DONE_T *pCmdDone = NULL; + int err; + + mbim_debug("%s()", __func__); + pRequest = compose_basic_connect_command(MBIM_CID_RADIO_STATE, MBIM_CID_CMD_TYPE_QUERY, NULL, 0); + err = mbim_send_command(pRequest, &pCmdDone, mbim_default_timeout); + mbim_check_err(err, pRequest, pCmdDone); + + if (pCmdDone->InformationBufferLength) { + MBIM_RADIO_STATE_INFO_T *pInfo = (MBIM_RADIO_STATE_INFO_T *)pCmdDone->InformationBuffer; + mbim_debug("HwRadioState: %d, SwRadioState: %d", pInfo->HwRadioState, pInfo->SwRadioState); + } + mbim_free(pRequest); mbim_free(pCmdDone); + return err; +} +#endif + +static int mbim_set_radio_state(MBIM_RADIO_SWITCH_STATE_E RadioState) { + MBIM_MESSAGE_HEADER *pRequest = NULL; + MBIM_COMMAND_DONE_T *pCmdDone = NULL; + UINT32 value = htole32(RadioState); + int err; + + mbim_debug("%s( %d )", __func__, RadioState); + pRequest = compose_basic_connect_command(MBIM_CID_RADIO_STATE, MBIM_CID_CMD_TYPE_SET, &value, sizeof(value)); + err = mbim_send_command(pRequest, &pCmdDone, mbim_default_timeout); + mbim_check_err(err, pRequest, pCmdDone); + + if (le32toh(pCmdDone->InformationBufferLength)) { + MBIM_RADIO_STATE_INFO_T *pInfo = (MBIM_RADIO_STATE_INFO_T *)pCmdDone->InformationBuffer; + mbim_debug("HwRadioState: %d, SwRadioState: %d", le32toh(pInfo->HwRadioState), le32toh(pInfo->SwRadioState)); + } + mbim_free(pRequest); mbim_free(pCmdDone); + return err; +} + +static int mbim_subscriber_status_query(void) { + MBIM_MESSAGE_HEADER *pRequest = NULL; + MBIM_COMMAND_DONE_T *pCmdDone = NULL; + int err; + + mbim_debug("%s()", __func__); + pRequest = compose_basic_connect_command(MBIM_CID_SUBSCRIBER_READY_STATUS, MBIM_CID_CMD_TYPE_QUERY, NULL, 0); + err = mbim_send_command(pRequest, &pCmdDone, mbim_default_timeout); + mbim_check_err(err, pRequest, pCmdDone); + + if (le32toh(pCmdDone->InformationBufferLength)) { + MBIM_SUBSCRIBER_READY_STATUS_T *pInfo = (MBIM_SUBSCRIBER_READY_STATUS_T *)pCmdDone->InformationBuffer; + ReadyState = le32toh(pInfo->ReadyState); + } + mbim_free(pRequest); mbim_free(pCmdDone); + return err; +} + +static int mbim_register_state_query(void) { + MBIM_MESSAGE_HEADER *pRequest = NULL; + MBIM_COMMAND_DONE_T *pCmdDone = NULL; + int err; + + mbim_debug("%s()", __func__); + pRequest = compose_basic_connect_command(MBIM_CID_REGISTER_STATE, MBIM_CID_CMD_TYPE_QUERY, NULL, 0); + err = mbim_send_command(pRequest, &pCmdDone, mbim_default_timeout); + mbim_check_err(err, pRequest, pCmdDone); + + if (le32toh(pCmdDone->InformationBufferLength)) { + MBIM_REGISTRATION_STATE_INFO_T *pInfo = (MBIM_REGISTRATION_STATE_INFO_T *)pCmdDone->InformationBuffer;; + RegisterState = le32toh(pInfo->RegisterState); + } + mbim_free(pRequest); mbim_free(pCmdDone); + return err; +} + +static int mbim_packet_service_query(void) { + MBIM_MESSAGE_HEADER *pRequest = NULL; + MBIM_COMMAND_DONE_T *pCmdDone = NULL; + int err; + + mbim_debug("%s()", __func__); + pRequest = compose_basic_connect_command(MBIM_CID_PACKET_SERVICE, MBIM_CID_CMD_TYPE_QUERY, NULL, 0); + err = mbim_send_command(pRequest, &pCmdDone, mbim_default_timeout); + mbim_check_err(err, pRequest, pCmdDone); + + if (le32toh(pCmdDone->InformationBufferLength)) { + MBIM_PACKET_SERVICE_INFO_T *pInfo = (MBIM_PACKET_SERVICE_INFO_T *)pCmdDone->InformationBuffer; + PacketServiceState = le32toh(pInfo->PacketServiceState); + + if (le32toh(pCmdDone->InformationBufferLength) == sizeof(MBIM_PACKET_SERVICE_INFO_V2_T)) { + MBIM_PACKET_SERVICE_INFO_V2_T *pInfo = (MBIM_PACKET_SERVICE_INFO_V2_T *)pCmdDone->InformationBuffer; + mbim_debug("CurrentDataClass = %s", MBIMDataClassStr(le32toh(pInfo->CurrentDataClass))); + } + } + mbim_free(pRequest); mbim_free(pCmdDone); + return err; +} + +static int mbim_packet_service_set(MBIM_PACKET_SERVICE_ACTION_E action) { + MBIM_MESSAGE_HEADER *pRequest = NULL; + MBIM_COMMAND_DONE_T *pCmdDone = NULL; + UINT32 value = htole32(action); + int err; + + mbim_debug("%s()", __func__); + pRequest = compose_basic_connect_command(MBIM_CID_PACKET_SERVICE, MBIM_CID_CMD_TYPE_SET, &value, sizeof(value)); + err = mbim_send_command(pRequest, &pCmdDone, mbim_default_timeout); + mbim_check_err(err, pRequest, pCmdDone); + + if (le32toh(pCmdDone->InformationBufferLength)) { + MBIM_PACKET_SERVICE_INFO_T *pInfo = (MBIM_PACKET_SERVICE_INFO_T *)pCmdDone->InformationBuffer; + PacketServiceState = le32toh(pInfo->PacketServiceState); + } + mbim_free(pRequest); mbim_free(pCmdDone); + return err; +} + +#define _align_32(len) {len += (len % 4) ? (4 - (len % 4)) : 0;} +static int mbim_populate_connect_data(MBIM_SET_CONNECT_T **connect_req_ptr) { + int offset; + int buflen = 0; + int i; + + if (mbim_apn && strlen(mbim_apn) > 0) buflen += 2*strlen(mbim_apn) ; + _align_32(buflen); + if (mbim_user && strlen(mbim_user) > 0) buflen += 2*strlen(mbim_user); + _align_32(buflen); + if (mbim_passwd && strlen(mbim_passwd) > 0) buflen += 2*strlen(mbim_passwd); + _align_32(buflen); + + *connect_req_ptr = (MBIM_SET_CONNECT_T*)malloc(sizeof(MBIM_SET_CONNECT_T) + buflen); + if (! *connect_req_ptr) { + mbim_debug("not enough memory\n"); + return -1; + } + memset(*connect_req_ptr, 0, sizeof(MBIM_SET_CONNECT_T) + buflen); + + offset = 0; + if (mbim_apn && strlen(mbim_apn) > 0) { + (*connect_req_ptr)->AccessStringSize = htole32(2*strlen(mbim_apn)); + (*connect_req_ptr)->AccessStringOffset = htole32(offset + sizeof(MBIM_SET_CONNECT_T)); + for (i = 0; i < (int)strlen(mbim_apn); i++) { + (*connect_req_ptr)->DataBuffer[offset + i*2] = mbim_apn[i]; + (*connect_req_ptr)->DataBuffer[offset + i*2 + 1] = 0; + } + offset += 2 * strlen(mbim_apn); + _align_32(offset); + } + + if (mbim_user && strlen(mbim_user) > 0) { + (*connect_req_ptr)->UserNameSize = htole32(2*strlen(mbim_user)); + (*connect_req_ptr)->UserNameOffset = htole32(offset + sizeof(MBIM_SET_CONNECT_T)); + for (i = 0; i < (int)strlen(mbim_user); i++) { + (*connect_req_ptr)->DataBuffer[offset + i*2] = mbim_user[i]; + (*connect_req_ptr)->DataBuffer[offset + i*2 + 1] = 0; + } + offset += 2 * strlen(mbim_user); + _align_32(offset); + } + + if (mbim_passwd && strlen(mbim_passwd) > 0) { + (*connect_req_ptr)->PasswordSize = htole32(2*strlen(mbim_passwd)); + (*connect_req_ptr)->PasswordOffset = htole32(offset + sizeof(MBIM_SET_CONNECT_T)); + for (i = 0; i < (int)strlen(mbim_passwd); i++) { + (*connect_req_ptr)->DataBuffer[offset + i*2] = mbim_passwd[i]; + (*connect_req_ptr)->DataBuffer[offset + i*2 + 1] = 0; + } + } + + return buflen; +} + +static int mbim_set_connect(int onoff, int sessionID) { + MBIM_MESSAGE_HEADER *pRequest = NULL; + MBIM_COMMAND_DONE_T *pCmdDone = NULL; + MBIM_SET_CONNECT_T *set_connect = NULL; + int err; + + mbim_debug("%s(onoff=%d, sessionID=%d)", __func__, onoff, sessionID); + /* alloc memory then populate APN USERNAME PASSWORD */ + int buflen = mbim_populate_connect_data(&set_connect); + if (buflen < 0) { + return ENOMEM; + } + + set_connect->SessionId = htole32(sessionID); + if (onoff == 0) + set_connect->ActivationCommand = htole32(MBIMActivationCommandDeactivate); + else + set_connect->ActivationCommand = htole32(MBIMActivationCommandActivate); + + set_connect->Compression = htole32(MBIMCompressionNone); + set_connect->AuthProtocol = htole32(mbim_auth); + set_connect->IPType = htole32(mbim_iptype); + memcpy(set_connect->ContextType.uuid, str2uuid(UUID_MBIMContextTypeInternet), 16); + + pRequest = compose_basic_connect_command(MBIM_CID_CONNECT, MBIM_CID_CMD_TYPE_SET, set_connect, sizeof(MBIM_SET_CONNECT_T) + buflen); + mbim_free(set_connect); + err = mbim_send_command(pRequest, &pCmdDone, mbim_default_timeout*10); + mbim_check_err(err, pRequest, pCmdDone); + + if (le32toh(pCmdDone->InformationBufferLength)) { + MBIM_CONNECT_T *pInfo = (MBIM_CONNECT_T *)pCmdDone->InformationBuffer; + ActivationState = le32toh(pInfo->ActivationState); + } + + mbim_free(pRequest); mbim_free(pCmdDone); + return err; +} + +static int mbim_ip_config(int sessionID) { + MBIM_MESSAGE_HEADER *pRequest = NULL; + MBIM_COMMAND_DONE_T *pCmdDone = NULL; + MBIM_IP_CONFIGURATION_INFO_T ip_info; + int err; + + mbim_debug("%s(sessionID=%d)", __func__, sessionID); + ip_info.SessionId = htole32(sessionID); + pRequest = compose_basic_connect_command(MBIM_CID_IP_CONFIGURATION, MBIM_CID_CMD_TYPE_QUERY, &ip_info, sizeof(ip_info)); + err = mbim_send_command(pRequest, &pCmdDone, mbim_default_timeout); + mbim_check_err(err, pRequest, pCmdDone); + + if (le32toh(pCmdDone->InformationBufferLength)) { + UINT8 prefix, *ipv4=NULL, *ipv6=NULL, *gw=NULL, *dns1=NULL, *dns2=NULL; + UINT32 mtu = 1500; + MBIM_IP_CONFIGURATION_INFO_T *pInfo = (MBIM_IP_CONFIGURATION_INFO_T *)pCmdDone->InformationBuffer; + + if (mbim_verbose == 0) mbim_dump_ipconfig(pInfo, "<"); + + /* IPv4 network configration */ + if (le32toh(pInfo->IPv4ConfigurationAvailable)&0x1) { + MBIM_IPV4_ELEMENT_T *pAddress = (MBIM_IPV4_ELEMENT_T *)(&pInfo->DataBuffer[le32toh(pInfo->IPv4AddressOffset)-sizeof(MBIM_IP_CONFIGURATION_INFO_T)]); + prefix = le32toh(pAddress->OnLinkPrefixLength); + ipv4 = pAddress->IPv4Address; + + if (le32toh(pInfo->IPv4ConfigurationAvailable)&0x2) + gw = (UINT8 *)(&pInfo->DataBuffer[le32toh(pInfo->IPv4GatewayOffset)-sizeof(MBIM_IP_CONFIGURATION_INFO_T)]); + + if (le32toh(pInfo->IPv4ConfigurationAvailable)&0x4) { + dns1 = (UINT8 *)(&pInfo->DataBuffer[le32toh(pInfo->IPv4DnsServerOffset)-sizeof(MBIM_IP_CONFIGURATION_INFO_T)]); + if (le32toh(pInfo->IPv4DnsServerCount) == 2) + dns2 = dns1 + 4; + } + + if (le32toh(pInfo->IPv4ConfigurationAvailable)&0x8) + mtu = le32toh(pInfo->IPv4Mtu); + + mbim_ifconfig(4, mbim_netcard, ipv4, gw, dns1, dns2, prefix, mtu); + } + + /* IPv6 network configration */ + if (le32toh(pInfo->IPv6ConfigurationAvailable)&0x1) { + MBIM_IPV6_ELEMENT_T *pAddress = (MBIM_IPV6_ELEMENT_T *)(&pInfo->DataBuffer[le32toh(pInfo->IPv6AddressOffset)-sizeof(MBIM_IP_CONFIGURATION_INFO_T)]); + prefix = le32toh(pAddress->OnLinkPrefixLength); + ipv6 = pAddress->IPv6Address; + + if (le32toh(pInfo->IPv6ConfigurationAvailable)&0x2) + gw = (UINT8 *)(&pInfo->DataBuffer[le32toh(pInfo->IPv6GatewayOffset)-sizeof(MBIM_IP_CONFIGURATION_INFO_T)]); + + if (le32toh(pInfo->IPv6ConfigurationAvailable)&0x4) { + dns1 = (UINT8 *)(&pInfo->DataBuffer[le32toh(pInfo->IPv6DnsServerOffset)-sizeof(MBIM_IP_CONFIGURATION_INFO_T)]); + if (le32toh(pInfo->IPv6DnsServerCount) == 2) + dns2 = dns1 + 16; + } + + if (le32toh(pInfo->IPv6ConfigurationAvailable)&0x8) + mtu = le32toh(pInfo->IPv6Mtu); + + mbim_ifconfig(6, mbim_netcard, ipv6, gw, dns1, dns2, prefix, mtu); + } + } + return err; +} + +static void ql_sigaction(int signo) { + mbim_debug("MBIM catch signo %d", signo); + if (SIGTERM == signo || SIGHUP == signo || SIGINT == signo) { + mbim_quit++; + mbim_recv_command(NULL, 0); + _notify_state_chage(); + } +} + +static void mbim_reset_state(void) { + ReadyState = oldReadyState = MBIMSubscriberReadyStateNotInitialized; + RegisterState = oldRegisterState = MBIMRegisterStateUnknown; + PacketServiceState = oldPacketServiceState = MBIMPacketServiceStateUnknown; + ActivationState = oldActivationState = MBIMActivationStateUnknown; + mbim_ms_version = 1; +} + +static int mbim_update_state(PROFILE_T *profile) { + int chages = 0; + + if (oldReadyState != ReadyState) { + mbim_debug("SubscriberReadyState %s -> %s ", MBIMSubscriberReadyStateStr(oldReadyState), MBIMSubscriberReadyStateStr(ReadyState)); + oldReadyState = ReadyState; chages++; + } + if (oldRegisterState != RegisterState) { + mbim_debug("RegisterState %s -> %s ", MBIMRegisterStateStr(oldRegisterState), MBIMRegisterStateStr(RegisterState)); + oldRegisterState = RegisterState; chages++; + } + if (oldPacketServiceState != PacketServiceState) { + mbim_debug("PacketServiceState %s -> %s ", MBIMPacketServiceStateStr(oldPacketServiceState), MBIMPacketServiceStateStr(PacketServiceState)); + oldPacketServiceState = PacketServiceState; chages++; + } + if (ActivationState != oldActivationState) { + ql_set_driver_link_state(profile, ActivationState == MBIMActivationStateActivated); + mbim_debug("ActivationState %s -> %s ", MBIMActivationStateStr(oldActivationState), MBIMActivationStateStr(ActivationState)); + oldActivationState = ActivationState; chages++; + } + + return chages; +} + +#ifdef QUECTEL_MBIM_PROXY +static int mbim_proxy_open(const char *name) { + int sockfd = -1; + int reuse_addr = 1; + struct sockaddr_un sockaddr; + socklen_t alen; + + /*Create server socket*/ + (sockfd = socket(AF_LOCAL, SOCK_STREAM, 0)); + if (sockfd < 0) + return sockfd; + + memset(&sockaddr, 0, sizeof(sockaddr)); + sockaddr.sun_family = AF_LOCAL; + sockaddr.sun_path[0] = 0; + memcpy(sockaddr.sun_path + 1, name, strlen(name) ); + + alen = strlen(name) + offsetof(struct sockaddr_un, sun_path) + 1; + if(connect(sockfd, (struct sockaddr *)&sockaddr, alen) < 0) { + close(sockfd); + dbg_time("%s connect %s errno: %d (%s)\n", __func__, name, errno, strerror(errno)); + return -1; + } + setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse_addr,sizeof(reuse_addr)); + + mbim_debug("connect to %s sockfd = %d\n", name, sockfd); + + return sockfd; +} +#endif + +int mbim_main(PROFILE_T *profile) +{ + int retval; + int sessionID = 0; + + signal(SIGINT, ql_sigaction); + signal(SIGTERM, ql_sigaction); + signal(SIGHUP, ql_sigaction); + + profile->qmap_mode = 1; + + if (profile->qmichannel) + mbim_dev = profile->qmichannel; + + if (profile->qmapnet_adapter) { + mbim_netcard = profile->qmapnet_adapter; + real_netcard = profile->usbnet_adapter; + } else if(profile->usbnet_adapter) { +#ifdef QUECTEL_MBIM_PROXY + static char vlan[32]; + + sessionID = mbim_sessionID = profile->pdp; + snprintf(vlan, sizeof(vlan), "%s.%d", profile->usbnet_adapter, sessionID); + mbim_dev = QUECTEL_MBIM_PROXY; + mbim_netcard = vlan; + real_netcard = profile->usbnet_adapter; +#else + mbim_netcard = profile->usbnet_adapter; +#endif + } + + if (profile->apn) + mbim_apn = profile->apn; + if (profile->user) + mbim_user = profile->user; + if (profile->password) + mbim_passwd = profile->password; + if (profile->auth) + mbim_auth = profile->auth; + if (profile->enable_ipv4) + mbim_iptype = MBIMContextIPTypeIPv4; + if (profile->enable_ipv6) + mbim_iptype = MBIMContextIPTypeIPv6; + if (profile->enable_ipv4 && profile->enable_ipv6) + mbim_iptype = MBIMContextIPTypeIPv4AndIPv6; + mbim_verbose = debug_qmi; + mbim_debug("apn %s, user %s, passwd %s, auth %d", mbim_apn, mbim_user, mbim_passwd, mbim_auth); + mbim_debug("IP Proto %s", MBIMContextIPTypeStr(mbim_iptype)); + + /* set relative time, for pthread_cond_timedwait */ + cond_setclock_attr(&mbim_state_cond, CLOCK_MONOTONIC); + cond_setclock_attr(&mbim_command_cond, CLOCK_MONOTONIC); + +#ifdef QUECTEL_MBIM_PROXY + mbim_fd = mbim_proxy_open(mbim_dev); +#else + mbim_fd = open(mbim_dev, O_RDWR | O_NONBLOCK | O_NOCTTY); +#endif + if (mbim_fd <= 0) { + mbim_debug("fail to open (%s), errno: %d (%s)", mbim_dev, errno, strerror(errno)); + goto exit; + } + fcntl(mbim_fd, F_SETFL, fcntl(mbim_fd,F_GETFL) | O_NONBLOCK); + fcntl(mbim_fd, F_SETFD, FD_CLOEXEC); + pthread_create(&read_tid, NULL, mbim_read_thread, (void *)mbim_dev); + mbim_open_state = 0; + + while (mbim_quit == 0 && read_tid != 0) { + uint32_t wait_time = 24*60*60; + + if (mbim_open_state == 0) { + mbim_ifconfig(4, mbim_netcard, NULL, NULL, NULL, NULL, 0, 0); + TransactionId = 1; + retval = mbim_open_device(4096); + if (retval) goto exit; + mbim_open_state = 1; + mbim_reset_state(); + retval = mbim_device_caps_query(); + if (retval) goto exit; + retval = mbim_device_services_query(); + if (retval) goto exit; + retval = mbim_set_radio_state(MBIMRadioOn); + if (retval) goto exit; + } + + if (ReadyState != MBIMSubscriberReadyStateInitialized) { + retval = mbim_subscriber_status_query(); + if (retval) goto exit; + mbim_update_state(profile); + } + if (ReadyState != MBIMSubscriberReadyStateInitialized) goto _wait_state_change; + + if (RegisterState != MBIMRegisterStateHome && RegisterState != MBIMRegisterStateRoaming) { + retval = mbim_register_state_query(); + if (retval) goto exit; + mbim_update_state(profile); + } + if (RegisterState != MBIMRegisterStateHome && RegisterState != MBIMRegisterStateRoaming) goto _wait_state_change; + + if (PacketServiceState != MBIMPacketServiceStateAttached) { + retval = mbim_packet_service_query(); + if (retval) goto exit; + mbim_update_state(profile); + if ((PacketServiceState == MBIMPacketServiceStateUnknown || PacketServiceState == MBIMPacketServiceStateDetached) + && (RegisterState == MBIMRegisterStateHome || RegisterState == MBIMRegisterStateRoaming)) { + retval = mbim_packet_service_set(MBIMPacketServiceActionAttach); //at+cgatt=0/1 + if (retval) goto exit; + } + mbim_update_state(profile); + } + if (PacketServiceState != MBIMPacketServiceStateAttached) goto _wait_state_change; + + if (ActivationState == MBIMActivationStateUnknown) { + retval = mbim_query_connect(sessionID); + if (retval) goto exit; + mbim_update_state(profile); + /* if user forget to deactive data connection, or previous 'close command' failed + the state should still be activated, just fetch IP info */ + if (ActivationState == MBIMActivationStateActivated) { + retval = mbim_ip_config(sessionID); + if (retval) goto exit; + mbim_update_state(profile); + } + } + + if (ActivationState != MBIMActivationStateActivated && ActivationState != MBIMActivationStateActivating) { + retval = mbim_set_connect(1, sessionID); + if (retval) goto exit; + mbim_update_state(profile); + if (ActivationState == MBIMActivationStateActivated) { + retval = mbim_ip_config(sessionID); + if (retval) goto exit; + mbim_update_state(profile); + } + else { + wait_time = 30; //retry call mbim_set_connect 30 seconds later + } + } + if (ActivationState != MBIMActivationStateActivated) goto _wait_state_change; + +_wait_state_change: + wait_state_change(wait_time); + do { + mbim_update_state(profile); + } while (mbim_quit == 0 && read_tid != 0 && wait_state_change(1) != ETIMEDOUT); + } + +exit: + if (read_tid) { //error: ordered comparison of pointer with integer zero [-Werror=extra] + if (ActivationState == MBIMActivationStateActivated || ActivationState == MBIMActivationStateActivating) + mbim_set_connect(0, sessionID); + mbim_close_device(); + while (read_tid) { + pthread_kill(read_tid, SIGTERM); + usleep(10*1000); + } + mbim_ifconfig(4, mbim_netcard, NULL, NULL, NULL, NULL, 0, 0); + } + + if (mbim_fd > 0) { + close(mbim_fd); + } + pthread_mutex_destroy(&mbim_command_mutex); + pthread_mutex_destroy(&mbim_state_mutex); + pthread_cond_destroy(&mbim_command_cond); + pthread_cond_destroy(&mbim_state_cond); + + mbim_debug("MBIM CM exit...\n"); + return 0; +} diff --git a/root/package/link4all/quectel-CM/src/ql-ifconfig.c b/root/package/link4all/quectel-CM/src/ql-ifconfig.c new file mode 100644 index 00000000..e69de29b diff --git a/root/package/link4all/quectel-CM/src/qmap_bridge_mode.c b/root/package/link4all/quectel-CM/src/qmap_bridge_mode.c new file mode 100644 index 00000000..0eb984fb --- /dev/null +++ b/root/package/link4all/quectel-CM/src/qmap_bridge_mode.c @@ -0,0 +1,409 @@ +/****************************************************************************** + @file qmap_bridge_mode.c + @brief Connectivity bridge manager. + + DESCRIPTION + Connectivity Management Tool for USB network adapter of Quectel wireless cellular modules. + + INITIALIZATION AND SEQUENCING REQUIREMENTS + None. + + --------------------------------------------------------------------------- + Copyright (c) 2016 - 2020 Quectel Wireless Solution, Co., Ltd. All Rights Reserved. + Quectel Wireless Solution Proprietary and Confidential. + --------------------------------------------------------------------------- +******************************************************************************/ +#include "QMIThread.h" + +static size_t ql_fread(const char *filename, void *buf, size_t size) { + FILE *fp = fopen(filename , "r"); + size_t n = 0; + + memset(buf, 0x00, size); + + if (fp) { + n = fread(buf, 1, size, fp); + if (n <= 0 || n == size) { + dbg_time("warnning: fail to fread(%s), fread=%zu, buf_size=%zu: (%s)", filename, n, size, strerror(errno)); + } + fclose(fp); + } + + return n > 0 ? n : 0; +} + +static size_t ql_fwrite(const char *filename, const void *buf, size_t size) { + FILE *fp = fopen(filename , "w"); + size_t n = 0; + + if (fp) { + n = fwrite(buf, 1, size, fp); + if (n != size) { + dbg_time("warnning: fail to fwrite(%s), fwrite=%zu, buf_size=%zu: (%s)", filename, n, size, strerror(errno)); + } + fclose(fp); + } + + return n > 0 ? n : 0; +} + +int ql_bridge_mode_detect(PROFILE_T *profile) { + const char *ifname = profile->qmapnet_adapter ? profile->qmapnet_adapter : profile->usbnet_adapter; + const char *driver; + char bridge_mode[128]; + char bridge_ipv4[128]; + char ipv4[128]; + char buf[64]; + size_t n; + int in_bridge = 0; + + driver = profile->driver_name; + snprintf(bridge_mode, sizeof(bridge_mode), "/sys/class/net/%s/bridge_mode", ifname); + snprintf(bridge_ipv4, sizeof(bridge_ipv4), "/sys/class/net/%s/bridge_ipv4", ifname); + + if (access(bridge_ipv4, R_OK)) { + if (errno != ENOENT) { + dbg_time("fail to access %s, errno: %d (%s)", bridge_mode, errno, strerror(errno)); + return 0; + } + + snprintf(bridge_mode, sizeof(bridge_mode), "/sys/module/%s/parameters/bridge_mode", driver); + snprintf(bridge_ipv4, sizeof(bridge_ipv4), "/sys/module/%s/parameters/bridge_ipv4", driver); + + if (access(bridge_mode, R_OK)) { + if (errno != ENOENT) { + dbg_time("fail to access %s, errno: %d (%s)", bridge_mode, errno, strerror(errno)); + } + return 0; + } + } + + n = ql_fread(bridge_mode, buf, sizeof(buf)); + if (n > 0) { + in_bridge = (buf[0] != '0'); + } + if (!in_bridge) + return 0; + + memset(ipv4, 0, sizeof(ipv4)); + + if (strstr(bridge_ipv4, "/sys/class/net/") || profile->qmap_mode == 0 || profile->qmap_mode == 1) { + snprintf(ipv4, sizeof(ipv4), "0x%x", profile->ipv4.Address); + dbg_time("echo '%s' > %s", ipv4, bridge_ipv4); + ql_fwrite(bridge_ipv4, ipv4, strlen(ipv4)); + } + else { + snprintf(ipv4, sizeof(ipv4), "0x%x:%d", profile->ipv4.Address, profile->muxid); + dbg_time("echo '%s' > %s", ipv4, bridge_ipv4); + ql_fwrite(bridge_ipv4, ipv4, strlen(ipv4)); + } + + return in_bridge; +} + +int ql_enable_qmi_wwan_rawip_mode(PROFILE_T *profile) { + char filename[256]; + char buf[4]; + size_t n; + FILE *fp; + + if (!qmidev_is_qmiwwan(profile->qmichannel)) + return 0; + + snprintf(filename, sizeof(filename), "/sys/class/net/%s/qmi/rawip", profile->usbnet_adapter); + n = ql_fread(filename, buf, sizeof(buf)); + + if (n == 0) + return 0; + + if (buf[0] == '1' || buf[0] == 'Y') + return 0; + + fp = fopen(filename , "w"); + if (fp == NULL) { + dbg_time("Fail to fopen(%s, \"w\"), errno: %d (%s)", filename, errno, strerror(errno)); + return 1; + } + + buf[0] = 'Y'; + n = fwrite(buf, 1, 1, fp); + if (n != 1) { + dbg_time("Fail to fwrite(%s), errno: %d (%s)", filename, errno, strerror(errno)); + fclose(fp); + return 1; + } + fclose(fp); + + return 0; +} + +int ql_driver_type_detect(PROFILE_T *profile) { + if (qmidev_is_gobinet(profile->qmichannel)) { + profile->qmi_ops = &gobi_qmidev_ops; + } + else { + profile->qmi_ops = &qmiwwan_qmidev_ops; + } + qmidev_send = profile->qmi_ops->send; + + return 0; +} + +void ql_set_driver_bridge_mode(PROFILE_T *profile) { + char enable[8]; + char filename[256]; + + if(profile->qmap_mode) + snprintf(filename, sizeof(filename), "/sys/class/net/%s/bridge_mode", profile->qmapnet_adapter); + else + snprintf(filename, sizeof(filename), "/sys/class/net/%s/bridge_mode", profile->usbnet_adapter); + snprintf(enable, sizeof(enable), "%2d\n", profile->enable_bridge); + ql_fwrite(filename, enable, sizeof(enable)); +} + +static int ql_qmi_qmap_mode_detect(PROFILE_T *profile) { + char buf[128]; + int n; + char qmap_netcard[128]; + struct { + char filename[255 * 2]; + char linkname[255 * 2]; + } *pl; + + pl = (typeof(pl)) malloc(sizeof(*pl)); + + if (profile->qmapnet_adapter) { + free(profile->qmapnet_adapter);; + profile->qmapnet_adapter = NULL; + } + + snprintf(pl->linkname, sizeof(pl->linkname), "/sys/class/net/%s/device/driver", profile->usbnet_adapter); + n = readlink(pl->linkname, pl->filename, sizeof(pl->filename)); + pl->filename[n] = '\0'; + while (pl->filename[n] != '/') + n--; + strset(profile->driver_name, &pl->filename[n+1]); + + ql_get_driver_rmnet_info(profile, &profile->rmnet_info); + if (profile->rmnet_info.size) { + profile->qmap_mode = profile->rmnet_info.qmap_mode; + if (profile->qmap_mode) { + int offset_id = profile->pdp - 1; + + if (profile->qmap_mode == 1) + offset_id = 0; + profile->muxid = profile->rmnet_info.mux_id[offset_id]; + profile->qmapnet_adapter = strdup( profile->rmnet_info.ifname[offset_id]); + profile->qmap_size = profile->rmnet_info.rx_urb_size; + profile->qmap_version = profile->rmnet_info.qmap_version; + } + + goto _out; + } + + snprintf(pl->filename, sizeof(pl->filename), "/sys/class/net/%s/qmap_mode", profile->usbnet_adapter); + if (access(pl->filename, R_OK)) { + if (errno != ENOENT) { + dbg_time("fail to access %s, errno: %d (%s)", pl->filename, errno, strerror(errno)); + goto _out; + } + + snprintf(pl->filename, sizeof(pl->filename), "/sys/module/%s/parameters/qmap_mode", profile->driver_name); + if (access(pl->filename, R_OK)) { + if (errno != ENOENT) { + dbg_time("fail to access %s, errno: %d (%s)", pl->filename, errno, strerror(errno)); + goto _out; + } + + snprintf(pl->filename, sizeof(pl->filename), "/sys/class/net/%s/device/driver/module/parameters/qmap_mode", profile->usbnet_adapter); + if (access(pl->filename, R_OK)) { + if (errno != ENOENT) { + dbg_time("fail to access %s, errno: %d (%s)", pl->filename, errno, strerror(errno)); + goto _out; + } + } + } + } + + if (!access(pl->filename, R_OK)) { + n = ql_fread(pl->filename, buf, sizeof(buf)); + if (n > 0) { + profile->qmap_mode = atoi(buf); + + if (profile->qmap_mode > 1) { + profile->muxid = profile->pdp + 0x80; //muxis is 0x8X for PDN-X + sprintf(qmap_netcard, "%s.%d", profile->usbnet_adapter, profile->pdp); + profile->qmapnet_adapter = strdup(qmap_netcard); + } if (profile->qmap_mode == 1) { + profile->muxid = 0x81; + profile->qmapnet_adapter = strdup(profile->usbnet_adapter); + } + } + } + else if (qmidev_is_qmiwwan(profile->qmichannel)) { + snprintf(pl->filename, sizeof(pl->filename), "/sys/class/net/qmimux%d", profile->pdp - 1); + if (access(pl->filename, R_OK)) { + if (errno != ENOENT) { + dbg_time("fail to access %s, errno: %d (%s)", pl->filename, errno, strerror(errno)); + } + goto _out; + } + + //upstream Kernel Style QMAP qmi_wwan.c + snprintf(pl->filename, sizeof(pl->filename), "/sys/class/net/%s/qmi/add_mux", profile->usbnet_adapter); + n = ql_fread(pl->filename, buf, sizeof(buf)); + if (n >= 5) { + dbg_time("If use QMAP by /sys/class/net/%s/qmi/add_mux", profile->usbnet_adapter); + dbg_time("File:%s Line:%d Please make sure add next patch to qmi_wwan.c", __func__, __LINE__); + /* + diff --git a/drivers/net/usb/qmi_wwan.c b/drivers/net/usb/qmi_wwan.c + index 74bebbd..db8a777 100644 + --- a/drivers/net/usb/qmi_wwan.c + +++ b/drivers/net/usb/qmi_wwan.c + @@ -379,6 +379,24 @@ static ssize_t add_mux_store(struct device *d, struct device_attribute *attr, c + if (!ret) { + info->flags |= QMI_WWAN_FLAG_MUX; + ret = len; + +#if 1 //Add by Quectel + + if (le16_to_cpu(dev->udev->descriptor.idVendor) == 0x2c7c) { + + int idProduct = le16_to_cpu(dev->udev->descriptor.idProduct); + + + + if (idProduct == 0x0121 || idProduct == 0x0125 || idProduct == 0x0435) //MDM9x07 + + dev->rx_urb_size = 4*1024; + + else if (idProduct == 0x0306) //MDM9x40 + + dev->rx_urb_size = 16*1024; + + else if (idProduct == 0x0512) //SDX20 + + dev->rx_urb_size = 32*1024; + + else if (idProduct == 0x0620) //SDX24 + + dev->rx_urb_size = 32*1024; + + else if (idProduct == 0x0800) //SDX55 + + dev->rx_urb_size = 32*1024; + + else + + dev->rx_urb_size = 32*1024; + + } + +#endif + } + err: + rtnl_unlock(); + */ + profile->qmap_mode = n/5; //0x11\n0x22\n0x33\n + if (profile->qmap_mode > 1) { + //PDN-X map to qmimux-X + profile->muxid = (buf[5*(profile->pdp - 1) + 2] - '0')*16 + (buf[5*(profile->pdp - 1) + 3] - '0'); + sprintf(qmap_netcard, "qmimux%d", profile->pdp - 1); + profile->qmapnet_adapter = strdup(qmap_netcard); + } else if (profile->qmap_mode == 1) { + profile->muxid = (buf[5*0 + 2] - '0')*16 + (buf[5*0 + 3] - '0'); + sprintf(qmap_netcard, "qmimux%d", 0); + profile->qmapnet_adapter = strdup(qmap_netcard); + } + } + } + +_out: + if (profile->qmap_mode) { + if (profile->qmap_size == 0) { + profile->qmap_size = 16*1024; + snprintf(pl->filename, sizeof(pl->filename), "/sys/class/net/%s/qmap_size", profile->usbnet_adapter); + if (!access(pl->filename, R_OK)) { + size_t n; + char buf[32]; + n = ql_fread(pl->filename, buf, sizeof(buf)); + if (n > 0) { + profile->qmap_size = atoi(buf); + } + } + } + + if (profile->qmap_version == 0) { + profile->qmap_version = WDA_DL_DATA_AGG_QMAP_ENABLED; + } + + dbg_time("qmap_mode = %d, qmap_version = %d, qmap_size = %d, muxid = 0x%02x, qmap_netcard = %s", + profile->qmap_mode, profile->qmap_version, profile->qmap_size, profile->muxid, profile->qmapnet_adapter); + } + ql_set_driver_bridge_mode(profile); + free(pl); + + return 0; +} + + +static int ql_mbim_qmap_mode_detect(PROFILE_T *profile) { + char buf[128]; + int n; + char qmap_netcard[128]; + struct { + char filename[255 * 2]; + char linkname[255 * 2]; + } *pl; + + pl = (typeof(pl)) malloc(sizeof(*pl)); + + if (profile->qmapnet_adapter) { + free(profile->qmapnet_adapter);; + profile->qmapnet_adapter = NULL; + } + + snprintf(pl->linkname, sizeof(pl->linkname), "/sys/class/net/%s/device/driver", profile->usbnet_adapter); + n = readlink(pl->linkname, pl->filename, sizeof(pl->filename)); + pl->filename[n] = '\0'; + while (pl->filename[n] != '/') + n--; + strset(profile->driver_name, &pl->filename[n+1]); + + ql_get_driver_rmnet_info(profile, &profile->rmnet_info); + if (profile->rmnet_info.size) { + profile->qmap_mode = profile->rmnet_info.qmap_mode; + if (profile->qmap_mode) { + int offset_id = profile->pdp - 1; + + if (profile->qmap_mode == 1) + offset_id = 0; + profile->muxid = profile->rmnet_info.mux_id[offset_id]; + profile->qmapnet_adapter = strdup( profile->rmnet_info.ifname[offset_id]); + profile->qmap_size = profile->rmnet_info.rx_urb_size; + profile->qmap_version = profile->rmnet_info.qmap_version; + } + + goto _out; + } + + snprintf(pl->filename, sizeof(pl->filename), "/sys/module/%s/parameters/qmap_mode", profile->driver_name); + if (access(pl->filename, R_OK)) { + if (errno != ENOENT) { + dbg_time("fail to access %s, errno: %d (%s)", pl->filename, errno, strerror(errno)); + goto _out; + } + } else { + n = ql_fread(pl->filename, buf, sizeof(buf)); + if (n > 0) { + profile->qmap_mode = atoi(buf); + + if (profile->qmap_mode > 1) { + profile->muxid = profile->pdp + 0x80; //muxis is 0x8X for PDN-X + sprintf(qmap_netcard, "%s.%d", profile->usbnet_adapter, profile->pdp); + profile->qmapnet_adapter = strdup(qmap_netcard); + } + } + } + +_out: + if (profile->qmap_mode) { + dbg_time("mbim_qmap_mode = %d, vlan_id = 0x%02x, qmap_netcard = %s", + profile->qmap_mode, profile->muxid, profile->qmapnet_adapter); + } + + free(pl); + + return 0; +} + +int ql_qmap_mode_detect(PROFILE_T *profile) { + if (profile->software_interface == SOFTWARE_MBIM) { + return ql_mbim_qmap_mode_detect(profile); + } else if (profile->software_interface == SOFTWARE_QMI) { + return ql_qmi_qmap_mode_detect(profile); + } + return 0; +} diff --git a/root/package/link4all/quectel-CM/src/quectel-mbim-proxy.c b/root/package/link4all/quectel-CM/src/quectel-mbim-proxy.c new file mode 100644 index 00000000..cf87905e --- /dev/null +++ b/root/package/link4all/quectel-CM/src/quectel-mbim-proxy.c @@ -0,0 +1,437 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define QUECTEL_MBIM_PROXY "quectel-mbim-proxy" +#define safe_close(_fd) do { if (_fd > 0) { close(_fd); _fd = -1; } } while(0) + +#define CM_MAX_CLIENT 32 +#define TID_MASK (0xFFFFFF) +#define TID_SHIFT (24) + +typedef enum { + MBIM_OPEN_MSG = 1, + MBIM_CLOSE_MSG = 2, + MBIM_OPEN_DONE = 0x80000001, + MBIM_CLOSE_DONE = 0x80000002, +} MBIM_MSG; + +typedef struct { + unsigned int MessageType; + unsigned int MessageLength; + unsigned int TransactionId; +} MBIM_MESSAGE_HEADER; + +typedef struct { + MBIM_MESSAGE_HEADER MessageHeader; + unsigned int MaxControlTransfer; +} MBIM_OPEN_MSG_T; + +typedef struct { + MBIM_MESSAGE_HEADER MessageHeader; + unsigned int Status; +} MBIM_OPEN_DONE_T; + +typedef struct { + int client_fd; + int client_idx; +} CM_CLIENT_T; + +static unsigned char cm_recv_buffer[4096]; +static CM_CLIENT_T cm_clients[CM_MAX_CLIENT]; +static int verbose = 0; + +const char * get_time(void) { + static char time_buf[50]; + struct timeval tv; + time_t time; + suseconds_t millitm; + struct tm *ti; + + gettimeofday (&tv, NULL); + + time= tv.tv_sec; + millitm = (tv.tv_usec + 500) / 1000; + + if (millitm == 1000) { + ++time; + millitm = 0; + } + + ti = localtime(&time); + sprintf(time_buf, "%02d-%02d_%02d:%02d:%02d:%03d", ti->tm_mon+1, ti->tm_mday, ti->tm_hour, ti->tm_min, ti->tm_sec, (int)millitm); + return time_buf; +} + +#define mbim_debug(fmt, args...) do { fprintf(stdout, "%s " fmt, get_time(), ##args); } while(0); + +static int non_block_write(int fd, void *data, int len) +{ + int ret; + struct pollfd pollfd = {fd, POLLOUT, 0}; + ret = poll(&pollfd, 1, 3000); + + if (ret <= 0) { + mbim_debug("%s poll ret=%d, errno: %d(%s)\n", __func__, ret, errno, strerror(errno)); + } + + ret = write (fd, data, len); + if (ret != len) + mbim_debug("%s write ret=%d, errno: %d(%s)\n", __func__, ret, errno, strerror(errno)); + + return len; +} + +static int mbim_send_open_msg(int mbim_dev_fd, uint32_t MaxControlTransfer) { + MBIM_OPEN_MSG_T open_msg; + MBIM_OPEN_MSG_T *pRequest = &open_msg; + + pRequest->MessageHeader.MessageType = (MBIM_OPEN_MSG); + pRequest->MessageHeader.MessageLength = (sizeof(MBIM_OPEN_MSG_T)); + pRequest->MessageHeader.TransactionId = (1); + pRequest->MaxControlTransfer = (MaxControlTransfer); + + mbim_debug("%s()\n", __func__); + return non_block_write(mbim_dev_fd, pRequest, sizeof(MBIM_OPEN_MSG_T)); +} + +/* + * parameter: proxy name + * return: local proxy server fd or -1 +*/ +static int proxy_make_server(const char *proxy_name) +{ + int len, flag; + struct sockaddr_un sockaddr; + int mbim_server_fd; + + mbim_server_fd = socket(AF_LOCAL, SOCK_STREAM, 0); + if (mbim_server_fd < 0) { + mbim_debug("socket failed: %s\n", strerror(errno)); + return -1; + } + if (fcntl(mbim_server_fd, F_SETFL, fcntl(mbim_server_fd, F_GETFL) | O_NONBLOCK) < 0) + mbim_debug("fcntl set server(%d) NONBLOCK attribute failed: %s\n", mbim_server_fd, strerror(errno)); + + memset(&sockaddr, 0, sizeof(sockaddr)); + sockaddr.sun_family = AF_LOCAL; + sockaddr.sun_path[0] = 0; + snprintf(sockaddr.sun_path, UNIX_PATH_MAX, "0%s", proxy_name); + sockaddr.sun_path[0] = '\0'; // string starts with leading '\0' + flag = 1; + if (setsockopt(mbim_server_fd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag)) < 0) { + safe_close(mbim_server_fd); + mbim_debug("setsockopt failed\n"); + } + + len = strlen(proxy_name) + offsetof(struct sockaddr_un, sun_path) + 1; + if (bind(mbim_server_fd, (struct sockaddr*)&sockaddr, len) < 0) { + safe_close(mbim_server_fd); + mbim_debug("bind failed: %s\n", strerror(errno)); + return -1; + } + + listen(mbim_server_fd, 4); + return mbim_server_fd; +} + +static int handle_client_connect(int server_fd) +{ + int i, client_fd; + struct sockaddr_in cli_addr; + socklen_t len = sizeof(cli_addr); + + client_fd = accept(server_fd, (struct sockaddr *)&cli_addr, &len); + if (client_fd < 0) { + mbim_debug("proxy accept failed: %s\n", strerror(errno)); + return -1; + } + + if (fcntl(client_fd, F_SETFL, fcntl(client_fd, F_GETFL) | O_NONBLOCK) < 0) + mbim_debug("fcntl set client(%d) NONBLOCK attribute failed: %s\n", client_fd, strerror(errno)); + + for (i = 0; i < CM_MAX_CLIENT; i++) { + if (cm_clients[i].client_fd <= 0) { + cm_clients[i].client_fd = client_fd; + cm_clients[i].client_idx= i+1; + mbim_debug("%s client_fd=%d, client_idx=%d\n", __func__, cm_clients[i].client_fd, cm_clients[i].client_idx); + return 0; + } + } + + close(client_fd); + return -1; +} + +static void handle_client_disconnect(int client_fd) +{ + int i; + + for (i = 0; i < CM_MAX_CLIENT; i++) { + if (cm_clients[i].client_fd == client_fd) { + mbim_debug("%s client_fd=%d, client_idx=%d\n", __func__, cm_clients[i].client_fd, cm_clients[i].client_idx); + safe_close(cm_clients[i].client_fd); + return; + } + } +} + +static int handle_client_request(int mbim_dev_fd, int client_fd, void *pdata, int len) +{ + int i; + int client_idx = -1; + int ret; + MBIM_MESSAGE_HEADER *pRequest = (MBIM_MESSAGE_HEADER *)pdata; + + for (i = 0; i < CM_MAX_CLIENT; i++) { + if (cm_clients[i].client_fd == client_fd) { + client_idx = cm_clients[i].client_idx; + break; + } + } + + if (client_idx == -1) { + goto error; + } + + /* transfer TransicationID to proxy transicationID and record in sender list */ + pRequest->TransactionId = (pRequest->TransactionId & TID_MASK) + (client_idx << TID_SHIFT); + if (verbose) mbim_debug("REQ client_fd=%d, client_idx=%d, tid=%u\n", cm_clients[i].client_fd, cm_clients[i].client_idx, (pRequest->TransactionId & TID_MASK)); + ret = non_block_write (mbim_dev_fd, pRequest, len); + if (ret == len) + return 0; + +error: + return -1; +} + +/* + * Will read message from device and transfer it to clients/client + * Notice: + * unsocial message will be send to all clients + */ +static int handle_device_response(void *pdata, int len) +{ + int i; + MBIM_MESSAGE_HEADER *pResponse = (MBIM_MESSAGE_HEADER *)pdata; + + /* unsocial/function error message */ + if (pResponse->TransactionId == 0) { + for (i = 0; i < CM_MAX_CLIENT; i++) { + if (cm_clients[i].client_fd > 0) { + non_block_write(cm_clients[i].client_fd, pResponse, len); + } + } + } + else { + /* try to find the sender */ + int client_idx = (pResponse->TransactionId >> TID_SHIFT); + + for (i = 0; i < CM_MAX_CLIENT; i++) { + if (cm_clients[i].client_idx == client_idx && cm_clients[i].client_fd > 0) { + pResponse->TransactionId &= TID_MASK; + if (verbose) mbim_debug("RSP client_fd=%d, client_idx=%d, tid=%u\n", cm_clients[i].client_fd, cm_clients[i].client_idx, (pResponse->TransactionId & TID_MASK)); + non_block_write(cm_clients[i].client_fd, pResponse, len); + break; + } + } + + if ( i == CM_MAX_CLIENT) { + mbim_debug("%s nobody care tid=%u\n", __func__, pResponse->TransactionId); + } + } + + return 0; +} + +static int proxy_loop(int mbim_dev_fd) +{ + int i; + int mbim_server_fd = -1; + + while (mbim_dev_fd > 0) { + struct pollfd pollfds[2+CM_MAX_CLIENT]; + int ne, ret, nevents = 0; + + pollfds[nevents].fd = mbim_dev_fd; + pollfds[nevents].events = POLLIN; + pollfds[nevents].revents= 0; + nevents++; + + if (mbim_server_fd > 0) { + pollfds[nevents].fd = mbim_server_fd; + pollfds[nevents].events = POLLIN; + pollfds[nevents].revents= 0; + nevents++; + + for (i = 0; i < CM_MAX_CLIENT; i++) { + if (cm_clients[i].client_fd > 0) { + pollfds[nevents].fd = cm_clients[i].client_fd; + pollfds[nevents].events = POLLIN; + pollfds[nevents].revents= 0; + nevents++; + } + } + } + + ret = poll(pollfds, nevents, (mbim_server_fd > 0) ? -1 : (10*1000)); + if (ret <= 0) { + goto error; + } + + for (ne = 0; ne < nevents; ne++) { + int fd = pollfds[ne].fd; + short revents = pollfds[ne].revents; + + if (revents & (POLLERR | POLLHUP | POLLNVAL)) { + mbim_debug("%s poll fd = %d, revents = %04x\n", __func__, fd, revents); + if (fd == mbim_dev_fd) { + goto error; + } else if(fd == mbim_server_fd) { + + } else { + handle_client_disconnect(fd); + } + continue; + } + + if (!(pollfds[ne].revents & POLLIN)) { + continue; + } + + if (fd == mbim_server_fd) { + handle_client_connect(fd); + } + else { + int len = read(fd, cm_recv_buffer, sizeof(cm_recv_buffer)); + + if (len <= 0) { + mbim_debug("%s read fd=%d, len=%d, errno: %d(%s)\n", __func__, fd, len, errno, strerror(errno)); + if (fd == mbim_dev_fd) + goto error; + else + handle_client_disconnect(fd); + + return len; + } + + if (fd == mbim_dev_fd) { + if (mbim_server_fd == -1) { + MBIM_OPEN_DONE_T *pOpenDone = (MBIM_OPEN_DONE_T *)cm_recv_buffer; + + if (pOpenDone->MessageHeader.MessageType == MBIM_OPEN_DONE) { + mbim_debug("receive MBIM_OPEN_DONE, status=%d\n", pOpenDone->Status); + if (pOpenDone->Status) + goto error; + mbim_server_fd = proxy_make_server(QUECTEL_MBIM_PROXY); + mbim_debug("mbim_server_fd=%d\n", mbim_server_fd); + } + } + else { + handle_device_response(cm_recv_buffer, len); + } + } + else { + handle_client_request(mbim_dev_fd, fd, cm_recv_buffer, len); + } + } + } + } + +error: + safe_close(mbim_dev_fd); + safe_close(mbim_server_fd); + for (i = 0; i < CM_MAX_CLIENT; i++) { + safe_close(cm_clients[i].client_fd); + } + + mbim_debug("%s exit\n", __func__); + return 0; +} + +/* + * How to use this proxy? + * 1. modprobe -a 8021q + * 2. Create network interface for channels: + * ip link add link wwan0 name wwan0.1 type vlan id 1 + * ip link add link wwan0 name wwan0.2 type vlan id 2 + * 3. Start './mbim-proxy' with -d 'device' + * 4. Start Clients: ./quectel-CM -n id1 + * 5. Start Clients: ./quectel-CM -n id2 + * ... + * Notice: + * mbim-proxy can work in backgroud as a daemon + * '-n' sessionID + * The modem may not support multi-PDN mode or how many PDN it supports is undefined. It depends!!! + * Besides, some modem also may not support some sessionID. For instance EC20 doesn't support SessionId 1... + */ +int main(int argc, char **argv) +{ + int optidx = 0; + int opt; + char *optstr = "d:vh"; + const char *device = "/dev/cdc-wdm0"; + + struct option options[] = { + {"verbose", no_argument, NULL, 'v'}, + {"device", required_argument, NULL, 'd'}, + {0, 0, 0, 0}, + }; + while ((opt = getopt_long(argc, argv, optstr, options, &optidx)) != -1) { + switch (opt) { + case 'v': + verbose = 1; + break; + case 'd': + device = optarg; + break; + case 'h': + mbim_debug("-h Show this message\n"); + mbim_debug("-v Verbose\n"); + mbim_debug("-d [device] MBIM device\n"); + return 0; + default: + mbim_debug("illegal argument\n"); + return -1; + } + } + + if (!device) { + mbim_debug("Missing parameter: device\n"); + return -1; + } + + while (1) { + int mbim_dev_fd = open(device, O_RDWR | O_NONBLOCK | O_NOCTTY); + if (mbim_dev_fd < 0) { + mbim_debug("cannot open mbim_device %s: %s\n", device, strerror(errno)); + sleep(2); + continue; + } + mbim_debug ("mbim_dev_fd=%d\n", mbim_dev_fd); + + memset(cm_clients, 0, sizeof(cm_clients)); + mbim_send_open_msg(mbim_dev_fd, sizeof(cm_recv_buffer)); + proxy_loop(mbim_dev_fd); + } + + return -1; +} diff --git a/root/package/link4all/quectel-CM/src/quectel-qmi-proxy.c b/root/package/link4all/quectel-CM/src/quectel-qmi-proxy.c new file mode 100644 index 00000000..a8658ee9 --- /dev/null +++ b/root/package/link4all/quectel-CM/src/quectel-qmi-proxy.c @@ -0,0 +1,1602 @@ +/****************************************************************************** + @file quectel-qmi-proxy.c + @brief The qmi proxy. + + DESCRIPTION + Connectivity Management Tool for USB network adapter of Quectel wireless cellular modules. + + INITIALIZATION AND SEQUENCING REQUIREMENTS + None. + + --------------------------------------------------------------------------- + Copyright (c) 2016 - 2020 Quectel Wireless Solution, Co., Ltd. All Rights Reserved. + Quectel Wireless Solution Proprietary and Confidential. + --------------------------------------------------------------------------- +******************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef MIN +#define MIN(a, b) ((a) < (b)? (a): (b)) +#endif + +#ifndef htole32 +#if __BYTE_ORDER == __LITTLE_ENDIAN +#define htole16(x) (uint16_t)(x) +#define le16toh(x) (uint16_t)(x) +#define letoh16(x) (uint16_t)(x) +#define htole32(x) (uint32_t)(x) +#define le32toh(x) (uint32_t)(x) +#define letoh32(x) (uint32_t)(x) +#define htole64(x) (uint64_t)(x) +#define le64toh(x) (uint64_t)(x) +#define letoh64(x) (uint64_t)(x) +#else +static __inline uint16_t __bswap16(uint16_t __x) { + return (__x<<8) | (__x>>8); +} + +static __inline uint32_t __bswap32(uint32_t __x) { + return (__x>>24) | (__x>>8&0xff00) | (__x<<8&0xff0000) | (__x<<24); +} + +static __inline uint64_t __bswap64(uint64_t __x) { + return (__bswap32(__x)+0ULL<<32) | (__bswap32(__x>>32)); +} + +#define htole16(x) __bswap16(x) +#define le16toh(x) __bswap16(x) +#define letoh16(x) __bswap16(x) +#define htole32(x) __bswap32(x) +#define le32toh(x) __bswap32(x) +#define letoh32(x) __bswap32(x) +#define htole64(x) __bswap64(x) +#define le64toh(x) __bswap64(x) +#define letoh64(x) __bswap64(x) +#endif +#endif + +#define dprintf(fmt, arg...) do { printf(fmt, ##arg); } while(0) +#define SYSCHECK(c) do{if((c)<0) {dprintf("%s %d error: '%s' (code: %d)\n", __func__, __LINE__, strerror(errno), errno); return -1;}}while(0) +#define cfmakenoblock(fd) do{fcntl(fd, F_SETFL, fcntl(fd,F_GETFL) | O_NONBLOCK);}while(0) + +#define qmidev_is_pciemhi(_qmichannel) (strncmp(_qmichannel, "/dev/mhi_", strlen("/dev/mhi_")) == 0) + +typedef struct _QCQMI_HDR +{ + uint8_t IFType; + uint16_t Length; + uint8_t CtlFlags; // reserved + uint8_t QMIType; + uint8_t ClientId; +} __attribute__ ((packed)) QCQMI_HDR, *PQCQMI_HDR; + +typedef struct _QMICTL_SYNC_REQ_MSG +{ + uint8_t CtlFlags; // QMICTL_FLAG_REQUEST + uint8_t TransactionId; + uint16_t QMICTLType; // QMICTL_CTL_SYNC_REQ + uint16_t Length; // 0 +} __attribute__ ((packed)) QMICTL_SYNC_REQ_MSG, *PQMICTL_SYNC_REQ_MSG; + +typedef struct _QMICTL_SYNC_RESP_MSG +{ + uint8_t CtlFlags; // QMICTL_FLAG_RESPONSE + uint8_t TransactionId; + uint16_t QMICTLType; // QMICTL_CTL_SYNC_RESP + uint16_t Length; + uint8_t TLVType; // QCTLV_TYPE_RESULT_CODE + uint16_t TLVLength; // 0x0004 + uint16_t QMIResult; + uint16_t QMIError; +} __attribute__ ((packed)) QMICTL_SYNC_RESP_MSG, *PQMICTL_SYNC_RESP_MSG; + +typedef struct _QMICTL_SYNC_IND_MSG +{ + uint8_t CtlFlags; // QMICTL_FLAG_INDICATION + uint8_t TransactionId; + uint16_t QMICTLType; // QMICTL_REVOKE_CLIENT_ID_IND + uint16_t Length; +} __attribute__ ((packed)) QMICTL_SYNC_IND_MSG, *PQMICTL_SYNC_IND_MSG; + +typedef struct _QMICTL_GET_CLIENT_ID_REQ_MSG +{ + uint8_t CtlFlags; // QMICTL_FLAG_REQUEST + uint8_t TransactionId; + uint16_t QMICTLType; // QMICTL_GET_CLIENT_ID_REQ + uint16_t Length; + uint8_t TLVType; // QCTLV_TYPE_REQUIRED_PARAMETER + uint16_t TLVLength; // 1 + uint8_t QMIType; // QMUX type +} __attribute__ ((packed)) QMICTL_GET_CLIENT_ID_REQ_MSG, *PQMICTL_GET_CLIENT_ID_REQ_MSG; + +typedef struct _QMICTL_GET_CLIENT_ID_RESP_MSG +{ + uint8_t CtlFlags; // QMICTL_FLAG_RESPONSE + uint8_t TransactionId; + uint16_t QMICTLType; // QMICTL_GET_CLIENT_ID_RESP + uint16_t Length; + uint8_t TLVType; // QCTLV_TYPE_RESULT_CODE + uint16_t TLVLength; // 0x0004 + uint16_t QMIResult; // result code + uint16_t QMIError; // error code + uint8_t TLV2Type; // QCTLV_TYPE_REQUIRED_PARAMETER + uint16_t TLV2Length; // 2 + uint8_t QMIType; + uint8_t ClientId; +} __attribute__ ((packed)) QMICTL_GET_CLIENT_ID_RESP_MSG, *PQMICTL_GET_CLIENT_ID_RESP_MSG; + +typedef struct _QMICTL_RELEASE_CLIENT_ID_REQ_MSG +{ + uint8_t CtlFlags; // QMICTL_FLAG_REQUEST + uint8_t TransactionId; + uint16_t QMICTLType; // QMICTL_RELEASE_CLIENT_ID_REQ + uint16_t Length; + uint8_t TLVType; // QCTLV_TYPE_REQUIRED_PARAMETER + uint16_t TLVLength; // 0x0002 + uint8_t QMIType; + uint8_t ClientId; +} __attribute__ ((packed)) QMICTL_RELEASE_CLIENT_ID_REQ_MSG, *PQMICTL_RELEASE_CLIENT_ID_REQ_MSG; + +typedef struct _QMICTL_RELEASE_CLIENT_ID_RESP_MSG +{ + uint8_t CtlFlags; // QMICTL_FLAG_RESPONSE + uint8_t TransactionId; + uint16_t QMICTLType; // QMICTL_RELEASE_CLIENT_ID_RESP + uint16_t Length; + uint8_t TLVType; // QCTLV_TYPE_RESULT_CODE + uint16_t TLVLength; // 0x0004 + uint16_t QMIResult; // result code + uint16_t QMIError; // error code + uint8_t TLV2Type; // QCTLV_TYPE_REQUIRED_PARAMETER + uint16_t TLV2Length; // 2 + uint8_t QMIType; + uint8_t ClientId; +} __attribute__ ((packed)) QMICTL_RELEASE_CLIENT_ID_RESP_MSG, *PQMICTL_RELEASE_CLIENT_ID_RESP_MSG; + +// QMICTL Control Flags +#define QMICTL_CTL_FLAG_CMD 0x00 +#define QMICTL_CTL_FLAG_RSP 0x01 +#define QMICTL_CTL_FLAG_IND 0x02 + +typedef struct _QCQMICTL_MSG_HDR +{ + uint8_t CtlFlags; // 00-cmd, 01-rsp, 10-ind + uint8_t TransactionId; + uint16_t QMICTLType; + uint16_t Length; +} __attribute__ ((packed)) QCQMICTL_MSG_HDR, *PQCQMICTL_MSG_HDR; + +#define QCQMICTL_MSG_HDR_SIZE sizeof(QCQMICTL_MSG_HDR) + +typedef struct _QCQMICTL_MSG_HDR_RESP +{ + uint8_t CtlFlags; // 00-cmd, 01-rsp, 10-ind + uint8_t TransactionId; + uint16_t QMICTLType; + uint16_t Length; + uint8_t TLVType; // 0x02 - result code + uint16_t TLVLength; // 4 + uint16_t QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + uint16_t QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} __attribute__ ((packed)) QCQMICTL_MSG_HDR_RESP, *PQCQMICTL_MSG_HDR_RESP; + +typedef struct _QMICTL_GET_VERSION_REQ_MSG +{ + uint8_t CtlFlags; // QMICTL_FLAG_REQUEST + uint8_t TransactionId; + uint16_t QMICTLType; // QMICTL_GET_VERSION_REQ + uint16_t Length; // 0 + uint8_t TLVType; // QCTLV_TYPE_REQUIRED_PARAMETER + uint16_t TLVLength; // var + uint8_t QMUXTypes; // List of one byte QMUX_TYPE values + // 0xFF returns a list of versions for all + // QMUX_TYPEs implemented on the device +} __attribute__ ((packed)) QMICTL_GET_VERSION_REQ_MSG, *PQMICTL_GET_VERSION_REQ_MSG; + +typedef struct _QMUX_TYPE_VERSION_STRUCT +{ + uint8_t QMUXType; + uint16_t MajorVersion; + uint16_t MinorVersion; +} __attribute__ ((packed)) QMUX_TYPE_VERSION_STRUCT, *PQMUX_TYPE_VERSION_STRUCT; + +typedef struct _QMICTL_GET_VERSION_RESP_MSG +{ + uint8_t CtlFlags; // QMICTL_FLAG_RESPONSE + uint8_t TransactionId; + uint16_t QMICTLType; // QMICTL_GET_VERSION_RESP + uint16_t Length; + uint8_t TLVType; // QCTLV_TYPE_RESULT_CODE + uint16_t TLVLength; // 0x0004 + uint16_t QMIResult; + uint16_t QMIError; + uint8_t TLV2Type; // QCTLV_TYPE_REQUIRED_PARAMETER + uint16_t TLV2Length; // var + uint8_t NumElements; // Num of QMUX_TYPE_VERSION_STRUCT + QMUX_TYPE_VERSION_STRUCT TypeVersion[0]; +} __attribute__ ((packed)) QMICTL_GET_VERSION_RESP_MSG, *PQMICTL_GET_VERSION_RESP_MSG; + + +typedef struct _QMICTL_MSG +{ + union + { + // Message Header + QCQMICTL_MSG_HDR QMICTLMsgHdr; + QCQMICTL_MSG_HDR_RESP QMICTLMsgHdrRsp; + + // QMICTL Message + //QMICTL_SET_INSTANCE_ID_REQ_MSG SetInstanceIdReq; + //QMICTL_SET_INSTANCE_ID_RESP_MSG SetInstanceIdRsp; + QMICTL_GET_VERSION_REQ_MSG GetVersionReq; + QMICTL_GET_VERSION_RESP_MSG GetVersionRsp; + QMICTL_GET_CLIENT_ID_REQ_MSG GetClientIdReq; + QMICTL_GET_CLIENT_ID_RESP_MSG GetClientIdRsp; + //QMICTL_RELEASE_CLIENT_ID_REQ_MSG ReleaseClientIdReq; + QMICTL_RELEASE_CLIENT_ID_RESP_MSG ReleaseClientIdRsp; + //QMICTL_REVOKE_CLIENT_ID_IND_MSG RevokeClientIdInd; + //QMICTL_INVALID_CLIENT_ID_IND_MSG InvalidClientIdInd; + //QMICTL_SET_DATA_FORMAT_REQ_MSG SetDataFormatReq; + //QMICTL_SET_DATA_FORMAT_RESP_MSG SetDataFormatRsp; + QMICTL_SYNC_REQ_MSG SyncReq; + QMICTL_SYNC_RESP_MSG SyncRsp; + QMICTL_SYNC_IND_MSG SyncInd; + }; +} __attribute__ ((packed)) QMICTL_MSG, *PQMICTL_MSG; + +typedef struct _QCQMUX_MSG_HDR +{ + uint8_t CtlFlags; // 0: single QMUX Msg; 1: + uint16_t TransactionId; + uint16_t Type; + uint16_t Length; + uint8_t payload[0]; +} __attribute__ ((packed)) QCQMUX_MSG_HDR, *PQCQMUX_MSG_HDR; + +typedef struct _QCQMUX_MSG_HDR_RESP +{ + uint8_t CtlFlags; // 0: single QMUX Msg; 1: + uint16_t TransactionId; + uint16_t Type; + uint16_t Length; + uint8_t TLVType; // 0x02 - result code + uint16_t TLVLength; // 4 + uint16_t QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + uint16_t QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} __attribute__ ((packed)) QCQMUX_MSG_HDR_RESP, *PQCQMUX_MSG_HDR_RESP; + +typedef struct _QMIWDS_ADMIN_SET_DATA_FORMAT +{ + uint16_t Type; // QMUX type 0x0000 + uint16_t Length; +} __attribute__ ((packed)) QMIWDS_ADMIN_SET_DATA_FORMAT, *PQMIWDS_ADMIN_SET_DATA_FORMAT; + +typedef struct _QMIWDS_ADMIN_SET_DATA_FORMAT_TLV_QOS +{ + uint8_t TLVType; + uint16_t TLVLength; + uint8_t QOSSetting; +} __attribute__ ((packed)) QMIWDS_ADMIN_SET_DATA_FORMAT_TLV_QOS, *PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV_QOS; + +typedef struct _QMIWDS_ADMIN_SET_DATA_FORMAT_TLV +{ + uint8_t TLVType; + uint16_t TLVLength; + uint32_t Value; +} __attribute__ ((packed)) QMIWDS_ADMIN_SET_DATA_FORMAT_TLV, *PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV; + +typedef struct _QMIWDS_ENDPOINT_TLV +{ + uint8_t TLVType; + uint16_t TLVLength; + uint32_t ep_type; + uint32_t iface_id; +} __attribute__ ((packed)) QMIWDS_ENDPOINT_TLV, *PQMIWDS_ENDPOINT_TLV; + +#define QUECTEL_UL_DATA_AGG +//#define QUECTEL_QMI_MERGE +typedef uint32_t UINT; +typedef struct { + UINT size; + UINT rx_urb_size; + UINT ep_type; + UINT iface_id; + UINT MuxId; + UINT ul_data_aggregation_max_datagrams; //0x17 + UINT ul_data_aggregation_max_size ;//0x18 + UINT dl_minimum_padding; //0x1A +} QMAP_SETTING; + +typedef struct { + unsigned int size; + unsigned int rx_urb_size; + unsigned int ep_type; + unsigned int iface_id; + unsigned int qmap_mode; + unsigned int qmap_version; + unsigned int dl_minimum_padding; + char ifname[8][16]; + unsigned char mux_id[8]; +} RMNET_INFO; + +typedef struct _QMIWDS_ADMIN_SET_DATA_FORMAT_REQ_MSG +{ + uint8_t CtlFlags; // 0: single QMUX Msg; 1: + uint16_t TransactionId; + uint16_t Type; + uint16_t Length; + QMIWDS_ADMIN_SET_DATA_FORMAT_TLV_QOS QosDataFormatTlv; + QMIWDS_ADMIN_SET_DATA_FORMAT_TLV UnderlyingLinkLayerProtocolTlv; + QMIWDS_ADMIN_SET_DATA_FORMAT_TLV UplinkDataAggregationProtocolTlv; + QMIWDS_ADMIN_SET_DATA_FORMAT_TLV DownlinkDataAggregationProtocolTlv; + QMIWDS_ADMIN_SET_DATA_FORMAT_TLV DownlinkDataAggregationMaxDatagramsTlv; + QMIWDS_ADMIN_SET_DATA_FORMAT_TLV DownlinkDataAggregationMaxSizeTlv; + QMIWDS_ENDPOINT_TLV epTlv; +#ifdef QUECTEL_UL_DATA_AGG + QMIWDS_ADMIN_SET_DATA_FORMAT_TLV DlMinimumPassingTlv; + QMIWDS_ADMIN_SET_DATA_FORMAT_TLV UplinkDataAggregationMaxDatagramsTlv; + QMIWDS_ADMIN_SET_DATA_FORMAT_TLV UplinkDataAggregationMaxSizeTlv; +#endif +} __attribute__ ((packed)) QMIWDS_ADMIN_SET_DATA_FORMAT_REQ_MSG, *PQMIWDS_ADMIN_SET_DATA_FORMAT_REQ_MSG; + +typedef struct _QCQMUX_TLV +{ + uint8_t Type; + uint16_t Length; + uint8_t Value[0]; +} __attribute__ ((packed)) QCQMUX_TLV, *PQCQMUX_TLV; + +typedef struct _QMUX_MSG +{ + union + { + // Message Header + QCQMUX_MSG_HDR QMUXMsgHdr; + QCQMUX_MSG_HDR_RESP QMUXMsgHdrResp; + QMIWDS_ADMIN_SET_DATA_FORMAT_REQ_MSG SetDataFormatReq; + }; +} __attribute__ ((packed)) QMUX_MSG, *PQMUX_MSG; + +typedef struct _QCQMIMSG { + QCQMI_HDR QMIHdr; + union { + QMICTL_MSG CTLMsg; + QMUX_MSG MUXMsg; + }; +} __attribute__ ((packed)) QCQMIMSG, *PQCQMIMSG; + + +// QMUX Message Definitions -- QMI SDU +#define QMUX_CTL_FLAG_SINGLE_MSG 0x00 +#define QMUX_CTL_FLAG_COMPOUND_MSG 0x01 +#define QMUX_CTL_FLAG_TYPE_CMD 0x00 +#define QMUX_CTL_FLAG_TYPE_RSP 0x02 +#define QMUX_CTL_FLAG_TYPE_IND 0x04 +#define QMUX_CTL_FLAG_MASK_COMPOUND 0x01 +#define QMUX_CTL_FLAG_MASK_TYPE 0x06 // 00-cmd, 01-rsp, 10-ind + +#define USB_CTL_MSG_TYPE_QMI 0x01 + +#define QMICTL_FLAG_REQUEST 0x00 +#define QMICTL_FLAG_RESPONSE 0x01 +#define QMICTL_FLAG_INDICATION 0x02 + +// QMICTL Type +#define QMICTL_SET_INSTANCE_ID_REQ 0x0020 +#define QMICTL_SET_INSTANCE_ID_RESP 0x0020 +#define QMICTL_GET_VERSION_REQ 0x0021 +#define QMICTL_GET_VERSION_RESP 0x0021 +#define QMICTL_GET_CLIENT_ID_REQ 0x0022 +#define QMICTL_GET_CLIENT_ID_RESP 0x0022 +#define QMICTL_RELEASE_CLIENT_ID_REQ 0x0023 +#define QMICTL_RELEASE_CLIENT_ID_RESP 0x0023 +#define QMICTL_REVOKE_CLIENT_ID_IND 0x0024 +#define QMICTL_INVALID_CLIENT_ID_IND 0x0025 +#define QMICTL_SET_DATA_FORMAT_REQ 0x0026 +#define QMICTL_SET_DATA_FORMAT_RESP 0x0026 +#define QMICTL_SYNC_REQ 0x0027 +#define QMICTL_SYNC_RESP 0x0027 +#define QMICTL_SYNC_IND 0x0027 + +#define QCTLV_TYPE_REQUIRED_PARAMETER 0x01 + +// Define QMI Type +typedef enum _QMI_SERVICE_TYPE +{ + QMUX_TYPE_CTL = 0x00, + QMUX_TYPE_WDS = 0x01, + QMUX_TYPE_DMS = 0x02, + QMUX_TYPE_NAS = 0x03, + QMUX_TYPE_QOS = 0x04, + QMUX_TYPE_WMS = 0x05, + QMUX_TYPE_PDS = 0x06, + QMUX_TYPE_UIM = 0x0B, + QMUX_TYPE_WDS_IPV6 = 0x11, + QMUX_TYPE_WDS_ADMIN = 0x1A, + QMUX_TYPE_MAX = 0xFF, + QMUX_TYPE_ALL = 0xFF +} QMI_SERVICE_TYPE; + +#define QMIWDS_ADMIN_SET_DATA_FORMAT_REQ 0x0020 +#define QMIWDS_ADMIN_SET_DATA_FORMAT_RESP 0x0020 + +struct qlistnode +{ + struct qlistnode *next; + struct qlistnode *prev; +}; + +#define qnode_to_item(node, container, member) \ + (container *) (((char*) (node)) - offsetof(container, member)) + +#define qlist_for_each(node, list) \ + for (node = (list)->next; node != (list); node = node->next) + +#define qlist_empty(list) ((list) == (list)->next) +#define qlist_head(list) ((list)->next) +#define qlist_tail(list) ((list)->prev) + +typedef struct { + struct qlistnode qnode; + uint8_t ClientFd; + QCQMIMSG qmi[0]; +} QMI_PROXY_MSG; + +typedef struct { + struct qlistnode qnode; + uint8_t QMIType; + uint8_t ClientId; + unsigned AccessTime; +} QMI_PROXY_CLINET; + +typedef struct { + struct qlistnode qnode; + struct qlistnode client_qnode; + uint8_t ClientFd; + unsigned AccessTime; +} QMI_PROXY_CONNECTION; + +#ifdef QUECTEL_QMI_MERGE +#define MERGE_PACKET_IDENTITY 0x2c7c +#define MERGE_PACKET_VERSION 0x0001 +#define MERGE_PACKET_MAX_PAYLOAD_SIZE 56 +typedef struct __QMI_MSG_HEADER { + uint16_t idenity; + uint16_t version; + uint16_t cur_len; + uint16_t total_len; +} QMI_MSG_HEADER; + +typedef struct __QMI_MSG_PACKET { + QMI_MSG_HEADER header; + uint16_t len; + char buf[4096]; +} QMI_MSG_PACKET; +#endif + +static void qlist_init(struct qlistnode *node) +{ + node->next = node; + node->prev = node; +} + +static void qlist_add_tail(struct qlistnode *head, struct qlistnode *item) +{ + item->next = head; + item->prev = head->prev; + head->prev->next = item; + head->prev = item; +} + +static void qlist_remove(struct qlistnode *item) +{ + item->next->prev = item->prev; + item->prev->next = item->next; +} + +static int qmi_proxy_quit = 0; +static pthread_t thread_id = 0; +static int cdc_wdm_fd = -1; +static int qmi_proxy_server_fd = -1; +static struct qlistnode qmi_proxy_connection; +static struct qlistnode qmi_proxy_ctl_msg; +static PQCQMIMSG s_pCtlReq; +static PQCQMIMSG s_pCtlRsq; +static pthread_mutex_t s_ctlmutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t s_ctlcond = PTHREAD_COND_INITIALIZER; + +#ifdef QUECTEL_QMI_MERGE +static int merge_qmi_rsp_packet(void *buf, ssize_t *src_size) { + static QMI_MSG_PACKET s_QMIPacket; + QMI_MSG_HEADER *header = NULL; + ssize_t size = *src_size; + + if((uint16_t)size < sizeof(QMI_MSG_HEADER)) + return -1; + + header = (QMI_MSG_HEADER *)buf; + if(le16toh(header->idenity) != MERGE_PACKET_IDENTITY || le16toh(header->version) != MERGE_PACKET_VERSION || le16toh(header->cur_len) > le16toh(header->total_len)) + return -1; + + if(le16toh(header->cur_len) == le16toh(header->total_len)) { + *src_size = le16toh(header->total_len); + memcpy(buf, buf + sizeof(QMI_MSG_HEADER), *src_size); + s_QMIPacket.len = 0; + return 0; + } + + memcpy(s_QMIPacket.buf + s_QMIPacket.len, buf + sizeof(QMI_MSG_HEADER), le16toh(header->cur_len)); + s_QMIPacket.len += le16toh(header->cur_len); + + if (le16toh(header->cur_len) < MERGE_PACKET_MAX_PAYLOAD_SIZE || s_QMIPacket.len >= le16toh(header->total_len)) { + memcpy(buf, s_QMIPacket.buf, s_QMIPacket.len); + *src_size = s_QMIPacket.len; + s_QMIPacket.len = 0; + return 0; + } + + return -1; +} +#endif + +static void ql_get_driver_rmnet_info(char *ifname, RMNET_INFO *rmnet_info) { + int ifc_ctl_sock; + struct ifreq ifr; + int rc; + int request = 0x89F3; + unsigned char data[512]; + + memset(rmnet_info, 0x00, sizeof(*rmnet_info)); + + ifc_ctl_sock = socket(AF_INET, SOCK_DGRAM, 0); + if (ifc_ctl_sock <= 0) { + dprintf("socket() failed: %s\n", strerror(errno)); + return; + } + + memset(&ifr, 0, sizeof(struct ifreq)); + strncpy(ifr.ifr_name, ifname, IFNAMSIZ); + ifr.ifr_name[IFNAMSIZ - 1] = 0; + ifr.ifr_ifru.ifru_data = (void *)data; + + rc = ioctl(ifc_ctl_sock, request, &ifr); + if (rc < 0) { + dprintf("ioctl(0x%x, rmnet_info) failed: %s, rc=%d", request, strerror(errno), rc); + } + else { + memcpy(rmnet_info, data, sizeof(*rmnet_info)); + } + + close(ifc_ctl_sock); +} + +static void ql_set_driver_qmap_setting(char *ifname, QMAP_SETTING *qmap_settings) { + int ifc_ctl_sock; + struct ifreq ifr; + int rc; + int request = 0x89F2; + + ifc_ctl_sock = socket(AF_INET, SOCK_DGRAM, 0); + if (ifc_ctl_sock <= 0) { + dprintf("socket() failed: %s\n", strerror(errno)); + return; + } + + memset(&ifr, 0, sizeof(struct ifreq)); + strncpy(ifr.ifr_name, ifname, IFNAMSIZ); + ifr.ifr_name[IFNAMSIZ - 1] = 0; + ifr.ifr_ifru.ifru_data = (void *)qmap_settings; + + rc = ioctl(ifc_ctl_sock, request, &ifr); + if (rc < 0) { + dprintf("ioctl(0x%x, qmap_settings) failed: %s, rc=%d\n", request, strerror(errno), rc); + } + + close(ifc_ctl_sock); +} + +static void setTimespecRelative(struct timespec *p_ts, long long msec) +{ + struct timeval tv; + + gettimeofday(&tv, (struct timezone *) NULL); + + /* what's really funny about this is that I know + pthread_cond_timedwait just turns around and makes this + a relative time again */ + p_ts->tv_sec = tv.tv_sec + (msec / 1000); + p_ts->tv_nsec = (tv.tv_usec + (msec % 1000) * 1000L ) * 1000L; + if ((unsigned long)p_ts->tv_nsec >= 1000000000UL) { + p_ts->tv_sec += 1; + p_ts->tv_nsec -= 1000000000UL; + } +} + +static int pthread_cond_timeout_np(pthread_cond_t *cond, pthread_mutex_t * mutex, unsigned msecs) { + if (msecs != 0) { + unsigned i; + unsigned t = msecs/4; + int ret = 0; + + if (t == 0) + t = 1; + + for (i = 0; i < msecs; i += t) { + struct timespec ts; + setTimespecRelative(&ts, t); + ret = pthread_cond_timedwait(cond, mutex, &ts); //to advoid system time change + if (ret != ETIMEDOUT) { + if(ret) dprintf("ret=%d, msecs=%u, t=%u", ret, msecs, t); + break; + } + } + + return ret; + } else { + return pthread_cond_wait(cond, mutex); + } +} + +static int create_local_server(const char *name) { + int sockfd = -1; + int reuse_addr = 1; + struct sockaddr_un sockaddr; + socklen_t alen; + + /*Create server socket*/ + SYSCHECK(sockfd = socket(AF_LOCAL, SOCK_STREAM, 0)); + + memset(&sockaddr, 0, sizeof(sockaddr)); + sockaddr.sun_family = AF_LOCAL; + sockaddr.sun_path[0] = 0; + memcpy(sockaddr.sun_path + 1, name, strlen(name) ); + + alen = strlen(name) + offsetof(struct sockaddr_un, sun_path) + 1; + SYSCHECK(setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse_addr,sizeof(reuse_addr))); + if(bind(sockfd, (struct sockaddr *)&sockaddr, alen) < 0) { + close(sockfd); + dprintf("%s bind %s errno: %d (%s)\n", __func__, name, errno, strerror(errno)); + return -1; + } + + dprintf("local server: %s sockfd = %d\n", name, sockfd); + cfmakenoblock(sockfd); + listen(sockfd, 1); + + return sockfd; +} + +static void accept_qmi_connection(int serverfd) { + int clientfd = -1; + unsigned char addr[128]; + socklen_t alen = sizeof(addr); + QMI_PROXY_CONNECTION *qmi_con; + + clientfd = accept(serverfd, (struct sockaddr *)addr, &alen); + + qmi_con = (QMI_PROXY_CONNECTION *)malloc(sizeof(QMI_PROXY_CONNECTION)); + if (qmi_con) { + qlist_init(&qmi_con->qnode); + qlist_init(&qmi_con->client_qnode); + qmi_con->ClientFd= clientfd; + qmi_con->AccessTime = 0; + dprintf("+++ ClientFd=%d\n", qmi_con->ClientFd); + qlist_add_tail(&qmi_proxy_connection, &qmi_con->qnode); + } + + cfmakenoblock(clientfd); +} + +static void cleanup_qmi_connection(int clientfd) { + struct qlistnode *con_node, *qmi_node; + + qlist_for_each(con_node, &qmi_proxy_connection) { + QMI_PROXY_CONNECTION *qmi_con = qnode_to_item(con_node, QMI_PROXY_CONNECTION, qnode); + + if (qmi_con->ClientFd == clientfd) { + while (!qlist_empty(&qmi_con->client_qnode)) { + QMI_PROXY_CLINET *qmi_client = qnode_to_item(qlist_head(&qmi_con->client_qnode), QMI_PROXY_CLINET, qnode); + + dprintf("xxx ClientFd=%d QMIType=%d ClientId=%d\n", qmi_con->ClientFd, qmi_client->QMIType, qmi_client->ClientId); + + qlist_remove(&qmi_client->qnode); + free(qmi_client); + } + + qlist_for_each(qmi_node, &qmi_proxy_ctl_msg) { + QMI_PROXY_MSG *qmi_msg = qnode_to_item(qmi_node, QMI_PROXY_MSG, qnode); + + if (qmi_msg->ClientFd == qmi_con->ClientFd) { + qlist_remove(&qmi_msg->qnode); + free(qmi_msg); + break; + } + } + + dprintf("--- ClientFd=%d\n", qmi_con->ClientFd); + close(qmi_con->ClientFd); + qlist_remove(&qmi_con->qnode); + free(qmi_con); + break; + } + } +} + +static void get_client_id(QMI_PROXY_CONNECTION *qmi_con, PQMICTL_GET_CLIENT_ID_RESP_MSG pClient) { + if (pClient->QMIResult == 0 && pClient->QMIError == 0) { + QMI_PROXY_CLINET *qmi_client = (QMI_PROXY_CLINET *)malloc(sizeof(QMI_PROXY_CLINET)); + + qlist_init(&qmi_client->qnode); + qmi_client->QMIType = pClient->QMIType; + qmi_client->ClientId = pClient->ClientId; + qmi_client->AccessTime = 0; + + dprintf("+++ ClientFd=%d QMIType=%d ClientId=%d\n", qmi_con->ClientFd, qmi_client->QMIType, qmi_client->ClientId); + qlist_add_tail(&qmi_con->client_qnode, &qmi_client->qnode); + } +} + +static void release_client_id(QMI_PROXY_CONNECTION *qmi_con, PQMICTL_RELEASE_CLIENT_ID_RESP_MSG pClient) { + struct qlistnode *client_node; + + if (pClient->QMIResult == 0 && pClient->QMIError == 0) { + qlist_for_each (client_node, &qmi_con->client_qnode) { + QMI_PROXY_CLINET *qmi_client = qnode_to_item(client_node, QMI_PROXY_CLINET, qnode); + + if (pClient->QMIType == qmi_client->QMIType && pClient->ClientId == qmi_client->ClientId) { + dprintf("--- ClientFd=%d QMIType=%d ClientId=%d\n", qmi_con->ClientFd, qmi_client->QMIType, qmi_client->ClientId); + qlist_remove(&qmi_client->qnode); + free(qmi_client); + break; + } + } + } +} + +static int verbose_debug = 0; +static int send_qmi_to_cdc_wdm(PQCQMIMSG pQMI) { + struct pollfd pollfds[]= {{cdc_wdm_fd, POLLOUT, 0}}; + ssize_t ret = 0; + + do { + ret = poll(pollfds, sizeof(pollfds)/sizeof(pollfds[0]), 5000); + } while ((ret < 0) && (errno == EINTR)); + + if (pollfds[0].revents & POLLOUT) { + ssize_t size = le16toh(pQMI->QMIHdr.Length) + 1; + ret = write(cdc_wdm_fd, pQMI, size); + if (verbose_debug) + { + ssize_t i; + printf("w %d %zd: ", cdc_wdm_fd, ret); + for (i = 0; i < size; i++) + printf("%02x ", ((uint8_t *)pQMI)[i]); + printf("\n"); + } + } + + return ret; +} + +static int send_qmi_to_client(PQCQMIMSG pQMI, int clientFd) { + struct pollfd pollfds[]= {{clientFd, POLLOUT, 0}}; + ssize_t ret = 0; + + do { + ret = poll(pollfds, sizeof(pollfds)/sizeof(pollfds[0]), 5000); + } while ((ret < 0) && (errno == EINTR)); + + if (pollfds[0].revents & POLLOUT) { + ssize_t size = le16toh(pQMI->QMIHdr.Length) + 1; + ret = write(clientFd, pQMI, size); + if (verbose_debug) + { + ssize_t i; + printf("w %d %zd: ", clientFd, ret); + for (i = 0; i < 16; i++) + printf("%02x ", ((uint8_t *)pQMI)[i]); + printf("\n"); + } + } + + return ret; +} + +static int modem_reset_flag = 0; +static void recv_qmi(PQCQMIMSG pQMI, unsigned size) { + struct qlistnode *con_node, *client_node; + + if (qmi_proxy_server_fd <= 0) { + pthread_mutex_lock(&s_ctlmutex); + + if (s_pCtlReq != NULL) { + if (pQMI->QMIHdr.QMIType == QMUX_TYPE_CTL + && s_pCtlReq->CTLMsg.QMICTLMsgHdrRsp.TransactionId == pQMI->CTLMsg.QMICTLMsgHdrRsp.TransactionId) { + s_pCtlRsq = malloc(size); + memcpy(s_pCtlRsq, pQMI, size); + pthread_cond_signal(&s_ctlcond); + } + else if (pQMI->QMIHdr.QMIType != QMUX_TYPE_CTL + && s_pCtlReq->MUXMsg.QMUXMsgHdr.TransactionId == pQMI->MUXMsg.QMUXMsgHdr.TransactionId) { + s_pCtlRsq = malloc(size); + memcpy(s_pCtlRsq, pQMI, size); + pthread_cond_signal(&s_ctlcond); + } + } + + pthread_mutex_unlock(&s_ctlmutex); + } + else if (pQMI->QMIHdr.QMIType == QMUX_TYPE_CTL) { + if (pQMI->CTLMsg.QMICTLMsgHdr.CtlFlags == QMICTL_CTL_FLAG_RSP) { + if (!qlist_empty(&qmi_proxy_ctl_msg)) { + QMI_PROXY_MSG *qmi_msg = qnode_to_item(qlist_head(&qmi_proxy_ctl_msg), QMI_PROXY_MSG, qnode); + + qlist_for_each(con_node, &qmi_proxy_connection) { + QMI_PROXY_CONNECTION *qmi_con = qnode_to_item(con_node, QMI_PROXY_CONNECTION, qnode); + + if (qmi_con->ClientFd == qmi_msg->ClientFd) { + send_qmi_to_client(pQMI, qmi_msg->ClientFd); + + if (le16toh(pQMI->CTLMsg.QMICTLMsgHdrRsp.QMICTLType) == QMICTL_GET_CLIENT_ID_RESP) + get_client_id(qmi_con, &pQMI->CTLMsg.GetClientIdRsp); + else if ((le16toh(pQMI->CTLMsg.QMICTLMsgHdrRsp.QMICTLType) == QMICTL_RELEASE_CLIENT_ID_RESP) || + (le16toh(pQMI->CTLMsg.QMICTLMsgHdrRsp.QMICTLType) == QMICTL_REVOKE_CLIENT_ID_IND)) { + release_client_id(qmi_con, &pQMI->CTLMsg.ReleaseClientIdRsp); + if (le16toh(pQMI->CTLMsg.QMICTLMsgHdrRsp.QMICTLType) == QMICTL_REVOKE_CLIENT_ID_IND) + modem_reset_flag = 1; + } + else { + } + } + } + + qlist_remove(&qmi_msg->qnode); + free(qmi_msg); + } + } + + if (!qlist_empty(&qmi_proxy_ctl_msg)) { + QMI_PROXY_MSG *qmi_msg = qnode_to_item(qlist_head(&qmi_proxy_ctl_msg), QMI_PROXY_MSG, qnode); + + qlist_for_each(con_node, &qmi_proxy_connection) { + QMI_PROXY_CONNECTION *qmi_con = qnode_to_item(con_node, QMI_PROXY_CONNECTION, qnode); + + if (qmi_con->ClientFd == qmi_msg->ClientFd) { + send_qmi_to_cdc_wdm(qmi_msg->qmi); + } + } + } + } + else { + qlist_for_each(con_node, &qmi_proxy_connection) { + QMI_PROXY_CONNECTION *qmi_con = qnode_to_item(con_node, QMI_PROXY_CONNECTION, qnode); + + qlist_for_each(client_node, &qmi_con->client_qnode) { + QMI_PROXY_CLINET *qmi_client = qnode_to_item(client_node, QMI_PROXY_CLINET, qnode); + if (pQMI->QMIHdr.QMIType == qmi_client->QMIType) { + if (pQMI->QMIHdr.ClientId == 0 || pQMI->QMIHdr.ClientId == qmi_client->ClientId) { + send_qmi_to_client(pQMI, qmi_con->ClientFd); + } + } + } + } + } +} + +static int send_qmi(PQCQMIMSG pQMI, unsigned size, int clientfd) { + if (qmi_proxy_server_fd <= 0) { + send_qmi_to_cdc_wdm(pQMI); + } + else if (pQMI->QMIHdr.QMIType == QMUX_TYPE_CTL) { + QMI_PROXY_MSG *qmi_msg; + + if (qlist_empty(&qmi_proxy_ctl_msg)) + send_qmi_to_cdc_wdm(pQMI); + + qmi_msg = malloc(sizeof(QMI_PROXY_MSG) + size); + qlist_init(&qmi_msg->qnode); + qmi_msg->ClientFd = clientfd; + memcpy(qmi_msg->qmi, pQMI, size); + qlist_add_tail(&qmi_proxy_ctl_msg, &qmi_msg->qnode); + } + else { + send_qmi_to_cdc_wdm(pQMI); + } + + return 0; +} + +static int send_qmi_timeout(PQCQMIMSG pRequest, PQCQMIMSG *ppResponse, unsigned mseconds) { + int ret; + + pthread_mutex_lock(&s_ctlmutex); + + s_pCtlReq = pRequest; + s_pCtlRsq = NULL; + if (ppResponse) *ppResponse = NULL; + + send_qmi_to_cdc_wdm(pRequest); + ret = pthread_cond_timeout_np(&s_ctlcond, &s_ctlmutex, mseconds); + if (!ret) { + if (s_pCtlRsq && ppResponse) { + *ppResponse = s_pCtlRsq; + } else if (s_pCtlRsq) { + free(s_pCtlRsq); + } + } else { + dprintf("%s ret=%d\n", __func__, ret); + } + + s_pCtlReq = NULL; + pthread_mutex_unlock(&s_ctlmutex); + + return ret; +} + +static PQCQMUX_TLV qmi_find_tlv (PQCQMIMSG pQMI, uint8_t TLVType) { + int Length = 0; + + while (Length < le16toh(pQMI->MUXMsg.QMUXMsgHdr.Length)) { + PQCQMUX_TLV pTLV = (PQCQMUX_TLV)(&pQMI->MUXMsg.QMUXMsgHdr.payload[Length]); + + //dprintf("TLV {%02x, %04x}\n", pTLV->Type, pTLV->Length); + if (pTLV->Type == TLVType) { + return pTLV; + } + + Length += (le16toh((pTLV->Length)) + sizeof(QCQMUX_TLV)); + } + + return NULL; +} + +static int qmi_proxy_init(char *qmi_device, char *ifname) { + unsigned i; + int ret; + QCQMIMSG _QMI; + PQCQMIMSG pQMI = &_QMI; + PQCQMIMSG pRsp; + uint8_t TransactionId = 0xC1; + uint8_t WDAClientId = 0; + unsigned rx_urb_size = 0; + unsigned ep_type, iface_id; + unsigned qmap_version; + QMAP_SETTING qmap_settings = {0}; + RMNET_INFO rmnet_info; + + dprintf("%s enter\n", __func__); + + pQMI->QMIHdr.IFType = USB_CTL_MSG_TYPE_QMI; + pQMI->QMIHdr.CtlFlags = 0x00; + pQMI->QMIHdr.QMIType = QMUX_TYPE_CTL; + pQMI->QMIHdr.ClientId= 0x00; + + pQMI->CTLMsg.QMICTLMsgHdr.CtlFlags = QMICTL_FLAG_REQUEST; + + for (i = 0; i < 10; i++) { + pQMI->CTLMsg.SyncReq.TransactionId = TransactionId++; + pQMI->CTLMsg.SyncReq.QMICTLType = QMICTL_SYNC_REQ; + pQMI->CTLMsg.SyncReq.Length = 0; + + pQMI->QMIHdr.Length = + htole16(le16toh(pQMI->CTLMsg.QMICTLMsgHdr.Length) + sizeof(QCQMI_HDR) + sizeof(QCQMICTL_MSG_HDR) - 1); + + ret = send_qmi_timeout(pQMI, NULL, 1000); + if (!ret) + break; + } + + if (ret) + goto qmi_proxy_init_fail; + + pQMI->CTLMsg.GetVersionReq.TransactionId = TransactionId++; + pQMI->CTLMsg.GetVersionReq.QMICTLType = htole16(QMICTL_GET_VERSION_REQ); + pQMI->CTLMsg.GetVersionReq.Length = htole16(0x0004); + pQMI->CTLMsg.GetVersionReq.TLVType= QCTLV_TYPE_REQUIRED_PARAMETER; + pQMI->CTLMsg.GetVersionReq.TLVLength = htole16(0x0001); + pQMI->CTLMsg.GetVersionReq.QMUXTypes = QMUX_TYPE_ALL; + + pQMI->QMIHdr.Length = htole16(le16toh(pQMI->CTLMsg.QMICTLMsgHdr.Length) + sizeof(QCQMI_HDR) + sizeof(QCQMICTL_MSG_HDR) - 1); + + ret = send_qmi_timeout(pQMI, &pRsp, 3000); + if (ret || (pRsp == NULL)) + goto qmi_proxy_init_fail; + + if (pRsp) { + uint8_t NumElements = 0; + if (pRsp->CTLMsg.QMICTLMsgHdrRsp.QMUXResult ||pRsp->CTLMsg.QMICTLMsgHdrRsp.QMUXError) { + dprintf("QMICTL_GET_VERSION_REQ QMUXResult=%d, QMUXError=%d\n", + le16toh(pRsp->CTLMsg.QMICTLMsgHdrRsp.QMUXResult), le16toh(pRsp->CTLMsg.QMICTLMsgHdrRsp.QMUXError)); + goto qmi_proxy_init_fail; + } + + for (NumElements = 0; NumElements < pRsp->CTLMsg.GetVersionRsp.NumElements; NumElements++) { + if (verbose_debug) + dprintf("QMUXType = %02x Version = %d.%d\n", + pRsp->CTLMsg.GetVersionRsp.TypeVersion[NumElements].QMUXType, + le16toh(pRsp->CTLMsg.GetVersionRsp.TypeVersion[NumElements].MajorVersion), + le16toh(pRsp->CTLMsg.GetVersionRsp.TypeVersion[NumElements].MinorVersion)); + } + } + free(pRsp); + + pQMI->CTLMsg.GetClientIdReq.TransactionId = TransactionId++; + pQMI->CTLMsg.GetClientIdReq.QMICTLType = htole16(QMICTL_GET_CLIENT_ID_REQ); + pQMI->CTLMsg.GetClientIdReq.Length = htole16(0x0004); + pQMI->CTLMsg.GetClientIdReq.TLVType= QCTLV_TYPE_REQUIRED_PARAMETER; + pQMI->CTLMsg.GetClientIdReq.TLVLength = htole16(0x0001); + pQMI->CTLMsg.GetClientIdReq.QMIType = QMUX_TYPE_WDS_ADMIN; + + pQMI->QMIHdr.Length = htole16(le16toh(pQMI->CTLMsg.QMICTLMsgHdr.Length) + sizeof(QCQMI_HDR) + sizeof(QCQMICTL_MSG_HDR) - 1); + + ret = send_qmi_timeout(pQMI, &pRsp, 3000); + if (ret || (pRsp == NULL)) + goto qmi_proxy_init_fail; + + if (pRsp) { + if (pRsp->CTLMsg.QMICTLMsgHdrRsp.QMUXResult ||pRsp->CTLMsg.QMICTLMsgHdrRsp.QMUXError) { + dprintf("QMICTL_GET_CLIENT_ID_REQ QMUXResult=%d, QMUXError=%d\n", + le16toh(pRsp->CTLMsg.QMICTLMsgHdrRsp.QMUXResult), le16toh(pRsp->CTLMsg.QMICTLMsgHdrRsp.QMUXError)); + goto qmi_proxy_init_fail; + } + + WDAClientId = pRsp->CTLMsg.GetClientIdRsp.ClientId; + if (verbose_debug) dprintf("WDAClientId = %d\n", WDAClientId); + } + free(pRsp); + + rx_urb_size = 16*1024; //must same as rx_urb_size defined in GobiNet&qmi_wwan driver //SDX24&SDX55 support 32KB + if(qmidev_is_pciemhi(qmi_device)) + ep_type = 0x03; + else + ep_type = 0x02; + iface_id = 0x04; + qmap_version = 5; + + qmap_settings.size = sizeof(qmap_settings); + qmap_settings.ul_data_aggregation_max_datagrams = 11; //16->11 + qmap_settings.ul_data_aggregation_max_size = 8*1024; + qmap_settings.dl_minimum_padding = 0; //no effect when register to real netowrk + + ql_get_driver_rmnet_info(ifname, &rmnet_info); + if (rmnet_info.size) { + rx_urb_size = rmnet_info.rx_urb_size; + ep_type = rmnet_info.ep_type; + iface_id = rmnet_info.iface_id; + qmap_version = rmnet_info.qmap_version; + qmap_settings.dl_minimum_padding = rmnet_info.dl_minimum_padding; + } + + pQMI->QMIHdr.IFType = USB_CTL_MSG_TYPE_QMI; + pQMI->QMIHdr.CtlFlags = 0x00; + pQMI->QMIHdr.QMIType = QMUX_TYPE_WDS_ADMIN; + pQMI->QMIHdr.ClientId= WDAClientId; + + pQMI->MUXMsg.QMUXMsgHdr.CtlFlags = QMICTL_FLAG_REQUEST; + pQMI->MUXMsg.QMUXMsgHdr.TransactionId = htole16(TransactionId++); + + pQMI->MUXMsg.SetDataFormatReq.Type = htole16(QMIWDS_ADMIN_SET_DATA_FORMAT_REQ); + pQMI->MUXMsg.SetDataFormatReq.Length = htole16(sizeof(QMIWDS_ADMIN_SET_DATA_FORMAT_REQ_MSG) - sizeof(QCQMUX_MSG_HDR)); + +//Indicates whether the Quality of Service(QOS) data format is used by the client. + pQMI->MUXMsg.SetDataFormatReq.QosDataFormatTlv.TLVType = 0x10; + pQMI->MUXMsg.SetDataFormatReq.QosDataFormatTlv.TLVLength = htole16(0x0001); + pQMI->MUXMsg.SetDataFormatReq.QosDataFormatTlv.QOSSetting = 0; /* no-QOS header */ +//Underlying Link Layer Protocol + pQMI->MUXMsg.SetDataFormatReq.UnderlyingLinkLayerProtocolTlv.TLVType = 0x11; + pQMI->MUXMsg.SetDataFormatReq.UnderlyingLinkLayerProtocolTlv.TLVLength = htole16(4); + pQMI->MUXMsg.SetDataFormatReq.UnderlyingLinkLayerProtocolTlv.Value = htole32(0x02); /* Set IP mode */ +//Uplink (UL) data aggregation protocol to be used for uplink data transfer. + pQMI->MUXMsg.SetDataFormatReq.UplinkDataAggregationProtocolTlv.TLVType = 0x12; + pQMI->MUXMsg.SetDataFormatReq.UplinkDataAggregationProtocolTlv.TLVLength = htole16(4); + pQMI->MUXMsg.SetDataFormatReq.UplinkDataAggregationProtocolTlv.Value = htole32(qmap_version); //UL QMAP is enabled +//Downlink (DL) data aggregation protocol to be used for downlink data transfer + pQMI->MUXMsg.SetDataFormatReq.DownlinkDataAggregationProtocolTlv.TLVType = 0x13; + pQMI->MUXMsg.SetDataFormatReq.DownlinkDataAggregationProtocolTlv.TLVLength = htole16(4); + pQMI->MUXMsg.SetDataFormatReq.DownlinkDataAggregationProtocolTlv.Value = htole32(qmap_version); //UL QMAP is enabled +//Maximum number of datagrams in a single aggregated packet on downlink + pQMI->MUXMsg.SetDataFormatReq.DownlinkDataAggregationMaxDatagramsTlv.TLVType = 0x15; + pQMI->MUXMsg.SetDataFormatReq.DownlinkDataAggregationMaxDatagramsTlv.TLVLength = htole16(4); + pQMI->MUXMsg.SetDataFormatReq.DownlinkDataAggregationMaxDatagramsTlv.Value = htole32((rx_urb_size>2048)?(rx_urb_size/1024):1); +//Maximum size in bytes of a single aggregated packet allowed on downlink + pQMI->MUXMsg.SetDataFormatReq.DownlinkDataAggregationMaxSizeTlv.TLVType = 0x16; + pQMI->MUXMsg.SetDataFormatReq.DownlinkDataAggregationMaxSizeTlv.TLVLength = htole16(4); + pQMI->MUXMsg.SetDataFormatReq.DownlinkDataAggregationMaxSizeTlv.Value = htole32(rx_urb_size); +//Peripheral End Point ID + pQMI->MUXMsg.SetDataFormatReq.epTlv.TLVType = 0x17; + pQMI->MUXMsg.SetDataFormatReq.epTlv.TLVLength = htole16(8); + pQMI->MUXMsg.SetDataFormatReq.epTlv.ep_type = htole32(ep_type); // DATA_EP_TYPE_BAM_DMUX + pQMI->MUXMsg.SetDataFormatReq.epTlv.iface_id = htole32(iface_id); + +#ifdef QUECTEL_UL_DATA_AGG +//Maximum number of datagrams in a single aggregated packet on uplink + pQMI->MUXMsg.SetDataFormatReq.DlMinimumPassingTlv.TLVType = 0x19; + pQMI->MUXMsg.SetDataFormatReq.DlMinimumPassingTlv.TLVLength = htole16(4); + pQMI->MUXMsg.SetDataFormatReq.DlMinimumPassingTlv.Value = htole32(qmap_settings.dl_minimum_padding); + +//Maximum number of datagrams in a single aggregated packet on uplink + pQMI->MUXMsg.SetDataFormatReq.UplinkDataAggregationMaxDatagramsTlv.TLVType = 0x1B; + pQMI->MUXMsg.SetDataFormatReq.UplinkDataAggregationMaxDatagramsTlv.TLVLength = htole16(4); + pQMI->MUXMsg.SetDataFormatReq.UplinkDataAggregationMaxDatagramsTlv.Value = htole32(qmap_settings.ul_data_aggregation_max_datagrams); + +//Maximum size in bytes of a single aggregated packet allowed on downlink + pQMI->MUXMsg.SetDataFormatReq.UplinkDataAggregationMaxSizeTlv.TLVType = 0x1C; + pQMI->MUXMsg.SetDataFormatReq.UplinkDataAggregationMaxSizeTlv.TLVLength = htole16(4); + pQMI->MUXMsg.SetDataFormatReq.UplinkDataAggregationMaxSizeTlv.Value = htole32(qmap_settings.ul_data_aggregation_max_size); +#endif + + pQMI->QMIHdr.Length = htole16(le16toh(pQMI->MUXMsg.QMUXMsgHdr.Length) + sizeof(QCQMI_HDR) + sizeof(QCQMUX_MSG_HDR) - 1); + + ret = send_qmi_timeout(pQMI, &pRsp, 3000); + if (ret || (pRsp == NULL)) + goto qmi_proxy_init_fail; + + if (pRsp) { + PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV pFormat; + + if (pRsp->MUXMsg.QMUXMsgHdrResp.QMUXResult || pRsp->MUXMsg.QMUXMsgHdrResp.QMUXError) { + dprintf("QMIWDS_ADMIN_SET_DATA_FORMAT_REQ QMUXResult=%d, QMUXError=%d\n", + le16toh(pRsp->MUXMsg.QMUXMsgHdrResp.QMUXResult), le16toh(pRsp->MUXMsg.QMUXMsgHdrResp.QMUXError)); + goto qmi_proxy_init_fail; + } + + pFormat = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV)qmi_find_tlv(pRsp, 0x11); + if (pFormat) + dprintf("link_prot %d\n", le32toh(pFormat->Value)); + pFormat = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV)qmi_find_tlv(pRsp, 0x12); + if (pFormat) + dprintf("ul_data_aggregation_protocol %d\n", le32toh(pFormat->Value)); + pFormat = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV)qmi_find_tlv(pRsp, 0x13); + if (pFormat) + dprintf("dl_data_aggregation_protocol %d\n", le32toh(pFormat->Value)); + pFormat = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV)qmi_find_tlv(pRsp, 0x15); + if (pFormat) + dprintf("dl_data_aggregation_max_datagrams %d\n", le32toh(pFormat->Value)); + pFormat = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV)qmi_find_tlv(pRsp, 0x16); + if (pFormat) { + dprintf("dl_data_aggregation_max_size %d\n", le32toh(pFormat->Value)); + rx_urb_size = le32toh(pFormat->Value); + } + pFormat = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV)qmi_find_tlv(pRsp, 0x17); + if (pFormat) { + qmap_settings.ul_data_aggregation_max_datagrams = MIN(qmap_settings.ul_data_aggregation_max_datagrams, le32toh(pFormat->Value)); + dprintf("ul_data_aggregation_max_datagrams %d\n", qmap_settings.ul_data_aggregation_max_datagrams); + } + pFormat = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV)qmi_find_tlv(pRsp, 0x18); + if (pFormat) { + qmap_settings.ul_data_aggregation_max_size = MIN(qmap_settings.ul_data_aggregation_max_size, le32toh(pFormat->Value)); + dprintf("ul_data_aggregation_max_size %d\n", qmap_settings.ul_data_aggregation_max_size); + } + + if (qmap_settings.ul_data_aggregation_max_datagrams > 1) { + ql_set_driver_qmap_setting(ifname, &qmap_settings); + } + } + free(pRsp); + + dprintf("%s finished, rx_urb_size is %u\n", __func__, rx_urb_size); + return 0; + +qmi_proxy_init_fail: + dprintf("%s failed\n", __func__); + return -1; +} + +static void qmi_start_server(const char* servername) { + qmi_proxy_server_fd = create_local_server(servername); + printf("%s: qmi_proxy_server_fd = %d\n", __func__, qmi_proxy_server_fd); + if (qmi_proxy_server_fd == -1) { + dprintf("%s Failed to create %s, errno: %d (%s)\n", __func__, "quectel-qmi-proxy", errno, strerror(errno)); + } +} + +static void qmi_close_server(const char* servername) { + if (qmi_proxy_server_fd != -1) { + dprintf("%s %s close server\n", __func__, servername); + close(qmi_proxy_server_fd); + qmi_proxy_server_fd = -1; + } +} + +static uint8_t qmi_buf[2048]; +static void *qmi_proxy_loop(void *param) +{ + PQCQMIMSG pQMI = (PQCQMIMSG)qmi_buf; + struct qlistnode *con_node; + QMI_PROXY_CONNECTION *qmi_con; + + (void)param; + dprintf("%s enter thread_id %p\n", __func__, (void *)pthread_self()); + + qlist_init(&qmi_proxy_connection); + qlist_init(&qmi_proxy_ctl_msg); + + while (cdc_wdm_fd > 0 && qmi_proxy_quit == 0) { + struct pollfd pollfds[2+64]; + int ne, ret, nevents = 0; + ssize_t nreads; + + pollfds[nevents].fd = cdc_wdm_fd; + pollfds[nevents].events = POLLIN; + pollfds[nevents].revents= 0; + nevents++; + + if (qmi_proxy_server_fd > 0) { + pollfds[nevents].fd = qmi_proxy_server_fd; + pollfds[nevents].events = POLLIN; + pollfds[nevents].revents= 0; + nevents++; + } + + qlist_for_each(con_node, &qmi_proxy_connection) { + qmi_con = qnode_to_item(con_node, QMI_PROXY_CONNECTION, qnode); + + pollfds[nevents].fd = qmi_con->ClientFd; + pollfds[nevents].events = POLLIN; + pollfds[nevents].revents= 0; + nevents++; + + if (nevents == (sizeof(pollfds)/sizeof(pollfds[0]))) + break; + } + +#if 0 + dprintf("poll "); + for (ne = 0; ne < nevents; ne++) { + dprintf("%d ", pollfds[ne].fd); + } + dprintf("\n"); +#endif + + do { + //ret = poll(pollfds, nevents, -1); + ret = poll(pollfds, nevents, (qmi_proxy_server_fd > 0) ? -1 : 200); + } while (ret < 0 && errno == EINTR && qmi_proxy_quit == 0); + + if (ret < 0) { + dprintf("%s poll=%d, errno: %d (%s)\n", __func__, ret, errno, strerror(errno)); + goto qmi_proxy_loop_exit; + } + + for (ne = 0; ne < nevents; ne++) { + int fd = pollfds[ne].fd; + short revents = pollfds[ne].revents; + + if (revents & (POLLERR | POLLHUP | POLLNVAL)) { + dprintf("%s poll fd = %d, revents = %04x\n", __func__, fd, revents); + if (fd == cdc_wdm_fd) { + goto qmi_proxy_loop_exit; + } else if(fd == qmi_proxy_server_fd) { + + } else { + cleanup_qmi_connection(fd); + } + + continue; + } + + if (!(pollfds[ne].revents & POLLIN)) { + continue; + } + + if (fd == qmi_proxy_server_fd) { + accept_qmi_connection(fd); + } + else if (fd == cdc_wdm_fd) { + nreads = read(fd, pQMI, sizeof(qmi_buf)); + if (verbose_debug) + { + ssize_t i; + printf("r %d %zd: ", fd, nreads); + for (i = 0; i < nreads; i++) + printf("%02x ", ((uint8_t *)pQMI)[i]); + printf("\n"); + } + if (nreads <= 0) { + dprintf("%s read=%d errno: %d (%s)\n", __func__, (int)nreads, errno, strerror(errno)); + goto qmi_proxy_loop_exit; + } +#ifdef QUECTEL_QMI_MERGE + if(merge_qmi_rsp_packet(pQMI, &nreads)) + continue; +#endif + if (nreads != (le16toh(pQMI->QMIHdr.Length) + 1)) { + dprintf("%s nreads=%d, pQCQMI->QMIHdr.Length = %d\n", __func__, (int)nreads, le16toh(pQMI->QMIHdr.Length)); + continue; + } + + recv_qmi(pQMI, nreads); + if (modem_reset_flag) + goto qmi_proxy_loop_exit; + } + else { + nreads = read(fd, pQMI, sizeof(qmi_buf)); + if (verbose_debug) + { + ssize_t i; + printf("r %d %zd: ", fd, nreads); + for (i = 0; i < 16; i++) + printf("%02x ", ((uint8_t *)pQMI)[i]); + printf("\n"); + } + if (nreads <= 0) { + dprintf("%s read=%d errno: %d (%s)", __func__, (int)nreads, errno, strerror(errno)); + cleanup_qmi_connection(fd); + break; + } + + if (nreads != (le16toh(pQMI->QMIHdr.Length) + 1)) { + dprintf("%s nreads=%d, pQCQMI->QMIHdr.Length = %d\n", __func__, (int)nreads, le16toh(pQMI->QMIHdr.Length)); + continue; + } + + send_qmi(pQMI, nreads, fd); + } + } + } + +qmi_proxy_loop_exit: + while (!qlist_empty(&qmi_proxy_connection)) { + QMI_PROXY_CONNECTION *qmi_con = qnode_to_item(qlist_head(&qmi_proxy_connection), QMI_PROXY_CONNECTION, qnode); + + cleanup_qmi_connection(qmi_con->ClientFd); + } + + dprintf("%s exit, thread_id %p\n", __func__, (void *)pthread_self()); + + return NULL; +} + +static int dir_get_child(const char *dirname, char *buff, unsigned bufsize) +{ + struct dirent *entptr = NULL; + DIR *dirptr = opendir(dirname); + if (!dirptr) + goto error; + while ((entptr = readdir(dirptr))) { + if (entptr->d_name[0] == '.') + continue; + snprintf(buff, bufsize, "%s", entptr->d_name); + break; + } + + closedir(dirptr); + return 0; +error: + buff[0] = '\0'; + if (dirptr) closedir(dirptr); + return -1; +} + +static int mhidevice_detect(char *device_name, char *ifname) { + if (!access("/sys/class/net/pcie_mhi0", F_OK)) + strcpy(ifname, "pcie_mhi0"); + else if (!access("/sys/class/net/rmnet_mhi0", F_OK)) + strcpy(ifname, "rmnet_mhi0"); + else { + goto error; + } + + if (!access("/dev/mhi_QMI0", F_OK)) { + strcpy(device_name, "/dev/mhi_QMI0"); + } + else { + goto error; + } + + return 0; +error: + return -1; +} + +static int qmidevice_detect(char *device_name, char *ifname) { + struct dirent* ent = NULL; + DIR *pDir; + + char dir[255] = "/sys/bus/usb/devices"; + pDir = opendir(dir); + if (pDir) { + struct { + char subdir[255 * 3]; + char qmifile[255]; + char ifname[255]; + } *pl; + char qmidevice[255] = {'\0'}; + + pl = (typeof(pl)) malloc(sizeof(*pl)); + memset(pl, 0x00, sizeof(*pl)); + + while ((ent = readdir(pDir)) != NULL) { + char idVendor[4+1] = {0}; + char idProduct[4+1] = {0}; + int fd = 0; + + memset(pl, 0x00, sizeof(*pl)); + snprintf(pl->subdir, sizeof(pl->subdir), "%s/%s/idVendor", dir, ent->d_name); + fd = open(pl->subdir, O_RDONLY); + if (fd > 0) { + read(fd, idVendor, 4); + close(fd); + } + + snprintf(pl->subdir, sizeof(pl->subdir), "%s/%s/idProduct", dir, ent->d_name); + fd = open(pl->subdir, O_RDONLY); + if (fd > 0) { + read(fd, idProduct, 4); + close(fd); + } + + if (strncasecmp(idVendor, "05c6", 4) && strncasecmp(idVendor, "2c7c", 4)) + continue; + + dprintf("Find %s/%s idVendor=%s idProduct=%s\n", dir, ent->d_name, idVendor, idProduct); + snprintf(pl->subdir, sizeof(pl->subdir), "%s/%s:1.4/usbmisc", dir, ent->d_name); + if (access(pl->subdir, R_OK)) { + snprintf(pl->subdir, sizeof(pl->subdir), "%s/%s:1.4/usb", dir, ent->d_name); + if (access(pl->subdir, R_OK)) { + dprintf("no GobiQMI/usbmic/usb found in %s/%s:1.4\n", dir, ent->d_name); + continue; + } + } + + dir_get_child(pl->subdir, pl->qmifile, sizeof(pl->qmifile)); + snprintf(qmidevice, sizeof(qmidevice), "/dev/%.*s", 100, pl->qmifile); + strcpy(device_name, qmidevice); + + snprintf(pl->subdir, sizeof(pl->subdir), "%s/%s:1.4/net", dir, ent->d_name); + dir_get_child(pl->subdir, pl->ifname, sizeof(pl->ifname)); + strcpy(ifname, pl->ifname); + } + closedir(pDir); + free(pl); + if (device_name[0] == '\0' || ifname[0] == '\0') { + goto error; + } + return 0; + } + +error: + return -1; +} + +static void usage(void) { + dprintf(" -d A valid qmi device\n" + " default /dev/cdc-wdm0, but cdc-wdm0 may be invalid\n" + " -i netcard name\n" + " -v Will show all details\n"); +} + +static void sig_action(int sig) { + if (qmi_proxy_quit == 0) { + qmi_proxy_quit = 1; + if (thread_id) + pthread_kill(thread_id, sig); + } +} + +int main(int argc, char *argv[]) { + int opt; + char cdc_wdm[32+1] = {'\0'}; + char ifname[32+1] = {'\0'}; + int retry_times = 0; + char servername[64] = {0}; + + optind = 1; + + signal(SIGINT, sig_action); + + while ( -1 != (opt = getopt(argc, argv, "d:i:vh"))) { + switch (opt) { + case 'd': + strcpy(cdc_wdm, optarg); + break; + case 'v': + verbose_debug = 1; + break; + case 'i': + strcpy(ifname, optarg); + break; + default: + usage(); + return 0; + } + } + + if (cdc_wdm[0] == '\0' || ifname[0] == '\0') { + if(qmidevice_detect(cdc_wdm, ifname) && mhidevice_detect(cdc_wdm, ifname)) { + dprintf("network interface '%s' or qmidev '%s' is not exist\n", ifname, cdc_wdm); + return -1; + } + + } + + if (cdc_wdm[0] == '\0' || ifname[0] == '\0') { + dprintf("network interface '%s' or qmidev '%s' is not exist\n", ifname, cdc_wdm); + return -1; + } + + sprintf(servername, "quectel-qmi-proxy%d", cdc_wdm[strlen(cdc_wdm)-1]-'0'); + + if (access(cdc_wdm, R_OK | W_OK)) { + dprintf("Fail to access %s, errno: %d (%s). break\n", cdc_wdm, errno, strerror(errno)); + return -1; + } + + while (qmi_proxy_quit == 0) { + if (access(cdc_wdm, R_OK | W_OK)) { + dprintf("Fail to access %s, errno: %d (%s). continue\n", cdc_wdm, errno, strerror(errno)); + // wait device + sleep(3); + continue; + } + + dprintf("Will use cdc-wdm='%s', ifname='%s'\n", cdc_wdm, ifname); + + cdc_wdm_fd = open(cdc_wdm, O_RDWR | O_NONBLOCK | O_NOCTTY); + if (cdc_wdm_fd == -1) { + dprintf("Failed to open %s, errno: %d (%s). break\n", cdc_wdm, errno, strerror(errno)); + return -1; + } + cfmakenoblock(cdc_wdm_fd); + + /* no qmi_proxy_loop lives, create one */ + pthread_create(&thread_id, NULL, qmi_proxy_loop, NULL); + /* try to redo init if failed, init function must be successfully */ + while (qmi_proxy_init(cdc_wdm, ifname) != 0) { + if (retry_times < 5) { + dprintf("fail to init proxy, try again in 2 seconds.\n"); + sleep(2); + retry_times++; + } else { + dprintf("has failed too much times, restart the modem and have a try...\n"); + break; + } + /* break loop if modem is detached */ + if (access(cdc_wdm, F_OK|R_OK|W_OK)) + break; + } + retry_times = 0; + qmi_start_server(servername); + pthread_join(thread_id, NULL); + + /* close local server at last */ + qmi_close_server(servername); + close(cdc_wdm_fd); + /* DO RESTART IN 20s IF MODEM RESET ITSELF */ + if (modem_reset_flag) { + unsigned int time_to_wait = 20; + while (time_to_wait) { + time_to_wait = sleep(time_to_wait); + } + modem_reset_flag = 0; + } + } + + return 0; +} diff --git a/root/package/link4all/quectel-CM/src/udhcpc.bak.c b/root/package/link4all/quectel-CM/src/udhcpc.bak.c new file mode 100644 index 00000000..6c89ebcb --- /dev/null +++ b/root/package/link4all/quectel-CM/src/udhcpc.bak.c @@ -0,0 +1,716 @@ +/****************************************************************************** + @file udhcpc.c + @brief call DHCP tools to obtain IP address. + + DESCRIPTION + Connectivity Management Tool for USB network adapter of Quectel wireless cellular modules. + + INITIALIZATION AND SEQUENCING REQUIREMENTS + None. + + --------------------------------------------------------------------------- + Copyright (c) 2016 - 2020 Quectel Wireless Solution, Co., Ltd. All Rights Reserved. + Quectel Wireless Solution Proprietary and Confidential. + --------------------------------------------------------------------------- +******************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "util.h" +#include "QMIThread.h" + +static void get_iface(const char *ifname,char *result) +{ + FILE *fp; + char cmd[128]; + sprintf(cmd,"/sbin/getiface.sh %s",ifname); + fp=popen(cmd, "r"); + fgets(result,sizeof(result),fp); + //printf("%s",result); +} + +static __inline in_addr_t qmi2addr(uint32_t __x) { + return (__x>>24) | (__x>>8&0xff00) | (__x<<8&0xff0000) | (__x<<24); +} + +static int ql_system(const char *shell_cmd) { + dbg_time("%s", shell_cmd); + return system(shell_cmd); +} + +static void ifc_init_ifr(const char *name, struct ifreq *ifr) +{ + memset(ifr, 0, sizeof(struct ifreq)); + strncpy(ifr->ifr_name, name, IFNAMSIZ); + ifr->ifr_name[IFNAMSIZ - 1] = 0; +} + +static void ql_set_mtu(const char *ifname, int ifru_mtu) { + int inet_sock; + struct ifreq ifr; + + inet_sock = socket(AF_INET, SOCK_DGRAM, 0); + + if (inet_sock > 0) { + ifc_init_ifr(ifname, &ifr); + + if (!ioctl(inet_sock, SIOCGIFMTU, &ifr)) { + if (ifr.ifr_ifru.ifru_mtu != ifru_mtu) { + dbg_time("change mtu %d -> %d", ifr.ifr_ifru.ifru_mtu , ifru_mtu); + ifr.ifr_ifru.ifru_mtu = ifru_mtu; + ioctl(inet_sock, SIOCSIFMTU, &ifr); + } + } + + close(inet_sock); + } +} + +static int ifc_get_addr(const char *name, in_addr_t *addr) +{ + int inet_sock; + struct ifreq ifr; + int ret = 0; + + inet_sock = socket(AF_INET, SOCK_DGRAM, 0); + + ifc_init_ifr(name, &ifr); + if (addr != NULL) { + ret = ioctl(inet_sock, SIOCGIFADDR, &ifr); + if (ret < 0) { + *addr = 0; + } else { + *addr = ((struct sockaddr_in*) &ifr.ifr_addr)->sin_addr.s_addr; + } + } + close(inet_sock); + return ret; +} + +static short ifc_get_flags(const char *ifname) +{ + int inet_sock; + struct ifreq ifr; + int ret = 0; + + inet_sock = socket(AF_INET, SOCK_DGRAM, 0); + + if (inet_sock > 0) { + ifc_init_ifr(ifname, &ifr); + + if (!ioctl(inet_sock, SIOCGIFFLAGS, &ifr)) { + ret = ifr.ifr_ifru.ifru_flags; + } + + close(inet_sock); + } + + return ret; +} + +static int ql_netcard_ipv4_address_check(const char *ifname, in_addr_t ip) { + in_addr_t addr = 0; + + ifc_get_addr(ifname, &addr); + return addr == ip; +} + +static int ql_raw_ip_mode_check(const char *ifname, uint32_t ip) { + int fd; + char raw_ip[128]; + char shell_cmd[128]; + char mode[2] = "X"; + int mode_change = 0; + + if (ql_netcard_ipv4_address_check(ifname, qmi2addr(ip))) + return 0; + + snprintf(raw_ip, sizeof(raw_ip), "/sys/class/net/%s/qmi/raw_ip", ifname); + if (access(raw_ip, F_OK)) + return 0; + + fd = open(raw_ip, O_RDWR | O_NONBLOCK | O_NOCTTY); + if (fd < 0) { + dbg_time("%s %d fail to open(%s), errno:%d (%s)", __FILE__, __LINE__, raw_ip, errno, strerror(errno)); + return 0; + } + + read(fd, mode, 2); + if (mode[0] == '0' || mode[0] == 'N') { + dbg_time("File:%s Line:%d udhcpc fail to get ip address, try next:", __func__, __LINE__); + snprintf(shell_cmd, sizeof(shell_cmd), "ifconfig %s down", ifname); + ql_system(shell_cmd); + dbg_time("echo Y > /sys/class/net/%s/qmi/raw_ip", ifname); + mode[0] = 'Y'; + write(fd, mode, 2); + mode_change = 1; + snprintf(shell_cmd, sizeof(shell_cmd), "ifconfig %s up", ifname); + ql_system(shell_cmd); + } + + close(fd); + return mode_change; +} + +static void* udhcpc_thread_function(void* arg) { + FILE * udhcpc_fp; + char *udhcpc_cmd = (char *)arg; + + if (udhcpc_cmd == NULL) + return NULL; + + dbg_time("%s", udhcpc_cmd); + udhcpc_fp = popen(udhcpc_cmd, "r"); + free(udhcpc_cmd); + if (udhcpc_fp) { + char buf[0xff]; + + buf[sizeof(buf)-1] = '\0'; + while((fgets(buf, sizeof(buf)-1, udhcpc_fp)) != NULL) { + if ((strlen(buf) > 1) && (buf[strlen(buf) - 1] == '\n')) + buf[strlen(buf) - 1] = '\0'; + dbg_time("%s", buf); + } + + pclose(udhcpc_fp); + } + + return NULL; +} + +//#define USE_DHCLIENT +#ifdef USE_DHCLIENT +static int dhclient_alive = 0; +#endif +static int dibbler_client_alive = 0; + +void ql_set_driver_link_state(PROFILE_T *profile, int link_state) { + char link_file[128]; + int fd; + int new_state = 0; + + snprintf(link_file, sizeof(link_file), "/sys/class/net/%s/link_state", profile->usbnet_adapter); + fd = open(link_file, O_RDWR | O_NONBLOCK | O_NOCTTY); + if (fd == -1) { + if (errno != ENOENT) + dbg_time("Fail to access %s, errno: %d (%s)", link_file, errno, strerror(errno)); + return; + } + + if (profile->qmap_mode <= 1) + new_state = !!link_state; + else { + //0x80 means link off this pdp + new_state = (link_state ? 0x00 : 0x80) + profile->pdp; + } + + snprintf(link_file, sizeof(link_file), "%d\n", new_state); + write(fd, link_file, sizeof(link_file)); + + if (link_state == 0 && profile->qmapnet_adapter && strcmp(profile->qmapnet_adapter, profile->usbnet_adapter)) { + size_t rc; + + lseek(fd, 0, SEEK_SET); + rc = read(fd, link_file, sizeof(link_file)); + if (rc > 1 && (!strncasecmp(link_file, "0\n", 2) || !strncasecmp(link_file, "0x0\n", 4))) { + snprintf(link_file, sizeof(link_file), "ifconfig %s down", profile->usbnet_adapter); + ql_system(link_file); + } + } + + close(fd); +} + +static const char *ipv4Str(const uint32_t Address) { + static char str[] = {"255.225.255.255"}; + uint8_t *ip = (uint8_t *)&Address; + + snprintf(str, sizeof(str), "%d.%d.%d.%d", ip[3], ip[2], ip[1], ip[0]); + return str; +} + +static const char *ipv6Str(const UCHAR Address[16]) { + static char str[64]; + uint16_t ip[8]; + int i; + for (i = 0; i < 8; i++) { + ip[i] = (Address[i*2]<<8) + Address[i*2+1]; + } + + snprintf(str, sizeof(str), "%x:%x:%x:%x:%x:%x:%x:%x", + ip[0], ip[1], ip[2], ip[3], ip[4], ip[5], ip[6], ip[7]); + + return str; +} + +void update_ipv4_address(const char *ifname, const char *ip, const char *gw, unsigned prefix) +{ + char shell_cmd[128]; + + if (!ifname) + return; + + if (!access("/sbin/ip", X_OK)) { + snprintf(shell_cmd, sizeof(shell_cmd), "ip -%d address flush dev %s", 4, ifname); + ql_system(shell_cmd); + + snprintf(shell_cmd, sizeof(shell_cmd), "ip -%d address add %s/%u dev %s", 4, ip, prefix, ifname); + ql_system(shell_cmd); + + //ping6 www.qq.com + snprintf(shell_cmd, sizeof(shell_cmd), "ip -%d route add default via %s dev %s", 4, gw, ifname); + ql_system(shell_cmd); + } else { + unsigned n = (0xFFFFFFFF >> (32 - prefix)) << (32 - prefix); + n = (n>>24) | (n>>8&0xff00) | (n<<8&0xff0000) | (n<<24); + + snprintf(shell_cmd, sizeof(shell_cmd), "ifconfig %s %s netmask %s", ifname, ip, ipv4Str(n)); + ql_system(shell_cmd); + + //Resetting default routes + snprintf(shell_cmd, sizeof(shell_cmd), "route del default dev %s", ifname); + while(!system(shell_cmd)); + + // snprintf(shell_cmd, sizeof(shell_cmd), "route add default gw %s dev %s", gw, ifname); + char buf2[128]; + bzero(buf2,sizeof(buf2)); + get_iface(ifname,buf2); + printf("ifname is %s,buf2 value is: %s\n",ifname,buf2); + snprintf(shell_cmd, sizeof(shell_cmd), "ifup %s",buf2); + printf("openwrt system run \"ifup %s\" \n",buf2); + ql_system(shell_cmd); + } +} + +void update_ipv6_address(const char *ifname, const char *ip, const char *gw, unsigned prefix) { + char shell_cmd[128]; + + (void)gw; + if (!access("/sbin/ip", X_OK)) { + snprintf(shell_cmd, sizeof(shell_cmd), "ip -%d address flush dev %s", 6, ifname); + ql_system(shell_cmd); + + snprintf(shell_cmd, sizeof(shell_cmd), "ip -%d address add %s/%u dev %s", 6, ip, prefix, ifname); + ql_system(shell_cmd); + + //ping6 www.qq.com + snprintf(shell_cmd, sizeof(shell_cmd), "ip -%d route add default dev %s", 6, ifname); + ql_system(shell_cmd); + } else { + snprintf(shell_cmd, sizeof(shell_cmd), "ifconfig %s %s/%d", ifname, ip, prefix); + ql_system(shell_cmd); + + // snprintf(shell_cmd, sizeof(shell_cmd), "route -A inet6 add default dev %s", ifname); + char buf2[128]; + bzero(buf2,sizeof(buf2)); + get_iface(ifname,buf2); + printf("ifname is %s,buf2 value is: %s\n",ifname,buf2); + snprintf(shell_cmd, sizeof(shell_cmd), "ifup %s",buf2); + printf("openwrt system run \"ifup %s\" \n",buf2); + ql_system(shell_cmd); + } +} + +static void update_ip_address_by_qmi(const char *ifname, const IPV4_T *ipv4, const IPV6_T *ipv6) { + char *d1, *d2; + + if (ipv4 && ipv4->Address) { + d1 = strdup(ipv4Str(ipv4->Address)); + d2 = strdup(ipv4Str(ipv4->Gateway)); + unsigned prefix = 0; + unsigned n = ipv4->SubnetMask; + n = (n>>24) | (n>>8&0xff00) | (n<<8&0xff0000) | (n<<24); + + for (prefix = 0; prefix < 32; prefix++) { + if (n&(1<DnsPrimary) { + d1 = strdup(ipv4Str(ipv4->DnsPrimary)); + d2 = strdup(ipv4Str(ipv4->DnsSecondary ? ipv4->DnsSecondary : ipv4->DnsPrimary)); + update_resolv_conf(4, ifname, d1, d2); + free(d1); free(d2); + } + } + + if (ipv6 && ipv6->Address[0] && ipv6->PrefixLengthIPAddr) { + d1 = strdup(ipv6Str(ipv6->Address)); + d2 = strdup(ipv6Str(ipv6->Gateway)); + + update_ipv6_address(ifname, d1, d2, ipv6->PrefixLengthIPAddr); + free(d1); free(d2); + + //Adding DNS + if (ipv6->DnsPrimary[0]) { + d1 = strdup(ipv6Str(ipv6->DnsPrimary)); + d2 = strdup(ipv6Str(ipv6->DnsSecondary[0] ? ipv6->DnsSecondary : ipv6->DnsPrimary)); + update_resolv_conf(6, ifname, d1, d2); + free(d1); free(d2); + } + } +} + +//#define QL_OPENWER_NETWORK_SETUP +#ifdef QL_OPENWER_NETWORK_SETUP +static const char *openwrt_lan = "br-lan"; +static const char *openwrt_wan = "wwan0"; + +static int ql_openwrt_system(const char *cmd) { + int i; + int ret = 1; + char shell_cmd[128]; + + snprintf(shell_cmd, sizeof(shell_cmd), "%s 2>1 > /dev/null", cmd); + + for (i = 0; i < 15; i++) { + dbg_time("%s", cmd); + ret = system(shell_cmd); + if (!ret) + break; + sleep(1); + } + + return ret; +} + +static int ql_openwrt_is_wan(const char *ifname) { + if (openwrt_lan == NULL) { + system("uci show network.wan.ifname"); + } + + if (strcmp(ifname, openwrt_wan)) + return 0; + + return 1; +} + +static void ql_openwrt_setup_wan(const char *ifname, const IPV4_T *ipv4) { + FILE *fp = NULL; + char config[64]; + + snprintf(config, sizeof(config), "/tmp/rmnet_%s_ipv4config", ifname); + + if (ipv4 == NULL) { + if (ql_openwrt_is_wan(ifname)) + ql_openwrt_system("ifdown wan"); + return; + } + + fp = fopen(config, "w"); + if (fp == NULL) + return; + + fprintf(fp, "IFNAME=\"%s\"\n", ifname); + fprintf(fp, "PUBLIC_IP=\"%s\"\n", ipv4Str(ipv4->Address)); + fprintf(fp, "NETMASK=\"%s\"\n", ipv4Str(ipv4->SubnetMask)); + fprintf(fp, "GATEWAY=\"%s\"\n", ipv4Str(ipv4->Gateway)); + fprintf(fp, "DNSSERVERS=\"%s", ipv4Str(ipv4->DnsPrimary)); + if (ipv4->DnsSecondary != 0) + fprintf(fp, " %s", ipv4Str(ipv4->DnsSecondary)); + fprintf(fp, "\"\n"); + + fclose(fp); + + if (!ql_openwrt_is_wan(ifname)) + return; + + ql_openwrt_system("ifup wan"); +} + +static void ql_openwrt_setup_wan6(const char *ifname, const IPV6_T *ipv6) { + FILE *fp = NULL; + char config[64]; + int first_ifup; + + snprintf(config, sizeof(config), "/tmp/rmnet_%s_ipv6config", ifname); + + if (ipv6 == NULL) { + if (ql_openwrt_is_wan(ifname)) + ql_openwrt_system("ifdown wan6"); + return; + } + + first_ifup = (access(config, F_OK) != 0); + + fp = fopen(config, "w"); + if (fp == NULL) + return; + + fprintf(fp, "IFNAME=\"%s\"\n", ifname); + fprintf(fp, "PUBLIC_IP=\"%s\"\n", ipv6Str(ipv6->Address)); + fprintf(fp, "NETMASK=\"%s\"\n", ipv6Str(ipv6->SubnetMask)); + fprintf(fp, "GATEWAY=\"%s\"\n", ipv6Str(ipv6->Gateway)); + fprintf(fp, "PrefixLength=\"%d\"\n", ipv6->PrefixLengthIPAddr); + fprintf(fp, "DNSSERVERS=\"%s", ipv6Str(ipv6->DnsPrimary)); + if (ipv6->DnsSecondary[0]) + fprintf(fp, " %s", ipv6Str(ipv6->DnsSecondary)); + fprintf(fp, "\"\n"); + + fclose(fp); + + if (!ql_openwrt_is_wan(ifname)) + return; + + if (first_ifup) + ql_openwrt_system("ifup wan6"); + else + ql_openwrt_system("/etc/init.d/network restart"); //make PC to release old IPV6 address, and RS new IPV6 address + +#if 1 //TODO? why need this? + if (openwrt_lan) { + int i; + char shell_cmd[128]; + UCHAR Address[16] = {0}; + + ql_openwrt_system(("ifstatus lan")); + + for (i = 0; i < (ipv6->PrefixLengthIPAddr/8); i++) + Address[i] = ipv6->Address[i]; + + snprintf(shell_cmd, sizeof(shell_cmd), "ip route del %s/%u dev %s", ipv6Str(Address), ipv6->PrefixLengthIPAddr, ifname); + ql_openwrt_system(shell_cmd); + + snprintf(shell_cmd, sizeof(shell_cmd), "ip route add %s/%u dev %s", ipv6Str(Address), ipv6->PrefixLengthIPAddr, openwrt_lan); + ql_system(shell_cmd); + } +#endif +} +#endif + +void udhcpc_start(PROFILE_T *profile) { + char *ifname = profile->usbnet_adapter; + char shell_cmd[128]; + + ql_set_driver_link_state(profile, 1); + + if (profile->qmapnet_adapter) { + ifname = profile->qmapnet_adapter; + } + + if (profile->rawIP && profile->ipv4.Address && profile->ipv4.Mtu) { + ql_set_mtu(ifname, (profile->ipv4.Mtu)); + } + + if (strcmp(ifname, profile->usbnet_adapter)) { + snprintf(shell_cmd, sizeof(shell_cmd), "ifconfig %s up", profile->usbnet_adapter); + ql_system(shell_cmd); + if (ifc_get_flags(ifname)&IFF_UP) { + snprintf(shell_cmd, sizeof(shell_cmd), "ifconfig %s down", ifname); + ql_system(shell_cmd); + } + } + + snprintf(shell_cmd, sizeof(shell_cmd), "ifconfig %s up", ifname); + ql_system(shell_cmd); + + if (profile->ipv4.Address) { + if (profile->PCSCFIpv4Addr1) + dbg_time("pcscf1: %s", ipv4Str(profile->PCSCFIpv4Addr1)); + if (profile->PCSCFIpv4Addr2) + dbg_time("pcscf2: %s", ipv4Str(profile->PCSCFIpv4Addr2)); + } + + if (profile->ipv6.Address[0] && profile->ipv6.PrefixLengthIPAddr) { + if (profile->PCSCFIpv6Addr1[0]) + dbg_time("pcscf1: %s", ipv6Str(profile->PCSCFIpv6Addr1)); + if (profile->PCSCFIpv6Addr2[0]) + dbg_time("pcscf2: %s", ipv6Str(profile->PCSCFIpv6Addr2)); + } + +#if 1 //for bridge mode, only one public IP, so do udhcpc manually + if (ql_bridge_mode_detect(profile)) { + return; + } +#endif + +//because must use udhcpc to obtain IP when working on ETH mode, +//so it is better also use udhcpc to obtain IP when working on IP mode. +//use the same policy for all modules +#if 0 + if (profile->rawIP != 0) //mdm9x07/ec25,ec20 R2.0 + { + update_ip_address_by_qmi(ifname, &profile->ipv4, &profile->ipv6); + return; + } +#endif + +/* Do DHCP using busybox tools */ + { + char udhcpc_cmd[128]; + pthread_attr_t udhcpc_thread_attr; + pthread_t udhcpc_thread_id; + + pthread_attr_init(&udhcpc_thread_attr); + pthread_attr_setdetachstate(&udhcpc_thread_attr, PTHREAD_CREATE_DETACHED); + + if (profile->ipv4.Address) { +#ifdef USE_DHCLIENT + snprintf(udhcpc_cmd, sizeof(udhcpc_cmd), "dhclient -4 -d --no-pid %s", ifname); + dhclient_alive++; +#else + if (access("/usr/share/udhcpc/default.script", X_OK) + && access("/etc//udhcpc/default.script", X_OK)) { + dbg_time("No default.script found, it should be in '/usr/share/udhcpc/' or '/etc//udhcpc' depend on your udhcpc version!"); + } + + //-f,--foreground Run in foreground + //-b,--background Background if lease is not obtained + //-n,--now Exit if lease is not obtained + //-q,--quit Exit after obtaining lease + //-t,--retries N Send up to N discover packets (default 3) + // snprintf(udhcpc_cmd, sizeof(udhcpc_cmd), "busybox udhcpc -f -n -q -t 5 -i %s", ifname); + char buf2[128]; + bzero(buf2,sizeof(buf2)); + get_iface(ifname,buf2); + printf("ifname is %s,buf2 value is: %s\n",ifname,buf2); + snprintf(udhcpc_cmd, sizeof(udhcpc_cmd), "ifup %s",buf2); + printf("openwrt system run \"ifup %s\" \n",buf2); +#endif + +#if 1 //for OpenWrt + if (!access("/lib/netifd/dhcp.script", X_OK) && !access("/sbin/ifup", X_OK) && !access("/sbin/ifstatus", X_OK)) { + dbg_time("you are use OpenWrt?"); + dbg_time("should not calling udhcpc manually?"); + dbg_time("should modify /etc/config/network as below?"); + dbg_time("config interface wan"); + dbg_time("\toption ifname %s", ifname); + dbg_time("\toption proto dhcp"); + dbg_time("should use \"/sbin/ifstaus wan\" to check %s 's status?", ifname); + } +#endif + +#ifdef USE_DHCLIENT + pthread_create(&udhcpc_thread_id, &udhcpc_thread_attr, udhcpc_thread_function, (void*)strdup(udhcpc_cmd)); + sleep(1); +#else + pthread_create(&udhcpc_thread_id, NULL, udhcpc_thread_function, (void*)strdup(udhcpc_cmd)); + pthread_join(udhcpc_thread_id, NULL); + if (ql_raw_ip_mode_check(ifname, profile->ipv4.Address)) { + pthread_create(&udhcpc_thread_id, NULL, udhcpc_thread_function, (void*)strdup(udhcpc_cmd)); + pthread_join(udhcpc_thread_id, NULL); + } + + if (!ql_netcard_ipv4_address_check(ifname, qmi2addr(profile->ipv4.Address))) { + //no udhcpc's default.script exist, directly set ip and dns + update_ip_address_by_qmi(ifname, &profile->ipv4, NULL); + } +#endif +#ifdef QL_OPENWER_NETWORK_SETUP + ql_openwrt_setup_wan(ifname, &profile->ipv4); +#endif + } + + if (profile->ipv6.Address[0] && profile->ipv6.PrefixLengthIPAddr) { +#if 1 + //module do not support DHCPv6, only support 'Router Solicit' + //and it seem if enable /proc/sys/net/ipv6/conf/all/forwarding, Kernel do not send RS + const char *forward_file = "/proc/sys/net/ipv6/conf/all/forwarding"; + int forward_fd = open(forward_file, O_RDONLY); + if (forward_fd > 0) { + char forward_state[2]; + read(forward_fd, forward_state, 2); + if (forward_state[0] == '1') { + //dbg_time("%s enabled, kernel maybe donot send 'Router Solicit'", forward_file); + } + close(forward_fd); + } + + update_ip_address_by_qmi(ifname, NULL, &profile->ipv6); + + if (profile->ipv6.DnsPrimary[0] || profile->ipv6.DnsSecondary[0]) { + char dns1str[64], dns2str[64]; + + if (profile->ipv6.DnsPrimary[0]) { + strcpy(dns1str, ipv6Str(profile->ipv6.DnsPrimary)); + } + + if (profile->ipv6.DnsSecondary[0]) { + strcpy(dns2str, ipv6Str(profile->ipv6.DnsSecondary)); + } + + update_resolv_conf(6, ifname, profile->ipv6.DnsPrimary[0] ? dns1str : NULL, + profile->ipv6.DnsSecondary[0] != '\0' ? dns2str : NULL); + } + +#ifdef QL_OPENWER_NETWORK_SETUP + ql_openwrt_setup_wan6(ifname, &profile->ipv6); +#endif +#else +#ifdef USE_DHCLIENT + snprintf(udhcpc_cmd, sizeof(udhcpc_cmd), "dhclient -6 -d --no-pid %s", ifname); + dhclient_alive++; +#else + /* + DHCPv6: Dibbler - a portable DHCPv6 + 1. download from http://klub.com.pl/dhcpv6/ + 2. cross-compile + 2.1 ./configure --host=arm-linux-gnueabihf + 2.2 copy dibbler-client to your board + 3. mkdir -p /var/log/dibbler/ /var/lib/ on your board + 4. create /etc/dibbler/client.conf on your board, the content is + log-mode short + log-level 7 + iface wwan0 { + ia + option dns-server + } + 5. run "dibbler-client start" to get ipV6 address + 6. run "route -A inet6 add default dev wwan0" to add default route + */ + snprintf(shell_cmd, sizeof(shell_cmd), "route -A inet6 add default %s", ifname); + ql_system(shell_cmd); + snprintf(udhcpc_cmd, sizeof(udhcpc_cmd), "dibbler-client run"); + dibbler_client_alive++; +#endif + + pthread_create(&udhcpc_thread_id, &udhcpc_thread_attr, udhcpc_thread_function, (void*)strdup(udhcpc_cmd)); +#endif + } + } +} + +void udhcpc_stop(PROFILE_T *profile) { + char *ifname = profile->usbnet_adapter; + char shell_cmd[128]; + + ql_set_driver_link_state(profile, 0); + + if (profile->qmapnet_adapter) { + ifname = profile->qmapnet_adapter; + } + +#ifdef USE_DHCLIENT + if (dhclient_alive) { + system("killall dhclient"); + dhclient_alive = 0; + } +#endif + if (dibbler_client_alive) { + system("killall dibbler-client"); + dibbler_client_alive = 0; + } + +//it seems when call netif_carrier_on(), and netcard 's IP is "0.0.0.0", will cause netif_queue_stopped() + snprintf(shell_cmd, sizeof(shell_cmd), "ifconfig %s 0.0.0.0", ifname); + ql_system(shell_cmd); + snprintf(shell_cmd, sizeof(shell_cmd), "ifconfig %s down", ifname); + ql_system(shell_cmd); + +#ifdef QL_OPENWER_NETWORK_SETUP + ql_openwrt_setup_wan(ifname, NULL); + ql_openwrt_setup_wan6(ifname, NULL); +#endif +} diff --git a/root/package/link4all/quectel-CM/src/udhcpc.c b/root/package/link4all/quectel-CM/src/udhcpc.c new file mode 100644 index 00000000..12894947 --- /dev/null +++ b/root/package/link4all/quectel-CM/src/udhcpc.c @@ -0,0 +1,842 @@ +/****************************************************************************** + @file udhcpc.c + @brief call DHCP tools to obtain IP address. + + DESCRIPTION + Connectivity Management Tool for USB network adapter of Quectel wireless cellular modules. + + INITIALIZATION AND SEQUENCING REQUIREMENTS + None. + + --------------------------------------------------------------------------- + Copyright (c) 2016 - 2020 Quectel Wireless Solution, Co., Ltd. All Rights Reserved. + Quectel Wireless Solution Proprietary and Confidential. + --------------------------------------------------------------------------- +******************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "util.h" +#include "QMIThread.h" + +#include +#include +#include +int read_config(const char *path,const char *package,const char *section,const char *option,char *value) +{ + if(NULL == section || NULL == option) + { + printf("Parameter error !\n"); + return -1; + } + + struct uci_context *c = NULL; + struct uci_ptr p; + char buf[64]; + + c = uci_alloc_context(); + if(NULL == c) + { + printf("uci_alloc_context error !\n"); + goto cleanup; + } + + if(NULL != path) + { + if(UCI_OK != uci_set_confdir( c, path)) //设置文件路径 + { + printf("uci_set_confdir error !\n"); + uci_perror(c,"no found! (The file may not exist)\n"); + uci_free_context(c); + c = NULL; + return -1; + } + + } + + + if(NULL == package) + { + sprintf(buf,"lte_info.%s.%s",section,option); + + } + else + { + sprintf(buf,"%s.%s.%s",package,section,option); //设置读å–的节点 + } + + + if(UCI_OK != uci_lookup_ptr(c,&p,buf,true)) + { + uci_perror(c,"no found! (The file may not exist)\n"); + uci_free_context(c); + c = NULL; + return -1; + } + + // bzero(value,sizeof(value)); + bzero(value,16); + + sprintf(value,"%s",p.o->v.string); + +cleanup: + uci_free_context(c); + c = NULL; + return 0; + +} + +char * get_iface(const char *path,const char *option,char *result) +{ + strcpy(result,"N"); + + if(NULL == option) + { + printf("Parameter error !\n"); + return result; + } + + static struct uci_context *ctx = NULL; //定义一个UCIä¸Šä¸‹æ–‡çš„é™æ€å˜é‡ + struct uci_package * pkg = NULL; + struct uci_element *e; + const char *value; + + ctx = uci_alloc_context(); //申请一个上下文 + + if(UCI_OK != uci_load(ctx,path,&pkg)) + { + goto cleanup; + } + + + /*é历UCIçš„æ¯ä¸€ä¸ªèŠ‚*/ + uci_foreach_element(&pkg->sections,e) + { + struct uci_section *s = uci_to_section(e); + if (NULL != (value = uci_lookup_option_string(ctx, s, "device"))) + { + if(!strcmp(option,value)) + { + bzero(result,16); + strcpy(result,s->e.name); + goto cleanup; + } + + } + } + +cleanup: + uci_free_context(ctx); + return result; +} + +// static void get_iface(const char *ifname,char *result) +// { +// FILE *fp; +// char cmd[128]; +// sprintf(cmd,"/sbin/getiface.sh %s",ifname); +// fp=popen(cmd, "r"); +// fgets(result,sizeof(result),fp); +// //printf("%s",result); +// } + +static __inline in_addr_t qmi2addr(uint32_t __x) { + return (__x>>24) | (__x>>8&0xff00) | (__x<<8&0xff0000) | (__x<<24); +} + +static int ql_system(const char *shell_cmd) { + dbg_time("%s", shell_cmd); + return system(shell_cmd); +} + +static void ifc_init_ifr(const char *name, struct ifreq *ifr) +{ + memset(ifr, 0, sizeof(struct ifreq)); + strncpy(ifr->ifr_name, name, IFNAMSIZ); + ifr->ifr_name[IFNAMSIZ - 1] = 0; +} + +static void ql_set_mtu(const char *ifname, int ifru_mtu) { + int inet_sock; + struct ifreq ifr; + + inet_sock = socket(AF_INET, SOCK_DGRAM, 0); + + if (inet_sock > 0) { + ifc_init_ifr(ifname, &ifr); + + if (!ioctl(inet_sock, SIOCGIFMTU, &ifr)) { + if (ifr.ifr_ifru.ifru_mtu != ifru_mtu) { + dbg_time("change mtu %d -> %d", ifr.ifr_ifru.ifru_mtu , ifru_mtu); + ifr.ifr_ifru.ifru_mtu = ifru_mtu; + ioctl(inet_sock, SIOCSIFMTU, &ifr); + } + } + + close(inet_sock); + } +} + +static int ifc_get_addr(const char *name, in_addr_t *addr) +{ + int inet_sock; + struct ifreq ifr; + int ret = 0; + + inet_sock = socket(AF_INET, SOCK_DGRAM, 0); + + ifc_init_ifr(name, &ifr); + if (addr != NULL) { + ret = ioctl(inet_sock, SIOCGIFADDR, &ifr); + if (ret < 0) { + *addr = 0; + } else { + *addr = ((struct sockaddr_in*) &ifr.ifr_addr)->sin_addr.s_addr; + } + } + close(inet_sock); + return ret; +} + +static short ifc_get_flags(const char *ifname) +{ + int inet_sock; + struct ifreq ifr; + int ret = 0; + + inet_sock = socket(AF_INET, SOCK_DGRAM, 0); + + if (inet_sock > 0) { + ifc_init_ifr(ifname, &ifr); + + if (!ioctl(inet_sock, SIOCGIFFLAGS, &ifr)) { + ret = ifr.ifr_ifru.ifru_flags; + } + + close(inet_sock); + } + + return ret; +} + +static int ql_netcard_ipv4_address_check(const char *ifname, in_addr_t ip) { + in_addr_t addr = 0; + + ifc_get_addr(ifname, &addr); + return addr == ip; +} + +static int ql_raw_ip_mode_check(const char *ifname, uint32_t ip) { + int fd; + char raw_ip[128]; + char shell_cmd[128]; + char mode[2] = "X"; + int mode_change = 0; + + if (ql_netcard_ipv4_address_check(ifname, qmi2addr(ip))) + return 0; + + snprintf(raw_ip, sizeof(raw_ip), "/sys/class/net/%s/qmi/raw_ip", ifname); + if (access(raw_ip, F_OK)) + return 0; + + fd = open(raw_ip, O_RDWR | O_NONBLOCK | O_NOCTTY); + if (fd < 0) { + dbg_time("%s %d fail to open(%s), errno:%d (%s)", __FILE__, __LINE__, raw_ip, errno, strerror(errno)); + return 0; + } + + read(fd, mode, 2); + if (mode[0] == '0' || mode[0] == 'N') { + dbg_time("File:%s Line:%d udhcpc fail to get ip address, try next:", __func__, __LINE__); + snprintf(shell_cmd, sizeof(shell_cmd), "ifconfig %s down", ifname); + ql_system(shell_cmd); + dbg_time("echo Y > /sys/class/net/%s/qmi/raw_ip", ifname); + mode[0] = 'Y'; + write(fd, mode, 2); + mode_change = 1; + snprintf(shell_cmd, sizeof(shell_cmd), "ifconfig %s up", ifname); + ql_system(shell_cmd); + } + + close(fd); + return mode_change; +} + +static void* udhcpc_thread_function(void* arg) { + FILE * udhcpc_fp; + char *udhcpc_cmd = (char *)arg; + + if (udhcpc_cmd == NULL) + return NULL; + + dbg_time("%s", udhcpc_cmd); + udhcpc_fp = popen(udhcpc_cmd, "r"); + free(udhcpc_cmd); + if (udhcpc_fp) { + char buf[0xff]; + + buf[sizeof(buf)-1] = '\0'; + while((fgets(buf, sizeof(buf)-1, udhcpc_fp)) != NULL) { + if ((strlen(buf) > 1) && (buf[strlen(buf) - 1] == '\n')) + buf[strlen(buf) - 1] = '\0'; + dbg_time("%s", buf); + } + + pclose(udhcpc_fp); + } + + return NULL; +} + +//#define USE_DHCLIENT +#ifdef USE_DHCLIENT +static int dhclient_alive = 0; +#endif +static int dibbler_client_alive = 0; + +void ql_set_driver_link_state(PROFILE_T *profile, int link_state) { + char link_file[128]; + int fd; + int new_state = 0; + + snprintf(link_file, sizeof(link_file), "/sys/class/net/%s/link_state", profile->usbnet_adapter); + fd = open(link_file, O_RDWR | O_NONBLOCK | O_NOCTTY); + if (fd == -1) { + if (errno != ENOENT) + dbg_time("Fail to access %s, errno: %d (%s)", link_file, errno, strerror(errno)); + return; + } + + if (profile->qmap_mode <= 1) + new_state = !!link_state; + else { + //0x80 means link off this pdp + new_state = (link_state ? 0x00 : 0x80) + profile->pdp; + } + + snprintf(link_file, sizeof(link_file), "%d\n", new_state); + write(fd, link_file, sizeof(link_file)); + + if (link_state == 0 && profile->qmapnet_adapter && strcmp(profile->qmapnet_adapter, profile->usbnet_adapter)) { + size_t rc; + + lseek(fd, 0, SEEK_SET); + rc = read(fd, link_file, sizeof(link_file)); + if (rc > 1 && (!strncasecmp(link_file, "0\n", 2) || !strncasecmp(link_file, "0x0\n", 4))) { + snprintf(link_file, sizeof(link_file), "ifconfig %s down", profile->usbnet_adapter); + ql_system(link_file); + } + } + + close(fd); +} + +static const char *ipv4Str(const uint32_t Address) { + static char str[] = {"255.225.255.255"}; + uint8_t *ip = (uint8_t *)&Address; + + snprintf(str, sizeof(str), "%d.%d.%d.%d", ip[3], ip[2], ip[1], ip[0]); + return str; +} + +static const char *ipv6Str(const UCHAR Address[16]) { + static char str[64]; + uint16_t ip[8]; + int i; + for (i = 0; i < 8; i++) { + ip[i] = (Address[i*2]<<8) + Address[i*2+1]; + } + + snprintf(str, sizeof(str), "%x:%x:%x:%x:%x:%x:%x:%x", + ip[0], ip[1], ip[2], ip[3], ip[4], ip[5], ip[6], ip[7]); + + return str; +} + +void update_ipv4_address(const char *ifname, const char *ip, const char *gw, unsigned prefix) +{ + char shell_cmd[128]; + + if (!ifname) + return; + + if (!access("/sbin/ip", X_OK)) { + printf("/sbin/ip is not exe file!\n\n"); + snprintf(shell_cmd, sizeof(shell_cmd), "ip -%d address flush dev %s", 4, ifname); + ql_system(shell_cmd); + + snprintf(shell_cmd, sizeof(shell_cmd), "ip -%d address add %s/%u dev %s", 4, ip, prefix, ifname); + ql_system(shell_cmd); + + char buf2[128]; + char metric[8]; + bzero(buf2,sizeof(buf2)); + get_iface("/etc/config/network",ifname,buf2); + read_config("/etc/config/","network",buf2, "metric",metric); + if(metric != NULL){ + printf("ifname is %s\n",ifname); + snprintf(shell_cmd, sizeof(shell_cmd), "ip -%d route add default via %s dev %s metric %s", 4, gw, ifname,metric); + }else{ + snprintf(shell_cmd, sizeof(shell_cmd), "ip -%d route add default via %s dev %s", 4, gw, ifname); + } + ql_system(shell_cmd); + // snprintf(shell_cmd, sizeof(shell_cmd), "cp /tmp/resolv.conf /tmp/resolv.conf.auto"); + // ql_system(shell_cmd); + } else { + unsigned n = (0xFFFFFFFF >> (32 - prefix)) << (32 - prefix); + n = (n>>24) | (n>>8&0xff00) | (n<<8&0xff0000) | (n<<24); + + snprintf(shell_cmd, sizeof(shell_cmd), "ifconfig %s %s netmask %s", ifname, ip, ipv4Str(n)); + ql_system(shell_cmd); + + //Resetting default routes + snprintf(shell_cmd, sizeof(shell_cmd), "route del default dev %s", ifname); + while(!system(shell_cmd)); + + // snprintf(shell_cmd, sizeof(shell_cmd), "route add default gw %s dev %s", gw, ifname); + char buf2[128]; + bzero(buf2,sizeof(buf2)); + // get_iface(ifname,buf2); + get_iface("/etc/config/network",ifname,buf2); + printf("ifname is %s,buf2 value is: %s\n",ifname,buf2); + snprintf(shell_cmd, sizeof(shell_cmd), "ifup %s",buf2); + printf("openwrt system run \"ifup %s\" \n",buf2); + ql_system(shell_cmd); + } +} + +void update_ipv6_address(const char *ifname, const char *ip, const char *gw, unsigned prefix) { + char shell_cmd[128]; + + (void)gw; + if (!access("/sbin/ip", X_OK)) { + snprintf(shell_cmd, sizeof(shell_cmd), "ip -%d address flush dev %s", 6, ifname); + ql_system(shell_cmd); + + snprintf(shell_cmd, sizeof(shell_cmd), "ip -%d address add %s/%u dev %s", 6, ip, prefix, ifname); + ql_system(shell_cmd); + + //ping6 www.qq.com + snprintf(shell_cmd, sizeof(shell_cmd), "ip -%d route add default dev %s", 6, ifname); + ql_system(shell_cmd); + } else { + snprintf(shell_cmd, sizeof(shell_cmd), "ifconfig %s %s/%d", ifname, ip, prefix); + ql_system(shell_cmd); + + // snprintf(shell_cmd, sizeof(shell_cmd), "route -A inet6 add default dev %s", ifname); + char buf2[128]; + bzero(buf2,sizeof(buf2)); + // get_iface(ifname,buf2); + get_iface("/etc/config/network",ifname,buf2); + printf("ifname is %s,buf2 value is: %s\n",ifname,buf2); + snprintf(shell_cmd, sizeof(shell_cmd), "ifup %s",buf2); + printf("openwrt system run \"ifup %s\" \n",buf2); + ql_system(shell_cmd); + } +} + +static void update_ip_address_by_qmi(const char *ifname, const IPV4_T *ipv4, const IPV6_T *ipv6) { + char *d1, *d2; + + if (ipv4 && ipv4->Address) { + d1 = strdup(ipv4Str(ipv4->Address)); + d2 = strdup(ipv4Str(ipv4->Gateway)); + unsigned prefix = 0; + unsigned n = ipv4->SubnetMask; + n = (n>>24) | (n>>8&0xff00) | (n<<8&0xff0000) | (n<<24); + + for (prefix = 0; prefix < 32; prefix++) { + if (n&(1<DnsPrimary) { + d1 = strdup(ipv4Str(ipv4->DnsPrimary)); + d2 = strdup(ipv4Str(ipv4->DnsSecondary ? ipv4->DnsSecondary : ipv4->DnsPrimary)); + update_resolv_conf(4, ifname, d1, d2); + free(d1); free(d2); + } + } + + if (ipv6 && ipv6->Address[0] && ipv6->PrefixLengthIPAddr) { + d1 = strdup(ipv6Str(ipv6->Address)); + d2 = strdup(ipv6Str(ipv6->Gateway)); + + update_ipv6_address(ifname, d1, d2, ipv6->PrefixLengthIPAddr); + free(d1); free(d2); + + //Adding DNS + if (ipv6->DnsPrimary[0]) { + d1 = strdup(ipv6Str(ipv6->DnsPrimary)); + d2 = strdup(ipv6Str(ipv6->DnsSecondary[0] ? ipv6->DnsSecondary : ipv6->DnsPrimary)); + update_resolv_conf(6, ifname, d1, d2); + free(d1); free(d2); + } + } +} + +//#define QL_OPENWER_NETWORK_SETUP +#ifdef QL_OPENWER_NETWORK_SETUP +static const char *openwrt_lan = "br-lan"; +static const char *openwrt_wan = "wwan0"; + +static int ql_openwrt_system(const char *cmd) { + int i; + int ret = 1; + char shell_cmd[128]; + + snprintf(shell_cmd, sizeof(shell_cmd), "%s 2>1 > /dev/null", cmd); + + for (i = 0; i < 15; i++) { + dbg_time("%s", cmd); + ret = system(shell_cmd); + if (!ret) + break; + sleep(1); + } + + return ret; +} + +static int ql_openwrt_is_wan(const char *ifname) { + if (openwrt_lan == NULL) { + system("uci show network.wan.ifname"); + } + + if (strcmp(ifname, openwrt_wan)) + return 0; + + return 1; +} + +static void ql_openwrt_setup_wan(const char *ifname, const IPV4_T *ipv4) { + FILE *fp = NULL; + char config[64]; + + snprintf(config, sizeof(config), "/tmp/rmnet_%s_ipv4config", ifname); + + if (ipv4 == NULL) { + if (ql_openwrt_is_wan(ifname)) + ql_openwrt_system("ifdown wan"); + return; + } + + fp = fopen(config, "w"); + if (fp == NULL) + return; + + fprintf(fp, "IFNAME=\"%s\"\n", ifname); + fprintf(fp, "PUBLIC_IP=\"%s\"\n", ipv4Str(ipv4->Address)); + fprintf(fp, "NETMASK=\"%s\"\n", ipv4Str(ipv4->SubnetMask)); + fprintf(fp, "GATEWAY=\"%s\"\n", ipv4Str(ipv4->Gateway)); + fprintf(fp, "DNSSERVERS=\"%s", ipv4Str(ipv4->DnsPrimary)); + if (ipv4->DnsSecondary != 0) + fprintf(fp, " %s", ipv4Str(ipv4->DnsSecondary)); + fprintf(fp, "\"\n"); + + fclose(fp); + + if (!ql_openwrt_is_wan(ifname)) + return; + + ql_openwrt_system("ifup wan"); +} + +static void ql_openwrt_setup_wan6(const char *ifname, const IPV6_T *ipv6) { + FILE *fp = NULL; + char config[64]; + int first_ifup; + + snprintf(config, sizeof(config), "/tmp/rmnet_%s_ipv6config", ifname); + + if (ipv6 == NULL) { + if (ql_openwrt_is_wan(ifname)) + ql_openwrt_system("ifdown wan6"); + return; + } + + first_ifup = (access(config, F_OK) != 0); + + fp = fopen(config, "w"); + if (fp == NULL) + return; + + fprintf(fp, "IFNAME=\"%s\"\n", ifname); + fprintf(fp, "PUBLIC_IP=\"%s\"\n", ipv6Str(ipv6->Address)); + fprintf(fp, "NETMASK=\"%s\"\n", ipv6Str(ipv6->SubnetMask)); + fprintf(fp, "GATEWAY=\"%s\"\n", ipv6Str(ipv6->Gateway)); + fprintf(fp, "PrefixLength=\"%d\"\n", ipv6->PrefixLengthIPAddr); + fprintf(fp, "DNSSERVERS=\"%s", ipv6Str(ipv6->DnsPrimary)); + if (ipv6->DnsSecondary[0]) + fprintf(fp, " %s", ipv6Str(ipv6->DnsSecondary)); + fprintf(fp, "\"\n"); + + fclose(fp); + + if (!ql_openwrt_is_wan(ifname)) + return; + + if (first_ifup) + ql_openwrt_system("ifup wan6"); + else + ql_openwrt_system("/etc/init.d/network restart"); //make PC to release old IPV6 address, and RS new IPV6 address + +#if 1 //TODO? why need this? + if (openwrt_lan) { + int i; + char shell_cmd[128]; + UCHAR Address[16] = {0}; + + ql_openwrt_system(("ifstatus lan")); + + for (i = 0; i < (ipv6->PrefixLengthIPAddr/8); i++) + Address[i] = ipv6->Address[i]; + + snprintf(shell_cmd, sizeof(shell_cmd), "ip route del %s/%u dev %s", ipv6Str(Address), ipv6->PrefixLengthIPAddr, ifname); + ql_openwrt_system(shell_cmd); + + snprintf(shell_cmd, sizeof(shell_cmd), "ip route add %s/%u dev %s", ipv6Str(Address), ipv6->PrefixLengthIPAddr, openwrt_lan); + ql_system(shell_cmd); + } +#endif +} +#endif + +void udhcpc_start(PROFILE_T *profile) { + char *ifname = profile->usbnet_adapter; + char shell_cmd[128]; + + ql_set_driver_link_state(profile, 1); + + if (profile->qmapnet_adapter) { + ifname = profile->qmapnet_adapter; + } + + if (profile->rawIP && profile->ipv4.Address && profile->ipv4.Mtu) { + ql_set_mtu(ifname, (profile->ipv4.Mtu)); + } + + if (strcmp(ifname, profile->usbnet_adapter)) { + snprintf(shell_cmd, sizeof(shell_cmd), "ifconfig %s up", profile->usbnet_adapter); + ql_system(shell_cmd); + if (ifc_get_flags(ifname)&IFF_UP) { + snprintf(shell_cmd, sizeof(shell_cmd), "ifconfig %s down", ifname); + ql_system(shell_cmd); + } + } + + snprintf(shell_cmd, sizeof(shell_cmd), "ifconfig %s up", ifname); + ql_system(shell_cmd); + + if (profile->ipv4.Address) { + if (profile->PCSCFIpv4Addr1) + dbg_time("pcscf1: %s", ipv4Str(profile->PCSCFIpv4Addr1)); + if (profile->PCSCFIpv4Addr2) + dbg_time("pcscf2: %s", ipv4Str(profile->PCSCFIpv4Addr2)); + } + + if (profile->ipv6.Address[0] && profile->ipv6.PrefixLengthIPAddr) { + if (profile->PCSCFIpv6Addr1[0]) + dbg_time("pcscf1: %s", ipv6Str(profile->PCSCFIpv6Addr1)); + if (profile->PCSCFIpv6Addr2[0]) + dbg_time("pcscf2: %s", ipv6Str(profile->PCSCFIpv6Addr2)); + } + +#if 1 //for bridge mode, only one public IP, so do udhcpc manually + if (ql_bridge_mode_detect(profile)) { + return; + } +#endif + +//because must use udhcpc to obtain IP when working on ETH mode, +//so it is better also use udhcpc to obtain IP when working on IP mode. +//use the same policy for all modules +#if 0 + if (profile->rawIP != 0) //mdm9x07/ec25,ec20 R2.0 + { + update_ip_address_by_qmi(ifname, &profile->ipv4, &profile->ipv6); + return; + } +#endif + +/* Do DHCP using busybox tools */ + { + char udhcpc_cmd[128]; + pthread_attr_t udhcpc_thread_attr; + pthread_t udhcpc_thread_id; + + pthread_attr_init(&udhcpc_thread_attr); + pthread_attr_setdetachstate(&udhcpc_thread_attr, PTHREAD_CREATE_DETACHED); + + if (profile->ipv4.Address) { +#ifdef USE_DHCLIENT + snprintf(udhcpc_cmd, sizeof(udhcpc_cmd), "dhclient -4 -d --no-pid %s", ifname); + dhclient_alive++; +#else + if (access("/usr/share/udhcpc/default.script", X_OK) + && access("/etc//udhcpc/default.script", X_OK)) { + dbg_time("No default.script found, it should be in '/usr/share/udhcpc/' or '/etc//udhcpc' depend on your udhcpc version!"); + } + + //-f,--foreground Run in foreground + //-b,--background Background if lease is not obtained + //-n,--now Exit if lease is not obtained + //-q,--quit Exit after obtaining lease + //-t,--retries N Send up to N discover packets (default 3) + // snprintf(udhcpc_cmd, sizeof(udhcpc_cmd), "busybox udhcpc -f -n -q -t 5 -i %s", ifname); + char buf2[128]; + bzero(buf2,sizeof(buf2)); + // get_iface(ifname,buf2); + get_iface("/etc/config/network",ifname,buf2); + printf("ifname is %s,buf2 value is: %s\n",ifname,buf2); + snprintf(udhcpc_cmd, sizeof(udhcpc_cmd), "ifup %s",buf2); + printf("openwrt system run \"ifup %s\" \n",buf2); +#endif + +#if 1 //for OpenWrt + if (!access("/lib/netifd/dhcp.script", X_OK) && !access("/sbin/ifup", X_OK) && !access("/sbin/ifstatus", X_OK)) { + dbg_time("you are use OpenWrt?"); + dbg_time("should not calling udhcpc manually?"); + dbg_time("should modify /etc/config/network as below?"); + dbg_time("config interface wan"); + dbg_time("\toption ifname %s", ifname); + dbg_time("\toption proto dhcp"); + dbg_time("should use \"/sbin/ifstaus wan\" to check %s 's status?", ifname); + } +#endif + +#ifdef USE_DHCLIENT + pthread_create(&udhcpc_thread_id, &udhcpc_thread_attr, udhcpc_thread_function, (void*)strdup(udhcpc_cmd)); + sleep(1); +#else + pthread_create(&udhcpc_thread_id, NULL, udhcpc_thread_function, (void*)strdup(udhcpc_cmd)); + pthread_join(udhcpc_thread_id, NULL); + if (ql_raw_ip_mode_check(ifname, profile->ipv4.Address)) { + pthread_create(&udhcpc_thread_id, NULL, udhcpc_thread_function, (void*)strdup(udhcpc_cmd)); + pthread_join(udhcpc_thread_id, NULL); + } + + if (!ql_netcard_ipv4_address_check(ifname, qmi2addr(profile->ipv4.Address))) { + //no udhcpc's default.script exist, directly set ip and dns + update_ip_address_by_qmi(ifname, &profile->ipv4, NULL); + } +#endif +#ifdef QL_OPENWER_NETWORK_SETUP + ql_openwrt_setup_wan(ifname, &profile->ipv4); +#endif + } + + if (profile->ipv6.Address[0] && profile->ipv6.PrefixLengthIPAddr) { +#if 1 + //module do not support DHCPv6, only support 'Router Solicit' + //and it seem if enable /proc/sys/net/ipv6/conf/all/forwarding, Kernel do not send RS + const char *forward_file = "/proc/sys/net/ipv6/conf/all/forwarding"; + int forward_fd = open(forward_file, O_RDONLY); + if (forward_fd > 0) { + char forward_state[2]; + read(forward_fd, forward_state, 2); + if (forward_state[0] == '1') { + //dbg_time("%s enabled, kernel maybe donot send 'Router Solicit'", forward_file); + } + close(forward_fd); + } + + update_ip_address_by_qmi(ifname, NULL, &profile->ipv6); + + if (profile->ipv6.DnsPrimary[0] || profile->ipv6.DnsSecondary[0]) { + char dns1str[64], dns2str[64]; + + if (profile->ipv6.DnsPrimary[0]) { + strcpy(dns1str, ipv6Str(profile->ipv6.DnsPrimary)); + } + + if (profile->ipv6.DnsSecondary[0]) { + strcpy(dns2str, ipv6Str(profile->ipv6.DnsSecondary)); + } + + update_resolv_conf(6, ifname, profile->ipv6.DnsPrimary[0] ? dns1str : NULL, + profile->ipv6.DnsSecondary[0] != '\0' ? dns2str : NULL); + } + +#ifdef QL_OPENWER_NETWORK_SETUP + ql_openwrt_setup_wan6(ifname, &profile->ipv6); +#endif +#else +#ifdef USE_DHCLIENT + snprintf(udhcpc_cmd, sizeof(udhcpc_cmd), "dhclient -6 -d --no-pid %s", ifname); + dhclient_alive++; +#else + /* + DHCPv6: Dibbler - a portable DHCPv6 + 1. download from http://klub.com.pl/dhcpv6/ + 2. cross-compile + 2.1 ./configure --host=arm-linux-gnueabihf + 2.2 copy dibbler-client to your board + 3. mkdir -p /var/log/dibbler/ /var/lib/ on your board + 4. create /etc/dibbler/client.conf on your board, the content is + log-mode short + log-level 7 + iface wwan0 { + ia + option dns-server + } + 5. run "dibbler-client start" to get ipV6 address + 6. run "route -A inet6 add default dev wwan0" to add default route + */ + snprintf(shell_cmd, sizeof(shell_cmd), "route -A inet6 add default %s", ifname); + ql_system(shell_cmd); + snprintf(udhcpc_cmd, sizeof(udhcpc_cmd), "dibbler-client run"); + dibbler_client_alive++; +#endif + + pthread_create(&udhcpc_thread_id, &udhcpc_thread_attr, udhcpc_thread_function, (void*)strdup(udhcpc_cmd)); +#endif + } + } +} + +void udhcpc_stop(PROFILE_T *profile) { + char *ifname = profile->usbnet_adapter; + char shell_cmd[128]; + + ql_set_driver_link_state(profile, 0); + + if (profile->qmapnet_adapter) { + ifname = profile->qmapnet_adapter; + } + +#ifdef USE_DHCLIENT + if (dhclient_alive) { + system("killall dhclient"); + dhclient_alive = 0; + } +#endif + if (dibbler_client_alive) { + system("killall dibbler-client"); + dibbler_client_alive = 0; + } + +//it seems when call netif_carrier_on(), and netcard 's IP is "0.0.0.0", will cause netif_queue_stopped() + snprintf(shell_cmd, sizeof(shell_cmd), "ifconfig %s 0.0.0.0", ifname); + ql_system(shell_cmd); + snprintf(shell_cmd, sizeof(shell_cmd), "ifconfig %s down", ifname); + ql_system(shell_cmd); + +#ifdef QL_OPENWER_NETWORK_SETUP + ql_openwrt_setup_wan(ifname, NULL); + ql_openwrt_setup_wan6(ifname, NULL); +#endif +} diff --git a/root/package/link4all/quectel-CM/src/udhcpc.orig.c b/root/package/link4all/quectel-CM/src/udhcpc.orig.c new file mode 100644 index 00000000..cbda1465 --- /dev/null +++ b/root/package/link4all/quectel-CM/src/udhcpc.orig.c @@ -0,0 +1,688 @@ +/****************************************************************************** + @file udhcpc.c + @brief call DHCP tools to obtain IP address. + + DESCRIPTION + Connectivity Management Tool for USB network adapter of Quectel wireless cellular modules. + + INITIALIZATION AND SEQUENCING REQUIREMENTS + None. + + --------------------------------------------------------------------------- + Copyright (c) 2016 - 2020 Quectel Wireless Solution, Co., Ltd. All Rights Reserved. + Quectel Wireless Solution Proprietary and Confidential. + --------------------------------------------------------------------------- +******************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "util.h" +#include "QMIThread.h" + +static __inline in_addr_t qmi2addr(uint32_t __x) { + return (__x>>24) | (__x>>8&0xff00) | (__x<<8&0xff0000) | (__x<<24); +} + +static int ql_system(const char *shell_cmd) { + dbg_time("%s", shell_cmd); + return system(shell_cmd); +} + +static void ifc_init_ifr(const char *name, struct ifreq *ifr) +{ + memset(ifr, 0, sizeof(struct ifreq)); + strncpy(ifr->ifr_name, name, IFNAMSIZ); + ifr->ifr_name[IFNAMSIZ - 1] = 0; +} + +static void ql_set_mtu(const char *ifname, int ifru_mtu) { + int inet_sock; + struct ifreq ifr; + + inet_sock = socket(AF_INET, SOCK_DGRAM, 0); + + if (inet_sock > 0) { + ifc_init_ifr(ifname, &ifr); + + if (!ioctl(inet_sock, SIOCGIFMTU, &ifr)) { + if (ifr.ifr_ifru.ifru_mtu != ifru_mtu) { + dbg_time("change mtu %d -> %d", ifr.ifr_ifru.ifru_mtu , ifru_mtu); + ifr.ifr_ifru.ifru_mtu = ifru_mtu; + ioctl(inet_sock, SIOCSIFMTU, &ifr); + } + } + + close(inet_sock); + } +} + +static int ifc_get_addr(const char *name, in_addr_t *addr) +{ + int inet_sock; + struct ifreq ifr; + int ret = 0; + + inet_sock = socket(AF_INET, SOCK_DGRAM, 0); + + ifc_init_ifr(name, &ifr); + if (addr != NULL) { + ret = ioctl(inet_sock, SIOCGIFADDR, &ifr); + if (ret < 0) { + *addr = 0; + } else { + *addr = ((struct sockaddr_in*) &ifr.ifr_addr)->sin_addr.s_addr; + } + } + close(inet_sock); + return ret; +} + +static short ifc_get_flags(const char *ifname) +{ + int inet_sock; + struct ifreq ifr; + int ret = 0; + + inet_sock = socket(AF_INET, SOCK_DGRAM, 0); + + if (inet_sock > 0) { + ifc_init_ifr(ifname, &ifr); + + if (!ioctl(inet_sock, SIOCGIFFLAGS, &ifr)) { + ret = ifr.ifr_ifru.ifru_flags; + } + + close(inet_sock); + } + + return ret; +} + +static int ql_netcard_ipv4_address_check(const char *ifname, in_addr_t ip) { + in_addr_t addr = 0; + + ifc_get_addr(ifname, &addr); + return addr == ip; +} + +static int ql_raw_ip_mode_check(const char *ifname, uint32_t ip) { + int fd; + char raw_ip[128]; + char shell_cmd[128]; + char mode[2] = "X"; + int mode_change = 0; + + if (ql_netcard_ipv4_address_check(ifname, qmi2addr(ip))) + return 0; + + snprintf(raw_ip, sizeof(raw_ip), "/sys/class/net/%s/qmi/raw_ip", ifname); + if (access(raw_ip, F_OK)) + return 0; + + fd = open(raw_ip, O_RDWR | O_NONBLOCK | O_NOCTTY); + if (fd < 0) { + dbg_time("%s %d fail to open(%s), errno:%d (%s)", __FILE__, __LINE__, raw_ip, errno, strerror(errno)); + return 0; + } + + read(fd, mode, 2); + if (mode[0] == '0' || mode[0] == 'N') { + dbg_time("File:%s Line:%d udhcpc fail to get ip address, try next:", __func__, __LINE__); + snprintf(shell_cmd, sizeof(shell_cmd), "ifconfig %s down", ifname); + ql_system(shell_cmd); + dbg_time("echo Y > /sys/class/net/%s/qmi/raw_ip", ifname); + mode[0] = 'Y'; + write(fd, mode, 2); + mode_change = 1; + snprintf(shell_cmd, sizeof(shell_cmd), "ifconfig %s up", ifname); + ql_system(shell_cmd); + } + + close(fd); + return mode_change; +} + +static void* udhcpc_thread_function(void* arg) { + FILE * udhcpc_fp; + char *udhcpc_cmd = (char *)arg; + + if (udhcpc_cmd == NULL) + return NULL; + + dbg_time("%s", udhcpc_cmd); + udhcpc_fp = popen(udhcpc_cmd, "r"); + free(udhcpc_cmd); + if (udhcpc_fp) { + char buf[0xff]; + + buf[sizeof(buf)-1] = '\0'; + while((fgets(buf, sizeof(buf)-1, udhcpc_fp)) != NULL) { + if ((strlen(buf) > 1) && (buf[strlen(buf) - 1] == '\n')) + buf[strlen(buf) - 1] = '\0'; + dbg_time("%s", buf); + } + + pclose(udhcpc_fp); + } + + return NULL; +} + +//#define USE_DHCLIENT +#ifdef USE_DHCLIENT +static int dhclient_alive = 0; +#endif +static int dibbler_client_alive = 0; + +void ql_set_driver_link_state(PROFILE_T *profile, int link_state) { + char link_file[128]; + int fd; + int new_state = 0; + + snprintf(link_file, sizeof(link_file), "/sys/class/net/%s/link_state", profile->usbnet_adapter); + fd = open(link_file, O_RDWR | O_NONBLOCK | O_NOCTTY); + if (fd == -1) { + if (errno != ENOENT) + dbg_time("Fail to access %s, errno: %d (%s)", link_file, errno, strerror(errno)); + return; + } + + if (profile->qmap_mode <= 1) + new_state = !!link_state; + else { + //0x80 means link off this pdp + new_state = (link_state ? 0x00 : 0x80) + profile->pdp; + } + + snprintf(link_file, sizeof(link_file), "%d\n", new_state); + write(fd, link_file, sizeof(link_file)); + + if (link_state == 0 && profile->qmapnet_adapter && strcmp(profile->qmapnet_adapter, profile->usbnet_adapter)) { + size_t rc; + + lseek(fd, 0, SEEK_SET); + rc = read(fd, link_file, sizeof(link_file)); + if (rc > 1 && (!strncasecmp(link_file, "0\n", 2) || !strncasecmp(link_file, "0x0\n", 4))) { + snprintf(link_file, sizeof(link_file), "ifconfig %s down", profile->usbnet_adapter); + ql_system(link_file); + } + } + + close(fd); +} + +static const char *ipv4Str(const uint32_t Address) { + static char str[] = {"255.225.255.255"}; + uint8_t *ip = (uint8_t *)&Address; + + snprintf(str, sizeof(str), "%d.%d.%d.%d", ip[3], ip[2], ip[1], ip[0]); + return str; +} + +static const char *ipv6Str(const UCHAR Address[16]) { + static char str[64]; + uint16_t ip[8]; + int i; + for (i = 0; i < 8; i++) { + ip[i] = (Address[i*2]<<8) + Address[i*2+1]; + } + + snprintf(str, sizeof(str), "%x:%x:%x:%x:%x:%x:%x:%x", + ip[0], ip[1], ip[2], ip[3], ip[4], ip[5], ip[6], ip[7]); + + return str; +} + +void update_ipv4_address(const char *ifname, const char *ip, const char *gw, unsigned prefix) +{ + char shell_cmd[128]; + + if (!ifname) + return; + + if (!access("/sbin/ip", X_OK)) { + snprintf(shell_cmd, sizeof(shell_cmd), "ip -%d address flush dev %s", 4, ifname); + ql_system(shell_cmd); + + snprintf(shell_cmd, sizeof(shell_cmd), "ip -%d address add %s/%u dev %s", 4, ip, prefix, ifname); + ql_system(shell_cmd); + + //ping6 www.qq.com + snprintf(shell_cmd, sizeof(shell_cmd), "ip -%d route add default via %s dev %s", 4, gw, ifname); + ql_system(shell_cmd); + } else { + unsigned n = (0xFFFFFFFF >> (32 - prefix)) << (32 - prefix); + n = (n>>24) | (n>>8&0xff00) | (n<<8&0xff0000) | (n<<24); + + snprintf(shell_cmd, sizeof(shell_cmd), "ifconfig %s %s netmask %s", ifname, ip, ipv4Str(n)); + ql_system(shell_cmd); + + //Resetting default routes + snprintf(shell_cmd, sizeof(shell_cmd), "route del default dev %s", ifname); + while(!system(shell_cmd)); + + snprintf(shell_cmd, sizeof(shell_cmd), "route add default gw %s dev %s", gw, ifname); + ql_system(shell_cmd); + } +} + +void update_ipv6_address(const char *ifname, const char *ip, const char *gw, unsigned prefix) { + char shell_cmd[128]; + + (void)gw; + if (!access("/sbin/ip", X_OK)) { + snprintf(shell_cmd, sizeof(shell_cmd), "ip -%d address flush dev %s", 6, ifname); + ql_system(shell_cmd); + + snprintf(shell_cmd, sizeof(shell_cmd), "ip -%d address add %s/%u dev %s", 6, ip, prefix, ifname); + ql_system(shell_cmd); + + //ping6 www.qq.com + snprintf(shell_cmd, sizeof(shell_cmd), "ip -%d route add default dev %s", 6, ifname); + ql_system(shell_cmd); + } else { + snprintf(shell_cmd, sizeof(shell_cmd), "ifconfig %s %s/%d", ifname, ip, prefix); + ql_system(shell_cmd); + + snprintf(shell_cmd, sizeof(shell_cmd), "route -A inet6 add default dev %s", ifname); + ql_system(shell_cmd); + } +} + +static void update_ip_address_by_qmi(const char *ifname, const IPV4_T *ipv4, const IPV6_T *ipv6) { + char *d1, *d2; + + if (ipv4 && ipv4->Address) { + d1 = strdup(ipv4Str(ipv4->Address)); + d2 = strdup(ipv4Str(ipv4->Gateway)); + unsigned prefix = 0; + unsigned n = ipv4->SubnetMask; + n = (n>>24) | (n>>8&0xff00) | (n<<8&0xff0000) | (n<<24); + + for (prefix = 0; prefix < 32; prefix++) { + if (n&(1<DnsPrimary) { + d1 = strdup(ipv4Str(ipv4->DnsPrimary)); + d2 = strdup(ipv4Str(ipv4->DnsSecondary ? ipv4->DnsSecondary : ipv4->DnsPrimary)); + update_resolv_conf(4, ifname, d1, d2); + free(d1); free(d2); + } + } + + if (ipv6 && ipv6->Address[0] && ipv6->PrefixLengthIPAddr) { + d1 = strdup(ipv6Str(ipv6->Address)); + d2 = strdup(ipv6Str(ipv6->Gateway)); + + update_ipv6_address(ifname, d1, d2, ipv6->PrefixLengthIPAddr); + free(d1); free(d2); + + //Adding DNS + if (ipv6->DnsPrimary[0]) { + d1 = strdup(ipv6Str(ipv6->DnsPrimary)); + d2 = strdup(ipv6Str(ipv6->DnsSecondary[0] ? ipv6->DnsSecondary : ipv6->DnsPrimary)); + update_resolv_conf(6, ifname, d1, d2); + free(d1); free(d2); + } + } +} + +//#define QL_OPENWER_NETWORK_SETUP +#ifdef QL_OPENWER_NETWORK_SETUP +static const char *openwrt_lan = "br-lan"; +static const char *openwrt_wan = "wwan0"; + +static int ql_openwrt_system(const char *cmd) { + int i; + int ret = 1; + char shell_cmd[128]; + + snprintf(shell_cmd, sizeof(shell_cmd), "%s 2>1 > /dev/null", cmd); + + for (i = 0; i < 15; i++) { + dbg_time("%s", cmd); + ret = system(shell_cmd); + if (!ret) + break; + sleep(1); + } + + return ret; +} + +static int ql_openwrt_is_wan(const char *ifname) { + if (openwrt_lan == NULL) { + system("uci show network.wan.ifname"); + } + + if (strcmp(ifname, openwrt_wan)) + return 0; + + return 1; +} + +static void ql_openwrt_setup_wan(const char *ifname, const IPV4_T *ipv4) { + FILE *fp = NULL; + char config[64]; + + snprintf(config, sizeof(config), "/tmp/rmnet_%s_ipv4config", ifname); + + if (ipv4 == NULL) { + if (ql_openwrt_is_wan(ifname)) + ql_openwrt_system("ifdown wan"); + return; + } + + fp = fopen(config, "w"); + if (fp == NULL) + return; + + fprintf(fp, "IFNAME=\"%s\"\n", ifname); + fprintf(fp, "PUBLIC_IP=\"%s\"\n", ipv4Str(ipv4->Address)); + fprintf(fp, "NETMASK=\"%s\"\n", ipv4Str(ipv4->SubnetMask)); + fprintf(fp, "GATEWAY=\"%s\"\n", ipv4Str(ipv4->Gateway)); + fprintf(fp, "DNSSERVERS=\"%s", ipv4Str(ipv4->DnsPrimary)); + if (ipv4->DnsSecondary != 0) + fprintf(fp, " %s", ipv4Str(ipv4->DnsSecondary)); + fprintf(fp, "\"\n"); + + fclose(fp); + + if (!ql_openwrt_is_wan(ifname)) + return; + + ql_openwrt_system("ifup wan"); +} + +static void ql_openwrt_setup_wan6(const char *ifname, const IPV6_T *ipv6) { + FILE *fp = NULL; + char config[64]; + int first_ifup; + + snprintf(config, sizeof(config), "/tmp/rmnet_%s_ipv6config", ifname); + + if (ipv6 == NULL) { + if (ql_openwrt_is_wan(ifname)) + ql_openwrt_system("ifdown wan6"); + return; + } + + first_ifup = (access(config, F_OK) != 0); + + fp = fopen(config, "w"); + if (fp == NULL) + return; + + fprintf(fp, "IFNAME=\"%s\"\n", ifname); + fprintf(fp, "PUBLIC_IP=\"%s\"\n", ipv6Str(ipv6->Address)); + fprintf(fp, "NETMASK=\"%s\"\n", ipv6Str(ipv6->SubnetMask)); + fprintf(fp, "GATEWAY=\"%s\"\n", ipv6Str(ipv6->Gateway)); + fprintf(fp, "PrefixLength=\"%d\"\n", ipv6->PrefixLengthIPAddr); + fprintf(fp, "DNSSERVERS=\"%s", ipv6Str(ipv6->DnsPrimary)); + if (ipv6->DnsSecondary[0]) + fprintf(fp, " %s", ipv6Str(ipv6->DnsSecondary)); + fprintf(fp, "\"\n"); + + fclose(fp); + + if (!ql_openwrt_is_wan(ifname)) + return; + + if (first_ifup) + ql_openwrt_system("ifup wan6"); + else + ql_openwrt_system("/etc/init.d/network restart"); //make PC to release old IPV6 address, and RS new IPV6 address + +#if 1 //TODO? why need this? + if (openwrt_lan) { + int i; + char shell_cmd[128]; + UCHAR Address[16] = {0}; + + ql_openwrt_system(("ifstatus lan")); + + for (i = 0; i < (ipv6->PrefixLengthIPAddr/8); i++) + Address[i] = ipv6->Address[i]; + + snprintf(shell_cmd, sizeof(shell_cmd), "ip route del %s/%u dev %s", ipv6Str(Address), ipv6->PrefixLengthIPAddr, ifname); + ql_openwrt_system(shell_cmd); + + snprintf(shell_cmd, sizeof(shell_cmd), "ip route add %s/%u dev %s", ipv6Str(Address), ipv6->PrefixLengthIPAddr, openwrt_lan); + ql_system(shell_cmd); + } +#endif +} +#endif + +void udhcpc_start(PROFILE_T *profile) { + char *ifname = profile->usbnet_adapter; + char shell_cmd[128]; + + ql_set_driver_link_state(profile, 1); + + if (profile->qmapnet_adapter) { + ifname = profile->qmapnet_adapter; + } + + if (profile->rawIP && profile->ipv4.Address && profile->ipv4.Mtu) { + ql_set_mtu(ifname, (profile->ipv4.Mtu)); + } + + if (strcmp(ifname, profile->usbnet_adapter)) { + snprintf(shell_cmd, sizeof(shell_cmd), "ifconfig %s up", profile->usbnet_adapter); + ql_system(shell_cmd); + if (ifc_get_flags(ifname)&IFF_UP) { + snprintf(shell_cmd, sizeof(shell_cmd), "ifconfig %s down", ifname); + ql_system(shell_cmd); + } + } + + snprintf(shell_cmd, sizeof(shell_cmd), "ifconfig %s up", ifname); + ql_system(shell_cmd); + + if (profile->ipv4.Address) { + if (profile->PCSCFIpv4Addr1) + dbg_time("pcscf1: %s", ipv4Str(profile->PCSCFIpv4Addr1)); + if (profile->PCSCFIpv4Addr2) + dbg_time("pcscf2: %s", ipv4Str(profile->PCSCFIpv4Addr2)); + } + + if (profile->ipv6.Address[0] && profile->ipv6.PrefixLengthIPAddr) { + if (profile->PCSCFIpv6Addr1[0]) + dbg_time("pcscf1: %s", ipv6Str(profile->PCSCFIpv6Addr1)); + if (profile->PCSCFIpv6Addr2[0]) + dbg_time("pcscf2: %s", ipv6Str(profile->PCSCFIpv6Addr2)); + } + +#if 1 //for bridge mode, only one public IP, so do udhcpc manually + if (ql_bridge_mode_detect(profile)) { + return; + } +#endif + +//because must use udhcpc to obtain IP when working on ETH mode, +//so it is better also use udhcpc to obtain IP when working on IP mode. +//use the same policy for all modules +#if 0 + if (profile->rawIP != 0) //mdm9x07/ec25,ec20 R2.0 + { + update_ip_address_by_qmi(ifname, &profile->ipv4, &profile->ipv6); + return; + } +#endif + +/* Do DHCP using busybox tools */ + { + char udhcpc_cmd[128]; + pthread_attr_t udhcpc_thread_attr; + pthread_t udhcpc_thread_id; + + pthread_attr_init(&udhcpc_thread_attr); + pthread_attr_setdetachstate(&udhcpc_thread_attr, PTHREAD_CREATE_DETACHED); + + if (profile->ipv4.Address) { +#ifdef USE_DHCLIENT + snprintf(udhcpc_cmd, sizeof(udhcpc_cmd), "dhclient -4 -d --no-pid %s", ifname); + dhclient_alive++; +#else + if (access("/usr/share/udhcpc/default.script", X_OK) + && access("/etc//udhcpc/default.script", X_OK)) { + dbg_time("No default.script found, it should be in '/usr/share/udhcpc/' or '/etc//udhcpc' depend on your udhcpc version!"); + } + + //-f,--foreground Run in foreground + //-b,--background Background if lease is not obtained + //-n,--now Exit if lease is not obtained + //-q,--quit Exit after obtaining lease + //-t,--retries N Send up to N discover packets (default 3) + snprintf(udhcpc_cmd, sizeof(udhcpc_cmd), "busybox udhcpc -f -n -q -t 5 -i %s", ifname); +#endif + +#if 1 //for OpenWrt + if (!access("/lib/netifd/dhcp.script", X_OK) && !access("/sbin/ifup", X_OK) && !access("/sbin/ifstatus", X_OK)) { + dbg_time("you are use OpenWrt?"); + dbg_time("should not calling udhcpc manually?"); + dbg_time("should modify /etc/config/network as below?"); + dbg_time("config interface wan"); + dbg_time("\toption ifname %s", ifname); + dbg_time("\toption proto dhcp"); + dbg_time("should use \"/sbin/ifstaus wan\" to check %s 's status?", ifname); + } +#endif + +#ifdef USE_DHCLIENT + pthread_create(&udhcpc_thread_id, &udhcpc_thread_attr, udhcpc_thread_function, (void*)strdup(udhcpc_cmd)); + sleep(1); +#else + pthread_create(&udhcpc_thread_id, NULL, udhcpc_thread_function, (void*)strdup(udhcpc_cmd)); + pthread_join(udhcpc_thread_id, NULL); + if (ql_raw_ip_mode_check(ifname, profile->ipv4.Address)) { + pthread_create(&udhcpc_thread_id, NULL, udhcpc_thread_function, (void*)strdup(udhcpc_cmd)); + pthread_join(udhcpc_thread_id, NULL); + } + + if (!ql_netcard_ipv4_address_check(ifname, qmi2addr(profile->ipv4.Address))) { + //no udhcpc's default.script exist, directly set ip and dns + update_ip_address_by_qmi(ifname, &profile->ipv4, NULL); + } +#endif +#ifdef QL_OPENWER_NETWORK_SETUP + ql_openwrt_setup_wan(ifname, &profile->ipv4); +#endif + } + + if (profile->ipv6.Address[0] && profile->ipv6.PrefixLengthIPAddr) { +#if 1 + //module do not support DHCPv6, only support 'Router Solicit' + //and it seem if enable /proc/sys/net/ipv6/conf/all/forwarding, Kernel do not send RS + const char *forward_file = "/proc/sys/net/ipv6/conf/all/forwarding"; + int forward_fd = open(forward_file, O_RDONLY); + if (forward_fd > 0) { + char forward_state[2]; + read(forward_fd, forward_state, 2); + if (forward_state[0] == '1') { + //dbg_time("%s enabled, kernel maybe donot send 'Router Solicit'", forward_file); + } + close(forward_fd); + } + + update_ip_address_by_qmi(ifname, NULL, &profile->ipv6); + + if (profile->ipv6.DnsPrimary[0] || profile->ipv6.DnsSecondary[0]) { + char dns1str[64], dns2str[64]; + + if (profile->ipv6.DnsPrimary[0]) { + strcpy(dns1str, ipv6Str(profile->ipv6.DnsPrimary)); + } + + if (profile->ipv6.DnsSecondary[0]) { + strcpy(dns2str, ipv6Str(profile->ipv6.DnsSecondary)); + } + + update_resolv_conf(6, ifname, profile->ipv6.DnsPrimary[0] ? dns1str : NULL, + profile->ipv6.DnsSecondary[0] != '\0' ? dns2str : NULL); + } + +#ifdef QL_OPENWER_NETWORK_SETUP + ql_openwrt_setup_wan6(ifname, &profile->ipv6); +#endif +#else +#ifdef USE_DHCLIENT + snprintf(udhcpc_cmd, sizeof(udhcpc_cmd), "dhclient -6 -d --no-pid %s", ifname); + dhclient_alive++; +#else + /* + DHCPv6: Dibbler - a portable DHCPv6 + 1. download from http://klub.com.pl/dhcpv6/ + 2. cross-compile + 2.1 ./configure --host=arm-linux-gnueabihf + 2.2 copy dibbler-client to your board + 3. mkdir -p /var/log/dibbler/ /var/lib/ on your board + 4. create /etc/dibbler/client.conf on your board, the content is + log-mode short + log-level 7 + iface wwan0 { + ia + option dns-server + } + 5. run "dibbler-client start" to get ipV6 address + 6. run "route -A inet6 add default dev wwan0" to add default route + */ + snprintf(shell_cmd, sizeof(shell_cmd), "route -A inet6 add default %s", ifname); + ql_system(shell_cmd); + snprintf(udhcpc_cmd, sizeof(udhcpc_cmd), "dibbler-client run"); + dibbler_client_alive++; +#endif + + pthread_create(&udhcpc_thread_id, &udhcpc_thread_attr, udhcpc_thread_function, (void*)strdup(udhcpc_cmd)); +#endif + } + } +} + +void udhcpc_stop(PROFILE_T *profile) { + char *ifname = profile->usbnet_adapter; + char shell_cmd[128]; + + ql_set_driver_link_state(profile, 0); + + if (profile->qmapnet_adapter) { + ifname = profile->qmapnet_adapter; + } + +#ifdef USE_DHCLIENT + if (dhclient_alive) { + system("killall dhclient"); + dhclient_alive = 0; + } +#endif + if (dibbler_client_alive) { + system("killall dibbler-client"); + dibbler_client_alive = 0; + } + +//it seems when call netif_carrier_on(), and netcard 's IP is "0.0.0.0", will cause netif_queue_stopped() + snprintf(shell_cmd, sizeof(shell_cmd), "ifconfig %s 0.0.0.0", ifname); + ql_system(shell_cmd); + snprintf(shell_cmd, sizeof(shell_cmd), "ifconfig %s down", ifname); + ql_system(shell_cmd); + +#ifdef QL_OPENWER_NETWORK_SETUP + ql_openwrt_setup_wan(ifname, NULL); + ql_openwrt_setup_wan6(ifname, NULL); +#endif +} diff --git a/root/package/link4all/quectel-CM/src/udhcpc_netlink.c b/root/package/link4all/quectel-CM/src/udhcpc_netlink.c new file mode 100644 index 00000000..5e052236 --- /dev/null +++ b/root/package/link4all/quectel-CM/src/udhcpc_netlink.c @@ -0,0 +1,179 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libmnl/ifutils.h" +#include "libmnl/dhcp/dhcp.h" +#include "util.h" +#include "QMIThread.h" + +static int ql_raw_ip_mode_check(const char *ifname) +{ + int fd; + char raw_ip[128]; + char mode[2] = "X"; + int mode_change = 0; + + snprintf(raw_ip, sizeof(raw_ip), "/sys/class/net/%s/qmi/raw_ip", ifname); + if (access(raw_ip, F_OK)) + return 0; + + fd = open(raw_ip, O_RDWR | O_NONBLOCK | O_NOCTTY); + if (fd < 0) + { + dbg_time("%s %d fail to open(%s), errno:%d (%s)", __FILE__, __LINE__, raw_ip, errno, strerror(errno)); + return 0; + } + + read(fd, mode, 2); + if (mode[0] == '0' || mode[0] == 'N') + { + if_link_down(ifname); + dbg_time("echo Y > /sys/class/net/%s/qmi/raw_ip", ifname); + mode[0] = 'Y'; + write(fd, mode, 2); + mode_change = 1; + if_link_up(ifname); + } + + close(fd); + return mode_change; +} + +void ql_set_driver_link_state(PROFILE_T *profile, int link_state) +{ + char link_file[128]; + int fd; + int new_state = 0; + + snprintf(link_file, sizeof(link_file), "/sys/class/net/%s/link_state", profile->usbnet_adapter); + fd = open(link_file, O_RDWR | O_NONBLOCK | O_NOCTTY); + if (fd == -1) + { + if (errno != ENOENT) + dbg_time("Fail to access %s, errno: %d (%s)", link_file, errno, strerror(errno)); + return; + } + + if (profile->qmap_mode <= 1) + new_state = !!link_state; + else + { + //0x80 means link off this pdp + new_state = (link_state ? 0x00 : 0x80) + profile->pdp; + } + + snprintf(link_file, sizeof(link_file), "%d\n", new_state); + write(fd, link_file, sizeof(link_file)); + + if (link_state == 0 && profile->qmap_mode > 1) + { + size_t rc; + + lseek(fd, 0, SEEK_SET); + rc = read(fd, link_file, sizeof(link_file)); + if (rc > 1 && (!strcasecmp(link_file, "0\n") || !strcasecmp(link_file, "0x0\n"))) + { + if_link_down(profile->usbnet_adapter); + } + } + + close(fd); +} + +void udhcpc_start(PROFILE_T *profile) +{ + char *ifname = profile->usbnet_adapter; + + ql_set_driver_link_state(profile, 1); + ql_raw_ip_mode_check(ifname); + + if (profile->qmapnet_adapter) + { + ifname = profile->qmapnet_adapter; + } + if (profile->rawIP && profile->ipv4.Address && profile->ipv4.Mtu) + { + if_set_mtu(ifname, (profile->ipv4.Mtu)); + } + + if (strcmp(ifname, profile->usbnet_adapter)) + { + if_link_up(profile->usbnet_adapter); + } + + if_link_up(ifname); + +#if 1 //for bridge mode, only one public IP, so do udhcpc manually + if (ql_bridge_mode_detect(profile)) + { + return; + } +#endif + // if use DHCP(should make with ${DHCP} src files) + // do_dhcp(ifname); + // return 0; + /* IPv4 Addr Info */ + if (profile->ipv4.Address) + { + dbg_time("IPv4 MTU: %d", profile->ipv4.Mtu); + dbg_time("IPv4 Address: %s", ipaddr_to_string_v4(ntohl(profile->ipv4.Address))); + dbg_time("IPv4 Netmask: %d", mask_to_prefix_v4(ntohl(profile->ipv4.SubnetMask))); + dbg_time("IPv4 Gateway: %s", ipaddr_to_string_v4(ntohl(profile->ipv4.Gateway))); + dbg_time("IPv4 DNS1: %s", ipaddr_to_string_v4(ntohl(profile->ipv4.DnsPrimary))); + dbg_time("IPv4 DNS2: %s", ipaddr_to_string_v4(ntohl(profile->ipv4.DnsSecondary))); + if_set_network_v4(ifname, ntohl(profile->ipv4.Address), + mask_to_prefix_v4(profile->ipv4.SubnetMask), + ntohl(profile->ipv4.Gateway), + ntohl(profile->ipv4.DnsPrimary), + ntohl(profile->ipv4.DnsSecondary)); + } + + if (profile->ipv6.Address[0] && profile->ipv6.PrefixLengthIPAddr) + { + //module do not support DHCPv6, only support 'Router Solicit' + //and it seem if enable /proc/sys/net/ipv6/conf/all/forwarding, Kernel do not send RS + const char *forward_file = "/proc/sys/net/ipv6/conf/all/forwarding"; + int forward_fd = open(forward_file, O_RDONLY); + if (forward_fd > 0) + { + char forward_state[2]; + read(forward_fd, forward_state, 2); + if (forward_state[0] == '1') + { + dbg_time("%s enabled, kernel maybe donot send 'Router Solicit'", forward_file); + } + close(forward_fd); + } + + dbg_time("IPv6 MTU: %d", profile->ipv6.Mtu); + dbg_time("IPv6 Address: %s", ipaddr_to_string_v6(profile->ipv6.Address)); + dbg_time("IPv6 Netmask: %d", profile->ipv6.PrefixLengthIPAddr); + dbg_time("IPv6 Gateway: %s", ipaddr_to_string_v6(profile->ipv6.Gateway)); + dbg_time("IPv6 DNS1: %s", ipaddr_to_string_v6(profile->ipv6.DnsPrimary)); + dbg_time("IPv6 DNS2: %s", ipaddr_to_string_v6(profile->ipv6.DnsSecondary)); + if_set_network_v6(ifname, profile->ipv6.Address, profile->ipv6.PrefixLengthIPAddr, + profile->ipv6.Gateway, profile->ipv6.DnsPrimary, profile->ipv6.DnsSecondary); + } +} + +void udhcpc_stop(PROFILE_T *profile) +{ + char *ifname = profile->usbnet_adapter; + + ql_set_driver_link_state(profile, 0); + + if (profile->qmapnet_adapter) + { + ifname = profile->qmapnet_adapter; + } + + if_link_down(ifname); + if_flush_v4_addr(ifname); + if_flush_v6_addr(ifname); +} diff --git a/root/package/link4all/quectel-CM/src/udhcpc_script.c b/root/package/link4all/quectel-CM/src/udhcpc_script.c new file mode 100644 index 00000000..032f8cfa --- /dev/null +++ b/root/package/link4all/quectel-CM/src/udhcpc_script.c @@ -0,0 +1,132 @@ +/****************************************************************************** + @file udhcpc.c + @brief call DHCP tools to obtain IP address. + + DESCRIPTION + Connectivity Management Tool for USB network adapter of Quectel wireless cellular modules. + + INITIALIZATION AND SEQUENCING REQUIREMENTS + None. + + --------------------------------------------------------------------------- + Copyright (c) 2016 - 2020 Quectel Wireless Solution, Co., Ltd. All Rights Reserved. + Quectel Wireless Solution Proprietary and Confidential. + --------------------------------------------------------------------------- +******************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "util.h" +#include "QMIThread.h" + +#define IFDOWN_SCRIPT "/etc/quectel/ifdown.sh" +#define IFUP_SCRIPT "/etc/quectel/ifup.sh" + +static int ql_system(const char *shell_cmd) +{ + dbg_time("%s", shell_cmd); + return system(shell_cmd); +} + +uint32_t mask_to_prefix_v4(uint32_t mask) +{ + uint32_t prefix = 0; + while (mask) + { + mask = mask & (mask - 1); + prefix++; + } + return prefix; +} + +uint32_t mask_from_prefix_v4(uint32_t prefix) +{ + return ~((1 << (32 - prefix)) - 1); +} + +/* mask in int */ +uint32_t broadcast_from_mask(uint32_t ip, uint32_t mask) +{ + return (ip & mask) | (~mask); +} + +const char *ipaddr_to_string_v4(in_addr_t ipaddr, char *buf, size_t size) +{ + // static char buf[INET6_ADDRSTRLEN] = {'\0'}; + buf[0] = '\0'; + uint32_t addr = ipaddr; + return inet_ntop(AF_INET, &addr, buf, size); +} + +const char *ipaddr_to_string_v6(uint8_t *ipaddr, char *buf, size_t size) +{ + buf[0] = '\0'; + return inet_ntop(AF_INET6, ipaddr, buf, size); +} + +/** + * For more details see default.script + * + * The main aim of this function is offload ip management to script, CM has not interest in manage IP address. + * just tell script all the info about ip, mask, router, dns... + */ +void udhcpc_start(PROFILE_T *profile) +{ + char shell_cmd[1024]; + char ip[128]; + char subnet[128]; + char broadcast[128]; + char router[128]; + char domain1[128]; + char domain2[128]; + + if (NULL == getenv(IFUP_SCRIPT)) + return; + + // manage IPv4??? + // check rawip ??? + snprintf(shell_cmd, sizeof(shell_cmd), + " netiface=%s interface=%s mtu=%u ip=%s subnet=%s broadcast=%s router=%s" + " domain=\"%s %s\" %s", + profile->usbnet_adapter, + profile->qmapnet_adapter ? profile->qmapnet_adapter : profile->usbnet_adapter, + profile->ipv4.Mtu, + ipaddr_to_string_v4(ntohl(profile->ipv4.Address), ip, sizeof(ip)), + ipaddr_to_string_v4(ntohl(profile->ipv4.SubnetMask), subnet, sizeof(subnet)), + ipaddr_to_string_v4(ntohl(broadcast_from_mask(profile->ipv4.Address, profile->ipv4.SubnetMask)), + broadcast, sizeof(broadcast)), + ipaddr_to_string_v4(ntohl(profile->ipv4.Gateway), router, sizeof(router)), + ipaddr_to_string_v4(ntohl(profile->ipv4.DnsPrimary), domain1, sizeof(domain1)), + ipaddr_to_string_v4(ntohl(profile->ipv4.DnsSecondary), domain2, sizeof(domain2)), + getenv(IFUP_SCRIPT)); + ql_system(shell_cmd); + + // manage IPv6??? +} + +/** + * For more details see default.script + * + * The main aim of this function is offload ip management to script, CM has not interest in manage IP address. + * just tell script all the info about ip, mask, router, dns... + */ +void udhcpc_stop(PROFILE_T *profile) +{ + char shell_cmd[1024]; + + if (NULL == getenv(IFDOWN_SCRIPT)) + return; + + snprintf(shell_cmd, sizeof(shell_cmd), + "netiface=%s interface=%s %s", + profile->usbnet_adapter, + profile->qmapnet_adapter ? profile->qmapnet_adapter : profile->usbnet_adapter, + getenv(IFDOWN_SCRIPT)); + ql_system(shell_cmd); +} diff --git a/root/package/link4all/quectel-CM/src/util.c b/root/package/link4all/quectel-CM/src/util.c new file mode 100644 index 00000000..82b54890 --- /dev/null +++ b/root/package/link4all/quectel-CM/src/util.c @@ -0,0 +1,362 @@ +/****************************************************************************** + @file util.c + @brief some utils for this QCM tool. + + DESCRIPTION + Connectivity Management Tool for USB network adapter of Quectel wireless cellular modules. + + INITIALIZATION AND SEQUENCING REQUIREMENTS + None. + + --------------------------------------------------------------------------- + Copyright (c) 2016 - 2020 Quectel Wireless Solution, Co., Ltd. All Rights Reserved. + Quectel Wireless Solution Proprietary and Confidential. + --------------------------------------------------------------------------- +******************************************************************************/ + +#include +#include + +#if defined(__STDC__) +#include +#define __V(x) x +#else +#include +#define __V(x) (va_alist) va_dcl +#define const +#define volatile +#endif + +#include + +#include "QMIThread.h" + +static void setTimespecRelative(struct timespec *p_ts, long long msec) +{ + struct timeval tv; + + gettimeofday(&tv, (struct timezone *) NULL); + + /* what's really funny about this is that I know + pthread_cond_timedwait just turns around and makes this + a relative time again */ + p_ts->tv_sec = tv.tv_sec + (msec / 1000); + p_ts->tv_nsec = (tv.tv_usec + (msec % 1000) * 1000L ) * 1000L; + if ((unsigned long)p_ts->tv_nsec >= 1000000000UL) { + p_ts->tv_sec += 1; + p_ts->tv_nsec -= 1000000000UL; + } +} + +int pthread_cond_timeout_np(pthread_cond_t *cond, pthread_mutex_t * mutex, unsigned msecs) { + if (msecs != 0) { + unsigned i; + unsigned t = msecs/4; + int ret = 0; + + if (t == 0) + t = 1; + + for (i = 0; i < msecs; i += t) { + struct timespec ts; + setTimespecRelative(&ts, t); + ret = pthread_cond_timedwait(cond, mutex, &ts); //to advoid system time change + if (ret != ETIMEDOUT) { + if(ret) dbg_time("ret=%d, msecs=%u, t=%u", ret, msecs, t); + break; + } + } + + return ret; + } else { + return pthread_cond_wait(cond, mutex); + } +} + +void cond_setclock_attr(pthread_cond_t *cond, clockid_t clock) +{ +#if 0 //very old uclibc do not support pthread_condattr_setclock + /* set relative time, for pthread_cond_timedwait */ + pthread_condattr_t attr; + pthread_condattr_init (&attr); + pthread_condattr_setclock(&attr, clock); + pthread_cond_init(cond, &attr); + pthread_condattr_destroy (&attr); +#else + (void)cond; + (void)clock; +#endif +} + +const char * get_time(void) { + static char time_buf[50]; + struct timeval tv; + time_t time; + suseconds_t millitm; + struct tm *ti; + + gettimeofday (&tv, NULL); + + time= tv.tv_sec; + millitm = (tv.tv_usec + 500) / 1000; + + if (millitm == 1000) { + ++time; + millitm = 0; + } + + ti = localtime(&time); + sprintf(time_buf, "%02d-%02d_%02d:%02d:%02d:%03d", ti->tm_mon+1, ti->tm_mday, ti->tm_hour, ti->tm_min, ti->tm_sec, (int)millitm); + return time_buf; +} + +unsigned long clock_msec(void) +{ + struct timespec tm; + clock_gettime( CLOCK_MONOTONIC, &tm); + return (unsigned long)(tm.tv_sec*1000 + (tm.tv_nsec/1000000)); +} + +FILE *logfilefp = NULL; + +const int i = 1; +#define is_bigendian() ( (*(char*)&i) == 0 ) + +USHORT le16_to_cpu(USHORT v16) { + USHORT tmp = v16; + if (is_bigendian()) { + unsigned char *s = (unsigned char *)(&v16); + unsigned char *d = (unsigned char *)(&tmp); + d[0] = s[1]; + d[1] = s[0]; + } + return tmp; +} + +UINT le32_to_cpu (UINT v32) { + UINT tmp = v32; + if (is_bigendian()) { + unsigned char *s = (unsigned char *)(&v32); + unsigned char *d = (unsigned char *)(&tmp); + d[0] = s[3]; + d[1] = s[2]; + d[2] = s[1]; + d[3] = s[0]; + } + return tmp; +} + +UINT ql_swap32(UINT v32) { + UINT tmp = v32; + { + unsigned char *s = (unsigned char *)(&v32); + unsigned char *d = (unsigned char *)(&tmp); + d[0] = s[3]; + d[1] = s[2]; + d[2] = s[1]; + d[3] = s[0]; + } + return tmp; +} + +USHORT cpu_to_le16(USHORT v16) { + USHORT tmp = v16; + if (is_bigendian()) { + unsigned char *s = (unsigned char *)(&v16); + unsigned char *d = (unsigned char *)(&tmp); + d[0] = s[1]; + d[1] = s[0]; + } + return tmp; +} + +UINT cpu_to_le32 (UINT v32) { + UINT tmp = v32; + if (is_bigendian()) { + unsigned char *s = (unsigned char *)(&v32); + unsigned char *d = (unsigned char *)(&tmp); + d[0] = s[3]; + d[1] = s[2]; + d[2] = s[1]; + d[3] = s[0]; + } + return tmp; +} + +void update_resolv_conf(int iptype, const char *ifname, const char *dns1, const char *dns2) { + const char *dns_file = "/etc/resolv.conf"; + FILE *dns_fp; + char dns_line[256]; + #define MAX_DNS 16 + char *dns_info[MAX_DNS]; + char dns_tag[64]; + int dns_match = 0; + int i; + + snprintf(dns_tag, sizeof(dns_tag), "# IPV%d %s", iptype, ifname); + + for (i = 0; i < MAX_DNS; i++) + dns_info[i] = NULL; + + dns_fp = fopen(dns_file, "r"); + if (dns_fp) { + i = 0; + dns_line[sizeof(dns_line)-1] = '\0'; + + while((fgets(dns_line, sizeof(dns_line)-1, dns_fp)) != NULL) { + if ((strlen(dns_line) > 1) && (dns_line[strlen(dns_line) - 1] == '\n')) + dns_line[strlen(dns_line) - 1] = '\0'; + //dbg_time("%s", dns_line); + if (strstr(dns_line, dns_tag)) { + dns_match++; + continue; + } + dns_info[i++] = strdup(dns_line); + if (i == MAX_DNS) + break; + } + + fclose(dns_fp); + } + else if (errno != ENOENT) { + dbg_time("fopen %s fail, errno:%d (%s)", dns_file, errno, strerror(errno)); + return; + } + + if (dns1 == NULL && dns_match == 0) + return; + + dns_fp = fopen(dns_file, "w"); + if (dns_fp) { + if (dns1) + fprintf(dns_fp, "nameserver %s %s\n", dns1, dns_tag); + if (dns2) + fprintf(dns_fp, "nameserver %s %s\n", dns2, dns_tag); + + for (i = 0; i < MAX_DNS && dns_info[i]; i++) + fprintf(dns_fp, "%s\n", dns_info[i]); + fclose(dns_fp); + } + else { + dbg_time("fopen %s fail, errno:%d (%s)", dns_file, errno, strerror(errno)); + } + + for (i = 0; i < MAX_DNS && dns_info[i]; i++) + free(dns_info[i]); +} + +pid_t getpid_by_pdp(int pdp, const char* program_name) +{ + glob_t gt; + int ret; + char filter[5] = {0}; + pid_t pid; + + sprintf(filter, "-n %d", pdp); + ret = glob("/proc/*/cmdline", GLOB_NOSORT, NULL, >); + if (ret != 0) { + dbg_time("glob error, errno = %d(%s)", errno, strerror(errno)); + return -1; + } else { + int i = 0, fd = -1; + ssize_t nreads; + char cmdline[512] = {0}; + + for (i = 0; i < (int)gt.gl_pathc; i++) { + fd = open(gt.gl_pathv[i], O_RDONLY); + if (fd == -1) { + dbg_time("open %s failed, errno = %d(%s)", gt.gl_pathv[i], errno, strerror(errno)); + globfree(>); + return -1; + } + + nreads = read(fd, cmdline, sizeof(cmdline)); + if (nreads > 0) { + int pos = 0; + while (pos < nreads-1) { + if (cmdline[pos] == '\0') + cmdline[pos] = ' '; // space + pos++; + } + // printf("%s\n", cmdline); + } + + if (strstr(cmdline, program_name) && strstr(cmdline, filter)) { + char path[64] = {0}; + char pidstr[64] = {0}; + char *p; + + dbg_time("%s: %s", gt.gl_pathv[i], cmdline); + strcpy(path, gt.gl_pathv[i]); + p = strstr(gt.gl_pathv[i], "/cmdline"); + *p = '\0'; + while (*(--p) != '/') ; + + strcpy(pidstr, p+1); + pid = atoi(pidstr); + globfree(>); + + return pid; + } + } + } + + globfree(>); + return -1; +} + +void ql_get_driver_rmnet_info(PROFILE_T *profile, RMNET_INFO *rmnet_info) { + int ifc_ctl_sock; + struct ifreq ifr; + int rc; + int request = 0x89F3; + unsigned char data[512]; + + memset(rmnet_info, 0x00, sizeof(*rmnet_info)); + + ifc_ctl_sock = socket(AF_INET, SOCK_DGRAM, 0); + if (ifc_ctl_sock <= 0) { + dbg_time("socket() failed: %s\n", strerror(errno)); + return; + } + + memset(&ifr, 0, sizeof(struct ifreq)); + strncpy(ifr.ifr_name, profile->usbnet_adapter, IFNAMSIZ); + ifr.ifr_name[IFNAMSIZ - 1] = 0; + ifr.ifr_ifru.ifru_data = (void *)data; + + rc = ioctl(ifc_ctl_sock, request, &ifr); + if (rc < 0) { + dbg_time("ioctl(0x%x, qmap_settings) failed: %s, rc=%d", request, strerror(errno), rc); + } + else { + memcpy(rmnet_info, data, sizeof(*rmnet_info)); + } + + close(ifc_ctl_sock); +} + +void ql_set_driver_qmap_setting(PROFILE_T *profile, QMAP_SETTING *qmap_settings) { + int ifc_ctl_sock; + struct ifreq ifr; + int rc; + int request = 0x89F2; + + ifc_ctl_sock = socket(AF_INET, SOCK_DGRAM, 0); + if (ifc_ctl_sock <= 0) { + dbg_time("socket() failed: %s\n", strerror(errno)); + return; + } + + memset(&ifr, 0, sizeof(struct ifreq)); + strncpy(ifr.ifr_name, profile->usbnet_adapter, IFNAMSIZ); + ifr.ifr_name[IFNAMSIZ - 1] = 0; + ifr.ifr_ifru.ifru_data = (void *)qmap_settings; + + rc = ioctl(ifc_ctl_sock, request, &ifr); + if (rc < 0) { + dbg_time("ioctl(0x%x, qmap_settings) failed: %s, rc=%d", request, strerror(errno), rc); + } + + close(ifc_ctl_sock); +} diff --git a/root/package/link4all/quectel-CM/src/util.h b/root/package/link4all/quectel-CM/src/util.h new file mode 100644 index 00000000..392d4014 --- /dev/null +++ b/root/package/link4all/quectel-CM/src/util.h @@ -0,0 +1,52 @@ +/** + @file + util.h + + @brief + This file provides the definitions, and declares some common APIs for list-algorithm. + + */ + +#ifndef _UTILS_H_ +#define _UTILS_H_ + +#include +#include + +struct listnode +{ + struct listnode *next; + struct listnode *prev; +}; + +#define node_to_item(node, container, member) \ + (container *) (((char*) (node)) - offsetof(container, member)) + +#define list_declare(name) \ + struct listnode name = { \ + .next = &name, \ + .prev = &name, \ + } + +#define list_for_each(node, list) \ + for (node = (list)->next; node != (list); node = node->next) + +#define list_for_each_reverse(node, list) \ + for (node = (list)->prev; node != (list); node = node->prev) + +void list_init(struct listnode *list); +void list_add_tail(struct listnode *list, struct listnode *item); +void list_add_head(struct listnode *head, struct listnode *item); +void list_remove(struct listnode *item); + +#define list_empty(list) ((list) == (list)->next) +#define list_head(list) ((list)->next) +#define list_tail(list) ((list)->prev) + +int epoll_register(int epoll_fd, int fd, unsigned int events); +int epoll_deregister(int epoll_fd, int fd); +const char * get_time(void); +unsigned long clock_msec(void); +pid_t getpid_by_pdp(int, const char*); + +#endif diff --git a/root/package/link4all/quectel-CM/src_fangge/GobiNetCM.c b/root/package/link4all/quectel-CM/src_fangge/GobiNetCM.c new file mode 100755 index 00000000..3a207ce1 --- /dev/null +++ b/root/package/link4all/quectel-CM/src_fangge/GobiNetCM.c @@ -0,0 +1,212 @@ +#include +#include +#include +#include +#include +#include "QMIThread.h" + +#ifdef CONFIG_GOBINET + +// 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 + +int GobiNetSendQMI(PQCQMIMSG pRequest) { + int ret, fd; + + fd = qmiclientId[pRequest->QMIHdr.QMIType]; + + if (fd <= 0) { + dbg_time("%s QMIType: %d has no clientID", __func__, pRequest->QMIHdr.QMIType); + return -ENODEV; + } + + // Always ready to write + if (1 == 1) { + ssize_t nwrites = le16_to_cpu(pRequest->QMIHdr.Length) + 1 - sizeof(QCQMI_HDR); + ret = write(fd, &pRequest->MUXMsg, nwrites); + if (ret == nwrites) { + ret = 0; + } else { + dbg_time("%s write=%d, errno: %d (%s)", __func__, ret, errno, strerror(errno)); + } + } else { + dbg_time("%s poll=%d, errno: %d (%s)", __func__, ret, errno, strerror(errno)); + } + + return ret; +} + +static int GobiNetGetClientID(const char *qcqmi, UCHAR QMIType) { + int ClientId; + ClientId = open(qcqmi, O_RDWR | O_NONBLOCK | O_NOCTTY); + if (ClientId == -1) { + dbg_time("failed to open %s, errno: %d (%s)", qcqmi, errno, strerror(errno)); + return -1; + } + if (ioctl(ClientId, IOCTL_QMI_GET_SERVICE_FILE, QMIType) != 0) { + dbg_time("failed to get ClientID for 0x%02x errno: %d (%s)", QMIType, errno, strerror(errno)); + close(ClientId); + ClientId = 0; + } + + qmiclientId[QMIType] = ClientId; + switch (QMIType) { + case QMUX_TYPE_WDS: dbg_time("Get clientWDS = %d", ClientId); break; + case QMUX_TYPE_DMS: dbg_time("Get clientDMS = %d", ClientId); break; + case QMUX_TYPE_NAS: dbg_time("Get clientNAS = %d", ClientId); break; + case QMUX_TYPE_QOS: dbg_time("Get clientQOS = %d", ClientId); break; + case QMUX_TYPE_WMS: dbg_time("Get clientWMS = %d", ClientId); break; + case QMUX_TYPE_PDS: dbg_time("Get clientPDS = %d", ClientId); break; + case QMUX_TYPE_UIM: dbg_time("Get clientUIM = %d", ClientId); break; + case QMUX_TYPE_WDS_ADMIN: dbg_time("Get clientWDA = %d", ClientId); + break; + default: break; + } + + return ClientId; +} + +int GobiNetDeInit(void) { + unsigned int i; + for (i = 0; i < sizeof(qmiclientId)/sizeof(qmiclientId[0]); i++) + { + if (qmiclientId[i] != 0) + { + close(qmiclientId[i]); + qmiclientId[i] = 0; + } + } + + return 0; +} + + +void * GobiNetThread(void *pData) { + const char *qcqmi = (const char *)pData; + GobiNetGetClientID(qcqmi, QMUX_TYPE_WDS); + GobiNetGetClientID(qcqmi, QMUX_TYPE_DMS); + GobiNetGetClientID(qcqmi, QMUX_TYPE_NAS); + GobiNetGetClientID(qcqmi, QMUX_TYPE_UIM); + GobiNetGetClientID(qcqmi, QMUX_TYPE_WDS_ADMIN); + + if ((qmiclientId[QMUX_TYPE_WDS] == 0) /*|| (clientWDA == -1)*/) { + GobiNetDeInit(); + dbg_time("%s Failed to open %s, errno: %d (%s)", __func__, qcqmi, errno, strerror(errno)); + qmidevice_send_event_to_main(RIL_INDICATE_DEVICE_DISCONNECTED); + pthread_exit(NULL); + return NULL; + } + + qmidevice_send_event_to_main(RIL_INDICATE_DEVICE_CONNECTED); + + while (1) { + struct pollfd pollfds[32] = {{qmidevice_control_fd[1], POLLIN, 0}}; + int ne, ret, nevents = 1; + unsigned int i; + + for (i = 0; i < sizeof(qmiclientId)/sizeof(qmiclientId[0]); i++) + { + if (qmiclientId[i] != 0) + { + pollfds[nevents].fd = qmiclientId[i]; + pollfds[nevents].events = POLLIN; + pollfds[nevents].revents = 0; + nevents++; + } + } + + do { + ret = poll(pollfds, nevents, -1); + } while ((ret < 0) && (errno == EINTR)); + + if (ret <= 0) { + dbg_time("%s poll=%d, errno: %d (%s)", __func__, ret, errno, strerror(errno)); + break; + } + + for (ne = 0; ne < nevents; ne++) { + int fd = pollfds[ne].fd; + short revents = pollfds[ne].revents; + + if (revents & (POLLERR | POLLHUP | POLLNVAL)) { + dbg_time("%s poll err/hup/inval", __func__); + dbg_time("epoll fd = %d, events = 0x%04x", fd, revents); + if (fd == qmidevice_control_fd[1]) { + } else { + } + if (revents & (POLLERR | POLLHUP | POLLNVAL)) + goto __GobiNetThread_quit; + } + + if ((revents & POLLIN) == 0) + continue; + + if (fd == qmidevice_control_fd[1]) { + int triger_event; + if (read(fd, &triger_event, sizeof(triger_event)) == sizeof(triger_event)) { + //DBG("triger_event = 0x%x", triger_event); + switch (triger_event) { + case RIL_REQUEST_QUIT: + goto __GobiNetThread_quit; + break; + case SIGTERM: + case SIGHUP: + case SIGINT: + QmiThreadRecvQMI(NULL); + break; + default: + break; + } + } + continue; + } + + { + ssize_t nreads; + UCHAR QMIBuf[512]; + PQCQMIMSG pResponse = (PQCQMIMSG)QMIBuf; + + nreads = read(fd, &pResponse->MUXMsg, sizeof(QMIBuf) - sizeof(QCQMI_HDR)); + if (nreads <= 0) + { + dbg_time("%s read=%d errno: %d (%s)", __func__, (int)nreads, errno, strerror(errno)); + break; + } + + for (i = 0; i < sizeof(qmiclientId)/sizeof(qmiclientId[0]); i++) + { + if (qmiclientId[i] == fd) + { + pResponse->QMIHdr.QMIType = i; + } + } + + pResponse->QMIHdr.IFType = USB_CTL_MSG_TYPE_QMI; + pResponse->QMIHdr.Length = cpu_to_le16(nreads + sizeof(QCQMI_HDR) - 1); + pResponse->QMIHdr.CtlFlags = 0x00; + pResponse->QMIHdr.ClientId = fd & 0xFF; + + QmiThreadRecvQMI(pResponse); + } + } + } + +__GobiNetThread_quit: + GobiNetDeInit(); + qmidevice_send_event_to_main(RIL_INDICATE_DEVICE_DISCONNECTED); + QmiThreadRecvQMI(NULL); //main thread may pending on QmiThreadSendQMI() + dbg_time("%s exit", __func__); + pthread_exit(NULL); + return NULL; +} + +#else +int GobiNetSendQMI(PQCQMIMSG pRequest) {return -1;} +void * GobiNetThread(void *pData) {dbg_time("please set CONFIG_GOBINET"); return NULL;} +#endif diff --git a/root/package/link4all/quectel-CM/src_fangge/MPQCTL.h b/root/package/link4all/quectel-CM/src_fangge/MPQCTL.h new file mode 100755 index 00000000..679ba2fe --- /dev/null +++ b/root/package/link4all/quectel-CM/src_fangge/MPQCTL.h @@ -0,0 +1,376 @@ +/*=========================================================================== + + M P Q C T L. H +DESCRIPTION: + + This module contains QMI QCTL module. + +INITIALIZATION AND SEQUENCING REQUIREMENTS: + +Copyright (C) 2011 by Qualcomm Technologies, Incorporated. All Rights Reserved. +===========================================================================*/ + +#ifndef MPQCTL_H +#define MPQCTL_H + +#include "MPQMI.h" + +#pragma pack(push, 1) + +// ================= QMICTL ================== + +// QMICTL Control Flags +#define QMICTL_CTL_FLAG_CMD 0x00 +#define QMICTL_CTL_FLAG_RSP 0x01 +#define QMICTL_CTL_FLAG_IND 0x02 + +#if 0 +typedef struct _QMICTL_TRANSACTION_ITEM +{ + LIST_ENTRY List; + UCHAR TransactionId; // QMICTL transaction id + PVOID Context; // Adapter or IocDev + PIRP Irp; +} QMICTL_TRANSACTION_ITEM, *PQMICTL_TRANSACTION_ITEM; +#endif + +typedef struct _QCQMICTL_MSG_HDR +{ + UCHAR CtlFlags; // 00-cmd, 01-rsp, 10-ind + UCHAR TransactionId; + USHORT QMICTLType; + USHORT Length; +} __attribute__ ((packed)) QCQMICTL_MSG_HDR, *PQCQMICTL_MSG_HDR; + +#define QCQMICTL_MSG_HDR_SIZE sizeof(QCQMICTL_MSG_HDR) + +typedef struct _QCQMICTL_MSG_HDR_RESP +{ + UCHAR CtlFlags; // 00-cmd, 01-rsp, 10-ind + UCHAR TransactionId; + USHORT QMICTLType; + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} __attribute__ ((packed)) QCQMICTL_MSG_HDR_RESP, *PQCQMICTL_MSG_HDR_RESP; + +typedef struct _QCQMICTL_MSG +{ + UCHAR CtlFlags; // 00-cmd, 01-rsp, 10-ind + UCHAR TransactionId; + USHORT QMICTLType; + USHORT Length; + UCHAR Payload; +} __attribute__ ((packed)) QCQMICTL_MSG, *PQCQMICTL_MSG; + +// TLV Header +typedef struct _QCQMICTL_TLV_HDR +{ + UCHAR TLVType; + USHORT TLVLength; +} __attribute__ ((packed)) QCQMICTL_TLV_HDR, *PQCQMICTL_TLV_HDR; + +#define QCQMICTL_TLV_HDR_SIZE sizeof(QCQMICTL_TLV_HDR) + +// QMICTL Type +#define QMICTL_SET_INSTANCE_ID_REQ 0x0020 +#define QMICTL_SET_INSTANCE_ID_RESP 0x0020 +#define QMICTL_GET_VERSION_REQ 0x0021 +#define QMICTL_GET_VERSION_RESP 0x0021 +#define QMICTL_GET_CLIENT_ID_REQ 0x0022 +#define QMICTL_GET_CLIENT_ID_RESP 0x0022 +#define QMICTL_RELEASE_CLIENT_ID_REQ 0x0023 +#define QMICTL_RELEASE_CLIENT_ID_RESP 0x0023 +#define QMICTL_REVOKE_CLIENT_ID_IND 0x0024 +#define QMICTL_INVALID_CLIENT_ID_IND 0x0025 +#define QMICTL_SET_DATA_FORMAT_REQ 0x0026 +#define QMICTL_SET_DATA_FORMAT_RESP 0x0026 +#define QMICTL_SYNC_REQ 0x0027 +#define QMICTL_SYNC_RESP 0x0027 +#define QMICTL_SYNC_IND 0x0027 + +#define QMICTL_FLAG_REQUEST 0x00 +#define QMICTL_FLAG_RESPONSE 0x01 +#define QMICTL_FLAG_INDICATION 0x02 + +// QMICTL Message Definitions + +typedef struct _QMICTL_SET_INSTANCE_ID_REQ_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_REQUEST + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_SET_INSTANCE_ID_REQ + USHORT Length; // 4 + UCHAR TLVType; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLVLength; // 1 + UCHAR Value; // Host-unique QMI instance for this device driver +} __attribute__ ((packed)) QMICTL_SET_INSTANCE_ID_REQ_MSG, *PQMICTL_SET_INSTANCE_ID_REQ_MSG; + +typedef struct _QMICTL_SET_INSTANCE_ID_RESP_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_RESPONSE + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_SET_INSTANCE_ID_RESP + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_RESULT_CODE + USHORT TLVLength; // 0x0004 + USHORT QMIResult; + USHORT QMIError; + UCHAR TLV2Type; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLV2Length; // 0x0002 + USHORT QMI_ID; // Upper byte is assigned by MSM, + // lower assigned by host +} __attribute__ ((packed)) QMICTL_SET_INSTANCE_ID_RESP_MSG, *PQMICTL_SET_INSTANCE_ID_RESP_MSG; + +typedef struct _QMICTL_GET_VERSION_REQ_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_REQUEST + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_GET_VERSION_REQ + USHORT Length; // 0 + UCHAR TLVType; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLVLength; // var + UCHAR QMUXTypes; // List of one byte QMUX_TYPE values + // 0xFF returns a list of versions for all + // QMUX_TYPEs implemented on the device +} __attribute__ ((packed)) QMICTL_GET_VERSION_REQ_MSG, *PQMICTL_GET_VERSION_REQ_MSG; + +typedef struct _QMUX_TYPE_VERSION_STRUCT +{ + UCHAR QMUXType; + USHORT MajorVersion; + USHORT MinorVersion; +} __attribute__ ((packed)) QMUX_TYPE_VERSION_STRUCT, *PQMUX_TYPE_VERSION_STRUCT; + +typedef struct _ADDENDUM_VERSION_PREAMBLE +{ + UCHAR LabelLength; + UCHAR Label; +} __attribute__ ((packed)) ADDENDUM_VERSION_PREAMBLE, *PADDENDUM_VERSION_PREAMBLE; + +#define QMICTL_GET_VERSION_RSP_TLV_TYPE_VERSION 0x01 +#define QMICTL_GET_VERSION_RSP_TLV_TYPE_ADD_VERSION 0x10 + +typedef struct _QMICTL_GET_VERSION_RESP_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_RESPONSE + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_GET_VERSION_RESP + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_RESULT_CODE + USHORT TLVLength; // 0x0004 + USHORT QMIResult; + USHORT QMIError; + UCHAR TLV2Type; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLV2Length; // var + UCHAR NumElements; // Num of QMUX_TYPE_VERSION_STRUCT + QMUX_TYPE_VERSION_STRUCT TypeVersion; +} __attribute__ ((packed)) QMICTL_GET_VERSION_RESP_MSG, *PQMICTL_GET_VERSION_RESP_MSG; + +typedef struct _QMICTL_GET_CLIENT_ID_REQ_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_REQUEST + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_GET_CLIENT_ID_REQ + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLVLength; // 1 + UCHAR QMIType; // QMUX type +} __attribute__ ((packed)) QMICTL_GET_CLIENT_ID_REQ_MSG, *PQMICTL_GET_CLIENT_ID_REQ_MSG; + +typedef struct _QMICTL_GET_CLIENT_ID_RESP_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_RESPONSE + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_GET_CLIENT_ID_RESP + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_RESULT_CODE + USHORT TLVLength; // 0x0004 + USHORT QMIResult; // result code + USHORT QMIError; // error code + UCHAR TLV2Type; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLV2Length; // 2 + UCHAR QMIType; + UCHAR ClientId; +} __attribute__ ((packed)) QMICTL_GET_CLIENT_ID_RESP_MSG, *PQMICTL_GET_CLIENT_ID_RESP_MSG; + +typedef struct _QMICTL_RELEASE_CLIENT_ID_REQ_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_REQUEST + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_RELEASE_CLIENT_ID_REQ + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLVLength; // 0x0002 + UCHAR QMIType; + UCHAR ClientId; +} __attribute__ ((packed)) QMICTL_RELEASE_CLIENT_ID_REQ_MSG, *PQMICTL_RELEASE_CLIENT_ID_REQ_MSG; + +typedef struct _QMICTL_RELEASE_CLIENT_ID_RESP_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_RESPONSE + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_RELEASE_CLIENT_ID_RESP + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_RESULT_CODE + USHORT TLVLength; // 0x0004 + USHORT QMIResult; // result code + USHORT QMIError; // error code + UCHAR TLV2Type; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLV2Length; // 2 + UCHAR QMIType; + UCHAR ClientId; +} __attribute__ ((packed)) QMICTL_RELEASE_CLIENT_ID_RESP_MSG, *PQMICTL_RELEASE_CLIENT_ID_RESP_MSG; + +typedef struct _QMICTL_REVOKE_CLIENT_ID_IND_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_INDICATION + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_REVOKE_CLIENT_ID_IND + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLVLength; // 0x0002 + UCHAR QMIType; + UCHAR ClientId; +} __attribute__ ((packed)) QMICTL_REVOKE_CLIENT_ID_IND_MSG, *PQMICTL_REVOKE_CLIENT_ID_IND_MSG; + +typedef struct _QMICTL_INVALID_CLIENT_ID_IND_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_INDICATION + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_REVOKE_CLIENT_ID_IND + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLVLength; // 0x0002 + UCHAR QMIType; + UCHAR ClientId; +} __attribute__ ((packed)) QMICTL_INVALID_CLIENT_ID_IND_MSG, *PQMICTL_INVALID_CLIENT_ID_IND_MSG; + +typedef struct _QMICTL_SET_DATA_FORMAT_REQ_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_REQUEST + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_SET_DATA_FORMAT_REQ + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLVLength; // 1 + UCHAR DataFormat; // 0-default; 1-QoS hdr present +} __attribute__ ((packed)) QMICTL_SET_DATA_FORMAT_REQ_MSG, *PQMICTL_SET_DATA_FORMAT_REQ_MSG; + +#ifdef QC_IP_MODE +#define SET_DATA_FORMAT_TLV_TYPE_LINK_PROTO 0x10 +#define SET_DATA_FORMAT_LINK_PROTO_ETH 0x0001 +#define SET_DATA_FORMAT_LINK_PROTO_IP 0x0002 +typedef struct _QMICTL_SET_DATA_FORMAT_TLV_LINK_PROT +{ + UCHAR TLVType; // Link-Layer Protocol + USHORT TLVLength; // 2 + USHORT LinkProt; // 0x1: ETH; 0x2: IP +} QMICTL_SET_DATA_FORMAT_TLV_LINK_PROT, *PQMICTL_SET_DATA_FORMAT_TLV_LINK_PROT; + +#ifdef QCMP_UL_TLP +#define SET_DATA_FORMAT_TLV_TYPE_UL_TLP 0x11 +typedef struct _QMICTL_SET_DATA_FORMAT_TLV_UL_TLP +{ + UCHAR TLVType; // 0x11, Uplink TLP Setting + USHORT TLVLength; // 1 + UCHAR UlTlpSetting; // 0x0: Disable; 0x01: Enable +} QMICTL_SET_DATA_FORMAT_TLV_UL_TLP, *PQMICTL_SET_DATA_FORMAT_TLV_UL_TLP; +#endif // QCMP_UL_TLP + +#ifdef QCMP_DL_TLP +#define SET_DATA_FORMAT_TLV_TYPE_DL_TLP 0x13 +typedef struct _QMICTL_SET_DATA_FORMAT_TLV_DL_TLP +{ + UCHAR TLVType; // 0x11, Uplink TLP Setting + USHORT TLVLength; // 1 + UCHAR DlTlpSetting; // 0x0: Disable; 0x01: Enable +} QMICTL_SET_DATA_FORMAT_TLV_DL_TLP, *PQMICTL_SET_DATA_FORMAT_TLV_DL_TLP; +#endif // QCMP_DL_TLP + +#endif // QC_IP_MODE + +#ifdef MP_QCQOS_ENABLED +#define SET_DATA_FORMAT_TLV_TYPE_QOS_SETTING 0x12 +typedef struct _QMICTL_SET_DATA_FORMAT_TLV_QOS_SETTING +{ + UCHAR TLVType; // 0x12, QoS setting + USHORT TLVLength; // 1 + UCHAR QosSetting; // 0x0: Disable; 0x01: Enable +} QMICTL_SET_DATA_FORMAT_TLV_QOS_SETTING, *PQMICTL_SET_DATA_FORMAT_TLV_QOS_SETTING; +#endif // MP_QCQOS_ENABLED + +typedef struct _QMICTL_SET_DATA_FORMAT_RESP_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_RESPONSE + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_SET_DATA_FORMAT_RESP + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_RESULT_CODE + USHORT TLVLength; // 0x0004 + USHORT QMIResult; // result code + USHORT QMIError; // error code +} __attribute__ ((packed)) QMICTL_SET_DATA_FORMAT_RESP_MSG, *PQMICTL_SET_DATA_FORMAT_RESP_MSG; + +typedef struct _QMICTL_SYNC_REQ_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_REQUEST + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_CTL_SYNC_REQ + USHORT Length; // 0 +} __attribute__ ((packed)) QMICTL_SYNC_REQ_MSG, *PQMICTL_SYNC_REQ_MSG; + +typedef struct _QMICTL_SYNC_RESP_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_RESPONSE + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_CTL_SYNC_RESP + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_RESULT_CODE + USHORT TLVLength; // 0x0004 + USHORT QMIResult; + USHORT QMIError; +} __attribute__ ((packed)) QMICTL_SYNC_RESP_MSG, *PQMICTL_SYNC_RESP_MSG; + +typedef struct _QMICTL_SYNC_IND_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_INDICATION + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_REVOKE_CLIENT_ID_IND + USHORT Length; +} __attribute__ ((packed)) QMICTL_SYNC_IND_MSG, *PQMICTL_SYNC_IND_MSG; + +typedef struct _QMICTL_MSG +{ + union + { + // Message Header + QCQMICTL_MSG_HDR QMICTLMsgHdr; + QCQMICTL_MSG_HDR_RESP QMICTLMsgHdrRsp; + + // QMICTL Message + QMICTL_SET_INSTANCE_ID_REQ_MSG SetInstanceIdReq; + QMICTL_SET_INSTANCE_ID_RESP_MSG SetInstanceIdRsp; + QMICTL_GET_VERSION_REQ_MSG GetVersionReq; + QMICTL_GET_VERSION_RESP_MSG GetVersionRsp; + QMICTL_GET_CLIENT_ID_REQ_MSG GetClientIdReq; + QMICTL_GET_CLIENT_ID_RESP_MSG GetClientIdRsp; + QMICTL_RELEASE_CLIENT_ID_REQ_MSG ReleaseClientIdReq; + QMICTL_RELEASE_CLIENT_ID_RESP_MSG ReleaseClientIdRsp; + QMICTL_REVOKE_CLIENT_ID_IND_MSG RevokeClientIdInd; + QMICTL_INVALID_CLIENT_ID_IND_MSG InvalidClientIdInd; + QMICTL_SET_DATA_FORMAT_REQ_MSG SetDataFormatReq; + QMICTL_SET_DATA_FORMAT_RESP_MSG SetDataFormatRsp; + QMICTL_SYNC_REQ_MSG SyncReq; + QMICTL_SYNC_RESP_MSG SyncRsp; + QMICTL_SYNC_IND_MSG SyncInd; + }; +} __attribute__ ((packed)) QMICTL_MSG, *PQMICTL_MSG; + +#endif // MPQCTL_H diff --git a/root/package/link4all/quectel-CM/src_fangge/MPQMI.h b/root/package/link4all/quectel-CM/src_fangge/MPQMI.h new file mode 100755 index 00000000..815bc239 --- /dev/null +++ b/root/package/link4all/quectel-CM/src_fangge/MPQMI.h @@ -0,0 +1,220 @@ +/*=========================================================================== + + M P Q M I. H +DESCRIPTION: + + This module contains forward references to the QMI module. + +INITIALIZATION AND SEQUENCING REQUIREMENTS: + +Copyright (C) 2011 by Qualcomm Technologies, Incorporated. All Rights Reserved. +===========================================================================*/ +/*=========================================================================== + + EDIT HISTORY FOR FILE + $Header: //depot/QMI/win/qcdrivers/ndis/MPQMI.h#3 $ + +when who what, where, why +-------- --- ---------------------------------------------------------- +11/20/04 hg Initial version. +===========================================================================*/ + +#ifndef USBQMI_H +#define USBQMI_H + +typedef char CHAR; +typedef unsigned char UCHAR; +typedef unsigned short USHORT; +typedef int INT; +typedef unsigned int UINT; +typedef long LONG; +typedef unsigned int ULONG; +typedef unsigned long long ULONG64; +typedef char *PCHAR; +typedef unsigned char *PUCHAR; +typedef int *PINT; +typedef int BOOL; + +#define TRUE (1 == 1) +#define FALSE (1 != 1) + +#define QMICTL_SUPPORTED_MAJOR_VERSION 1 +#define QMICTL_SUPPORTED_MINOR_VERSION 0 + +#pragma pack(push, 1) + +// ========= USB Control Message ========== + +#define USB_CTL_MSG_TYPE_QMI 0x01 + +// USB Control Message +typedef struct _QCUSB_CTL_MSG_HDR +{ + UCHAR IFType; +} __attribute__ ((packed)) QCUSB_CTL_MSG_HDR, *PQCUSB_CTL_MSG_HDR; + +#define QCUSB_CTL_MSG_HDR_SIZE sizeof(QCUSB_CTL_MSG_HDR) + +typedef struct _QCUSB_CTL_MSG +{ + UCHAR IFType; + UCHAR Message; +} __attribute__ ((packed)) QCUSB_CTL_MSG, *PQCUSB_CTL_MSG; + +#define QCTLV_TYPE_REQUIRED_PARAMETER 0x01 +#define QCTLV_TYPE_RESULT_CODE 0x02 + +// ================= QMI ================== + +// Define QMI Type +typedef enum _QMI_SERVICE_TYPE +{ + QMUX_TYPE_CTL = 0x00, + QMUX_TYPE_WDS = 0x01, + QMUX_TYPE_DMS = 0x02, + QMUX_TYPE_NAS = 0x03, + QMUX_TYPE_QOS = 0x04, + QMUX_TYPE_WMS = 0x05, + QMUX_TYPE_PDS = 0x06, + QMUX_TYPE_UIM = 0x0B, + QMUX_TYPE_WDS_ADMIN = 0x1A, + QMUX_TYPE_MAX = 0xFF, + QMUX_TYPE_ALL = 0xFF +} QMI_SERVICE_TYPE; + +typedef enum _QMI_RESULT_CODE_TYPE +{ + QMI_RESULT_SUCCESS = 0x0000, + QMI_RESULT_FAILURE = 0x0001 +} QMI_RESULT_CODE_TYPE; + +typedef enum _QMI_ERROR_CODE_TYPE +{ + QMI_ERR_NONE = 0x0000, + QMI_ERR_INTERNAL = 0x0003, + QMI_ERR_CLIENT_IDS_EXHAUSTED = 0x0005, + QMI_ERR_DENIED = 0x0006, + QMI_ERR_INVALID_CLIENT_IDS = 0x0007, + QMI_ERR_NO_BATTERY = 0x0008, + QMI_ERR_INVALID_HANDLE = 0x0009, + QMI_ERR_INVALID_PROFILE = 0x000A, + QMI_ERR_STORAGE_EXCEEDED = 0x000B, + QMI_ERR_INCORRECT_PIN = 0x000C, + QMI_ERR_NO_NETWORK = 0x000D, + QMI_ERR_PIN_LOCKED = 0x000E, + QMI_ERR_OUT_OF_CALL = 0x000F, + QMI_ERR_NOT_PROVISIONED = 0x0010, + QMI_ERR_ARG_TOO_LONG = 0x0013, + QMI_ERR_DEVICE_IN_USE = 0x0017, + QMI_ERR_OP_DEVICE_UNSUPPORTED = 0x0019, + QMI_ERR_NO_EFFECT = 0x001A, + QMI_ERR_INVALID_ARG = 0x0020, + QMI_ERR_NO_MEMORY = 0x0021, + QMI_ERR_PIN_BLOCKED = 0x0023, + QMI_ERR_PIN_PERM_BLOCKED = 0x0024, + QMI_ERR_INVALID_INDEX = 0x0031, + QMI_ERR_NO_ENTRY = 0x0032, + QMI_ERR_EXTENDED_INTERNAL = 0x0051, + QMI_ERR_ACCESS_DENIED = 0x0052 +} QMI_ERROR_CODE_TYPE; + +#define QCQMI_CTL_FLAG_SERVICE 0x80 +#define QCQMI_CTL_FLAG_CTL_POINT 0x00 + +typedef struct _QCQMI_HDR +{ + UCHAR IFType; + USHORT Length; + UCHAR CtlFlags; // reserved + UCHAR QMIType; + UCHAR ClientId; +} __attribute__ ((packed)) QCQMI_HDR, *PQCQMI_HDR; + +#define QCQMI_HDR_SIZE (sizeof(QCQMI_HDR)-1) + +typedef struct _QCQMI +{ + UCHAR IFType; + USHORT Length; + UCHAR CtlFlags; // reserved + UCHAR QMIType; + UCHAR ClientId; + UCHAR SDU; +} __attribute__ ((packed)) QCQMI, *PQCQMI; + +typedef struct _QMI_SERVICE_VERSION +{ + USHORT Major; + USHORT Minor; + USHORT AddendumMajor; + USHORT AddendumMinor; +} __attribute__ ((packed)) QMI_SERVICE_VERSION, *PQMI_SERVICE_VERSION; + +// ================= QMUX ================== + +#define QMUX_MSG_OVERHEAD_BYTES 4 // Type(USHORT) Length(USHORT) -- header + +#define QMUX_BROADCAST_CID 0xFF + +typedef struct _QCQMUX_HDR +{ + UCHAR CtlFlags; // 0: single QMUX Msg; 1: + USHORT TransactionId; +} __attribute__ ((packed)) QCQMUX_HDR, *PQCQMUX_HDR; + +typedef struct _QCQMUX +{ + UCHAR CtlFlags; // 0: single QMUX Msg; 1: + USHORT TransactionId; + UCHAR Message; // Type(2), Length(2), Value +} __attribute__ ((packed)) QCQMUX, *PQCQMUX; + +#define QCQMUX_HDR_SIZE sizeof(QCQMUX_HDR) + +typedef struct _QCQMUX_MSG_HDR +{ + USHORT Type; + USHORT Length; +} __attribute__ ((packed)) QCQMUX_MSG_HDR, *PQCQMUX_MSG_HDR; + +#define QCQMUX_MSG_HDR_SIZE sizeof(QCQMUX_MSG_HDR) + +typedef struct _QCQMUX_MSG_HDR_RESP +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} __attribute__ ((packed)) QCQMUX_MSG_HDR_RESP, *PQCQMUX_MSG_HDR_RESP; + +typedef struct _QCQMUX_TLV +{ + UCHAR Type; + USHORT Length; + UCHAR Value; +} __attribute__ ((packed)) QCQMUX_TLV, *PQCQMUX_TLV; + +typedef struct _QMI_TLV_HDR +{ + UCHAR TLVType; + USHORT TLVLength; +} __attribute__ ((packed)) QMI_TLV_HDR, *PQMI_TLV_HDR; + +// QMUX Message Definitions -- QMI SDU +#define QMUX_CTL_FLAG_SINGLE_MSG 0x00 +#define QMUX_CTL_FLAG_COMPOUND_MSG 0x01 +#define QMUX_CTL_FLAG_TYPE_CMD 0x00 +#define QMUX_CTL_FLAG_TYPE_RSP 0x02 +#define QMUX_CTL_FLAG_TYPE_IND 0x04 +#define QMUX_CTL_FLAG_MASK_COMPOUND 0x01 +#define QMUX_CTL_FLAG_MASK_TYPE 0x06 // 00-cmd, 01-rsp, 10-ind + +#pragma pack(pop) + +#endif // USBQMI_H diff --git a/root/package/link4all/quectel-CM/src_fangge/MPQMUX.c b/root/package/link4all/quectel-CM/src_fangge/MPQMUX.c new file mode 100755 index 00000000..dba7340c --- /dev/null +++ b/root/package/link4all/quectel-CM/src_fangge/MPQMUX.c @@ -0,0 +1,413 @@ +#include "QMIThread.h" +static char line[1024]; +static pthread_mutex_t dumpQMIMutex = PTHREAD_MUTEX_INITIALIZER; +#undef dbg +#define dbg( format, arg... ) do {if (strlen(line) < sizeof(line)) snprintf(&line[strlen(line)], sizeof(line) - strlen(line), format, ## arg);} while (0) + +PQMI_TLV_HDR GetTLV (PQCQMUX_MSG_HDR pQMUXMsgHdr, int TLVType); + +typedef struct { + UINT type; + const char *name; +} QMI_NAME_T; + +#define qmi_name_item(type) {type, #type} + +static const QMI_NAME_T qmi_IFType[] = { +{USB_CTL_MSG_TYPE_QMI, "USB_CTL_MSG_TYPE_QMI"}, +}; + +static const QMI_NAME_T qmi_CtlFlags[] = { +qmi_name_item(QMICTL_CTL_FLAG_CMD), +qmi_name_item(QCQMI_CTL_FLAG_SERVICE), +}; + +static const QMI_NAME_T qmi_QMIType[] = { +qmi_name_item(QMUX_TYPE_CTL), +qmi_name_item(QMUX_TYPE_WDS), +qmi_name_item(QMUX_TYPE_DMS), +qmi_name_item(QMUX_TYPE_NAS), +qmi_name_item(QMUX_TYPE_QOS), +qmi_name_item(QMUX_TYPE_WMS), +qmi_name_item(QMUX_TYPE_PDS), +qmi_name_item(QMUX_TYPE_WDS_ADMIN), +}; + +static const QMI_NAME_T qmi_ctl_CtlFlags[] = { +qmi_name_item(QMICTL_FLAG_REQUEST), +qmi_name_item(QMICTL_FLAG_RESPONSE), +qmi_name_item(QMICTL_FLAG_INDICATION), +}; + +static const QMI_NAME_T qmux_ctl_QMICTLType[] = { +// QMICTL Type +qmi_name_item(QMICTL_SET_INSTANCE_ID_REQ), // 0x0020 +qmi_name_item(QMICTL_SET_INSTANCE_ID_RESP), // 0x0020 +qmi_name_item(QMICTL_GET_VERSION_REQ), // 0x0021 +qmi_name_item(QMICTL_GET_VERSION_RESP), // 0x0021 +qmi_name_item(QMICTL_GET_CLIENT_ID_REQ), // 0x0022 +qmi_name_item(QMICTL_GET_CLIENT_ID_RESP), // 0x0022 +qmi_name_item(QMICTL_RELEASE_CLIENT_ID_REQ), // 0x0023 +qmi_name_item(QMICTL_RELEASE_CLIENT_ID_RESP), // 0x0023 +qmi_name_item(QMICTL_REVOKE_CLIENT_ID_IND), // 0x0024 +qmi_name_item(QMICTL_INVALID_CLIENT_ID_IND), // 0x0025 +qmi_name_item(QMICTL_SET_DATA_FORMAT_REQ), // 0x0026 +qmi_name_item(QMICTL_SET_DATA_FORMAT_RESP), // 0x0026 +qmi_name_item(QMICTL_SYNC_REQ), // 0x0027 +qmi_name_item(QMICTL_SYNC_RESP), // 0x0027 +qmi_name_item(QMICTL_SYNC_IND), // 0x0027 +}; + +static const QMI_NAME_T qmux_CtlFlags[] = { +qmi_name_item(QMUX_CTL_FLAG_TYPE_CMD), +qmi_name_item(QMUX_CTL_FLAG_TYPE_RSP), +qmi_name_item(QMUX_CTL_FLAG_TYPE_IND), +}; + + +static const QMI_NAME_T qmux_wds_Type[] = { +qmi_name_item(QMIWDS_SET_EVENT_REPORT_REQ), // 0x0001 +qmi_name_item(QMIWDS_SET_EVENT_REPORT_RESP), // 0x0001 +qmi_name_item(QMIWDS_EVENT_REPORT_IND), // 0x0001 +qmi_name_item(QMIWDS_START_NETWORK_INTERFACE_REQ), // 0x0020 +qmi_name_item(QMIWDS_START_NETWORK_INTERFACE_RESP), // 0x0020 +qmi_name_item(QMIWDS_STOP_NETWORK_INTERFACE_REQ), // 0x0021 +qmi_name_item(QMIWDS_STOP_NETWORK_INTERFACE_RESP), // 0x0021 +qmi_name_item(QMIWDS_GET_PKT_SRVC_STATUS_REQ), // 0x0022 +qmi_name_item(QMIWDS_GET_PKT_SRVC_STATUS_RESP), // 0x0022 +qmi_name_item(QMIWDS_GET_PKT_SRVC_STATUS_IND), // 0x0022 +qmi_name_item(QMIWDS_GET_CURRENT_CHANNEL_RATE_REQ), // 0x0023 +qmi_name_item(QMIWDS_GET_CURRENT_CHANNEL_RATE_RESP), // 0x0023 +qmi_name_item(QMIWDS_GET_PKT_STATISTICS_REQ), // 0x0024 +qmi_name_item(QMIWDS_GET_PKT_STATISTICS_RESP), // 0x0024 +qmi_name_item(QMIWDS_MODIFY_PROFILE_SETTINGS_REQ), // 0x0028 +qmi_name_item(QMIWDS_MODIFY_PROFILE_SETTINGS_RESP), // 0x0028 +qmi_name_item(QMIWDS_GET_PROFILE_SETTINGS_REQ), // 0x002B +qmi_name_item(QMIWDS_GET_PROFILE_SETTINGS_RESP), // 0x002BD +qmi_name_item(QMIWDS_GET_DEFAULT_SETTINGS_REQ), // 0x002C +qmi_name_item(QMIWDS_GET_DEFAULT_SETTINGS_RESP), // 0x002C +qmi_name_item(QMIWDS_GET_RUNTIME_SETTINGS_REQ), // 0x002D +qmi_name_item(QMIWDS_GET_RUNTIME_SETTINGS_RESP), // 0x002D +qmi_name_item(QMIWDS_GET_MIP_MODE_REQ), // 0x002F +qmi_name_item(QMIWDS_GET_MIP_MODE_RESP), // 0x002F +qmi_name_item(QMIWDS_GET_DATA_BEARER_REQ), // 0x0037 +qmi_name_item(QMIWDS_GET_DATA_BEARER_RESP), // 0x0037 +qmi_name_item(QMIWDS_DUN_CALL_INFO_REQ), // 0x0038 +qmi_name_item(QMIWDS_DUN_CALL_INFO_RESP), // 0x0038 +qmi_name_item(QMIWDS_DUN_CALL_INFO_IND), // 0x0038 +qmi_name_item(QMIWDS_SET_CLIENT_IP_FAMILY_PREF_REQ), // 0x004D +qmi_name_item(QMIWDS_SET_CLIENT_IP_FAMILY_PREF_RESP), // 0x004D +qmi_name_item(QMIWDS_BIND_MUX_DATA_PORT_REQ), // 0x00A2 +qmi_name_item(QMIWDS_BIND_MUX_DATA_PORT_RESP), // 0x00A2 +}; + +static const QMI_NAME_T qmux_dms_Type[] = { +// ======================= DMS ============================== +qmi_name_item(QMIDMS_SET_EVENT_REPORT_REQ), // 0x0001 +qmi_name_item(QMIDMS_SET_EVENT_REPORT_RESP), // 0x0001 +qmi_name_item(QMIDMS_EVENT_REPORT_IND), // 0x0001 +qmi_name_item(QMIDMS_GET_DEVICE_CAP_REQ), // 0x0020 +qmi_name_item(QMIDMS_GET_DEVICE_CAP_RESP), // 0x0020 +qmi_name_item(QMIDMS_GET_DEVICE_MFR_REQ), // 0x0021 +qmi_name_item(QMIDMS_GET_DEVICE_MFR_RESP), // 0x0021 +qmi_name_item(QMIDMS_GET_DEVICE_MODEL_ID_REQ), // 0x0022 +qmi_name_item(QMIDMS_GET_DEVICE_MODEL_ID_RESP), // 0x0022 +qmi_name_item(QMIDMS_GET_DEVICE_REV_ID_REQ), // 0x0023 +qmi_name_item(QMIDMS_GET_DEVICE_REV_ID_RESP), // 0x0023 +qmi_name_item(QMIDMS_GET_MSISDN_REQ), // 0x0024 +qmi_name_item(QMIDMS_GET_MSISDN_RESP), // 0x0024 +qmi_name_item(QMIDMS_GET_DEVICE_SERIAL_NUMBERS_REQ), // 0x0025 +qmi_name_item(QMIDMS_GET_DEVICE_SERIAL_NUMBERS_RESP), // 0x0025 +qmi_name_item(QMIDMS_UIM_SET_PIN_PROTECTION_REQ), // 0x0027 +qmi_name_item(QMIDMS_UIM_SET_PIN_PROTECTION_RESP), // 0x0027 +qmi_name_item(QMIDMS_UIM_VERIFY_PIN_REQ), // 0x0028 +qmi_name_item(QMIDMS_UIM_VERIFY_PIN_RESP), // 0x0028 +qmi_name_item(QMIDMS_UIM_UNBLOCK_PIN_REQ), // 0x0029 +qmi_name_item(QMIDMS_UIM_UNBLOCK_PIN_RESP), // 0x0029 +qmi_name_item(QMIDMS_UIM_CHANGE_PIN_REQ), // 0x002A +qmi_name_item(QMIDMS_UIM_CHANGE_PIN_RESP), // 0x002A +qmi_name_item(QMIDMS_UIM_GET_PIN_STATUS_REQ), // 0x002B +qmi_name_item(QMIDMS_UIM_GET_PIN_STATUS_RESP), // 0x002B +qmi_name_item(QMIDMS_GET_DEVICE_HARDWARE_REV_REQ), // 0x002C +qmi_name_item(QMIDMS_GET_DEVICE_HARDWARE_REV_RESP), // 0x002C +qmi_name_item(QMIDMS_GET_OPERATING_MODE_REQ), // 0x002D +qmi_name_item(QMIDMS_GET_OPERATING_MODE_RESP), // 0x002D +qmi_name_item(QMIDMS_SET_OPERATING_MODE_REQ), // 0x002E +qmi_name_item(QMIDMS_SET_OPERATING_MODE_RESP), // 0x002E +qmi_name_item(QMIDMS_GET_ACTIVATED_STATUS_REQ), // 0x0031 +qmi_name_item(QMIDMS_GET_ACTIVATED_STATUS_RESP), // 0x0031 +qmi_name_item(QMIDMS_ACTIVATE_AUTOMATIC_REQ), // 0x0032 +qmi_name_item(QMIDMS_ACTIVATE_AUTOMATIC_RESP), // 0x0032 +qmi_name_item(QMIDMS_ACTIVATE_MANUAL_REQ), // 0x0033 +qmi_name_item(QMIDMS_ACTIVATE_MANUAL_RESP), // 0x0033 +qmi_name_item(QMIDMS_UIM_GET_ICCID_REQ), // 0x003C +qmi_name_item(QMIDMS_UIM_GET_ICCID_RESP), // 0x003C +qmi_name_item(QMIDMS_UIM_GET_CK_STATUS_REQ), // 0x0040 +qmi_name_item(QMIDMS_UIM_GET_CK_STATUS_RESP), // 0x0040 +qmi_name_item(QMIDMS_UIM_SET_CK_PROTECTION_REQ), // 0x0041 +qmi_name_item(QMIDMS_UIM_SET_CK_PROTECTION_RESP), // 0x0041 +qmi_name_item(QMIDMS_UIM_UNBLOCK_CK_REQ), // 0x0042 +qmi_name_item(QMIDMS_UIM_UNBLOCK_CK_RESP), // 0x0042 +qmi_name_item(QMIDMS_UIM_GET_IMSI_REQ), // 0x0043 +qmi_name_item(QMIDMS_UIM_GET_IMSI_RESP), // 0x0043 +qmi_name_item(QMIDMS_UIM_GET_STATE_REQ), // 0x0044 +qmi_name_item(QMIDMS_UIM_GET_STATE_RESP), // 0x0044 +qmi_name_item(QMIDMS_GET_BAND_CAP_REQ), // 0x0045 +qmi_name_item(QMIDMS_GET_BAND_CAP_RESP), // 0x0045 +}; + +static const QMI_NAME_T qmux_nas_Type[] = { +// ======================= NAS ============================== +qmi_name_item(QMINAS_SET_EVENT_REPORT_REQ), // 0x0002 +qmi_name_item(QMINAS_SET_EVENT_REPORT_RESP), // 0x0002 +qmi_name_item(QMINAS_EVENT_REPORT_IND), // 0x0002 +qmi_name_item(QMINAS_GET_SIGNAL_STRENGTH_REQ), // 0x0020 +qmi_name_item(QMINAS_GET_SIGNAL_STRENGTH_RESP), // 0x0020 +qmi_name_item(QMINAS_PERFORM_NETWORK_SCAN_REQ), // 0x0021 +qmi_name_item(QMINAS_PERFORM_NETWORK_SCAN_RESP), // 0x0021 +qmi_name_item(QMINAS_INITIATE_NW_REGISTER_REQ), // 0x0022 +qmi_name_item(QMINAS_INITIATE_NW_REGISTER_RESP), // 0x0022 +qmi_name_item(QMINAS_INITIATE_ATTACH_REQ), // 0x0023 +qmi_name_item(QMINAS_INITIATE_ATTACH_RESP), // 0x0023 +qmi_name_item(QMINAS_GET_SERVING_SYSTEM_REQ), // 0x0024 +qmi_name_item(QMINAS_GET_SERVING_SYSTEM_RESP), // 0x0024 +qmi_name_item(QMINAS_SERVING_SYSTEM_IND), // 0x0024 +qmi_name_item(QMINAS_GET_HOME_NETWORK_REQ), // 0x0025 +qmi_name_item(QMINAS_GET_HOME_NETWORK_RESP), // 0x0025 +qmi_name_item(QMINAS_GET_PREFERRED_NETWORK_REQ), // 0x0026 +qmi_name_item(QMINAS_GET_PREFERRED_NETWORK_RESP), // 0x0026 +qmi_name_item(QMINAS_SET_PREFERRED_NETWORK_REQ), // 0x0027 +qmi_name_item(QMINAS_SET_PREFERRED_NETWORK_RESP), // 0x0027 +qmi_name_item(QMINAS_GET_FORBIDDEN_NETWORK_REQ), // 0x0028 +qmi_name_item(QMINAS_GET_FORBIDDEN_NETWORK_RESP), // 0x0028 +qmi_name_item(QMINAS_SET_FORBIDDEN_NETWORK_REQ), // 0x0029 +qmi_name_item(QMINAS_SET_FORBIDDEN_NETWORK_RESP), // 0x0029 +qmi_name_item(QMINAS_SET_TECHNOLOGY_PREF_REQ), // 0x002A +qmi_name_item(QMINAS_SET_TECHNOLOGY_PREF_RESP), // 0x002A +qmi_name_item(QMINAS_GET_RF_BAND_INFO_REQ), // 0x0031 +qmi_name_item(QMINAS_GET_RF_BAND_INFO_RESP), // 0x0031 +qmi_name_item(QMINAS_GET_PLMN_NAME_REQ), // 0x0044 +qmi_name_item(QMINAS_GET_PLMN_NAME_RESP), // 0x0044 +qmi_name_item(MEIGE_PACKET_TRANSFER_START_IND), // 0X100 +qmi_name_item(MEIGE_PACKET_TRANSFER_END_IND), // 0X101 +qmi_name_item(QMINAS_GET_SYS_INFO_REQ), // 0x004D +qmi_name_item(QMINAS_GET_SYS_INFO_RESP), // 0x004D +qmi_name_item(QMINAS_SYS_INFO_IND), // 0x004D +}; + +static const QMI_NAME_T qmux_wms_Type[] = { +// ======================= WMS ============================== +qmi_name_item(QMIWMS_SET_EVENT_REPORT_REQ), // 0x0001 +qmi_name_item(QMIWMS_SET_EVENT_REPORT_RESP), // 0x0001 +qmi_name_item(QMIWMS_EVENT_REPORT_IND), // 0x0001 +qmi_name_item(QMIWMS_RAW_SEND_REQ), // 0x0020 +qmi_name_item(QMIWMS_RAW_SEND_RESP), // 0x0020 +qmi_name_item(QMIWMS_RAW_WRITE_REQ), // 0x0021 +qmi_name_item(QMIWMS_RAW_WRITE_RESP), // 0x0021 +qmi_name_item(QMIWMS_RAW_READ_REQ), // 0x0022 +qmi_name_item(QMIWMS_RAW_READ_RESP), // 0x0022 +qmi_name_item(QMIWMS_MODIFY_TAG_REQ), // 0x0023 +qmi_name_item(QMIWMS_MODIFY_TAG_RESP), // 0x0023 +qmi_name_item(QMIWMS_DELETE_REQ), // 0x0024 +qmi_name_item(QMIWMS_DELETE_RESP), // 0x0024 +qmi_name_item(QMIWMS_GET_MESSAGE_PROTOCOL_REQ), // 0x0030 +qmi_name_item(QMIWMS_GET_MESSAGE_PROTOCOL_RESP), // 0x0030 +qmi_name_item(QMIWMS_LIST_MESSAGES_REQ), // 0x0031 +qmi_name_item(QMIWMS_LIST_MESSAGES_RESP), // 0x0031 +qmi_name_item(QMIWMS_GET_SMSC_ADDRESS_REQ), // 0x0034 +qmi_name_item(QMIWMS_GET_SMSC_ADDRESS_RESP), // 0x0034 +qmi_name_item(QMIWMS_SET_SMSC_ADDRESS_REQ), // 0x0035 +qmi_name_item(QMIWMS_SET_SMSC_ADDRESS_RESP), // 0x0035 +qmi_name_item(QMIWMS_GET_STORE_MAX_SIZE_REQ), // 0x0036 +qmi_name_item(QMIWMS_GET_STORE_MAX_SIZE_RESP), // 0x0036 +}; + +static const QMI_NAME_T qmux_wds_admin_Type[] = { +qmi_name_item(QMIWDS_ADMIN_SET_DATA_FORMAT_REQ), // 0x0020 +qmi_name_item(QMIWDS_ADMIN_SET_DATA_FORMAT_RESP), // 0x0020 +qmi_name_item(QMIWDS_ADMIN_GET_DATA_FORMAT_REQ), // 0x0021 +qmi_name_item(QMIWDS_ADMIN_GET_DATA_FORMAT_RESP), // 0x0021 +qmi_name_item(QMIWDS_ADMIN_SET_QMAP_SETTINGS_REQ), // 0x002B +qmi_name_item(QMIWDS_ADMIN_SET_QMAP_SETTINGS_RESP), // 0x002B +qmi_name_item(QMIWDS_ADMIN_GET_QMAP_SETTINGS_REQ), // 0x002C +qmi_name_item(QMIWDS_ADMIN_GET_QMAP_SETTINGS_RESP), // 0x002C +}; + +static const QMI_NAME_T qmux_uim_Type[] = { +qmi_name_item( QMIUIM_READ_TRANSPARENT_REQ), // 0x0020 +qmi_name_item( QMIUIM_READ_TRANSPARENT_RESP), // 0x0020 +qmi_name_item( QMIUIM_READ_TRANSPARENT_IND), // 0x0020 +qmi_name_item( QMIUIM_READ_RECORD_REQ), // 0x0021 +qmi_name_item( QMIUIM_READ_RECORD_RESP), // 0x0021 +qmi_name_item( QMIUIM_READ_RECORD_IND), // 0x0021 +qmi_name_item( QMIUIM_WRITE_TRANSPARENT_REQ), // 0x0022 +qmi_name_item( QMIUIM_WRITE_TRANSPARENT_RESP), // 0x0022 +qmi_name_item( QMIUIM_WRITE_TRANSPARENT_IND), // 0x0022 +qmi_name_item( QMIUIM_WRITE_RECORD_REQ), // 0x0023 +qmi_name_item( QMIUIM_WRITE_RECORD_RESP), // 0x0023 +qmi_name_item( QMIUIM_WRITE_RECORD_IND), // 0x0023 +qmi_name_item( QMIUIM_SET_PIN_PROTECTION_REQ), // 0x0025 +qmi_name_item( QMIUIM_SET_PIN_PROTECTION_RESP), // 0x0025 +qmi_name_item( QMIUIM_SET_PIN_PROTECTION_IND), // 0x0025 +qmi_name_item( QMIUIM_VERIFY_PIN_REQ), // 0x0026 +qmi_name_item( QMIUIM_VERIFY_PIN_RESP), // 0x0026 +qmi_name_item( QMIUIM_VERIFY_PIN_IND), // 0x0026 +qmi_name_item( QMIUIM_UNBLOCK_PIN_REQ), // 0x0027 +qmi_name_item( QMIUIM_UNBLOCK_PIN_RESP), // 0x0027 +qmi_name_item( QMIUIM_UNBLOCK_PIN_IND), // 0x0027 +qmi_name_item( QMIUIM_CHANGE_PIN_REQ), // 0x0028 +qmi_name_item( QMIUIM_CHANGE_PIN_RESP), // 0x0028 +qmi_name_item( QMIUIM_CHANGE_PIN_IND), // 0x0028 +qmi_name_item( QMIUIM_DEPERSONALIZATION_REQ), // 0x0029 +qmi_name_item( QMIUIM_DEPERSONALIZATION_RESP), // 0x0029 +qmi_name_item( QMIUIM_EVENT_REG_REQ), // 0x002E +qmi_name_item( QMIUIM_EVENT_REG_RESP), // 0x002E +qmi_name_item( QMIUIM_GET_CARD_STATUS_REQ), // 0x002F +qmi_name_item( QMIUIM_GET_CARD_STATUS_RESP), // 0x002F +qmi_name_item( QMIUIM_STATUS_CHANGE_IND), // 0x0032 +}; + +static const char * qmi_name_get(const QMI_NAME_T *table, size_t size, int type, const char *tag) { + static char unknow[40]; + size_t i; + + if (qmux_CtlFlags == table) { + if (!strcmp(tag, "_REQ")) + tag = "_CMD"; + else if (!strcmp(tag, "_RESP")) + tag = "_RSP"; + } + + for (i = 0; i < size; i++) { + if (table[i].type == (UINT)type) { + if (!tag || (strstr(table[i].name, tag))) + return table[i].name; + } + } + sprintf(unknow, "unknow_%x", type); + return unknow; +} + +#define QMI_NAME(table, type) qmi_name_get(table, sizeof(table) / sizeof(table[0]), type, 0) +#define QMUX_NAME(table, type, tag) qmi_name_get(table, sizeof(table) / sizeof(table[0]), type, tag) + +void dump_tlv(PQCQMUX_MSG_HDR pQMUXMsgHdr) { + int TLVFind = 0; + int i; + //dbg("QCQMUX_TLV-----------------------------------\n"); + //dbg("{Type,\tLength,\tValue}\n"); + + while (1) { + PQMI_TLV_HDR TLVHdr = GetTLV(pQMUXMsgHdr, 0x1000 + (++TLVFind)); + if (TLVHdr == NULL) + break; + + //if ((TLVHdr->TLVType == 0x02) && ((USHORT *)(TLVHdr+1))[0]) + { + dbg("{%02x,\t%04x,\t", TLVHdr->TLVType, le16_to_cpu(TLVHdr->TLVLength)); + for (i = 0; i < le16_to_cpu(TLVHdr->TLVLength); i++) { + dbg("%02x ", ((UCHAR *)(TLVHdr+1))[i]); + } + dbg("}\n"); + } + } // while +} + +void dump_ctl(PQCQMICTL_MSG_HDR CTLHdr) { + const char *tag; + + dbg("TransactionId: %02x\n", CTLHdr->TransactionId); + switch (CTLHdr->CtlFlags) { + case QMICTL_FLAG_REQUEST: tag = "_REQ"; break; + case QMICTL_FLAG_RESPONSE: tag = "_RESP"; break; + case QMICTL_FLAG_INDICATION: tag = "_IND"; break; + default: tag = 0; break; + } + dbg("QMICTLType: %04x\t%s\n", le16_to_cpu(CTLHdr->QMICTLType), + QMUX_NAME(qmux_ctl_QMICTLType, le16_to_cpu(CTLHdr->QMICTLType), tag)); + dbg("Length: %04x\n", le16_to_cpu(CTLHdr->Length)); + + dump_tlv((PQCQMUX_MSG_HDR)(&CTLHdr->QMICTLType)); +} + +int dump_qmux(QMI_SERVICE_TYPE serviceType, PQCQMUX_HDR QMUXHdr) { + PQCQMUX_MSG_HDR QMUXMsgHdr = (PQCQMUX_MSG_HDR) (QMUXHdr + 1); + CHAR *tag; + + //dbg("QCQMUX--------------------------------------------\n"); + switch (QMUXHdr->CtlFlags&QMUX_CTL_FLAG_MASK_TYPE) { + case QMUX_CTL_FLAG_TYPE_CMD: tag = "_REQ"; break; + case QMUX_CTL_FLAG_TYPE_RSP: tag = "_RESP"; break; + case QMUX_CTL_FLAG_TYPE_IND: tag = "_IND"; break; + default: tag = 0; break; + } + //dbg("CtlFlags: %02x\t\t%s\n", QMUXHdr->CtlFlags, QMUX_NAME(qmux_CtlFlags, QMUXHdr->CtlFlags, tag)); + dbg("TransactionId: %04x\n", le16_to_cpu(QMUXHdr->TransactionId)); + + //dbg("QCQMUX_MSG_HDR-----------------------------------\n"); + switch (serviceType) { + case QMUX_TYPE_DMS: + dbg("Type: %04x\t%s\n", le16_to_cpu(QMUXMsgHdr->Type), + QMUX_NAME(qmux_dms_Type, le16_to_cpu(QMUXMsgHdr->Type), tag)); + break; + case QMUX_TYPE_NAS: + dbg("Type: %04x\t%s\n", le16_to_cpu(QMUXMsgHdr->Type), + QMUX_NAME(qmux_nas_Type, le16_to_cpu(QMUXMsgHdr->Type), tag)); + break; + case QMUX_TYPE_WDS: + dbg("Type: %04x\t%s\n", le16_to_cpu(QMUXMsgHdr->Type), + QMUX_NAME(qmux_wds_Type, le16_to_cpu(QMUXMsgHdr->Type), tag)); + break; + case QMUX_TYPE_WMS: + dbg("Type: %04x\t%s\n", le16_to_cpu(QMUXMsgHdr->Type), + QMUX_NAME(qmux_wms_Type, le16_to_cpu(QMUXMsgHdr->Type), tag)); + break; + case QMUX_TYPE_WDS_ADMIN: + dbg("Type: %04x\t%s\n", le16_to_cpu(QMUXMsgHdr->Type), + QMUX_NAME(qmux_wds_admin_Type, le16_to_cpu(QMUXMsgHdr->Type), tag)); + break; + case QMUX_TYPE_UIM: + dbg("Type: %04x\t%s\n", le16_to_cpu(QMUXMsgHdr->Type), + QMUX_NAME(qmux_uim_Type, le16_to_cpu(QMUXMsgHdr->Type), tag)); + break; + case QMUX_TYPE_PDS: + case QMUX_TYPE_QOS: + case QMUX_TYPE_CTL: + default: + dbg("Type: %04x\t%s\n", le16_to_cpu(QMUXMsgHdr->Type), "PDS/QOS/CTL/unknown!"); + break; + } + dbg("Length: %04x\n", le16_to_cpu(QMUXMsgHdr->Length)); + + dump_tlv(QMUXMsgHdr); + + return 0; +} + +void dump_qmi(void *dataBuffer, int dataLen) +{ + PQCQMI_HDR QMIHdr = (PQCQMI_HDR)dataBuffer; + PQCQMUX_HDR QMUXHdr = (PQCQMUX_HDR) (QMIHdr + 1); + PQCQMICTL_MSG_HDR CTLHdr = (PQCQMICTL_MSG_HDR) (QMIHdr + 1); + + int i; + + if (!debug_qmi) + return; + + pthread_mutex_lock(&dumpQMIMutex); + line[0] = 0; + for (i = 0; i < dataLen; i++) { + dbg("%02x ", ((unsigned char *)dataBuffer)[i]); + } + dbg_time("%s", line); + line[0] = 0; + + if ((QMIHdr->QMIType == QMUX_TYPE_CTL) ) { + dump_ctl(CTLHdr); + } else { + dump_qmux(QMIHdr->QMIType, QMUXHdr); + } + dbg_time("%s", line); + pthread_mutex_unlock(&dumpQMIMutex); +} diff --git a/root/package/link4all/quectel-CM/src_fangge/MPQMUX.h b/root/package/link4all/quectel-CM/src_fangge/MPQMUX.h new file mode 100755 index 00000000..39e4edbd --- /dev/null +++ b/root/package/link4all/quectel-CM/src_fangge/MPQMUX.h @@ -0,0 +1,1067 @@ +/*=========================================================================== + + M P Q M U X. H +DESCRIPTION: + + This file provides support for QMUX. + +INITIALIZATION AND SEQUENCING REQUIREMENTS: + +Copyright (C) 2011 by Qualcomm Technologies, Incorporated. All Rights Reserved. +===========================================================================*/ + +#ifndef MPQMUX_H +#define MPQMUX_H + +#include "MPQMI.h" + +#pragma pack(push, 1) + +#define QMIWDS_SET_EVENT_REPORT_REQ 0x0001 +#define QMIWDS_SET_EVENT_REPORT_RESP 0x0001 +#define QMIWDS_EVENT_REPORT_IND 0x0001 +#define QMIWDS_START_NETWORK_INTERFACE_REQ 0x0020 +#define QMIWDS_START_NETWORK_INTERFACE_RESP 0x0020 +#define QMIWDS_STOP_NETWORK_INTERFACE_REQ 0x0021 +#define QMIWDS_STOP_NETWORK_INTERFACE_RESP 0x0021 +#define QMIWDS_GET_PKT_SRVC_STATUS_REQ 0x0022 +#define QMIWDS_GET_PKT_SRVC_STATUS_RESP 0x0022 +#define QMIWDS_GET_PKT_SRVC_STATUS_IND 0x0022 +#define QMIWDS_GET_CURRENT_CHANNEL_RATE_REQ 0x0023 +#define QMIWDS_GET_CURRENT_CHANNEL_RATE_RESP 0x0023 +#define QMIWDS_GET_PKT_STATISTICS_REQ 0x0024 +#define QMIWDS_GET_PKT_STATISTICS_RESP 0x0024 +#define QMIWDS_MODIFY_PROFILE_SETTINGS_REQ 0x0028 +#define QMIWDS_MODIFY_PROFILE_SETTINGS_RESP 0x0028 +#define QMIWDS_GET_PROFILE_SETTINGS_REQ 0x002B +#define QMIWDS_GET_PROFILE_SETTINGS_RESP 0x002B +#define QMIWDS_GET_DEFAULT_SETTINGS_REQ 0x002C +#define QMIWDS_GET_DEFAULT_SETTINGS_RESP 0x002C +#define QMIWDS_GET_RUNTIME_SETTINGS_REQ 0x002D +#define QMIWDS_GET_RUNTIME_SETTINGS_RESP 0x002D +#define QMIWDS_GET_MIP_MODE_REQ 0x002F +#define QMIWDS_GET_MIP_MODE_RESP 0x002F +#define QMIWDS_GET_DATA_BEARER_REQ 0x0037 +#define QMIWDS_GET_DATA_BEARER_RESP 0x0037 +#define QMIWDS_DUN_CALL_INFO_REQ 0x0038 +#define QMIWDS_DUN_CALL_INFO_RESP 0x0038 +#define QMIWDS_DUN_CALL_INFO_IND 0x0038 +#define QMIWDS_SET_CLIENT_IP_FAMILY_PREF_REQ 0x004D +#define QMIWDS_SET_CLIENT_IP_FAMILY_PREF_RESP 0x004D +#define QMIWDS_BIND_MUX_DATA_PORT_REQ 0x00A2 +#define QMIWDS_BIND_MUX_DATA_PORT_RESP 0x00A2 + + +// Stats masks +#define QWDS_STAT_MASK_TX_PKT_OK 0x00000001 +#define QWDS_STAT_MASK_RX_PKT_OK 0x00000002 +#define QWDS_STAT_MASK_TX_PKT_ER 0x00000004 +#define QWDS_STAT_MASK_RX_PKT_ER 0x00000008 +#define QWDS_STAT_MASK_TX_PKT_OF 0x00000010 +#define QWDS_STAT_MASK_RX_PKT_OF 0x00000020 + +// TLV Types for xfer statistics +#define TLV_WDS_TX_GOOD_PKTS 0x10 +#define TLV_WDS_RX_GOOD_PKTS 0x11 +#define TLV_WDS_TX_ERROR 0x12 +#define TLV_WDS_RX_ERROR 0x13 +#define TLV_WDS_TX_OVERFLOW 0x14 +#define TLV_WDS_RX_OVERFLOW 0x15 +#define TLV_WDS_CHANNEL_RATE 0x16 +#define TLV_WDS_DATA_BEARER 0x17 +#define TLV_WDS_DORMANCY_STATUS 0x18 + +#define QWDS_PKT_DATA_DISCONNECTED 0x01 +#define QWDS_PKT_DATA_CONNECTED 0x02 +#define QWDS_PKT_DATA_SUSPENDED 0x03 +#define QWDS_PKT_DATA_AUTHENTICATING 0x04 + +#define QMIWDS_ADMIN_SET_DATA_FORMAT_REQ 0x0020 +#define QMIWDS_ADMIN_SET_DATA_FORMAT_RESP 0x0020 +#define QMIWDS_ADMIN_GET_DATA_FORMAT_REQ 0x0021 +#define QMIWDS_ADMIN_GET_DATA_FORMAT_RESP 0x0021 +#define QMIWDS_ADMIN_SET_QMAP_SETTINGS_REQ 0x002B +#define QMIWDS_ADMIN_SET_QMAP_SETTINGS_RESP 0x002B +#define QMIWDS_ADMIN_GET_QMAP_SETTINGS_REQ 0x002C +#define QMIWDS_ADMIN_GET_QMAP_SETTINGS_RESP 0x002C + +#define NETWORK_DESC_ENCODING_OCTET 0x00 +#define NETWORK_DESC_ENCODING_EXTPROTOCOL 0x01 +#define NETWORK_DESC_ENCODING_7BITASCII 0x02 +#define NETWORK_DESC_ENCODING_IA5 0x03 +#define NETWORK_DESC_ENCODING_UNICODE 0x04 +#define NETWORK_DESC_ENCODING_SHIFTJIS 0x05 +#define NETWORK_DESC_ENCODING_KOREAN 0x06 +#define NETWORK_DESC_ENCODING_LATINH 0x07 +#define NETWORK_DESC_ENCODING_LATIN 0x08 +#define NETWORK_DESC_ENCODING_GSM7BIT 0x09 +#define NETWORK_DESC_ENCODING_GSMDATA 0x0A +#define NETWORK_DESC_ENCODING_UNKNOWN 0xFF + +typedef struct _QMIWDS_ADMIN_SET_DATA_FORMAT +{ + USHORT Type; // QMUX type 0x0000 + USHORT Length; +} __attribute__ ((packed)) QMIWDS_ADMIN_SET_DATA_FORMAT, *PQMIWDS_ADMIN_SET_DATA_FORMAT; + +typedef struct _QMIWDS_ADMIN_SET_DATA_FORMAT_TLV_QOS +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR QOSSetting; +} __attribute__ ((packed)) QMIWDS_ADMIN_SET_DATA_FORMAT_TLV_QOS, *PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV_QOS; + +typedef struct _QMIWDS_ADMIN_SET_DATA_FORMAT_TLV +{ + UCHAR TLVType; + USHORT TLVLength; + ULONG Value; +} __attribute__ ((packed)) QMIWDS_ADMIN_SET_DATA_FORMAT_TLV, *PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV; + + +//#ifdef QC_IP_MODE + +#define QMIWDS_GET_RUNTIME_SETTINGS_MASK_IPV4DNS_ADDR 0x0010 +#define QMIWDS_GET_RUNTIME_SETTINGS_MASK_IPV4_ADDR 0x0100 +#define QMIWDS_GET_RUNTIME_SETTINGS_MASK_IPV4GATEWAY_ADDR 0x0200 +#define QMIWDS_GET_RUNTIME_SETTINGS_MASK_MTU 0x2000 + +typedef struct _QMIWDS_GET_RUNTIME_SETTINGS_REQ_MSG +{ + USHORT Type; // QMIWDS_GET_RUNTIME_SETTINGS_REQ + USHORT Length; + UCHAR TLVType; // 0x10 + USHORT TLVLength; // 0x0004 + ULONG Mask; // mask, bit 8: IP addr -- 0x0100 +} __attribute__ ((packed)) QMIWDS_GET_RUNTIME_SETTINGS_REQ_MSG, *PQMIWDS_GET_RUNTIME_SETTINGS_REQ_MSG; + +typedef struct _QMIWDS_BIND_MUX_DATA_PORT_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + ULONG ep_type; + ULONG iface_id; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR MuxId; + UCHAR TLV3Type; + USHORT TLV3Length; + ULONG client_type; +} __attribute__ ((packed)) QMIWDS_BIND_MUX_DATA_PORT_REQ_MSG, *PQMIWDS_BIND_MUX_DATA_PORT_REQ_MSG; + +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4PRIMARYDNS 0x15 +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4SECONDARYDNS 0x16 +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4 0x1E +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4GATEWAY 0x20 +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4SUBNET 0x21 + +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6 0x25 +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6GATEWAY 0x26 +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6PRIMARYDNS 0x27 +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6SECONDARYDNS 0x28 +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_MTU 0x29 + +typedef struct _QMIWDS_GET_RUNTIME_SETTINGS_TLV_MTU +{ + UCHAR TLVType; // QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_MTU + USHORT TLVLength; // 4 + ULONG Mtu; // MTU +} __attribute__ ((packed)) QMIWDS_GET_RUNTIME_SETTINGS_TLV_MTU, *PQMIWDS_GET_RUNTIME_SETTINGS_TLV_MTU; + +typedef struct _QMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR +{ + UCHAR TLVType; // QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4 + USHORT TLVLength; // 4 + ULONG IPV4Address; // address +} __attribute__ ((packed)) QMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR, *PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR; + +typedef struct _QMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV6_ADDR +{ + UCHAR TLVType; // QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6 + USHORT TLVLength; // 16 + UCHAR IPV6Address[16]; // address + UCHAR PrefixLength; // prefix length +} __attribute__ ((packed)) QMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV6_ADDR, *PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV6_ADDR; + +typedef struct _QMIWDS_GET_RUNTIME_SETTINGS_RESP_MSG +{ + USHORT Type; // QMIWDS_GET_RUNTIME_SETTINGS_RESP + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_RESULT_CODE + USHORT TLVLength; // 0x0004 + USHORT QMUXResult; // result code + USHORT QMUXError; // error code +} __attribute__ ((packed)) QMIWDS_GET_RUNTIME_SETTINGS_RESP_MSG, *PQMIWDS_GET_RUNTIME_SETTINGS_RESP_MSG; + +//#endif // QC_IP_MODE + +typedef struct _QMIWDS_IP_FAMILY_TLV +{ + UCHAR TLVType; // 0x12 + USHORT TLVLength; // 1 + UCHAR IpFamily; // IPV4-0x04, IPV6-0x06 +} __attribute__ ((packed)) QMIWDS_IP_FAMILY_TLV, *PQMIWDS_IP_FAMILY_TLV; + +typedef struct _QMIWDS_PKT_SRVC_TLV +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR ConnectionStatus; + UCHAR ReconfigReqd; +} __attribute__ ((packed)) QMIWDS_PKT_SRVC_TLV, *PQMIWDS_PKT_SRVC_TLV; + + +typedef struct _QMIWDS_TECHNOLOGY_PREFERECE +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR TechPreference; +} __attribute__ ((packed)) QMIWDS_TECHNOLOGY_PREFERECE, *PQMIWDS_TECHNOLOGY_PREFERECE; + +typedef struct _QMIWDS_PROFILE_IDENTIFIER +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR ProfileIndex; +} __attribute__ ((packed)) QMIWDS_PROFILE_IDENTIFIER, *PQMIWDS_PROFILE_IDENTIFIER; + + +typedef struct _QMIWDS_PROFILENAME +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR ProfileName; +} __attribute__ ((packed)) QMIWDS_PROFILENAME, *PQMIWDS_PROFILENAME; + +typedef struct _QMIWDS_PDPTYPE +{ + UCHAR TLVType; + USHORT TLVLength; +// 0 ¨C PDP-IP (IPv4) +// 1 ¨C PDP-PPP +// 2 ¨C PDP-IPv6 +// 3 ¨C PDP-IPv4v6 + UCHAR PdpType; +} __attribute__ ((packed)) QMIWDS_PDPTYPE, *PQMIWDS_PDPTYPE; + +typedef struct _QMIWDS_USERNAME +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR UserName; +} __attribute__ ((packed)) QMIWDS_USERNAME, *PQMIWDS_USERNAME; + +typedef struct _QMIWDS_PASSWD +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR Passwd; +} __attribute__ ((packed)) QMIWDS_PASSWD, *PQMIWDS_PASSWD; + +typedef struct _QMIWDS_AUTH_PREFERENCE +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR AuthPreference; +} __attribute__ ((packed)) QMIWDS_AUTH_PREFERENCE, *PQMIWDS_AUTH_PREFERENCE; + +typedef struct _QMIWDS_APNNAME +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR ApnName; +} __attribute__ ((packed)) QMIWDS_APNNAME, *PQMIWDS_APNNAME; + +typedef struct _QMIWDS_AUTOCONNECT +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR AutoConnect; +} __attribute__ ((packed)) QMIWDS_AUTOCONNECT, *PQMIWDS_AUTOCONNECT; + +typedef struct _QMIWDS_START_NETWORK_INTERFACE_REQ_MSG +{ + USHORT Type; + USHORT Length; +} __attribute__ ((packed)) QMIWDS_START_NETWORK_INTERFACE_REQ_MSG, *PQMIWDS_START_NETWORK_INTERFACE_REQ_MSG; + +typedef struct _QMIWDS_CALLENDREASON +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT Reason; +}__attribute__ ((packed)) QMIWDS_CALLENDREASON, *PQMIWDS_CALLENDREASON; + +typedef struct _QMIWDS_START_NETWORK_INTERFACE_RESP_MSG +{ + USHORT Type; // QMUX type 0x0040 + USHORT Length; + UCHAR TLVType; // 0x02 + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + + UCHAR TLV2Type; // 0x01 + USHORT TLV2Length; // 20 + ULONG Handle; // +} __attribute__ ((packed)) QMIWDS_START_NETWORK_INTERFACE_RESP_MSG, *PQMIWDS_START_NETWORK_INTERFACE_RESP_MSG; + +typedef struct _QMIWDS_STOP_NETWORK_INTERFACE_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + ULONG Handle; +} __attribute__ ((packed)) QMIWDS_STOP_NETWORK_INTERFACE_REQ_MSG, *PQMIWDS_STOP_NETWORK_INTERFACE_REQ_MSG; + +typedef struct _QMIWDS_STOP_NETWORK_INTERFACE_RESP_MSG +{ + USHORT Type; // QMUX type 0x0040 + USHORT Length; + UCHAR TLVType; // 0x02 + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + +} __attribute__ ((packed)) QMIWDS_STOP_NETWORK_INTERFACE_RESP_MSG, *PQMIWDS_STOP_NETWORK_INTERFACE_RESP_MSG; + +typedef struct _QMIWDS_GET_DEFAULT_SETTINGS_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR ProfileType; +} __attribute__ ((packed)) QMIWDS_GET_DEFAULT_SETTINGS_REQ_MSG, *PQMIWDS_GET_DEFAULT_SETTINGS_REQ_MSG; + +typedef struct _QMIWDS_GET_DEFAULT_SETTINGS_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} __attribute__ ((packed)) QMIWDS_GET_DEFAULT_SETTINGS_RESP_MSG, *PQMIWDS_GET_DEFAULT_SETTINGS_RESP_MSG; + +typedef struct _QMIWDS_MODIFY_PROFILE_SETTINGS_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR ProfileType; + UCHAR ProfileIndex; +} __attribute__ ((packed)) QMIWDS_MODIFY_PROFILE_SETTINGS_REQ_MSG, *PQMIWDS_MODIFY_PROFILE_SETTINGS_REQ_MSG; + +typedef struct _QMIWDS_MODIFY_PROFILE_SETTINGS_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} __attribute__ ((packed)) QMIWDS_MODIFY_PROFILE_SETTINGS_RESP_MSG, *PQMIWDS_MODIFY_PROFILE_SETTINGS_RESP_MSG; + +typedef struct _QMIWDS_GET_PROFILE_SETTINGS_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR ProfileType; + UCHAR ProfileIndex; +} __attribute__ ((packed)) QMIWDS_GET_PROFILE_SETTINGS_REQ_MSG, *PQMIWDS_GET_PROFILE_SETTINGS_REQ_MSG; + +// ======================= DMS ============================== +#define QMIDMS_SET_EVENT_REPORT_REQ 0x0001 +#define QMIDMS_SET_EVENT_REPORT_RESP 0x0001 +#define QMIDMS_EVENT_REPORT_IND 0x0001 +#define QMIDMS_GET_DEVICE_CAP_REQ 0x0020 +#define QMIDMS_GET_DEVICE_CAP_RESP 0x0020 +#define QMIDMS_GET_DEVICE_MFR_REQ 0x0021 +#define QMIDMS_GET_DEVICE_MFR_RESP 0x0021 +#define QMIDMS_GET_DEVICE_MODEL_ID_REQ 0x0022 +#define QMIDMS_GET_DEVICE_MODEL_ID_RESP 0x0022 +#define QMIDMS_GET_DEVICE_REV_ID_REQ 0x0023 +#define QMIDMS_GET_DEVICE_REV_ID_RESP 0x0023 +#define QMIDMS_GET_MSISDN_REQ 0x0024 +#define QMIDMS_GET_MSISDN_RESP 0x0024 +#define QMIDMS_GET_DEVICE_SERIAL_NUMBERS_REQ 0x0025 +#define QMIDMS_GET_DEVICE_SERIAL_NUMBERS_RESP 0x0025 +#define QMIDMS_UIM_SET_PIN_PROTECTION_REQ 0x0027 +#define QMIDMS_UIM_SET_PIN_PROTECTION_RESP 0x0027 +#define QMIDMS_UIM_VERIFY_PIN_REQ 0x0028 +#define QMIDMS_UIM_VERIFY_PIN_RESP 0x0028 +#define QMIDMS_UIM_UNBLOCK_PIN_REQ 0x0029 +#define QMIDMS_UIM_UNBLOCK_PIN_RESP 0x0029 +#define QMIDMS_UIM_CHANGE_PIN_REQ 0x002A +#define QMIDMS_UIM_CHANGE_PIN_RESP 0x002A +#define QMIDMS_UIM_GET_PIN_STATUS_REQ 0x002B +#define QMIDMS_UIM_GET_PIN_STATUS_RESP 0x002B +#define QMIDMS_GET_DEVICE_HARDWARE_REV_REQ 0x002C +#define QMIDMS_GET_DEVICE_HARDWARE_REV_RESP 0x002C +#define QMIDMS_GET_OPERATING_MODE_REQ 0x002D +#define QMIDMS_GET_OPERATING_MODE_RESP 0x002D +#define QMIDMS_SET_OPERATING_MODE_REQ 0x002E +#define QMIDMS_SET_OPERATING_MODE_RESP 0x002E +#define QMIDMS_GET_ACTIVATED_STATUS_REQ 0x0031 +#define QMIDMS_GET_ACTIVATED_STATUS_RESP 0x0031 +#define QMIDMS_ACTIVATE_AUTOMATIC_REQ 0x0032 +#define QMIDMS_ACTIVATE_AUTOMATIC_RESP 0x0032 +#define QMIDMS_ACTIVATE_MANUAL_REQ 0x0033 +#define QMIDMS_ACTIVATE_MANUAL_RESP 0x0033 +#define QMIDMS_UIM_GET_ICCID_REQ 0x003C +#define QMIDMS_UIM_GET_ICCID_RESP 0x003C +#define QMIDMS_UIM_GET_CK_STATUS_REQ 0x0040 +#define QMIDMS_UIM_GET_CK_STATUS_RESP 0x0040 +#define QMIDMS_UIM_SET_CK_PROTECTION_REQ 0x0041 +#define QMIDMS_UIM_SET_CK_PROTECTION_RESP 0x0041 +#define QMIDMS_UIM_UNBLOCK_CK_REQ 0x0042 +#define QMIDMS_UIM_UNBLOCK_CK_RESP 0x0042 +#define QMIDMS_UIM_GET_IMSI_REQ 0x0043 +#define QMIDMS_UIM_GET_IMSI_RESP 0x0043 +#define QMIDMS_UIM_GET_STATE_REQ 0x0044 +#define QMIDMS_UIM_GET_STATE_RESP 0x0044 +#define QMIDMS_GET_BAND_CAP_REQ 0x0045 +#define QMIDMS_GET_BAND_CAP_RESP 0x0045 + +typedef struct _QMIDMS_GET_DEVICE_REV_ID_REQ_MSG +{ + USHORT Type; // QMUX type 0x0005 + USHORT Length; +} __attribute__ ((packed)) QMIDMS_GET_DEVICE_REV_ID_REQ_MSG, *PQMIDMS_GET_DEVICE_REV_ID_REQ_MSG; + +typedef struct _DEVICE_REV_ID +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR RevisionID; +} __attribute__ ((packed)) DEVICE_REV_ID, *PDEVICE_REV_ID; + + +typedef struct _QMIDMS_UIM_GET_IMSI_REQ_MSG +{ + USHORT Type; + USHORT Length; +} __attribute__ ((packed)) QMIDMS_UIM_GET_IMSI_REQ_MSG, *PQMIDMS_UIM_GET_IMSI_REQ_MSG; + +typedef struct _QMIDMS_UIM_GET_IMSI_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR IMSI; +} __attribute__ ((packed)) QMIDMS_UIM_GET_IMSI_RESP_MSG, *PQMIDMS_UIM_GET_IMSI_RESP_MSG; + +typedef struct _QMIDMS_UIM_GET_STATE_REQ_MSG +{ + USHORT Type; + USHORT Length; +} __attribute__ ((packed)) QMIDMS_UIM_GET_STATE_REQ_MSG, *PQMIDMS_UIM_GET_STATE_REQ_MSG; + +typedef struct _QMIDMS_UIM_GET_STATE_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR UIMState; +} __attribute__ ((packed)) QMIDMS_UIM_GET_STATE_RESP_MSG, *PQMIDMS_UIM_GET_STATE_RESP_MSG; + +typedef struct _QMIDMS_UIM_GET_PIN_STATUS_REQ_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; +} __attribute__ ((packed)) QMIDMS_UIM_GET_PIN_STATUS_REQ_MSG, *PQMIDMS_UIM_GET_PIN_STATUS_REQ_MSG; + +typedef struct _QMIDMS_UIM_PIN_STATUS +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR PINStatus; + UCHAR PINVerifyRetriesLeft; + UCHAR PINUnblockRetriesLeft; +} __attribute__ ((packed)) QMIDMS_UIM_PIN_STATUS, *PQMIDMS_UIM_PIN_STATUS; + +#define QMI_PIN_STATUS_NOT_INIT 0 +#define QMI_PIN_STATUS_NOT_VERIF 1 +#define QMI_PIN_STATUS_VERIFIED 2 +#define QMI_PIN_STATUS_DISABLED 3 +#define QMI_PIN_STATUS_BLOCKED 4 +#define QMI_PIN_STATUS_PERM_BLOCKED 5 +#define QMI_PIN_STATUS_UNBLOCKED 6 +#define QMI_PIN_STATUS_CHANGED 7 + + +typedef struct _QMIDMS_UIM_GET_PIN_STATUS_RESP_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR PinStatus; +} __attribute__ ((packed)) QMIDMS_UIM_GET_PIN_STATUS_RESP_MSG, *PQMIDMS_UIM_GET_PIN_STATUS_RESP_MSG; + +typedef struct _QMIDMS_UIM_VERIFY_PIN_REQ_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + UCHAR PINID; + UCHAR PINLen; + UCHAR PINValue; +} __attribute__ ((packed)) QMIDMS_UIM_VERIFY_PIN_REQ_MSG, *PQMIDMS_UIM_VERIFY_PIN_REQ_MSG; + +typedef struct _QMIDMS_UIM_VERIFY_PIN_RESP_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR PINVerifyRetriesLeft; + UCHAR PINUnblockRetriesLeft; +} __attribute__ ((packed)) QMIDMS_UIM_VERIFY_PIN_RESP_MSG, *PQMIDMS_UIM_VERIFY_PIN_RESP_MSG; + + +// ============================ END OF DMS =============================== + +// ======================= QOS ============================== +typedef struct _MPIOC_DEV_INFO MPIOC_DEV_INFO, *PMPIOC_DEV_INFO; + +#define QMI_QOS_SET_EVENT_REPORT_REQ 0x0001 +#define QMI_QOS_SET_EVENT_REPORT_RESP 0x0001 +#define QMI_QOS_EVENT_REPORT_IND 0x0001 + + +// ======================= WMS ============================== +#define QMIWMS_SET_EVENT_REPORT_REQ 0x0001 +#define QMIWMS_SET_EVENT_REPORT_RESP 0x0001 +#define QMIWMS_EVENT_REPORT_IND 0x0001 +#define QMIWMS_RAW_SEND_REQ 0x0020 +#define QMIWMS_RAW_SEND_RESP 0x0020 +#define QMIWMS_RAW_WRITE_REQ 0x0021 +#define QMIWMS_RAW_WRITE_RESP 0x0021 +#define QMIWMS_RAW_READ_REQ 0x0022 +#define QMIWMS_RAW_READ_RESP 0x0022 +#define QMIWMS_MODIFY_TAG_REQ 0x0023 +#define QMIWMS_MODIFY_TAG_RESP 0x0023 +#define QMIWMS_DELETE_REQ 0x0024 +#define QMIWMS_DELETE_RESP 0x0024 +#define QMIWMS_GET_MESSAGE_PROTOCOL_REQ 0x0030 +#define QMIWMS_GET_MESSAGE_PROTOCOL_RESP 0x0030 +#define QMIWMS_LIST_MESSAGES_REQ 0x0031 +#define QMIWMS_LIST_MESSAGES_RESP 0x0031 +#define QMIWMS_GET_SMSC_ADDRESS_REQ 0x0034 +#define QMIWMS_GET_SMSC_ADDRESS_RESP 0x0034 +#define QMIWMS_SET_SMSC_ADDRESS_REQ 0x0035 +#define QMIWMS_SET_SMSC_ADDRESS_RESP 0x0035 +#define QMIWMS_GET_STORE_MAX_SIZE_REQ 0x0036 +#define QMIWMS_GET_STORE_MAX_SIZE_RESP 0x0036 + + +#define WMS_MESSAGE_PROTOCOL_CDMA 0x00 +#define WMS_MESSAGE_PROTOCOL_WCDMA 0x01 + +// ======================= End of WMS ============================== + + +// ======================= NAS ============================== +#define QMINAS_SET_EVENT_REPORT_REQ 0x0002 +#define QMINAS_SET_EVENT_REPORT_RESP 0x0002 +#define QMINAS_EVENT_REPORT_IND 0x0002 +#define QMINAS_GET_SIGNAL_STRENGTH_REQ 0x0020 +#define QMINAS_GET_SIGNAL_STRENGTH_RESP 0x0020 +#define QMINAS_PERFORM_NETWORK_SCAN_REQ 0x0021 +#define QMINAS_PERFORM_NETWORK_SCAN_RESP 0x0021 +#define QMINAS_INITIATE_NW_REGISTER_REQ 0x0022 +#define QMINAS_INITIATE_NW_REGISTER_RESP 0x0022 +#define QMINAS_INITIATE_ATTACH_REQ 0x0023 +#define QMINAS_INITIATE_ATTACH_RESP 0x0023 +#define QMINAS_GET_SERVING_SYSTEM_REQ 0x0024 +#define QMINAS_GET_SERVING_SYSTEM_RESP 0x0024 +#define QMINAS_SERVING_SYSTEM_IND 0x0024 +#define QMINAS_GET_HOME_NETWORK_REQ 0x0025 +#define QMINAS_GET_HOME_NETWORK_RESP 0x0025 +#define QMINAS_GET_PREFERRED_NETWORK_REQ 0x0026 +#define QMINAS_GET_PREFERRED_NETWORK_RESP 0x0026 +#define QMINAS_SET_PREFERRED_NETWORK_REQ 0x0027 +#define QMINAS_SET_PREFERRED_NETWORK_RESP 0x0027 +#define QMINAS_GET_FORBIDDEN_NETWORK_REQ 0x0028 +#define QMINAS_GET_FORBIDDEN_NETWORK_RESP 0x0028 +#define QMINAS_SET_FORBIDDEN_NETWORK_REQ 0x0029 +#define QMINAS_SET_FORBIDDEN_NETWORK_RESP 0x0029 +#define QMINAS_SET_TECHNOLOGY_PREF_REQ 0x002A +#define QMINAS_SET_TECHNOLOGY_PREF_RESP 0x002A +#define QMINAS_GET_RF_BAND_INFO_REQ 0x0031 +#define QMINAS_GET_RF_BAND_INFO_RESP 0x0031 +#define QMINAS_GET_PLMN_NAME_REQ 0x0044 +#define QMINAS_GET_PLMN_NAME_RESP 0x0044 +#define MEIGE_PACKET_TRANSFER_START_IND 0X100 +#define MEIGE_PACKET_TRANSFER_END_IND 0X101 +#define QMINAS_GET_SYS_INFO_REQ 0x004D +#define QMINAS_GET_SYS_INFO_RESP 0x004D +#define QMINAS_SYS_INFO_IND 0x004D + +typedef struct _QMINAS_GET_HOME_NETWORK_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; +} __attribute__ ((packed)) QMINAS_GET_HOME_NETWORK_REQ_MSG, *PQMINAS_GET_HOME_NETWORK_REQ_MSG; + +typedef struct _HOME_NETWORK_SYSTEMID +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT SystemID; + USHORT NetworkID; +} __attribute__ ((packed)) HOME_NETWORK_SYSTEMID, *PHOME_NETWORK_SYSTEMID; + +typedef struct _HOME_NETWORK +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT MobileCountryCode; + USHORT MobileNetworkCode; + UCHAR NetworkDesclen; + UCHAR NetworkDesc; +} __attribute__ ((packed)) HOME_NETWORK, *PHOME_NETWORK; + + +typedef struct _QMINAS_DATA_CAP +{ + UCHAR TLVType; // 0x01 - required parameter + USHORT TLVLength; // length of the mfr string + UCHAR DataCapListLen; + UCHAR DataCap; +} __attribute__ ((packed)) QMINAS_DATA_CAP, *PQMINAS_DATA_CAP; + +typedef struct _QMINAS_CURRENT_PLMN_MSG +{ + UCHAR TLVType; // 0x01 - required parameter + USHORT TLVLength; // length of the mfr string + USHORT MobileCountryCode; + USHORT MobileNetworkCode; + UCHAR NetworkDesclen; + UCHAR NetworkDesc; +} __attribute__ ((packed)) QMINAS_CURRENT_PLMN_MSG, *PQMINAS_CURRENT_PLMN_MSG; + +typedef struct _QMINAS_GET_SERVING_SYSTEM_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} __attribute__ ((packed)) QMINAS_GET_SERVING_SYSTEM_RESP_MSG, *PQMINAS_GET_SERVING_SYSTEM_RESP_MSG; + +typedef struct _SERVING_SYSTEM +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR RegistrationState; + UCHAR CSAttachedState; + UCHAR PSAttachedState; + UCHAR RegistredNetwork; + UCHAR InUseRadioIF; + UCHAR RadioIF; +} __attribute__ ((packed)) SERVING_SYSTEM, *PSERVING_SYSTEM; + +typedef struct _QMINAS_GET_SYS_INFO_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} __attribute__ ((packed)) QMINAS_GET_SYS_INFO_RESP_MSG, *PQMINAS_GET_SYS_INFO_RESP_MSG; + +typedef struct _QMINAS_SYS_INFO_IND_MSG +{ + USHORT Type; + USHORT Length; +} __attribute__ ((packed)) QMINAS_SYS_INFO_IND_MSG, *PQMINAS_SYS_INFO_IND_MSG; + +typedef struct _SERVICE_STATUS_INFO +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR SrvStatus; + UCHAR IsPrefDataPath; +} __attribute__ ((packed)) SERVICE_STATUS_INFO, *PSERVICE_STATUS_INFO; + +typedef struct _CDMA_SYSTEM_INFO +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR SrvDomainValid; + UCHAR SrvDomain; + UCHAR SrvCapabilityValid; + UCHAR SrvCapability; + UCHAR RoamStatusValid; + UCHAR RoamStatus; + UCHAR IsSysForbiddenValid; + UCHAR IsSysForbidden; + UCHAR IsSysPrlMatchValid; + UCHAR IsSysPrlMatch; + UCHAR PRevInUseValid; + UCHAR PRevInUse; + UCHAR BSPRevValid; + UCHAR BSPRev; + UCHAR CCSSupportedValid; + UCHAR CCSSupported; + UCHAR CDMASysIdValid; + USHORT SID; + USHORT NID; + UCHAR BSInfoValid; + USHORT BaseID; + ULONG BaseLAT; + ULONG BaseLONG; + UCHAR PacketZoneValid; + USHORT PacketZone; + UCHAR NetworkIdValid; + UCHAR MCC[3]; + UCHAR MNC[3]; +} __attribute__ ((packed)) CDMA_SYSTEM_INFO, *PCDMA_SYSTEM_INFO; + +typedef struct _HDR_SYSTEM_INFO +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR SrvDomainValid; + UCHAR SrvDomain; + UCHAR SrvCapabilityValid; + UCHAR SrvCapability; + UCHAR RoamStatusValid; + UCHAR RoamStatus; + UCHAR IsSysForbiddenValid; + UCHAR IsSysForbidden; + UCHAR IsSysPrlMatchValid; + UCHAR IsSysPrlMatch; + UCHAR HdrPersonalityValid; + UCHAR HdrPersonality; + UCHAR HdrActiveProtValid; + UCHAR HdrActiveProt; + UCHAR is856SysIdValid; + UCHAR is856SysId[16]; +} __attribute__ ((packed)) HDR_SYSTEM_INFO, *PHDR_SYSTEM_INFO; + +typedef struct _GSM_SYSTEM_INFO +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR SrvDomainValid; + UCHAR SrvDomain; + UCHAR SrvCapabilityValid; + UCHAR SrvCapability; + UCHAR RoamStatusValid; + UCHAR RoamStatus; + UCHAR IsSysForbiddenValid; + UCHAR IsSysForbidden; + UCHAR LacValid; + USHORT Lac; + UCHAR CellIdValid; + ULONG CellId; + UCHAR RegRejectInfoValid; + UCHAR RejectSrvDomain; + UCHAR RejCause; + UCHAR NetworkIdValid; + UCHAR MCC[3]; + UCHAR MNC[3]; + UCHAR EgprsSuppValid; + UCHAR EgprsSupp; + UCHAR DtmSuppValid; + UCHAR DtmSupp; +} __attribute__ ((packed)) GSM_SYSTEM_INFO, *PGSM_SYSTEM_INFO; + +typedef struct _WCDMA_SYSTEM_INFO +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR SrvDomainValid; + UCHAR SrvDomain; + UCHAR SrvCapabilityValid; + UCHAR SrvCapability; + UCHAR RoamStatusValid; + UCHAR RoamStatus; + UCHAR IsSysForbiddenValid; + UCHAR IsSysForbidden; + UCHAR LacValid; + USHORT Lac; + UCHAR CellIdValid; + ULONG CellId; + UCHAR RegRejectInfoValid; + UCHAR RejectSrvDomain; + UCHAR RejCause; + UCHAR NetworkIdValid; + UCHAR MCC[3]; + UCHAR MNC[3]; + UCHAR HsCallStatusValid; + UCHAR HsCallStatus; + UCHAR HsIndValid; + UCHAR HsInd; + UCHAR PscValid; + UCHAR Psc; +} __attribute__ ((packed)) WCDMA_SYSTEM_INFO, *PWCDMA_SYSTEM_INFO; + +typedef struct _LTE_SYSTEM_INFO +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR SrvDomainValid; + UCHAR SrvDomain; + UCHAR SrvCapabilityValid; + UCHAR SrvCapability; + UCHAR RoamStatusValid; + UCHAR RoamStatus; + UCHAR IsSysForbiddenValid; + UCHAR IsSysForbidden; + UCHAR LacValid; + USHORT Lac; + UCHAR CellIdValid; + ULONG CellId; + UCHAR RegRejectInfoValid; + UCHAR RejectSrvDomain; + UCHAR RejCause; + UCHAR NetworkIdValid; + UCHAR MCC[3]; + UCHAR MNC[3]; + UCHAR TacValid; + USHORT Tac; +} __attribute__ ((packed)) LTE_SYSTEM_INFO, *PLTE_SYSTEM_INFO; + +typedef struct _TDSCDMA_SYSTEM_INFO +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR SrvDomainValid; + UCHAR SrvDomain; + UCHAR SrvCapabilityValid; + UCHAR SrvCapability; + UCHAR RoamStatusValid; + UCHAR RoamStatus; + UCHAR IsSysForbiddenValid; + UCHAR IsSysForbidden; + UCHAR LacValid; + USHORT Lac; + UCHAR CellIdValid; + ULONG CellId; + UCHAR RegRejectInfoValid; + UCHAR RejectSrvDomain; + UCHAR RejCause; + UCHAR NetworkIdValid; + UCHAR MCC[3]; + UCHAR MNC[3]; + UCHAR HsCallStatusValid; + UCHAR HsCallStatus; + UCHAR HsIndValid; + UCHAR HsInd; + UCHAR CellParameterIdValid; + USHORT CellParameterId; + UCHAR CellBroadcastCapValid; + ULONG CellBroadcastCap; + UCHAR CsBarStatusValid; + ULONG CsBarStatus; + UCHAR PsBarStatusValid; + ULONG PsBarStatus; + UCHAR CipherDomainValid; + UCHAR CipherDomain; +} __attribute__ ((packed)) TDSCDMA_SYSTEM_INFO, *PTDSCDMA_SYSTEM_INFO; + +// ======================= End of NAS ============================== + +// ======================= UIM ============================== +#define QMIUIM_READ_TRANSPARENT_REQ 0x0020 +#define QMIUIM_READ_TRANSPARENT_RESP 0x0020 +#define QMIUIM_READ_TRANSPARENT_IND 0x0020 +#define QMIUIM_READ_RECORD_REQ 0x0021 +#define QMIUIM_READ_RECORD_RESP 0x0021 +#define QMIUIM_READ_RECORD_IND 0x0021 +#define QMIUIM_WRITE_TRANSPARENT_REQ 0x0022 +#define QMIUIM_WRITE_TRANSPARENT_RESP 0x0022 +#define QMIUIM_WRITE_TRANSPARENT_IND 0x0022 +#define QMIUIM_WRITE_RECORD_REQ 0x0023 +#define QMIUIM_WRITE_RECORD_RESP 0x0023 +#define QMIUIM_WRITE_RECORD_IND 0x0023 +#define QMIUIM_SET_PIN_PROTECTION_REQ 0x0025 +#define QMIUIM_SET_PIN_PROTECTION_RESP 0x0025 +#define QMIUIM_SET_PIN_PROTECTION_IND 0x0025 +#define QMIUIM_VERIFY_PIN_REQ 0x0026 +#define QMIUIM_VERIFY_PIN_RESP 0x0026 +#define QMIUIM_VERIFY_PIN_IND 0x0026 +#define QMIUIM_UNBLOCK_PIN_REQ 0x0027 +#define QMIUIM_UNBLOCK_PIN_RESP 0x0027 +#define QMIUIM_UNBLOCK_PIN_IND 0x0027 +#define QMIUIM_CHANGE_PIN_REQ 0x0028 +#define QMIUIM_CHANGE_PIN_RESP 0x0028 +#define QMIUIM_CHANGE_PIN_IND 0x0028 +#define QMIUIM_DEPERSONALIZATION_REQ 0x0029 +#define QMIUIM_DEPERSONALIZATION_RESP 0x0029 +#define QMIUIM_EVENT_REG_REQ 0x002E +#define QMIUIM_EVENT_REG_RESP 0x002E +#define QMIUIM_GET_CARD_STATUS_REQ 0x002F +#define QMIUIM_GET_CARD_STATUS_RESP 0x002F +#define QMIUIM_STATUS_CHANGE_IND 0x0032 + + +typedef struct _QMIUIM_GET_CARD_STATUS_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} __attribute__ ((packed)) QMIUIM_GET_CARD_STATUS_RESP_MSG, *PQMIUIM_GET_CARD_STATUS_RESP_MSG; + +typedef struct _QMIUIM_CARD_STATUS +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT IndexGWPri; + USHORT Index1XPri; + USHORT IndexGWSec; + USHORT Index1XSec; + UCHAR NumSlot; + UCHAR CardState; + UCHAR UPINState; + UCHAR UPINRetries; + UCHAR UPUKRetries; + UCHAR ErrorCode; + UCHAR NumApp; + UCHAR AppType; + UCHAR AppState; + UCHAR PersoState; + UCHAR PersoFeature; + UCHAR PersoRetries; + UCHAR PersoUnblockRetries; + UCHAR AIDLength; +} __attribute__ ((packed)) QMIUIM_CARD_STATUS, *PQMIUIM_CARD_STATUS; + +typedef struct _QMIUIM_PIN_STATE +{ + UCHAR UnivPIN; + UCHAR PIN1State; + UCHAR PIN1Retries; + UCHAR PUK1Retries; + UCHAR PIN2State; + UCHAR PIN2Retries; + UCHAR PUK2Retries; +} __attribute__ ((packed)) QMIUIM_PIN_STATE, *PQMIUIM_PIN_STATE; + +typedef struct _QMIUIM_VERIFY_PIN_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR Session_Type; + UCHAR Aid_Len; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR PINID; + UCHAR PINLen; + UCHAR PINValue; +} __attribute__ ((packed)) QMIUIM_VERIFY_PIN_REQ_MSG, *PQMIUIM_VERIFY_PIN_REQ_MSG; + +typedef struct _QMIUIM_VERIFY_PIN_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR PINVerifyRetriesLeft; + UCHAR PINUnblockRetriesLeft; +} __attribute__ ((packed)) QMIUIM_VERIFY_PIN_RESP_MSG, *PQMIUIM_VERIFY_PIN_RESP_MSG; + + +typedef struct _QMUX_MSG +{ + QCQMUX_HDR QMUXHdr; + union + { + // Message Header + QCQMUX_MSG_HDR QMUXMsgHdr; + QCQMUX_MSG_HDR_RESP QMUXMsgHdrResp; + + //#ifdef QC_IP_MODE + QMIWDS_GET_RUNTIME_SETTINGS_REQ_MSG GetRuntimeSettingsReq; + QMIWDS_GET_RUNTIME_SETTINGS_RESP_MSG GetRuntimeSettingsRsp; + //#endif // QC_IP_MODE + + QMIWDS_START_NETWORK_INTERFACE_REQ_MSG StartNwInterfaceReq; + QMIWDS_START_NETWORK_INTERFACE_RESP_MSG StartNwInterfaceResp; + QMIWDS_STOP_NETWORK_INTERFACE_REQ_MSG StopNwInterfaceReq; + QMIWDS_STOP_NETWORK_INTERFACE_RESP_MSG StopNwInterfaceResp; + QMIWDS_GET_DEFAULT_SETTINGS_REQ_MSG GetDefaultSettingsReq; + QMIWDS_GET_DEFAULT_SETTINGS_RESP_MSG GetDefaultSettingsResp; + QMIWDS_MODIFY_PROFILE_SETTINGS_REQ_MSG ModifyProfileSettingsReq; + QMIWDS_MODIFY_PROFILE_SETTINGS_RESP_MSG ModifyProfileSettingsResp; + QMIWDS_GET_PROFILE_SETTINGS_REQ_MSG GetProfileSettingsReq; + + QMIDMS_UIM_GET_PIN_STATUS_REQ_MSG UIMGetPinStatusReq; + QMIDMS_UIM_GET_PIN_STATUS_RESP_MSG UIMGetPinStatusResp; + QMIDMS_UIM_VERIFY_PIN_REQ_MSG UIMVerifyPinReq; + QMIDMS_UIM_VERIFY_PIN_RESP_MSG UIMVerifyPinResp; + QMIDMS_UIM_GET_STATE_REQ_MSG UIMGetStateReq; + QMIDMS_UIM_GET_STATE_RESP_MSG UIMGetStateResp; + QMIDMS_UIM_GET_IMSI_REQ_MSG UIMGetIMSIReq; + QMIDMS_UIM_GET_IMSI_RESP_MSG UIMGetIMSIResp; + + QMINAS_GET_SERVING_SYSTEM_RESP_MSG GetServingSystemResp; + QMINAS_GET_SYS_INFO_RESP_MSG GetSysInfoResp; + QMINAS_SYS_INFO_IND_MSG NasSysInfoInd; + + // QMIUIM Messages + QMIUIM_GET_CARD_STATUS_RESP_MSG UIMGetCardStatus; + QMIUIM_VERIFY_PIN_REQ_MSG UIMUIMVerifyPinReq; + QMIUIM_VERIFY_PIN_RESP_MSG UIMUIMVerifyPinResp; + + }; +} __attribute__ ((packed)) QMUX_MSG, *PQMUX_MSG; + +#pragma pack(pop) + +#endif // MPQMUX_H diff --git a/root/package/link4all/quectel-CM/src_fangge/Makefile b/root/package/link4all/quectel-CM/src_fangge/Makefile new file mode 100755 index 00000000..aaf8e4ed --- /dev/null +++ b/root/package/link4all/quectel-CM/src_fangge/Makefile @@ -0,0 +1,6 @@ +quectel-CM:clean + $(CC) -Wall -s QmiWwanCM.c GobiNetCM.c main.c MPQMUX.c QMIThread.c util.c udhcpc.c -o quectel-CM -lpthread + +clean: + rm -rf quectel-CM *~ + diff --git a/root/package/link4all/quectel-CM/src_fangge/QMIThread.c b/root/package/link4all/quectel-CM/src_fangge/QMIThread.c new file mode 100755 index 00000000..fac9811e --- /dev/null +++ b/root/package/link4all/quectel-CM/src_fangge/QMIThread.c @@ -0,0 +1,1705 @@ +#include "QMIThread.h" +extern char *strndup (const char *__string, size_t __n); + +int qmiclientId[QMUX_TYPE_WDS_ADMIN + 1]; //GobiNet use fd to indicate client ID, so type of qmiclientId must be int +static UINT WdsConnectionIPv4Handle = 0; +static int s_is_cdma = 0; +static int s_hdr_personality = 0; // 0x01-HRPD, 0x02-eHRPD +static char *qstrcpy(char *to, const char *from) { //no __strcpy_chk + char *save = to; + for (; (*to = *from) != '\0'; ++from, ++to); + return(save); +} + +static int s_9x07 = -1; + +typedef USHORT (*CUSTOMQMUX)(PQMUX_MSG pMUXMsg, void *arg); + +// To retrieve the ith (Index) TLV +PQMI_TLV_HDR GetTLV (PQCQMUX_MSG_HDR pQMUXMsgHdr, int TLVType) { + int TLVFind = 0; + USHORT Length = le16_to_cpu(pQMUXMsgHdr->Length); + PQMI_TLV_HDR pTLVHdr = (PQMI_TLV_HDR)(pQMUXMsgHdr + 1); + + while (Length >= sizeof(QMI_TLV_HDR)) { + TLVFind++; + if (TLVType > 0x1000) { + if ((TLVFind + 0x1000) == TLVType) + return pTLVHdr; + } else if (pTLVHdr->TLVType == TLVType) { + return pTLVHdr; + } + + Length -= (le16_to_cpu((pTLVHdr->TLVLength)) + sizeof(QMI_TLV_HDR)); + pTLVHdr = (PQMI_TLV_HDR)(((UCHAR *)pTLVHdr) + le16_to_cpu(pTLVHdr->TLVLength) + sizeof(QMI_TLV_HDR)); + } + + return NULL; +} + +static USHORT GetQMUXTransactionId(void) { + static int TransactionId = 0; + if (++TransactionId > 0xFFFF) + TransactionId = 1; + return TransactionId; +} + +static PQCQMIMSG ComposeQMUXMsg(UCHAR QMIType, USHORT Type, CUSTOMQMUX customQmuxMsgFunction, void *arg) { + UCHAR QMIBuf[WDM_DEFAULT_BUFSIZE]; + PQCQMIMSG pRequest = (PQCQMIMSG)QMIBuf; + int Length; + + memset(QMIBuf, 0x00, sizeof(QMIBuf)); + pRequest->QMIHdr.IFType = USB_CTL_MSG_TYPE_QMI; + pRequest->QMIHdr.CtlFlags = 0x00; + pRequest->QMIHdr.QMIType = QMIType; + pRequest->QMIHdr.ClientId = qmiclientId[pRequest->QMIHdr.QMIType] & 0xFF; + + if (qmiclientId[pRequest->QMIHdr.QMIType] == 0) { + dbg_time("QMIType %d has no clientID", pRequest->QMIHdr.QMIType); + return NULL; + } + + pRequest->MUXMsg.QMUXHdr.CtlFlags = QMUX_CTL_FLAG_SINGLE_MSG | QMUX_CTL_FLAG_TYPE_CMD; + pRequest->MUXMsg.QMUXHdr.TransactionId = cpu_to_le16(GetQMUXTransactionId()); + pRequest->MUXMsg.QMUXMsgHdr.Type = cpu_to_le16(Type); + if (customQmuxMsgFunction) + pRequest->MUXMsg.QMUXMsgHdr.Length = cpu_to_le16(customQmuxMsgFunction(&pRequest->MUXMsg, arg) - sizeof(QCQMUX_MSG_HDR)); + else + pRequest->MUXMsg.QMUXMsgHdr.Length = cpu_to_le16(0x0000); + + pRequest->QMIHdr.Length = cpu_to_le16(le16_to_cpu(pRequest->MUXMsg.QMUXMsgHdr.Length) + sizeof(QCQMUX_MSG_HDR) + sizeof(QCQMUX_HDR) + + sizeof(QCQMI_HDR) - 1); + Length = le16_to_cpu(pRequest->QMIHdr.Length) + 1; + + pRequest = (PQCQMIMSG)malloc(Length); + if (pRequest == NULL) { + dbg_time("%s fail to malloc", __func__); + } else { + memcpy(pRequest, QMIBuf, Length); + } + + return pRequest; +} + +static USHORT WdsStartNwInterfaceReq(PQMUX_MSG pMUXMsg, void *arg) { + PQMIWDS_TECHNOLOGY_PREFERECE pTechPref; + PQMIWDS_AUTH_PREFERENCE pAuthPref; + PQMIWDS_USERNAME pUserName; + PQMIWDS_PASSWD pPasswd; + PQMIWDS_APNNAME pApnName; + PQMIWDS_IP_FAMILY_TLV pIpFamily; + USHORT TLVLength = 0; + UCHAR *pTLV; + PROFILE_T *profile = (PROFILE_T *)arg; + + pTLV = (UCHAR *)(&pMUXMsg->StartNwInterfaceReq + 1); + pMUXMsg->StartNwInterfaceReq.Length = 0; + + // Set technology Preferece + pTechPref = (PQMIWDS_TECHNOLOGY_PREFERECE)(pTLV + TLVLength); + pTechPref->TLVType = 0x30; + pTechPref->TLVLength = cpu_to_le16(0x01); + if (s_is_cdma == 0) + pTechPref->TechPreference = 0x01; + else + pTechPref->TechPreference = 0x02; + TLVLength +=(le16_to_cpu(pTechPref->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + + // Set APN Name + if (profile->apn) { + pApnName = (PQMIWDS_APNNAME)(pTLV + TLVLength); + pApnName->TLVType = 0x14; + pApnName->TLVLength = cpu_to_le16(strlen(profile->apn)); + qstrcpy((char *)&pApnName->ApnName, profile->apn); + TLVLength +=(le16_to_cpu(pApnName->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + // Set User Name + if (profile->user) { + pUserName = (PQMIWDS_USERNAME)(pTLV + TLVLength); + pUserName->TLVType = 0x17; + pUserName->TLVLength = cpu_to_le16(strlen(profile->user)); + qstrcpy((char *)&pUserName->UserName, profile->user); + TLVLength += (le16_to_cpu(pUserName->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + // Set Password + if (profile->password) { + pPasswd = (PQMIWDS_PASSWD)(pTLV + TLVLength); + pPasswd->TLVType = 0x18; + pPasswd->TLVLength = cpu_to_le16(strlen(profile->password)); + qstrcpy((char *)&pPasswd->Passwd, profile->password); + TLVLength +=(le16_to_cpu(pPasswd->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + // Set Auth Protocol + if (profile->user && profile->password) { + pAuthPref = (PQMIWDS_AUTH_PREFERENCE)(pTLV + TLVLength); + pAuthPref->TLVType = 0x16; + pAuthPref->TLVLength = cpu_to_le16(0x01); + pAuthPref->AuthPreference = profile->auth; // 0 ~ None, 1 ~ Pap, 2 ~ Chap, 3 ~ MsChapV2 + TLVLength +=(le16_to_cpu(pAuthPref->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + // Add IP Family Preference + pIpFamily = (PQMIWDS_IP_FAMILY_TLV)(pTLV + TLVLength); + pIpFamily->TLVType = 0x19; + pIpFamily->TLVLength = cpu_to_le16(0x01); + pIpFamily->IpFamily = profile->IPType; + TLVLength += (le16_to_cpu(pIpFamily->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + + //Set Profile Index + if (profile->pdp) { + PQMIWDS_PROFILE_IDENTIFIER pProfileIndex = (PQMIWDS_PROFILE_IDENTIFIER)(pTLV + TLVLength); + pProfileIndex->TLVLength = cpu_to_le16(0x01); + pProfileIndex->TLVType = 0x31; + pProfileIndex->ProfileIndex = profile->pdp; + if (s_is_cdma && s_hdr_personality == 0x02) { + pProfileIndex->TLVType = 0x32; //profile_index_3gpp2 + pProfileIndex->ProfileIndex = 101; + } + TLVLength += (le16_to_cpu(pProfileIndex->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + return sizeof(QMIWDS_START_NETWORK_INTERFACE_REQ_MSG) + TLVLength; +} + +static USHORT WdsStopNwInterfaceReq(PQMUX_MSG pMUXMsg, void *arg) { + pMUXMsg->StopNwInterfaceReq.TLVType = 0x01; + pMUXMsg->StopNwInterfaceReq.TLVLength = cpu_to_le16(0x04); + pMUXMsg->StopNwInterfaceReq.Handle = cpu_to_le32(WdsConnectionIPv4Handle); + return sizeof(QMIWDS_STOP_NETWORK_INTERFACE_REQ_MSG); +} + +static USHORT WdaSetDataFormat(PQMUX_MSG pMUXMsg, void *arg) { + PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV_QOS pWdsAdminQosTlv; + PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV linkProto; + PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV dlTlp; + + pWdsAdminQosTlv = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV_QOS)(&pMUXMsg->QMUXMsgHdr + 1); + pWdsAdminQosTlv->TLVType = 0x10; + pWdsAdminQosTlv->TLVLength = cpu_to_le16(0x0001); + pWdsAdminQosTlv->QOSSetting = 0; /* no-QOS header */ + + linkProto = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV)(pWdsAdminQosTlv + 1); + linkProto->TLVType = 0x11; + linkProto->TLVLength = cpu_to_le16(4); + linkProto->Value = cpu_to_le32(0x01); /* Set Ethernet mode */ + + dlTlp = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV)(linkProto + 1);; + dlTlp->TLVType = 0x13; + dlTlp->TLVLength = cpu_to_le16(4); + dlTlp->Value = cpu_to_le32(0x00); + + if (sizeof(*linkProto) != 7 ) + dbg_time("%s sizeof(*linkProto) = %d, is not 7!", __func__, sizeof(*linkProto) ); + + return sizeof(QCQMUX_MSG_HDR) + sizeof(*pWdsAdminQosTlv) + sizeof(*linkProto) + sizeof(*dlTlp); +} + +#ifdef CONFIG_SIM +static USHORT DmsUIMVerifyPinReqSend(PQMUX_MSG pMUXMsg, void *arg) { + pMUXMsg->UIMVerifyPinReq.TLVType = 0x01; + pMUXMsg->UIMVerifyPinReq.PINID = 0x01; //Pin1, not Puk + pMUXMsg->UIMVerifyPinReq.PINLen = strlen((const char *)arg); + qstrcpy((PCHAR)&pMUXMsg->UIMVerifyPinReq.PINValue, ((const char *)arg)); + pMUXMsg->UIMVerifyPinReq.TLVLength = cpu_to_le16(2 + strlen((const char *)arg)); + return sizeof(QMIDMS_UIM_VERIFY_PIN_REQ_MSG) + (strlen((const char *)arg) - 1); +} + +static USHORT UimVerifyPinReqSend(PQMUX_MSG pMUXMsg, void *arg) +{ + pMUXMsg->UIMUIMVerifyPinReq.TLVType = 0x01; + pMUXMsg->UIMUIMVerifyPinReq.TLVLength = cpu_to_le16(0x02); + pMUXMsg->UIMUIMVerifyPinReq.Session_Type = 0x00; + pMUXMsg->UIMUIMVerifyPinReq.Aid_Len = 0x00; + pMUXMsg->UIMUIMVerifyPinReq.TLV2Type = 0x02; + pMUXMsg->UIMUIMVerifyPinReq.TLV2Length = cpu_to_le16(2 + strlen((const char *)arg)); + pMUXMsg->UIMUIMVerifyPinReq.PINID = 0x01; //Pin1, not Puk + pMUXMsg->UIMUIMVerifyPinReq.PINLen= strlen((const char *)arg); + qstrcpy((PCHAR)&pMUXMsg->UIMUIMVerifyPinReq.PINValue, ((const char *)arg)); + return sizeof(QMIUIM_VERIFY_PIN_REQ_MSG) + (strlen((const char *)arg) - 1); +} +#endif + +#ifdef CONFIG_APN +static USHORT WdsGetProfileSettingsReqSend(PQMUX_MSG pMUXMsg, void *arg) { + PROFILE_T *profile = (PROFILE_T *)arg; + pMUXMsg->GetProfileSettingsReq.Length = cpu_to_le16(sizeof(QMIWDS_GET_PROFILE_SETTINGS_REQ_MSG) - 4); + pMUXMsg->GetProfileSettingsReq.TLVType = 0x01; + pMUXMsg->GetProfileSettingsReq.TLVLength = cpu_to_le16(0x02); + pMUXMsg->GetProfileSettingsReq.ProfileType = 0x00; // 0 ~ 3GPP, 1 ~ 3GPP2 + pMUXMsg->GetProfileSettingsReq.ProfileIndex = profile->pdp; + return sizeof(QMIWDS_GET_PROFILE_SETTINGS_REQ_MSG); +} + +static USHORT WdsModifyProfileSettingsReq(PQMUX_MSG pMUXMsg, void *arg) { + USHORT TLVLength = 0; + UCHAR *pTLV; + PROFILE_T *profile = (PROFILE_T *)arg; + PQMIWDS_PDPTYPE pPdpType; + + pMUXMsg->ModifyProfileSettingsReq.Length = cpu_to_le16(sizeof(QMIWDS_MODIFY_PROFILE_SETTINGS_REQ_MSG) - 4); + pMUXMsg->ModifyProfileSettingsReq.TLVType = 0x01; + pMUXMsg->ModifyProfileSettingsReq.TLVLength = cpu_to_le16(0x02); + pMUXMsg->ModifyProfileSettingsReq.ProfileType = 0x00; // 0 ~ 3GPP, 1 ~ 3GPP2 + pMUXMsg->ModifyProfileSettingsReq.ProfileIndex = profile->pdp; + + pTLV = (UCHAR *)(&pMUXMsg->ModifyProfileSettingsReq + 1); + + pPdpType = (PQMIWDS_PDPTYPE)(pTLV + TLVLength); + pPdpType->TLVType = 0x11; + pPdpType->TLVLength = cpu_to_le16(0x01); +// 0 ¨C PDP-IP (IPv4) +// 1 ¨C PDP-PPP +// 2 ¨C PDP-IPv6 +// 3 ¨C PDP-IPv4v6 + pPdpType->PdpType = 3; + TLVLength +=(le16_to_cpu(pPdpType->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + + // Set APN Name + if (profile->apn) { + PQMIWDS_APNNAME pApnName = (PQMIWDS_APNNAME)(pTLV + TLVLength); + pApnName->TLVType = 0x14; + pApnName->TLVLength = cpu_to_le16(strlen(profile->apn)); + qstrcpy((char *)&pApnName->ApnName, profile->apn); + TLVLength +=(le16_to_cpu(pApnName->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + // Set User Name + if (profile->user) { + PQMIWDS_USERNAME pUserName = (PQMIWDS_USERNAME)(pTLV + TLVLength); + pUserName->TLVType = 0x1B; + pUserName->TLVLength = cpu_to_le16(strlen(profile->user)); + qstrcpy((char *)&pUserName->UserName, profile->user); + TLVLength += (le16_to_cpu(pUserName->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + // Set Password + if (profile->password) { + PQMIWDS_PASSWD pPasswd = (PQMIWDS_PASSWD)(pTLV + TLVLength); + pPasswd->TLVType = 0x1C; + pPasswd->TLVLength = cpu_to_le16(strlen(profile->password)); + qstrcpy((char *)&pPasswd->Passwd, profile->password); + TLVLength +=(le16_to_cpu(pPasswd->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + // Set Auth Protocol + if (profile->user && profile->password) { + PQMIWDS_AUTH_PREFERENCE pAuthPref = (PQMIWDS_AUTH_PREFERENCE)(pTLV + TLVLength); + pAuthPref->TLVType = 0x1D; + pAuthPref->TLVLength = cpu_to_le16(0x01); + pAuthPref->AuthPreference = profile->auth; // 0 ~ None, 1 ~ Pap, 2 ~ Chap, 3 ~ MsChapV2 + TLVLength += (le16_to_cpu(pAuthPref->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + return sizeof(QMIWDS_MODIFY_PROFILE_SETTINGS_REQ_MSG) + TLVLength; +} +#endif + +static USHORT WdsGetRuntimeSettingReq(PQMUX_MSG pMUXMsg, void *arg) { + pMUXMsg->GetRuntimeSettingsReq.TLVType = 0x10; + pMUXMsg->GetRuntimeSettingsReq.TLVLength = cpu_to_le16(0x04); + // the following mask also applies to IPV6 + pMUXMsg->GetRuntimeSettingsReq.Mask = cpu_to_le32(QMIWDS_GET_RUNTIME_SETTINGS_MASK_IPV4DNS_ADDR | + QMIWDS_GET_RUNTIME_SETTINGS_MASK_IPV4_ADDR | + QMIWDS_GET_RUNTIME_SETTINGS_MASK_MTU | + QMIWDS_GET_RUNTIME_SETTINGS_MASK_IPV4GATEWAY_ADDR); // | + // QMIWDS_GET_RUNTIME_SETTINGS_MASK_PCSCF_SV_ADDR | + // QMIWDS_GET_RUNTIME_SETTINGS_MASK_PCSCF_DOM_NAME; + + return sizeof(QMIWDS_GET_RUNTIME_SETTINGS_REQ_MSG); +} + +static PQCQMIMSG s_pRequest; +static PQCQMIMSG s_pResponse; +static pthread_mutex_t s_commandmutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t s_commandcond = PTHREAD_COND_INITIALIZER; + +static int is_response(const PQCQMIMSG pRequest, const PQCQMIMSG pResponse) { + if ((pRequest->QMIHdr.QMIType == pResponse->QMIHdr.QMIType) + && (pRequest->QMIHdr.ClientId == pResponse->QMIHdr.ClientId)) { + USHORT requestTID, responseTID; + if (pRequest->QMIHdr.QMIType == QMUX_TYPE_CTL) { + requestTID = pRequest->CTLMsg.QMICTLMsgHdr.TransactionId; + responseTID = pResponse->CTLMsg.QMICTLMsgHdr.TransactionId; + } else { + requestTID = le16_to_cpu(pRequest->MUXMsg.QMUXHdr.TransactionId); + responseTID = le16_to_cpu(pResponse->MUXMsg.QMUXHdr.TransactionId); + } + return (requestTID == responseTID); + } + return 0; +} + +int QmiThreadSendQMITimeout(PQCQMIMSG pRequest, PQCQMIMSG *ppResponse, unsigned msecs) { + int ret; + + if (!pRequest) + { + return -EINVAL; + } + + pthread_mutex_lock(&s_commandmutex); + + if (ppResponse) + *ppResponse = NULL; + + dump_qmi(pRequest, le16_to_cpu(pRequest->QMIHdr.Length) + 1); + + s_pRequest = pRequest; + s_pResponse = NULL; + + if (!strncmp(qmichannel, "/dev/qcqmi0", strlen("/dev/qcqmi0"))) + ret = GobiNetSendQMI(pRequest); + else + ret = QmiWwanSendQMI(pRequest); + + if (ret == 0) { + ret = pthread_cond_timeout_np(&s_commandcond, &s_commandmutex, msecs); + if (!ret) { + if (s_pResponse && ppResponse) { + *ppResponse = s_pResponse; + } else { + if (s_pResponse) { + free(s_pResponse); + s_pResponse = NULL; + } + } + } else { + dbg_time("%s pthread_cond_timeout_np=%d, errno: %d (%s)", __func__, ret, errno, strerror(errno)); + } + } + + pthread_mutex_unlock(&s_commandmutex); + + return ret; +} + +int QmiThreadSendQMI(PQCQMIMSG pRequest, PQCQMIMSG *ppResponse) { + return QmiThreadSendQMITimeout(pRequest, ppResponse, 30 * 1000); +} + +void QmiThreadRecvQMI(PQCQMIMSG pResponse) { + pthread_mutex_lock(&s_commandmutex); + if (pResponse == NULL) { + if (s_pRequest) { + free(s_pRequest); + s_pRequest = NULL; + s_pResponse = NULL; + pthread_cond_signal(&s_commandcond); + } + pthread_mutex_unlock(&s_commandmutex); + return; + } + dump_qmi(pResponse, le16_to_cpu(pResponse->QMIHdr.Length) + 1); + if (s_pRequest && is_response(s_pRequest, pResponse)) { + free(s_pRequest); + s_pRequest = NULL; + s_pResponse = malloc(le16_to_cpu(pResponse->QMIHdr.Length) + 1); + if (s_pResponse != NULL) { + memcpy(s_pResponse, pResponse, le16_to_cpu(pResponse->QMIHdr.Length) + 1); + } + pthread_cond_signal(&s_commandcond); + } else if ((pResponse->QMIHdr.QMIType == QMUX_TYPE_NAS) + && (le16_to_cpu(pResponse->MUXMsg.QMUXMsgHdrResp.Type) == QMINAS_SERVING_SYSTEM_IND)) { + qmidevice_send_event_to_main(RIL_UNSOL_RESPONSE_VOICE_NETWORK_STATE_CHANGED); + } else if ((pResponse->QMIHdr.QMIType == QMUX_TYPE_WDS) + && (le16_to_cpu(pResponse->MUXMsg.QMUXMsgHdrResp.Type) == QMIWDS_GET_PKT_SRVC_STATUS_IND)) { + qmidevice_send_event_to_main(RIL_UNSOL_DATA_CALL_LIST_CHANGED); + } else if ((pResponse->QMIHdr.QMIType == QMUX_TYPE_NAS) + && (le16_to_cpu(pResponse->MUXMsg.QMUXMsgHdrResp.Type) == QMINAS_SYS_INFO_IND)) { + qmidevice_send_event_to_main(RIL_UNSOL_RESPONSE_VOICE_NETWORK_STATE_CHANGED); + } else { + if (debug_qmi) + dbg_time("nobody care this qmi msg!!"); + } + pthread_mutex_unlock(&s_commandmutex); +} + +int requestSetEthMode(PROFILE_T *profile) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV linkProto; + + pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS_ADMIN, QMIWDS_ADMIN_SET_DATA_FORMAT_REQ, WdaSetDataFormat, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + linkProto = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x11); + if (linkProto != NULL) { + profile->rawIP = (le32_to_cpu(linkProto->Value) == 2); + s_9x07 = profile->rawIP; + } + + + free(pResponse); + return 0; +} + +#ifdef CONFIG_SIM +int requestGetPINStatus(SIM_Status *pSIMStatus) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + PQMIDMS_UIM_PIN_STATUS pPin1Status = NULL; + //PQMIDMS_UIM_PIN_STATUS pPin2Status = NULL; + + if (s_9x07 && qmiclientId[QMUX_TYPE_UIM]) + pRequest = ComposeQMUXMsg(QMUX_TYPE_UIM, QMIUIM_GET_CARD_STATUS_REQ, NULL, NULL); + else + pRequest = ComposeQMUXMsg(QMUX_TYPE_DMS, QMIDMS_UIM_GET_PIN_STATUS_REQ, NULL, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + pPin1Status = (PQMIDMS_UIM_PIN_STATUS)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x11); + //pPin2Status = (PQMIDMS_UIM_PIN_STATUS)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x12); + + if (pPin1Status != NULL) { + if (pPin1Status->PINStatus == QMI_PIN_STATUS_NOT_VERIF) { + *pSIMStatus = SIM_PIN; + } else if (pPin1Status->PINStatus == QMI_PIN_STATUS_BLOCKED) { + *pSIMStatus = SIM_PUK; + } else if (pPin1Status->PINStatus == QMI_PIN_STATUS_PERM_BLOCKED) { + *pSIMStatus = SIM_BAD; + } + } + + free(pResponse); + return 0; +} + +int requestGetSIMStatus(SIM_Status *pSIMStatus) { //RIL_REQUEST_GET_SIM_STATUS + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + const char * SIM_Status_String[] = { + "SIM_ABSENT", + "SIM_NOT_READY", + "SIM_READY", /* SIM_READY means the radio state is RADIO_STATE_SIM_READY */ + "SIM_PIN", + "SIM_PUK", + "SIM_NETWORK_PERSONALIZATION" + }; + + +__requestGetSIMStatus: + if (s_9x07 && qmiclientId[QMUX_TYPE_UIM]) + pRequest = ComposeQMUXMsg(QMUX_TYPE_UIM, QMIUIM_GET_CARD_STATUS_REQ, NULL, NULL); + else + pRequest = ComposeQMUXMsg(QMUX_TYPE_DMS, QMIDMS_UIM_GET_STATE_REQ, NULL, NULL); + + err = QmiThreadSendQMI(pRequest, &pResponse); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + if (QMI_ERR_OP_DEVICE_UNSUPPORTED == le16_to_cpu(pResponse->MUXMsg.QMUXMsgHdrResp.QMUXError)) { + sleep(1); + goto __requestGetSIMStatus; + } + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + *pSIMStatus = SIM_ABSENT; + if (s_9x07 && qmiclientId[QMUX_TYPE_UIM]) + { + PQMIUIM_CARD_STATUS pCardStatus = NULL; + PQMIUIM_PIN_STATE pPINState = NULL; + UCHAR CardState = 0; + UCHAR PIN1State = 0; + //UCHAR PIN1Retries; + //UCHAR PUK1Retries; + //UCHAR PIN2State; + //UCHAR PIN2Retries; + //UCHAR PUK2Retries; + + pCardStatus = (PQMIUIM_CARD_STATUS)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x10); + if (pCardStatus != NULL) + { + pPINState = (PQMIUIM_PIN_STATE)((PUCHAR)pCardStatus + sizeof(QMIUIM_CARD_STATUS) + pCardStatus->AIDLength); + CardState = pCardStatus->CardState; + if (pPINState->UnivPIN == 1) + { + PIN1State = pCardStatus->UPINState; + //PIN1Retries = pCardStatus->UPINRetries; + //PUK1Retries = pCardStatus->UPUKRetries; + } + else + { + PIN1State = pPINState->PIN1State; + //PIN1Retries = pPINState->PIN1Retries; + //PUK1Retries = pPINState->PUK1Retries; + } + //PIN2State = pPINState->PIN2State; + //PIN2Retries = pPINState->PIN2Retries; + //PUK2Retries = pPINState->PUK2Retries; + } + + *pSIMStatus = SIM_ABSENT; + if ((CardState == 0x01) && ((PIN1State == QMI_PIN_STATUS_VERIFIED)|| (PIN1State == QMI_PIN_STATUS_DISABLED))) + { + *pSIMStatus = SIM_READY; + } + else if (CardState == 0x01) + { + if (PIN1State == QMI_PIN_STATUS_NOT_VERIF) + { + *pSIMStatus = SIM_PIN; + } + if ( PIN1State == QMI_PIN_STATUS_BLOCKED) + { + *pSIMStatus = SIM_PUK; + } + else if (PIN1State == QMI_PIN_STATUS_PERM_BLOCKED) + { + *pSIMStatus = SIM_BAD; + } + else if (PIN1State == QMI_PIN_STATUS_NOT_INIT || PIN1State == QMI_PIN_STATUS_VERIFIED || PIN1State == QMI_PIN_STATUS_DISABLED) + { + *pSIMStatus = SIM_READY; + } + } + else if (CardState == 0x00 || CardState == 0x02) + { + } + else + { + } + } + else + { + //UIM state. Values: + // 0x00 UIM initialization completed + // 0x01 UIM is locked or the UIM failed + // 0x02 UIM is not present + // 0x03 Reserved + // 0xFF UIM state is currently + //unavailable + if (pResponse->MUXMsg.UIMGetStateResp.UIMState == 0x00) { + *pSIMStatus = SIM_READY; + } else if (pResponse->MUXMsg.UIMGetStateResp.UIMState == 0x01) { + *pSIMStatus = SIM_ABSENT; + err = requestGetPINStatus(pSIMStatus); + } else if ((pResponse->MUXMsg.UIMGetStateResp.UIMState == 0x02) || (pResponse->MUXMsg.UIMGetStateResp.UIMState == 0xFF)) { + *pSIMStatus = SIM_ABSENT; + } else { + *pSIMStatus = SIM_ABSENT; + } + } + dbg_time("%s SIMStatus: %s", __func__, SIM_Status_String[*pSIMStatus]); + + free(pResponse); + return 0; +} + +int requestEnterSimPin(const CHAR *pPinCode) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + + if (s_9x07 && qmiclientId[QMUX_TYPE_UIM]) + pRequest = ComposeQMUXMsg(QMUX_TYPE_UIM, QMIUIM_VERIFY_PIN_REQ, UimVerifyPinReqSend, (void *)pPinCode); + else + pRequest = ComposeQMUXMsg(QMUX_TYPE_DMS, QMIDMS_UIM_VERIFY_PIN_REQ, DmsUIMVerifyPinReqSend, (void *)pPinCode); + err = QmiThreadSendQMI(pRequest, &pResponse); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + free(pResponse); + return 0; +} +#endif + +#if 1 +static void meige_convert_cdma_mcc_2_ascii_mcc( USHORT *p_mcc, USHORT mcc ) +{ + unsigned int d1, d2, d3, buf = mcc + 111; + + if ( mcc == 0x3FF ) // wildcard + { + *p_mcc = 3; + } + else + { + d3 = buf % 10; + buf = ( d3 == 0 ) ? (buf-10)/10 : buf/10; + + d2 = buf % 10; + buf = ( d2 == 0 ) ? (buf-10)/10 : buf/10; + + d1 = ( buf == 10 ) ? 0 : buf; + +//dbg_time("d1:%d, d2:%d,d3:%d",d1,d2,d3); + if ( d1<10 && d2<10 && d3<10 ) + { + *p_mcc = d1*100+d2*10+d3; +#if 0 + *(p_mcc+0) = '0' + d1; + *(p_mcc+1) = '0' + d2; + *(p_mcc+2) = '0' + d3; +#endif + } + else + { + //dbg_time( "invalid digits %d %d %d", d1, d2, d3 ); + *p_mcc = 0; + } + } +} + +static void meige_convert_cdma_mnc_2_ascii_mnc( USHORT *p_mnc, USHORT imsi_11_12) +{ + unsigned int d1, d2, buf = imsi_11_12 + 11; + + if ( imsi_11_12 == 0x7F ) // wildcard + { + *p_mnc = 7; + } + else + { + d2 = buf % 10; + buf = ( d2 == 0 ) ? (buf-10)/10 : buf/10; + + d1 = ( buf == 10 ) ? 0 : buf; + + if ( d1<10 && d2<10 ) + { + *p_mnc = d1*10 + d2; + } + else + { + //dbg_time( "invalid digits %d %d", d1, d2, 0 ); + *p_mnc = 0; + } + } +} + +int requestGetHomeNetwork(USHORT *p_mcc, USHORT *p_mnc, USHORT *p_sid, USHORT *p_nid) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + PHOME_NETWORK pHomeNetwork; + PHOME_NETWORK_SYSTEMID pHomeNetworkSystemID; + + pRequest = ComposeQMUXMsg(QMUX_TYPE_NAS, QMINAS_GET_HOME_NETWORK_REQ, NULL, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + pHomeNetwork = (PHOME_NETWORK)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x01); + if (pHomeNetwork && p_mcc && p_mnc ) { + *p_mcc = le16_to_cpu(pHomeNetwork->MobileCountryCode); + *p_mnc = le16_to_cpu(pHomeNetwork->MobileNetworkCode); + //dbg_time("%s MobileCountryCode: %d, MobileNetworkCode: %d", __func__, *pMobileCountryCode, *pMobileNetworkCode); + } + + pHomeNetworkSystemID = (PHOME_NETWORK_SYSTEMID)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x10); + if (pHomeNetworkSystemID && p_sid && p_nid) { + *p_sid = le16_to_cpu(pHomeNetworkSystemID->SystemID); //china-hefei: sid 14451 + *p_nid = le16_to_cpu(pHomeNetworkSystemID->NetworkID); + //dbg_time("%s SystemID: %d, NetworkID: %d", __func__, *pSystemID, *pNetworkID); + } + + free(pResponse); + + return 0; +} +#endif + +#if 0 +// Lookup table for carriers known to produce SIMs which incorrectly indicate MNC length. +static const char * MCCMNC_CODES_HAVING_3DIGITS_MNC[] = { + "302370", "302720", "310260", + "405025", "405026", "405027", "405028", "405029", "405030", "405031", "405032", + "405033", "405034", "405035", "405036", "405037", "405038", "405039", "405040", + "405041", "405042", "405043", "405044", "405045", "405046", "405047", "405750", + "405751", "405752", "405753", "405754", "405755", "405756", "405799", "405800", + "405801", "405802", "405803", "405804", "405805", "405806", "405807", "405808", + "405809", "405810", "405811", "405812", "405813", "405814", "405815", "405816", + "405817", "405818", "405819", "405820", "405821", "405822", "405823", "405824", + "405825", "405826", "405827", "405828", "405829", "405830", "405831", "405832", + "405833", "405834", "405835", "405836", "405837", "405838", "405839", "405840", + "405841", "405842", "405843", "405844", "405845", "405846", "405847", "405848", + "405849", "405850", "405851", "405852", "405853", "405875", "405876", "405877", + "405878", "405879", "405880", "405881", "405882", "405883", "405884", "405885", + "405886", "405908", "405909", "405910", "405911", "405912", "405913", "405914", + "405915", "405916", "405917", "405918", "405919", "405920", "405921", "405922", + "405923", "405924", "405925", "405926", "405927", "405928", "405929", "405930", + "405931", "405932", "502142", "502143", "502145", "502146", "502147", "502148" +}; + +static const char * MCC_CODES_HAVING_3DIGITS_MNC[] = { + "302", //Canada + "310", //United States of America + "311", //United States of America + "312", //United States of America + "313", //United States of America + "314", //United States of America + "315", //United States of America + "316", //United States of America + "334", //Mexico + "338", //Jamaica + "342", //Barbados + "344", //Antigua and Barbuda + "346", //Cayman Islands + "348", //British Virgin Islands + "365", //Anguilla + "708", //Honduras (Republic of) + "722", //Argentine Republic + "732" //Colombia (Republic of) +}; + +int requestGetIMSI(const char **pp_imsi, USHORT *pMobileCountryCode, USHORT *pMobileNetworkCode) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + + if (pp_imsi) *pp_imsi = NULL; + if (pMobileCountryCode) *pMobileCountryCode = 0; + if (pMobileNetworkCode) *pMobileNetworkCode = 0; + + pRequest = ComposeQMUXMsg(QMUX_TYPE_DMS, QMIDMS_UIM_GET_IMSI_REQ, NULL, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + if (pMUXMsg->UIMGetIMSIResp.TLV2Type == 0x01 && le16_to_cpu(pMUXMsg->UIMGetIMSIResp.TLV2Length) >= 5) { + int mnc_len = 2; + unsigned i; + char tmp[4]; + + if (pp_imsi) *pp_imsi = strndup((const char *)(&pMUXMsg->UIMGetIMSIResp.IMSI), le16_to_cpu(pMUXMsg->UIMGetIMSIResp.TLV2Length)); + + for (i = 0; i < sizeof(MCCMNC_CODES_HAVING_3DIGITS_MNC)/sizeof(MCCMNC_CODES_HAVING_3DIGITS_MNC[0]); i++) { + if (!strncmp((const char *)(&pMUXMsg->UIMGetIMSIResp.IMSI), MCCMNC_CODES_HAVING_3DIGITS_MNC[i], 6)) { + mnc_len = 3; + break; + } + } + if (mnc_len == 2) { + for (i = 0; i < sizeof(MCC_CODES_HAVING_3DIGITS_MNC)/sizeof(MCC_CODES_HAVING_3DIGITS_MNC[0]); i++) { + if (!strncmp((const char *)(&pMUXMsg->UIMGetIMSIResp.IMSI), MCC_CODES_HAVING_3DIGITS_MNC[i], 3)) { + mnc_len = 3; + break; + } + } + } + + tmp[0] = (&pMUXMsg->UIMGetIMSIResp.IMSI)[0]; + tmp[1] = (&pMUXMsg->UIMGetIMSIResp.IMSI)[1]; + tmp[2] = (&pMUXMsg->UIMGetIMSIResp.IMSI)[2]; + tmp[3] = 0; + if (pMobileCountryCode) *pMobileCountryCode = atoi(tmp); + tmp[0] = (&pMUXMsg->UIMGetIMSIResp.IMSI)[3]; + tmp[1] = (&pMUXMsg->UIMGetIMSIResp.IMSI)[4]; + tmp[2] = 0; + if (mnc_len == 3) { + tmp[2] = (&pMUXMsg->UIMGetIMSIResp.IMSI)[6]; + } + if (pMobileNetworkCode) *pMobileNetworkCode = atoi(tmp); + } + + free(pResponse); + + return 0; +} +#endif + +struct wwan_data_class_str class2str[] = { + {WWAN_DATA_CLASS_NONE, "UNKNOWN"}, + {WWAN_DATA_CLASS_GPRS, "GPRS"}, + {WWAN_DATA_CLASS_EDGE, "EDGE"}, + {WWAN_DATA_CLASS_UMTS, "UMTS"}, + {WWAN_DATA_CLASS_HSDPA, "HSDPA"}, + {WWAN_DATA_CLASS_HSUPA, "HSUPA"}, + {WWAN_DATA_CLASS_LTE, "LTE"}, + {WWAN_DATA_CLASS_1XRTT, "1XRTT"}, + {WWAN_DATA_CLASS_1XEVDO, "1XEVDO"}, + {WWAN_DATA_CLASS_1XEVDO_REVA, "1XEVDO_REVA"}, + {WWAN_DATA_CLASS_1XEVDV, "1XEVDV"}, + {WWAN_DATA_CLASS_3XRTT, "3XRTT"}, + {WWAN_DATA_CLASS_1XEVDO_REVB, "1XEVDO_REVB"}, + {WWAN_DATA_CLASS_UMB, "UMB"}, + {WWAN_DATA_CLASS_CUSTOM, "CUSTOM"}, +}; + +CHAR *wwan_data_class2str(ULONG class) +{ + int i = 0; + for (i = 0; i < sizeof(class2str)/sizeof(class2str[0]); i++) { + if (class2str[i].class == class) { + return class2str[i].str; + } + } + return "UNKNOWN"; +} + +int requestRegistrationState2(UCHAR *pPSAttachedState) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + USHORT MobileCountryCode = 0; + USHORT MobileNetworkCode = 0; + const char *pDataCapStr = "UNKNOW"; + LONG remainingLen; + PSERVICE_STATUS_INFO pServiceStatusInfo; + int is_lte = 0; + PCDMA_SYSTEM_INFO pCdmaSystemInfo; + PHDR_SYSTEM_INFO pHdrSystemInfo; + PGSM_SYSTEM_INFO pGsmSystemInfo; + PWCDMA_SYSTEM_INFO pWcdmaSystemInfo; + PLTE_SYSTEM_INFO pLteSystemInfo; + PTDSCDMA_SYSTEM_INFO pTdscdmaSystemInfo; + UCHAR DeviceClass = 0; + ULONG DataCapList = 0; + + pRequest = ComposeQMUXMsg(QMUX_TYPE_NAS, QMINAS_GET_SYS_INFO_REQ, NULL, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + pServiceStatusInfo = (PSERVICE_STATUS_INFO)(((PCHAR)&pMUXMsg->GetSysInfoResp) + QCQMUX_MSG_HDR_SIZE); + remainingLen = le16_to_cpu(pMUXMsg->GetSysInfoResp.Length); + + s_is_cdma = 0; + while (remainingLen > 0) { + switch (pServiceStatusInfo->TLVType) { + case 0x10: // CDMA + if (pServiceStatusInfo->SrvStatus == 0x02) { + DataCapList = WWAN_DATA_CLASS_1XRTT| + WWAN_DATA_CLASS_1XEVDO| + WWAN_DATA_CLASS_1XEVDO_REVA| + WWAN_DATA_CLASS_1XEVDV| + WWAN_DATA_CLASS_1XEVDO_REVB; + DeviceClass = DEVICE_CLASS_CDMA; + s_is_cdma = (0 == is_lte); + } + break; + case 0x11: // HDR + if (pServiceStatusInfo->SrvStatus == 0x02) { + DataCapList = WWAN_DATA_CLASS_3XRTT| + WWAN_DATA_CLASS_UMB; + DeviceClass = DEVICE_CLASS_CDMA; + s_is_cdma = (0 == is_lte); + } + break; + case 0x12: // GSM + if (pServiceStatusInfo->SrvStatus == 0x02) { + DataCapList = WWAN_DATA_CLASS_GPRS| + WWAN_DATA_CLASS_EDGE; + DeviceClass = DEVICE_CLASS_GSM; + } + break; + case 0x13: // WCDMA + if (pServiceStatusInfo->SrvStatus == 0x02) { + DataCapList = WWAN_DATA_CLASS_UMTS; + DeviceClass = DEVICE_CLASS_GSM; + } + break; + case 0x14: // LTE + if (pServiceStatusInfo->SrvStatus == 0x02) { + DataCapList = WWAN_DATA_CLASS_LTE; + DeviceClass = DEVICE_CLASS_GSM; + is_lte = 1; + s_is_cdma = 0; + } + break; + case 0x24: // TDSCDMA + if (pServiceStatusInfo->SrvStatus == 0x02) { + pDataCapStr = "TD-SCDMA"; + } + break; + case 0x15: // CDMA + // CDMA_SYSTEM_INFO + pCdmaSystemInfo = (PCDMA_SYSTEM_INFO)pServiceStatusInfo; + if (pCdmaSystemInfo->SrvDomainValid == 0x01) { + *pPSAttachedState = 0; + if (pCdmaSystemInfo->SrvDomain & 0x02) { + *pPSAttachedState = 1; + s_is_cdma = (0 == is_lte); + } + } + if (pCdmaSystemInfo->SrvCapabilityValid == 0x01) { + *pPSAttachedState = 0; + if (pCdmaSystemInfo->SrvCapability & 0x02) { + *pPSAttachedState = 1; + s_is_cdma = (0 == is_lte); + } + } + if (pCdmaSystemInfo->NetworkIdValid == 0x01) { + int i; + CHAR temp[10]; + strncpy(temp, (CHAR *)pCdmaSystemInfo->MCC, 3); + temp[3] = '\0'; + for (i = 0; i < 4; i++) { + if ((UCHAR)temp[i] == 0xFF) { + temp[i] = '\0'; + } + } + MobileCountryCode = (USHORT)atoi(temp); + + strncpy(temp, (CHAR *)pCdmaSystemInfo->MNC, 3); + temp[3] = '\0'; + for (i = 0; i < 4; i++) { + if ((UCHAR)temp[i] == 0xFF) { + temp[i] = '\0'; + } + } + MobileNetworkCode = (USHORT)atoi(temp); + } + break; + case 0x16: // HDR + // HDR_SYSTEM_INFO + pHdrSystemInfo = (PHDR_SYSTEM_INFO)pServiceStatusInfo; + if (pHdrSystemInfo->SrvDomainValid == 0x01) { + *pPSAttachedState = 0; + if (pHdrSystemInfo->SrvDomain & 0x02) { + *pPSAttachedState = 1; + s_is_cdma = (0 == is_lte); + } + } + if (pHdrSystemInfo->SrvCapabilityValid == 0x01) { + *pPSAttachedState = 0; + if (pHdrSystemInfo->SrvCapability & 0x02) { + *pPSAttachedState = 1; + s_is_cdma = (0 == is_lte); + } + } + USHORT cmda_mcc = 0, cdma_mnc = 0; + if(!requestGetHomeNetwork(&cmda_mcc, &cdma_mnc,NULL, NULL) && cmda_mcc) { + meige_convert_cdma_mcc_2_ascii_mcc(&MobileCountryCode, cmda_mcc); + meige_convert_cdma_mnc_2_ascii_mnc(&MobileNetworkCode, cdma_mnc); + } + break; + case 0x17: // GSM + // GSM_SYSTEM_INFO + pGsmSystemInfo = (PGSM_SYSTEM_INFO)pServiceStatusInfo; + if (pGsmSystemInfo->SrvDomainValid == 0x01) { + *pPSAttachedState = 0; + if (pGsmSystemInfo->SrvDomain & 0x02) { + *pPSAttachedState = 1; + } + } + if (pGsmSystemInfo->SrvCapabilityValid == 0x01) { + *pPSAttachedState = 0; + if (pGsmSystemInfo->SrvCapability & 0x02) { + *pPSAttachedState = 1; + } + } + if (pGsmSystemInfo->NetworkIdValid == 0x01) { + int i; + CHAR temp[10]; + strncpy(temp, (CHAR *)pGsmSystemInfo->MCC, 3); + temp[3] = '\0'; + for (i = 0; i < 4; i++) { + if ((UCHAR)temp[i] == 0xFF) { + temp[i] = '\0'; + } + } + MobileCountryCode = (USHORT)atoi(temp); + + strncpy(temp, (CHAR *)pGsmSystemInfo->MNC, 3); + temp[3] = '\0'; + for (i = 0; i < 4; i++) { + if ((UCHAR)temp[i] == 0xFF) { + temp[i] = '\0'; + } + } + MobileNetworkCode = (USHORT)atoi(temp); + } + break; + case 0x18: // WCDMA + // WCDMA_SYSTEM_INFO + pWcdmaSystemInfo = (PWCDMA_SYSTEM_INFO)pServiceStatusInfo; + if (pWcdmaSystemInfo->SrvDomainValid == 0x01) { + *pPSAttachedState = 0; + if (pWcdmaSystemInfo->SrvDomain & 0x02) { + *pPSAttachedState = 1; + } + } + if (pWcdmaSystemInfo->SrvCapabilityValid == 0x01) { + *pPSAttachedState = 0; + if (pWcdmaSystemInfo->SrvCapability & 0x02) { + *pPSAttachedState = 1; + } + } + if (pWcdmaSystemInfo->NetworkIdValid == 0x01) { + int i; + CHAR temp[10]; + strncpy(temp, (CHAR *)pWcdmaSystemInfo->MCC, 3); + temp[3] = '\0'; + for (i = 0; i < 4; i++) { + if ((UCHAR)temp[i] == 0xFF) { + temp[i] = '\0'; + } + } + MobileCountryCode = (USHORT)atoi(temp); + + strncpy(temp, (CHAR *)pWcdmaSystemInfo->MNC, 3); + temp[3] = '\0'; + for (i = 0; i < 4; i++) { + if ((UCHAR)temp[i] == 0xFF) { + temp[i] = '\0'; + } + } + MobileNetworkCode = (USHORT)atoi(temp); + } + break; + case 0x19: // LTE_SYSTEM_INFO + // LTE_SYSTEM_INFO + pLteSystemInfo = (PLTE_SYSTEM_INFO)pServiceStatusInfo; + if (pLteSystemInfo->SrvDomainValid == 0x01) { + *pPSAttachedState = 0; + if (pLteSystemInfo->SrvDomain & 0x02) { + *pPSAttachedState = 1; + is_lte = 1; + s_is_cdma = 0; + } + } + if (pLteSystemInfo->SrvCapabilityValid == 0x01) { + *pPSAttachedState = 0; + if (pLteSystemInfo->SrvCapability & 0x02) { + *pPSAttachedState = 1; + is_lte = 1; + s_is_cdma = 0; + } + } + if (pLteSystemInfo->NetworkIdValid == 0x01) { + int i; + CHAR temp[10]; + strncpy(temp, (CHAR *)pLteSystemInfo->MCC, 3); + temp[3] = '\0'; + for (i = 0; i < 4; i++) { + if ((UCHAR)temp[i] == 0xFF) { + temp[i] = '\0'; + } + } + MobileCountryCode = (USHORT)atoi(temp); + + strncpy(temp, (CHAR *)pLteSystemInfo->MNC, 3); + temp[3] = '\0'; + for (i = 0; i < 4; i++) { + if ((UCHAR)temp[i] == 0xFF) { + temp[i] = '\0'; + } + } + MobileNetworkCode = (USHORT)atoi(temp); + } + break; + case 0x25: // TDSCDMA + // TDSCDMA_SYSTEM_INFO + pTdscdmaSystemInfo = (PTDSCDMA_SYSTEM_INFO)pServiceStatusInfo; + if (pTdscdmaSystemInfo->SrvDomainValid == 0x01) { + *pPSAttachedState = 0; + if (pTdscdmaSystemInfo->SrvDomain & 0x02) { + *pPSAttachedState = 1; + } + } + if (pTdscdmaSystemInfo->SrvCapabilityValid == 0x01) { + *pPSAttachedState = 0; + if (pTdscdmaSystemInfo->SrvCapability & 0x02) { + *pPSAttachedState = 1; + } + } + if (pTdscdmaSystemInfo->NetworkIdValid == 0x01) { + int i; + CHAR temp[10]; + strncpy(temp, (CHAR *)pTdscdmaSystemInfo->MCC, 3); + temp[3] = '\0'; + for (i = 0; i < 4; i++) { + if ((UCHAR)temp[i] == 0xFF) { + temp[i] = '\0'; + } + } + MobileCountryCode = (USHORT)atoi(temp); + + strncpy(temp, (CHAR *)pTdscdmaSystemInfo->MNC, 3); + temp[3] = '\0'; + for (i = 0; i < 4; i++) { + if ((UCHAR)temp[i] == 0xFF) { + temp[i] = '\0'; + } + } + MobileNetworkCode = (USHORT)atoi(temp); + } + break; + default: + break; + } /* switch (pServiceStatusInfo->TLYType) */ + remainingLen -= (le16_to_cpu(pServiceStatusInfo->TLVLength) + 3); + pServiceStatusInfo = (PSERVICE_STATUS_INFO)((PCHAR)&pServiceStatusInfo->TLVLength + le16_to_cpu(pServiceStatusInfo->TLVLength) + sizeof(USHORT)); + } /* while (remainingLen > 0) */ + + if (DeviceClass == DEVICE_CLASS_CDMA) { + if (DataCapList & WWAN_DATA_CLASS_1XEVDO_REVB) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_1XEVDO_REVB); + } else if (DataCapList & WWAN_DATA_CLASS_1XEVDO_REVA) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_1XEVDO_REVA); + } else if (DataCapList & WWAN_DATA_CLASS_1XEVDO) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_1XEVDO); + } else if (DataCapList & WWAN_DATA_CLASS_1XRTT) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_1XRTT); + } else if (DataCapList & WWAN_DATA_CLASS_3XRTT) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_3XRTT); + } else if (DataCapList & WWAN_DATA_CLASS_UMB) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_UMB); + } + } else { + if (DataCapList & WWAN_DATA_CLASS_LTE) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_LTE); + } else if ((DataCapList & WWAN_DATA_CLASS_HSDPA) && (DataCapList & WWAN_DATA_CLASS_HSUPA)) { + pDataCapStr = "HSDPA_HSUPA"; + } else if (DataCapList & WWAN_DATA_CLASS_HSDPA) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_HSDPA); + } else if (DataCapList & WWAN_DATA_CLASS_HSUPA) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_HSUPA); + } else if (DataCapList & WWAN_DATA_CLASS_UMTS) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_UMTS); + } else if (DataCapList & WWAN_DATA_CLASS_EDGE) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_EDGE); + } else if (DataCapList & WWAN_DATA_CLASS_GPRS) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_GPRS); + } + } + + dbg_time("%s MCC: %d, MNC: %d, PS: %s, DataCap: %s", __func__, + MobileCountryCode, MobileNetworkCode, (*pPSAttachedState == 1) ? "Attached" : "Detached" , pDataCapStr); + + free(pResponse); + + return 0; +} + +int requestRegistrationState(UCHAR *pPSAttachedState) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + PQMINAS_CURRENT_PLMN_MSG pCurrentPlmn; + PSERVING_SYSTEM pServingSystem; + PQMINAS_DATA_CAP pDataCap; + USHORT MobileCountryCode = 0; + USHORT MobileNetworkCode = 0; + const char *pDataCapStr = "UNKNOW"; + + if (s_9x07) { + return requestRegistrationState2(pPSAttachedState); + } + + pRequest = ComposeQMUXMsg(QMUX_TYPE_NAS, QMINAS_GET_SERVING_SYSTEM_REQ, NULL, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + pCurrentPlmn = (PQMINAS_CURRENT_PLMN_MSG)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x12); + if (pCurrentPlmn) { + MobileCountryCode = le16_to_cpu(pCurrentPlmn->MobileCountryCode); + MobileNetworkCode = le16_to_cpu(pCurrentPlmn->MobileNetworkCode); + } + + *pPSAttachedState = 0; + pServingSystem = (PSERVING_SYSTEM)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x01); + if (pServingSystem) { + //Packet-switched domain attach state of the mobile. + //0x00 PS_UNKNOWN ?Unknown or not applicable + //0x01 PS_ATTACHED ?Attached + //0x02 PS_DETACHED ?Detached + *pPSAttachedState = pServingSystem->RegistrationState; + if (pServingSystem->RegistrationState == 0x01) //0x01 ¨C REGISTERED ¨C Registered with a network + *pPSAttachedState = pServingSystem->PSAttachedState; + else { + //MobileCountryCode = MobileNetworkCode = 0; + *pPSAttachedState = 0x02; + } + } + + pDataCap = (PQMINAS_DATA_CAP)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x11); + if (pDataCap && pDataCap->DataCapListLen) { + UCHAR *DataCap = &pDataCap->DataCap; + if (pDataCap->DataCapListLen == 2) { + if ((DataCap[0] == 0x06) && ((DataCap[1] == 0x08) || (DataCap[1] == 0x0A))) + DataCap[0] = DataCap[1]; + } + switch (DataCap[0]) { + case 0x01: pDataCapStr = "GPRS"; break; + case 0x02: pDataCapStr = "EDGE"; break; + case 0x03: pDataCapStr = "HSDPA"; break; + case 0x04: pDataCapStr = "HSUPA"; break; + case 0x05: pDataCapStr = "UMTS"; break; + case 0x06: pDataCapStr = "1XRTT"; break; + case 0x07: pDataCapStr = "1XEVDO"; break; + case 0x08: pDataCapStr = "1XEVDO_REVA"; break; + case 0x09: pDataCapStr = "GPRS"; break; + case 0x0A: pDataCapStr = "1XEVDO_REVB"; break; + case 0x0B: pDataCapStr = "LTE"; break; + case 0x0C: pDataCapStr = "HSDPA"; break; + case 0x0D: pDataCapStr = "HSDPA"; break; + default: pDataCapStr = "UNKNOW"; break; + } + } + + if (pServingSystem && pServingSystem->RegistrationState == 0x01 && pServingSystem->InUseRadioIF && pServingSystem->RadioIF == 0x09) { + pDataCapStr = "TD-SCDMA"; + } + + s_is_cdma = 0; + if (pServingSystem && pServingSystem->RegistrationState == 0x01 && pServingSystem->InUseRadioIF && (pServingSystem->RadioIF == 0x01 || pServingSystem->RadioIF == 0x02)) { + USHORT cmda_mcc = 0, cdma_mnc = 0; + s_is_cdma = 1; + if(!requestGetHomeNetwork(&cmda_mcc, &cdma_mnc,NULL, NULL) && cmda_mcc) { + meige_convert_cdma_mcc_2_ascii_mcc(&MobileCountryCode, cmda_mcc); + meige_convert_cdma_mnc_2_ascii_mnc(&MobileNetworkCode, cdma_mnc); + } + if (1) { + PQCQMUX_TLV pTLV = (PQCQMUX_TLV)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x23); + if (pTLV) + s_hdr_personality = pTLV->Value; + else + s_hdr_personality = 0; + if (s_hdr_personality == 2) + pDataCapStr = "eHRPD"; + } + } + + dbg_time("%s MCC: %d, MNC: %d, PS: %s, DataCap: %s", __func__, + MobileCountryCode, MobileNetworkCode, (*pPSAttachedState == 1) ? "Attached" : "Detached" , pDataCapStr); + + free(pResponse); + + return 0; +} + +int requestQueryDataCall(UCHAR *pConnectionStatus) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + PQMIWDS_PKT_SRVC_TLV pPktSrvc; + UCHAR oldConnectionStatus = *pConnectionStatus; + + pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS, QMIWDS_GET_PKT_SRVC_STATUS_REQ, NULL, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + *pConnectionStatus = QWDS_PKT_DATA_DISCONNECTED; + pPktSrvc = (PQMIWDS_PKT_SRVC_TLV)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x01); + if (pPktSrvc) { + *pConnectionStatus = pPktSrvc->ConnectionStatus; + if ((le16_to_cpu(pPktSrvc->TLVLength) == 2) && (pPktSrvc->ReconfigReqd == 0x01)) + *pConnectionStatus = QWDS_PKT_DATA_DISCONNECTED; + } + + if (*pConnectionStatus == QWDS_PKT_DATA_DISCONNECTED) + WdsConnectionIPv4Handle = 0; + + if (oldConnectionStatus != *pConnectionStatus || debug_qmi) + dbg_time("%s ConnectionStatus: %s", __func__, (*pConnectionStatus == QWDS_PKT_DATA_CONNECTED) ? "CONNECTED" : "DISCONNECTED"); + + free(pResponse); + return 0; +} + +#if 0 +BOOLEAN QCMAIN_IsDualIPSupported(PMP_ADAPTER pAdapter) +{ + return (pAdapter->QMUXVersion[QMUX_TYPE_WDS].Major >= 1 && pAdapter->QMUXVersion[QMUX_TYPE_WDS].Minor >= 9); +} // QCMAIN_IsDualIPSupported +#endif + +int requestSetupDataCall(PROFILE_T *profile) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + int old_auth = 0; + +//DualIPSupported means can get ipv4 & ipv6 address at the same time, one wds for ipv4, the other wds for ipv6 + profile->IPType = 0x04; //ipv4 first +__requestSetupDataCall: + pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS, QMIWDS_START_NETWORK_INTERFACE_REQ, WdsStartNwInterfaceReq, profile); + err = QmiThreadSendQMITimeout(pRequest, &pResponse, 120 * 1000); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) + { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + + if (!old_auth && profile->user && profile->user[0] && profile->password && profile->password[0]) { + old_auth = profile->auth; + profile->auth = (profile->auth == 1) ? 2 : 1; + goto __requestSetupDataCall; + } else if (old_auth) { + profile->auth = old_auth; + } + + if (!access("/proc/net/if_inet6", R_OK) && QMI_ERR_PIN_LOCKED == le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError) && profile->IPType == 0x04) + { + free(pResponse); + profile->IPType = 0x06; //ipv6 + goto __requestSetupDataCall; + } + + profile->IPType = 0x04; //reset to ipv4 first + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + WdsConnectionIPv4Handle = le32_to_cpu(pResponse->MUXMsg.StartNwInterfaceResp.Handle); + dbg_time("%s %s: 0x%08x", __func__, + (profile->IPType == 0x04) ? "WdsConnectionIPv4Handle" : "WdsConnectionIPv6Handle", WdsConnectionIPv4Handle); + + free(pResponse); + return 0; +} + +int requestDeactivateDefaultPDP(void) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + + pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS, QMIWDS_STOP_NETWORK_INTERFACE_REQ , WdsStopNwInterfaceReq, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + WdsConnectionIPv4Handle = 0; + free(pResponse); + return 0; +} + +int requestGetIPAddress(PROFILE_T *profile) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR pIpv4Addr; + PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV6_ADDR pIpv6Addr = NULL; + PQMIWDS_GET_RUNTIME_SETTINGS_TLV_MTU pMtu; + IPV4_T *pIpv4 = &profile->ipv4; + IPV6_T *pIpv6 = &profile->ipv6; + + if (profile->IPType == 0x04) + memset(pIpv4, 0x00, sizeof(IPV4_T)); + else + memset(pIpv6, 0x00, sizeof(IPV6_T)); + + pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS, QMIWDS_GET_RUNTIME_SETTINGS_REQ, WdsGetRuntimeSettingReq, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + pIpv4Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4PRIMARYDNS); + if (pIpv4Addr) { + pIpv4->DnsPrimary = pIpv4Addr->IPV4Address; + } + + pIpv4Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4SECONDARYDNS); + if (pIpv4Addr) { + pIpv4->DnsSecondary = pIpv4Addr->IPV4Address; + } + + pIpv4Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4GATEWAY); + if (pIpv4Addr) { + pIpv4->Gateway = pIpv4Addr->IPV4Address; + } + + pIpv4Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4SUBNET); + if (pIpv4Addr) { + pIpv4->SubnetMask = pIpv4Addr->IPV4Address; + } + + pIpv4Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4); + if (pIpv4Addr) { + pIpv4->Address = pIpv4Addr->IPV4Address; + } + + pIpv6Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV6_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6PRIMARYDNS); + if (pIpv6Addr) { + memcpy(pIpv6->DnsPrimary, pIpv6Addr->IPV6Address, 16); + } + + pIpv6Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV6_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6SECONDARYDNS); + if (pIpv6Addr) { + memcpy(pIpv6->DnsSecondary, pIpv6Addr->IPV6Address, 16); + } + + pIpv6Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV6_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6GATEWAY); + if (pIpv6Addr) { + memcpy(pIpv6->Gateway, pIpv6Addr->IPV6Address, 16); + pIpv6->PrefixLengthGateway = pIpv6Addr->PrefixLength; + } + + pIpv6Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV6_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6); + if (pIpv6Addr) { + memcpy(pIpv6->Address, pIpv6Addr->IPV6Address, 16); + pIpv6->PrefixLengthIPAddr = pIpv6Addr->PrefixLength; + } + + pMtu = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_MTU)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_MTU); + if (pMtu) { + if (profile->IPType == 0x04) + pIpv4->Mtu = le32_to_cpu(pMtu->Mtu); + else + pIpv6->Mtu = le32_to_cpu(pMtu->Mtu); + } + + free(pResponse); + return 0; +} + +#ifdef CONFIG_APN +int requestSetProfile(PROFILE_T *profile) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + + dbg_time("%s[%d] %s/%s/%s/%d", __func__, profile->pdp, profile->apn, profile->user, profile->password, profile->auth); + pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS, QMIWDS_MODIFY_PROFILE_SETTINGS_REQ, WdsModifyProfileSettingsReq, profile); + err = QmiThreadSendQMI(pRequest, &pResponse); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + free(pResponse); + return 0; +} + +int requestGetProfile(PROFILE_T *profile) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + char *apn = NULL; + char *user = NULL; + char *password = NULL; + int auth = 0; + PQMIWDS_APNNAME pApnName; + PQMIWDS_USERNAME pUserName; + PQMIWDS_PASSWD pPassWd; + PQMIWDS_AUTH_PREFERENCE pAuthPref; + + pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS, QMIWDS_GET_PROFILE_SETTINGS_REQ, WdsGetProfileSettingsReqSend, profile); + err = QmiThreadSendQMI(pRequest, &pResponse); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + pApnName = (PQMIWDS_APNNAME)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x14); + pUserName = (PQMIWDS_USERNAME)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x1B); + pPassWd = (PQMIWDS_PASSWD)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x1C); + pAuthPref = (PQMIWDS_AUTH_PREFERENCE)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x1D); + + if (pApnName/* && le16_to_cpu(pApnName->TLVLength)*/) + apn = strndup((const char *)(&pApnName->ApnName), le16_to_cpu(pApnName->TLVLength)); + if (pUserName/* && pUserName->UserName*/) + user = strndup((const char *)(&pUserName->UserName), le16_to_cpu(pUserName->TLVLength)); + if (pPassWd/* && le16_to_cpu(pPassWd->TLVLength)*/) + password = strndup((const char *)(&pPassWd->Passwd), le16_to_cpu(pPassWd->TLVLength)); + if (pAuthPref/* && le16_to_cpu(pAuthPref->TLVLength)*/) { + auth = pAuthPref->AuthPreference; + } + +#if 0 + if (profile) { + profile->apn = apn; + profile->user = user; + profile->password = password; + profile->auth = auth; + } +#endif + + dbg_time("%s[%d] %s/%s/%s/%d", __func__, profile->pdp, apn, user, password, auth); + + free(pResponse); + return 0; +} +#endif + +#ifdef CONFIG_VERSION +int requestBaseBandVersion(const char **pp_reversion) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + PDEVICE_REV_ID revId; + int err; + + if (pp_reversion) *pp_reversion = NULL; + + pRequest = ComposeQMUXMsg(QMUX_TYPE_DMS, QMIDMS_GET_DEVICE_REV_ID_REQ, NULL, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + revId = (PDEVICE_REV_ID)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x01); + + if (revId && le16_to_cpu(revId->TLVLength)) + { + char *DeviceRevisionID = strndup((const char *)(&revId->RevisionID), le16_to_cpu(revId->TLVLength)); + dbg_time("%s %s", __func__, DeviceRevisionID); + if (s_9x07 == -1) { //fail to get QMUX_TYPE_WDS_ADMIN + if (strncmp(DeviceRevisionID, "EC20", strlen("EC20"))) + s_9x07 = 1; + else + s_9x07 = DeviceRevisionID[5] == 'F' || DeviceRevisionID[6] == 'F'; //EC20CF,EC20EF,EC20CEF + } + if (pp_reversion) *pp_reversion = DeviceRevisionID; + } + + free(pResponse); + return 0; +} +#endif diff --git a/root/package/link4all/quectel-CM/src_fangge/QMIThread.h b/root/package/link4all/quectel-CM/src_fangge/QMIThread.h new file mode 100755 index 00000000..2321ea71 --- /dev/null +++ b/root/package/link4all/quectel-CM/src_fangge/QMIThread.h @@ -0,0 +1,164 @@ +#ifndef __QMI_THREAD_H__ +#define __QMI_THREAD_H__ + +#define CONFIG_GOBINET +#define CONFIG_QMIWWAN +#define CONFIG_SIM +#define CONFIG_APN +#define CONFIG_VERSION +#define CONFIG_DEFAULT_PDP 1 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "MPQMI.h" +#include "MPQCTL.h" +#include "MPQMUX.h" + +#define DEVICE_CLASS_UNKNOWN 0 +#define DEVICE_CLASS_CDMA 1 +#define DEVICE_CLASS_GSM 2 + +#define WWAN_DATA_CLASS_NONE 0x00000000 +#define WWAN_DATA_CLASS_GPRS 0x00000001 +#define WWAN_DATA_CLASS_EDGE 0x00000002 /* EGPRS */ +#define WWAN_DATA_CLASS_UMTS 0x00000004 +#define WWAN_DATA_CLASS_HSDPA 0x00000008 +#define WWAN_DATA_CLASS_HSUPA 0x00000010 +#define WWAN_DATA_CLASS_LTE 0x00000020 +#define WWAN_DATA_CLASS_1XRTT 0x00010000 +#define WWAN_DATA_CLASS_1XEVDO 0x00020000 +#define WWAN_DATA_CLASS_1XEVDO_REVA 0x00040000 +#define WWAN_DATA_CLASS_1XEVDV 0x00080000 +#define WWAN_DATA_CLASS_3XRTT 0x00100000 +#define WWAN_DATA_CLASS_1XEVDO_REVB 0x00200000 /* for future use */ +#define WWAN_DATA_CLASS_UMB 0x00400000 +#define WWAN_DATA_CLASS_CUSTOM 0x80000000 + +struct wwan_data_class_str { + ULONG class; + CHAR *str; +}; + +#pragma pack(push, 1) + +typedef struct _QCQMIMSG { + QCQMI_HDR QMIHdr; + union { + QMICTL_MSG CTLMsg; + QMUX_MSG MUXMsg; + }; +} __attribute__ ((packed)) QCQMIMSG, *PQCQMIMSG; + +#pragma pack(pop) + +typedef struct __IPV4 { + ULONG Address; + ULONG Gateway; + ULONG SubnetMask; + ULONG DnsPrimary; + ULONG DnsSecondary; + ULONG Mtu; +} IPV4_T; + +typedef struct __IPV6 { + UCHAR Address[16]; + UCHAR Gateway[16]; + UCHAR SubnetMask[16]; + UCHAR DnsPrimary[16]; + UCHAR DnsSecondary[16]; + UCHAR PrefixLengthIPAddr; + UCHAR PrefixLengthGateway; + ULONG Mtu; +} IPV6_T; + +typedef struct __PROFILE { + char * qmichannel; + char * usbnet_adapter; + const char *apn; + const char *user; + const char *password; + const char *pincode; + int auth; + int pdp; + int IPType; + int rawIP; + IPV4_T ipv4; + IPV6_T ipv6; +} PROFILE_T; + +typedef enum { + SIM_ABSENT = 0, + SIM_NOT_READY = 1, + SIM_READY = 2, /* SIM_READY means the radio state is RADIO_STATE_SIM_READY */ + SIM_PIN = 3, + SIM_PUK = 4, + SIM_NETWORK_PERSONALIZATION = 5, + SIM_BAD = 6, +} SIM_Status; + +#define WDM_DEFAULT_BUFSIZE 256 +#define RIL_REQUEST_QUIT 0x1000 +#define RIL_INDICATE_DEVICE_CONNECTED 0x1002 +#define RIL_INDICATE_DEVICE_DISCONNECTED 0x1003 +#define RIL_UNSOL_RESPONSE_VOICE_NETWORK_STATE_CHANGED 0x1004 +#define RIL_UNSOL_DATA_CALL_LIST_CHANGED 0x1005 + +extern int pthread_cond_timeout_np(pthread_cond_t *cond, pthread_mutex_t * mutex, unsigned msecs); +extern int QmiThreadSendQMI(PQCQMIMSG pRequest, PQCQMIMSG *ppResponse); +extern int QmiThreadSendQMITimeout(PQCQMIMSG pRequest, PQCQMIMSG *ppResponse, unsigned msecs); +extern void QmiThreadRecvQMI(PQCQMIMSG pResponse); +extern int QmiWwanInit(void); +extern int QmiWwanDeInit(void); +extern int QmiWwanSendQMI(PQCQMIMSG pRequest); +extern void * QmiWwanThread(void *pData); +extern int GobiNetSendQMI(PQCQMIMSG pRequest); +extern void * GobiNetThread(void *pData); +extern void udhcpc_start(PROFILE_T *profile); +extern void udhcpc_stop(PROFILE_T *profile); +extern void dump_qmi(void *dataBuffer, int dataLen); +extern void qmidevice_send_event_to_main(int triger_event); +extern int requestSetEthMode(PROFILE_T *profile); +extern int requestGetSIMStatus(SIM_Status *pSIMStatus); +extern int requestEnterSimPin(const CHAR *pPinCode); +extern int requestRegistrationState(UCHAR *pPSAttachedState); +extern int requestQueryDataCall(UCHAR *pConnectionStatus); +extern int requestSetupDataCall(PROFILE_T *profile); +extern int requestDeactivateDefaultPDP(void); +extern int requestSetProfile(PROFILE_T *profile); +extern int requestGetProfile(PROFILE_T *profile); +extern int requestBaseBandVersion(const char **pp_reversion); +extern int requestGetIPAddress(PROFILE_T *profile); + +extern FILE *logfilefp; +extern int debug_qmi; +extern char * qmichannel; +extern int qmidevice_control_fd[2]; +extern int qmiclientId[QMUX_TYPE_WDS_ADMIN + 1]; +extern int cdc_wdm_fd; +extern void dbg_time (const char *fmt, ...); +extern USHORT le16_to_cpu(USHORT v16); +extern UINT le32_to_cpu (UINT v32); +extern UINT ql_swap32(UINT v32); +extern USHORT cpu_to_le16(USHORT v16); +extern UINT cpu_to_le32(UINT v32); +#endif diff --git a/root/package/link4all/quectel-CM/src_fangge/QmiWwanCM.c b/root/package/link4all/quectel-CM/src_fangge/QmiWwanCM.c new file mode 100755 index 00000000..fc9bab06 --- /dev/null +++ b/root/package/link4all/quectel-CM/src_fangge/QmiWwanCM.c @@ -0,0 +1,274 @@ +#include +#include +#include +#include +#include +#include "QMIThread.h" + +#ifdef CONFIG_QMIWWAN +int cdc_wdm_fd = -1; +static UCHAR GetQCTLTransactionId(void) { + static int TransactionId = 0; + if (++TransactionId > 0xFF) + TransactionId = 1; + return TransactionId; +} + +typedef USHORT (*CUSTOMQCTL)(PQMICTL_MSG pCTLMsg, void *arg); + +static PQCQMIMSG ComposeQCTLMsg(USHORT QMICTLType, CUSTOMQCTL customQctlMsgFunction, void *arg) { + UCHAR QMIBuf[WDM_DEFAULT_BUFSIZE] = {0}; + PQCQMIMSG pRequest = (PQCQMIMSG)QMIBuf; + int Length; + + pRequest->QMIHdr.IFType = USB_CTL_MSG_TYPE_QMI; + pRequest->QMIHdr.CtlFlags = 0x00; + pRequest->QMIHdr.QMIType = QMUX_TYPE_CTL; + pRequest->QMIHdr.ClientId= 0x00; + + pRequest->CTLMsg.QMICTLMsgHdr.CtlFlags = QMICTL_FLAG_REQUEST; + pRequest->CTLMsg.QMICTLMsgHdr.TransactionId = GetQCTLTransactionId(); + pRequest->CTLMsg.QMICTLMsgHdr.QMICTLType = cpu_to_le16(QMICTLType); + if (customQctlMsgFunction) + pRequest->CTLMsg.QMICTLMsgHdr.Length = cpu_to_le16(customQctlMsgFunction(&pRequest->CTLMsg, arg) - sizeof(QCQMICTL_MSG_HDR)); + else + pRequest->CTLMsg.QMICTLMsgHdr.Length = cpu_to_le16(0x0000); + + pRequest->QMIHdr.Length = cpu_to_le16(le16_to_cpu(pRequest->CTLMsg.QMICTLMsgHdr.Length) + sizeof(QCQMICTL_MSG_HDR) + sizeof(QCQMI_HDR) - 1); + Length = le16_to_cpu(pRequest->QMIHdr.Length) + 1; + + pRequest = (PQCQMIMSG)malloc(Length); + if (pRequest == NULL) { + dbg_time("%s fail to malloc", __func__); + } else { + memcpy(pRequest, QMIBuf, Length); + } + + return pRequest; +} + +static USHORT CtlGetVersionReq(PQMICTL_MSG QCTLMsg, void *arg) { + QCTLMsg->GetVersionReq.TLVType = QCTLV_TYPE_REQUIRED_PARAMETER; + QCTLMsg->GetVersionReq.TLVLength = cpu_to_le16(0x0001); + QCTLMsg->GetVersionReq.QMUXTypes = QMUX_TYPE_ALL; + return sizeof(QMICTL_GET_VERSION_REQ_MSG); +} + +static USHORT CtlGetClientIdReq(PQMICTL_MSG QCTLMsg, void *arg) { + QCTLMsg->GetClientIdReq.TLVType = QCTLV_TYPE_REQUIRED_PARAMETER; + QCTLMsg->GetClientIdReq.TLVLength = cpu_to_le16(0x0001); + QCTLMsg->GetClientIdReq.QMIType = ((UCHAR *)arg)[0]; + return sizeof(QMICTL_GET_CLIENT_ID_REQ_MSG); +} + +static USHORT CtlReleaseClientIdReq(PQMICTL_MSG QCTLMsg, void *arg) { + QCTLMsg->ReleaseClientIdReq.TLVType = QCTLV_TYPE_REQUIRED_PARAMETER; + QCTLMsg->ReleaseClientIdReq.TLVLength = cpu_to_le16(0x0002); + QCTLMsg->ReleaseClientIdReq.QMIType = ((UCHAR *)arg)[0]; + QCTLMsg->ReleaseClientIdReq.ClientId = ((UCHAR *)arg)[1] ; + return sizeof(QMICTL_RELEASE_CLIENT_ID_REQ_MSG); +} + +int QmiWwanSendQMI(PQCQMIMSG pRequest) { + struct pollfd pollfds[]= {{cdc_wdm_fd, POLLOUT, 0}}; + int ret; + + if (cdc_wdm_fd == -1) { + dbg_time("%s cdc_wdm_fd = -1", __func__); + return -ENODEV; + } + + do { + ret = poll(pollfds, sizeof(pollfds)/sizeof(pollfds[0]), 5000); + } while ((ret < 0) && (errno == EINTR)); + + if (pollfds[0].revents & POLLOUT) { + ssize_t nwrites = le16_to_cpu(pRequest->QMIHdr.Length) + 1; + ret = write(cdc_wdm_fd, pRequest, nwrites); + if (ret == nwrites) { + ret = 0; + } else { + dbg_time("%s write=%d, errno: %d (%s)", __func__, ret, errno, strerror(errno)); + } + } else { + dbg_time("%s poll=%d, revents = 0x%x, errno: %d (%s)", __func__, ret, pollfds[0].revents, errno, strerror(errno)); + } + + return ret; +} + +static int QmiWwanGetClientID(UCHAR QMIType) { + PQCQMIMSG pResponse; + + QmiThreadSendQMI(ComposeQCTLMsg(QMICTL_GET_CLIENT_ID_REQ, CtlGetClientIdReq, &QMIType), &pResponse); + + if (pResponse) { + USHORT QMUXResult = cpu_to_le16(pResponse->CTLMsg.QMICTLMsgHdrRsp.QMUXResult); // QMI_RESULT_SUCCESS + USHORT QMUXError = cpu_to_le16(pResponse->CTLMsg.QMICTLMsgHdrRsp.QMUXError); // QMI_ERR_INVALID_ARG + //UCHAR QMIType = pResponse->CTLMsg.GetClientIdRsp.QMIType; + UCHAR ClientId = pResponse->CTLMsg.GetClientIdRsp.ClientId; + + if (!QMUXResult && !QMUXError && (QMIType == pResponse->CTLMsg.GetClientIdRsp.QMIType)) { + qmiclientId[QMIType] = ClientId; + switch (QMIType) { + case QMUX_TYPE_WDS: dbg_time("Get clientWDS = %d", ClientId); break; + case QMUX_TYPE_DMS: dbg_time("Get clientDMS = %d", ClientId); break; + case QMUX_TYPE_NAS: dbg_time("Get clientNAS = %d", ClientId); break; + case QMUX_TYPE_QOS: dbg_time("Get clientQOS = %d", ClientId); break; + case QMUX_TYPE_WMS: dbg_time("Get clientWMS = %d", ClientId); break; + case QMUX_TYPE_PDS: dbg_time("Get clientPDS = %d", ClientId); break; + case QMUX_TYPE_UIM: dbg_time("Get clientUIM = %d", ClientId); break; + case QMUX_TYPE_WDS_ADMIN: dbg_time("Get clientWDA = %d", ClientId); + break; + default: break; + } + } + } + return 0; +} + +static int QmiWwanReleaseClientID(QMI_SERVICE_TYPE QMIType, UCHAR ClientId) { + UCHAR argv[] = {QMIType, ClientId}; + QmiThreadSendQMI(ComposeQCTLMsg(QMICTL_RELEASE_CLIENT_ID_REQ, CtlReleaseClientIdReq, argv), NULL); + return 0; +} + +int QmiWwanInit(void) { + unsigned i; + int ret; + PQCQMIMSG pResponse; + + for (i = 0; i < 10; i++) { + ret = QmiThreadSendQMITimeout(ComposeQCTLMsg(QMICTL_SYNC_REQ, NULL, NULL), NULL, 1 * 1000); + if (!ret) + break; + sleep(1); + } + if (ret) + return ret; + + QmiThreadSendQMI(ComposeQCTLMsg(QMICTL_GET_VERSION_REQ, CtlGetVersionReq, NULL), &pResponse); + if (pResponse) free(pResponse); + QmiWwanGetClientID(QMUX_TYPE_WDS); + QmiWwanGetClientID(QMUX_TYPE_DMS); + QmiWwanGetClientID(QMUX_TYPE_NAS); + QmiWwanGetClientID(QMUX_TYPE_UIM); + QmiWwanGetClientID(QMUX_TYPE_WDS_ADMIN); + return 0; +} + +int QmiWwanDeInit(void) { + unsigned int i; + for (i = 0; i < sizeof(qmiclientId)/sizeof(qmiclientId[0]); i++) + { + if (qmiclientId[i] != 0) + { + QmiWwanReleaseClientID(i, qmiclientId[i]); + qmiclientId[i] = 0; + } + } + + return 0; +} + +void * QmiWwanThread(void *pData) { + const char *cdc_wdm = (const char *)pData; + cdc_wdm_fd = open(cdc_wdm, O_RDWR | O_NONBLOCK | O_NOCTTY); + if (cdc_wdm_fd == -1) { + dbg_time("%s Failed to open %s, errno: %d (%s)", __func__, cdc_wdm, errno, strerror(errno)); + qmidevice_send_event_to_main(RIL_INDICATE_DEVICE_DISCONNECTED); + pthread_exit(NULL); + return NULL; + } + dbg_time("cdc_wdm_fd = %d", cdc_wdm_fd); + + qmidevice_send_event_to_main(RIL_INDICATE_DEVICE_CONNECTED); + + while (1) { + struct pollfd pollfds[] = {{qmidevice_control_fd[1], POLLIN, 0}, {cdc_wdm_fd, POLLIN, 0}}; + int ne, ret, nevents = sizeof(pollfds)/sizeof(pollfds[0]); + + do { + ret = poll(pollfds, nevents, -1); + } while ((ret < 0) && (errno == EINTR)); + + if (ret <= 0) { + dbg_time("%s poll=%d, errno: %d (%s)", __func__, ret, errno, strerror(errno)); + break; + } + + for (ne = 0; ne < nevents; ne++) { + int fd = pollfds[ne].fd; + short revents = pollfds[ne].revents; + + //dbg_time("{%d, %x, %x}", pollfds[ne].fd, pollfds[ne].events, pollfds[ne].revents); + + if (revents & (POLLERR | POLLHUP | POLLNVAL)) { + dbg_time("%s poll err/hup/inval", __func__); + dbg_time("poll fd = %d, events = 0x%04x", fd, revents); + if (fd == cdc_wdm_fd) { + } else { + } + if (revents & (POLLHUP | POLLNVAL)) //EC20 bug, Can get POLLERR + goto __QmiWwanThread_quit; + } + + if ((revents & POLLIN) == 0) + continue; + + if (fd == qmidevice_control_fd[1]) { + int triger_event; + if (read(fd, &triger_event, sizeof(triger_event)) == sizeof(triger_event)) { + //DBG("triger_event = 0x%x", triger_event); + switch (triger_event) { + case RIL_REQUEST_QUIT: + goto __QmiWwanThread_quit; + break; + case SIGTERM: + case SIGHUP: + case SIGINT: + QmiThreadRecvQMI(NULL); + break; + default: + break; + } + } + } + + if (fd == cdc_wdm_fd) { + ssize_t nreads; + UCHAR QMIBuf[512]; + PQCQMIMSG pResponse = (PQCQMIMSG)QMIBuf; + + nreads = read(fd, QMIBuf, sizeof(QMIBuf)); + //dbg_time("%s read=%d errno: %d (%s)", __func__, (int)nreads, errno, strerror(errno)); + if (nreads <= 0) { + dbg_time("%s read=%d errno: %d (%s)", __func__, (int)nreads, errno, strerror(errno)); + break; + } + + if (nreads != (le16_to_cpu(pResponse->QMIHdr.Length) + 1)) { + dbg_time("%s nreads=%d, pQCQMI->QMIHdr.Length = %d", __func__, (int)nreads, le16_to_cpu(pResponse->QMIHdr.Length)); + continue; + } + + QmiThreadRecvQMI(pResponse); + } + } + } + +__QmiWwanThread_quit: + if (cdc_wdm_fd != -1) { close(cdc_wdm_fd); cdc_wdm_fd = -1; } + qmidevice_send_event_to_main(RIL_INDICATE_DEVICE_DISCONNECTED); + QmiThreadRecvQMI(NULL); //main thread may pending on QmiThreadSendQMI() + dbg_time("%s exit", __func__); + pthread_exit(NULL); + return NULL; +} + +#else +int QmiWwanSendQMI(PQCQMIMSG pRequest) {return -1;} +int QmiWwanInit(void) {return -1;} +int QmiWwanDeInit(void) {return -1;} +void * QmiWwanThread(void *pData) {dbg_time("please set CONFIG_QMIWWAN"); return NULL;} +#endif diff --git a/root/package/link4all/quectel-CM/src_fangge/dhcpclient.c b/root/package/link4all/quectel-CM/src_fangge/dhcpclient.c new file mode 100755 index 00000000..1b669c26 --- /dev/null +++ b/root/package/link4all/quectel-CM/src_fangge/dhcpclient.c @@ -0,0 +1,90 @@ +#ifdef ANDROID +/* + * Copyright 2008, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "QMIThread.h" +#ifdef USE_NDK +extern int (*ifc_init)(void); +extern void (*ifc_close)(void); +extern int (*do_dhcp)(const char *iname); +extern void (*get_dhcp_info)(uint32_t *ipaddr, uint32_t *gateway, uint32_t *prefixLength, + uint32_t *dns1, uint32_t *dns2, uint32_t *server, + uint32_t *lease); +extern int (*property_set)(const char *key, const char *value); +#else +#include +#include +extern int do_dhcp(const char *iname); +extern void get_dhcp_info(uint32_t *ipaddr, uint32_t *gateway, uint32_t *prefixLength, + uint32_t *dns1, uint32_t *dns2, uint32_t *server, + uint32_t *lease); +#endif + +static const char *ipaddr_to_string(in_addr_t addr) +{ + struct in_addr in_addr; + + in_addr.s_addr = addr; + return inet_ntoa(in_addr); +} + +void do_dhcp_request(PROFILE_T *profile) { +#ifdef USE_NDK + if (!ifc_init ||!ifc_close ||!do_dhcp || !get_dhcp_info || !property_set) { + return; + } +#endif + + char *ifname = profile->usbnet_adapter; + uint32_t ipaddr, gateway, prefixLength, dns1, dns2, server, lease; + char propKey[128]; + +#if 0 + if (profile->rawIP && ((profile->IPType==0x04 && profile->ipv4.Address))) + { + snprintf(propKey, sizeof(propKey), "net.%s.dns1", ifname); + property_set(propKey, profile->ipv4.DnsPrimary ? ipaddr_to_string(ql_swap32(profile->ipv4.DnsPrimary)) : "8.8.8.8"); + snprintf(propKey, sizeof(propKey), "net.%s.dns2", ifname); + property_set(propKey, profile->ipv4.DnsSecondary ? ipaddr_to_string(ql_swap32(profile->ipv4.DnsSecondary)) : "8.8.8.8"); + snprintf(propKey, sizeof(propKey), "net.%s.gw", ifname); + property_set(propKey, profile->ipv4.Gateway ? ipaddr_to_string(ql_swap32(profile->ipv4.Gateway)) : "0.0.0.0"); + return; + } +#endif + + if(ifc_init()) { + dbg_time("failed to ifc_init(%s): %s\n", ifname, strerror(errno)); + } + + if (do_dhcp(ifname) < 0) { + dbg_time("failed to do_dhcp(%s): %s\n", ifname, strerror(errno)); + } + + ifc_close(); + + get_dhcp_info(&ipaddr, &gateway, &prefixLength, &dns1, &dns2, &server, &lease); + snprintf(propKey, sizeof(propKey), "net.%s.gw", ifname); + property_set(propKey, gateway ? ipaddr_to_string(gateway) : "0.0.0.0"); +} +#endif diff --git a/root/package/link4all/quectel-CM/src_fangge/main.c b/root/package/link4all/quectel-CM/src_fangge/main.c new file mode 100755 index 00000000..c6f6282d --- /dev/null +++ b/root/package/link4all/quectel-CM/src_fangge/main.c @@ -0,0 +1,825 @@ +#include "QMIThread.h" +#include +#include +#include + +#define POLL_DATA_CALL_STATE_SECONDS 15 + +int debug_qmi = 0; +int main_loop = 0; +char * qmichannel; +int qmidevice_control_fd[2]; +static int signal_control_fd[2]; + +#ifdef CONFIG_BACKGROUND_WHEN_GET_IP +static int daemon_pipe_fd[2]; + +static void ql_prepare_daemon(void) { + pid_t daemon_child_pid; + + if (pipe(daemon_pipe_fd) < 0) { + dbg_time("%s Faild to create daemon_pipe_fd: %d (%s)", __func__, errno, strerror(errno)); + return; + } + + daemon_child_pid = fork(); + if (daemon_child_pid > 0) { + struct pollfd pollfds[] = {{daemon_pipe_fd[0], POLLIN, 0}, {0, POLLIN, 0}}; + int ne, ret, nevents = sizeof(pollfds)/sizeof(pollfds[0]); + int signo; + + //dbg_time("father"); + + close(daemon_pipe_fd[1]); + + if (socketpair( AF_LOCAL, SOCK_STREAM, 0, signal_control_fd) < 0 ) { + dbg_time("%s Faild to create main_control_fd: %d (%s)", __func__, errno, strerror(errno)); + return; + } + + pollfds[1].fd = signal_control_fd[1]; + + while (1) { + do { + ret = poll(pollfds, nevents, -1); + } while ((ret < 0) && (errno == EINTR)); + + if (ret < 0) { + dbg_time("%s poll=%d, errno: %d (%s)", __func__, ret, errno, strerror(errno)); + goto __daemon_quit; + } + + for (ne = 0; ne < nevents; ne++) { + int fd = pollfds[ne].fd; + short revents = pollfds[ne].revents; + + if (revents & (POLLERR | POLLHUP | POLLNVAL)) { + //dbg_time("%s poll err/hup", __func__); + //dbg_time("poll fd = %d, events = 0x%04x", fd, revents); + if (revents & POLLHUP) + goto __daemon_quit; + } + + if ((revents & POLLIN) && read(fd, &signo, sizeof(signo)) == sizeof(signo)) { + if (signal_control_fd[1] == fd) { + if (signo == SIGCHLD) { + int status; + int pid = waitpid(daemon_child_pid, &status, 0); + dbg_time("waitpid pid=%d, status=%x", pid, status); + goto __daemon_quit; + } else { + kill(daemon_child_pid, signo); + } + } else if (daemon_pipe_fd[0] == fd) { + //dbg_time("daemon_pipe_signo = %d", signo); + goto __daemon_quit; + } + } + } + } +__daemon_quit: + //dbg_time("father exit"); + _exit(0); + } else if (daemon_child_pid == 0) { + close(daemon_pipe_fd[0]); + //dbg_time("child", getpid()); + } else { + close(daemon_pipe_fd[0]); + close(daemon_pipe_fd[1]); + dbg_time("%s Faild to create daemon_child_pid: %d (%s)", __func__, errno, strerror(errno)); + } +} + +static void ql_enter_daemon(int signo) { + if (daemon_pipe_fd[1] > 0) + if (signo) { + write(daemon_pipe_fd[1], &signo, sizeof(signo)); + sleep(1); + } + close(daemon_pipe_fd[1]); + daemon_pipe_fd[1] = -1; + setsid(); + } +#endif + +UINT ifc_get_addr(const char *ifname); +static int s_link = -1; + + +#define MEIGE_CM_DEBUG + +#ifdef MEIGE_CM_DEBUG +#define usbnet_link_change(link, profile) usbnet_link_changeEx(link, profile, __FUNCTION__, __LINE__) + +static void usbnet_link_changeEx(int link, PROFILE_T *profile, char *fun, int li) { + printf("%s %d, link(%d -> %d)\r\n", fun, li, s_link, link); +#else +static void usbnet_link_change(int link, PROFILE_T *profile) { +#endif + + if (s_link == link) + return; + + s_link = link; + + if (link) + udhcpc_start(profile); + else + udhcpc_stop(profile); + +#ifdef LINUX_RIL_SHLIB + if (link) { + int timeout = 6; + while (timeout-- && ifc_get_addr(profile->usbnet_adapter) == 0) { + sleep(1); + } + } + + if (link && requestGetIPAddress(profile) == 0) { + unsigned char *r; + + dbg_time("Using interface %s", profile->usbnet_adapter); + r = (unsigned char *)&profile->ipv4.Address; + dbg_time("local IP address %d.%d.%d.%d", r[3], r[2], r[1], r[0]); + r = (unsigned char *)&profile->ipv4.Gateway; + dbg_time("remote IP address %d.%d.%d.%d", r[3], r[2], r[1], r[0]); + r = (unsigned char *)&profile->ipv4.DnsPrimary; + dbg_time("primary DNS address %d.%d.%d.%d", r[3], r[2], r[1], r[0]); + r = (unsigned char *)&profile->ipv4.DnsSecondary; + dbg_time("secondary DNS address %d.%d.%d.%d", r[3], r[2], r[1], r[0]); + } +#endif + +#ifdef CONFIG_BACKGROUND_WHEN_GET_IP + if (link && daemon_pipe_fd[1] > 0) { + int timeout = 6; + while (timeout-- && ifc_get_addr(profile->usbnet_adapter) == 0) { + sleep(1); + } + ql_enter_daemon(SIGUSR1); + } +#endif +} + +static int check_ipv4_address(PROFILE_T *profile) { + if (requestGetIPAddress(profile) == 0) { + UINT localIP = ifc_get_addr(profile->usbnet_adapter); + UINT remoteIP = ql_swap32(profile->ipv4.Address); + unsigned char *l = (unsigned char *)&localIP; + unsigned char *r = (unsigned char *)&remoteIP; + if (remoteIP != remoteIP || debug_qmi) + dbg_time("localIP: %d.%d.%d.%d VS remoteIP: %d.%d.%d.%d", + l[0], l[1], l[2], l[3], r[0], r[1], r[2], r[3]); + if (profile->IPType == 0x04) + return (localIP == remoteIP); + } + return 0; +} + +static void main_send_event_to_qmidevice(int triger_event) { + write(qmidevice_control_fd[0], &triger_event, sizeof(triger_event)); +} + +static void send_signo_to_main(int signo) { + write(signal_control_fd[0], &signo, sizeof(signo)); +} + +void qmidevice_send_event_to_main(int triger_event) { + write(qmidevice_control_fd[1], &triger_event, sizeof(triger_event)); +} + +#define MAX_PATH 256 + +static int ls_dir(const char *dir, int (*match)(const char *dir, const char *file, void *argv[]), void *argv[]) +{ + DIR *pDir; + struct dirent* ent = NULL; + int match_times = 0; + + pDir = opendir(dir); + if (pDir == NULL) { + dbg_time("Cannot open directory: %s, errno: %d (%s)", dir, errno, strerror(errno)); + return 0; + } + + while ((ent = readdir(pDir)) != NULL) { + match_times += match(dir, ent->d_name, argv); + } + closedir(pDir); + + return match_times; +} + +static int is_same_linkfile(const char *dir, const char *file, void *argv[]) +{ + const char *qmichannel = (const char *)argv[1]; + char linkname[MAX_PATH]; + char filename[MAX_PATH]; + int linksize; + + snprintf(linkname, MAX_PATH, "%s/%s", dir, file); + linksize = readlink(linkname, filename, MAX_PATH); + if (linksize <= 0) + return 0; + + filename[linksize] = 0; + if (strcmp(filename, qmichannel)) + return 0; + + dbg_time("%s -> %s", linkname, filename); + return 1; +} + +static int is_brother_process(const char *dir, const char *file, void *argv[]) +{ + //const char *myself = (const char *)argv[0]; + char linkname[MAX_PATH]; + char filename[MAX_PATH]; + int linksize; + int i = 0, kill_timeout = 15; + pid_t pid; + + //dbg_time("%s", file); + while (file[i]) { + if (!isdigit(file[i])) + break; + i++; + } + + if (file[i]) { + //dbg_time("%s not digit", file); + return 0; + } + + snprintf(linkname, MAX_PATH, "%s/%s/exe", dir, file); + linksize = readlink(linkname, filename, MAX_PATH); + if (linksize <= 0) + return 0; + + filename[linksize] = 0; +#if 0 //check all process + if (strcmp(filename, myself)) + return 0; +#endif + + pid = atoi(file); + if (pid == getpid()) + return 0; + + snprintf(linkname, MAX_PATH, "%s/%s/fd", dir, file); + if (!ls_dir(linkname, is_same_linkfile, argv)) + return 0; + + dbg_time("%s/%s/exe -> %s", dir, file, filename); + while (kill_timeout-- && !kill(pid, 0)) + { + kill(pid, SIGTERM); + sleep(1); + } + if (!kill(pid, 0)) + { + dbg_time("force kill %s/%s/exe -> %s", dir, file, filename); + kill(pid, SIGKILL); + sleep(1); + } + + return 1; +} + +static int kill_brothers(const char *qmichannel) +{ + char myself[MAX_PATH]; + int filenamesize; + void *argv[2] = {myself, (void *)qmichannel}; + + filenamesize = readlink("/proc/self/exe", myself, MAX_PATH); + if (filenamesize <= 0) + return 0; + myself[filenamesize] = 0; + + if (ls_dir("/proc", is_brother_process, argv)) + sleep(1); + + return 0; +} + +static void ql_sigaction(int signo) { + if (SIGCHLD == signo) + waitpid(-1, NULL, WNOHANG); + else if (SIGALRM == signo) + send_signo_to_main(SIGUSR1); + else + { + if (SIGTERM == signo || SIGHUP == signo || SIGINT == signo) + main_loop = 0; + send_signo_to_main(signo); + main_send_event_to_qmidevice(signo); //main may be wating qmi response + } +} + +pthread_t gQmiThreadID; + +static int usage(const char *progname) { + dbg_time("Usage: %s [-s [apn [user password auth]]] [-p pincode] [-f logfilename] ", progname); + dbg_time("-s [apn [user password auth]] Set apn/user/password/auth get from your network provider"); + dbg_time("-p pincode Verify sim card pin if sim card is locked"); + dbg_time("-f logfilename Save log message of this program to file"); + dbg_time("Example 1: %s ", progname); + dbg_time("Example 2: %s -s 3gnet ", progname); + dbg_time("Example 3: %s -s 3gnet carl 1234 0 -p 1234 -f gobinet_log.txt", progname); + return 0; +} + +static int qmidevice_detect(char **pp_qmichannel, char **pp_usbnet_adapter) { + struct dirent* ent = NULL; + DIR *pDir; +#ifndef ANDROID + int osmaj, osmin, ospatch; + static struct utsname utsname; /* for the kernel version */ + static int kernel_version; +#define KVERSION(j,n,p) ((j)*1000000 + (n)*1000 + (p)) + + /* get the kernel version now, since we are called before sys_init */ + uname(&utsname); + osmaj = osmin = ospatch = 0; + sscanf(utsname.release, "%d.%d.%d", &osmaj, &osmin, &ospatch); + kernel_version = KVERSION(osmaj, osmin, ospatch); +#endif + + if ((pDir = opendir("/dev")) == NULL) { + dbg_time("Cannot open directory: %s, errno:%d (%s)", "/dev", errno, strerror(errno)); + return -ENODEV; + } + + while ((ent = readdir(pDir)) != NULL) { + if ((strncmp(ent->d_name, "cdc-wdm", strlen("cdc-wdm")) == 0) || (strncmp(ent->d_name, "qcqmi0", strlen("qcqmi0")) == 0)) { + char net_path[512]; + + *pp_qmichannel = (char *)malloc(32); + sprintf(*pp_qmichannel, "/dev/%s", ent->d_name); + dbg_time("Find qmichannel = %s", *pp_qmichannel); + + if (strncmp(ent->d_name, "cdc-wdm", strlen("cdc-wdm")) == 0) + sprintf(net_path, "/sys/class/net/wwan%s", &ent->d_name[strlen("cdc-wdm")]); + else + { + // sprintf(net_path, "/sys/class/net/eth%s", "0"); + + + #ifndef ANDROID + if (kernel_version >= KVERSION( 2,6,39 )) + sprintf(net_path, "/sys/class/net/eth%d", 0); + else + sprintf(net_path, "/sys/class/net/usb%d", 0); + #else + if (access(net_path, R_OK) && errno == ENOENT) + //sprintf(net_path, "/sys/class/net/eth%s", &ent->d_name[strlen("qcqmi")]); + sprintf(net_path, "/sys/class/net/usb%s", "0"); + #endif + } + + if (access(net_path, R_OK) == 0) + { + if (*pp_usbnet_adapter && strcmp(*pp_usbnet_adapter, (net_path + strlen("/sys/class/net/")))) + { + free(*pp_qmichannel); *pp_qmichannel = NULL; + continue; + } + *pp_usbnet_adapter = strdup(net_path + strlen("/sys/class/net/")); + dbg_time("Find usbnet_adapter = %s", *pp_usbnet_adapter); + break; + } + else + { + dbg_time("Failed to access %s, errno:%d (%s)", net_path, errno, strerror(errno)); + free(*pp_qmichannel); *pp_qmichannel = NULL; + } + } + } + closedir(pDir); + + return (*pp_qmichannel && *pp_usbnet_adapter); +} + +#if defined(ANDROID) || defined(LINUX_RIL_SHLIB) +int meige_proc(int argc, char *argv[]) +#else +int main(int argc, char *argv[]) +#endif +{ + int triger_event = 0; + int opt = 1; + int signo; +#ifdef CONFIG_SIM + SIM_Status SIMStatus; +#endif + UCHAR PSAttachedState; + UCHAR ConnectionStatus = 0xff; //unknow state + char * save_usbnet_adapter = NULL; + PROFILE_T profile; + int slient_seconds = 0; + + memset(&profile, 0x00, sizeof(profile)); +#if CONFIG_DEFAULT_PDP + profile.pdp = CONFIG_DEFAULT_PDP; +#else + profile.pdp = 1; +#endif + profile.IPType = 0x04; //ipv4 first + + { + FILE *meige_fs = NULL; + if((meige_fs=fopen("/var/meige_proc", "w+")) != NULL){ + fprintf(meige_fs, "%d\n", getpid()); + fclose(meige_fs); + } + } + + if (!strcmp(argv[argc-1], "&")) + argc--; + + opt = 1; + while (opt < argc) + { + if (argv[opt][0] != '-') + return usage(argv[0]); + + switch (argv[opt++][1]) + { +#define has_more_argv() ((opt < argc) && (argv[opt][0] != '-')) + case 's': + profile.apn = profile.user = profile.password = ""; + if (has_more_argv()) + profile.apn = argv[opt++]; + if (has_more_argv()) + profile.user = argv[opt++]; + if (has_more_argv()) + { + profile.password = argv[opt++]; + if (profile.password && profile.password[0]) + profile.auth = 2; //default chap, customers may miss auth + } + if (has_more_argv()) + profile.auth = argv[opt++][0] - '0'; + break; + + case 'p': + if (has_more_argv()) + profile.pincode = argv[opt++]; + break; + + case 'n': + if (has_more_argv()) + profile.pdp = argv[opt++][0] - '0'; + break; + + case 'f': + if (has_more_argv()) + { + const char * filename = argv[opt++]; + logfilefp = fopen(filename, "a+"); + if (!logfilefp) { + dbg_time("Fail to open %s, errno: %d(%s)", filename, errno, strerror(errno)); + } + } + break; + + case 'i': + if (has_more_argv()) + profile.usbnet_adapter = save_usbnet_adapter = argv[opt++]; + break; + + case 'v': + debug_qmi = 1; + break; + + case 'l': + main_loop = 1; + break; + + default: + return usage(argv[0]); + break; + } + } + + dbg_time("meige modem init start"); + dbg_time("%s profile[%d] = %s/%s/%s/%d, pincode = %s", argv[0], profile.pdp, profile.apn, profile.user, profile.password, profile.auth, profile.pincode); + + signal(SIGUSR1, ql_sigaction); + signal(SIGUSR2, ql_sigaction); + signal(SIGINT, ql_sigaction); + signal(SIGTERM, ql_sigaction); + signal(SIGHUP, ql_sigaction); + signal(SIGCHLD, ql_sigaction); + signal(SIGALRM, ql_sigaction); + +#ifdef CONFIG_BACKGROUND_WHEN_GET_IP + ql_prepare_daemon(); +#endif + + if (socketpair( AF_LOCAL, SOCK_STREAM, 0, signal_control_fd) < 0 ) { + dbg_time("%s Faild to create main_control_fd: %d (%s)", __func__, errno, strerror(errno)); + return -1; + } + + if ( socketpair( AF_LOCAL, SOCK_STREAM, 0, qmidevice_control_fd ) < 0 ) { + dbg_time("%s Failed to create thread control socket pair: %d (%s)", __func__, errno, strerror(errno)); + return 0; + } + +//sudo apt-get install udhcpc +//sudo apt-get remove ModemManager +__main_loop: + while (!profile.qmichannel) + { + if (qmidevice_detect(&profile.qmichannel, &profile.usbnet_adapter)) + break; + if (main_loop) + { + int wait_for_device = 3000; + dbg_time("Wait for meige modules connect"); + while (wait_for_device && main_loop) { + wait_for_device -= 100; + usleep(100*1000); + } + continue; + } + dbg_time("Cannot find qmichannel(%s) usbnet_adapter(%s) for meige modules", profile.qmichannel, profile.usbnet_adapter); + return -ENODEV; + } + + if (access(profile.qmichannel, R_OK | W_OK)) { + dbg_time("Fail to access %s, errno: %d (%s)", profile.qmichannel, errno, strerror(errno)); + return errno; + } + +#if 0 //for test only, make fd > 255 +{ + int max_dup = 255; + while (max_dup--) + dup(0); +} +#endif + + kill_brothers(profile.qmichannel); + + qmichannel = profile.qmichannel; + if (!strncmp(profile.qmichannel, "/dev/qcqmi0", strlen("/dev/qcqmi0"))) + { + if (pthread_create( &gQmiThreadID, 0, GobiNetThread, (void *)profile.qmichannel) != 0) + { + dbg_time("%s Failed to create GobiNetThread: %d (%s)", __func__, errno, strerror(errno)); + return 0; + } + } + else + { + if (pthread_create( &gQmiThreadID, 0, QmiWwanThread, (void *)profile.qmichannel) != 0) + { + dbg_time("%s Failed to create QmiWwanThread: %d (%s)", __func__, errno, strerror(errno)); + return 0; + } + } + + if ((read(qmidevice_control_fd[0], &triger_event, sizeof(triger_event)) != sizeof(triger_event)) + || (triger_event != RIL_INDICATE_DEVICE_CONNECTED)) { + dbg_time("%s Failed to init QMIThread: %d (%s)", __func__, errno, strerror(errno)); + return 0; + } + + if (!strncmp(profile.qmichannel, "/dev/cdc-wdm", strlen("/dev/cdc-wdm"))) { + if (QmiWwanInit()) { + dbg_time("%s Failed to QmiWwanInit: %d (%s)", __func__, errno, strerror(errno)); + return 0; + } + } + +#ifdef CONFIG_VERSION + requestBaseBandVersion(NULL); +#endif + requestSetEthMode(&profile); + if (profile.rawIP && !strncmp(profile.qmichannel, "/dev/cdc-wdm", strlen("/dev/cdc-wdm"))) { + char raw_ip_switch[128] = {0}; + sprintf(raw_ip_switch, "/sys/class/net/%s/qmi/raw_ip", profile.usbnet_adapter); + if (!access(raw_ip_switch, R_OK)) { + int raw_ip_fd = -1; + raw_ip_fd = open(raw_ip_switch, O_RDWR); + if (raw_ip_fd >= 0) { + write(raw_ip_fd, "1", strlen("1")); + close(raw_ip_fd); + raw_ip_fd = -1; + } else { + dbg_time("open %s failed, errno = %d(%s)\n", raw_ip_switch, errno, strerror(errno)); + } + } + } +#ifdef CONFIG_SIM + requestGetSIMStatus(&SIMStatus); + if ((SIMStatus == SIM_PIN) && profile.pincode) { + requestEnterSimPin(profile.pincode); + } +#endif +#ifdef CONFIG_APN + if (profile.apn || profile.user || profile.password) { + requestSetProfile(&profile); + } + requestGetProfile(&profile); +#endif + requestRegistrationState(&PSAttachedState); + + if (!requestQueryDataCall(&ConnectionStatus) && (QWDS_PKT_DATA_CONNECTED == ConnectionStatus)) + usbnet_link_change(1, &profile); + else + usbnet_link_change(0, &profile); + + send_signo_to_main(SIGUSR1); + +#ifdef CONFIG_PID_FILE_FORMAT + { + char cmd[255]; + sprintf(cmd, "echo %d > " CONFIG_PID_FILE_FORMAT, getpid(), profile.usbnet_adapter); + system(cmd); + } +#endif + + while (1) + { + struct pollfd pollfds[] = {{signal_control_fd[1], POLLIN, 0}, {qmidevice_control_fd[0], POLLIN, 0}}; + int ne, ret, nevents = sizeof(pollfds)/sizeof(pollfds[0]); + + do { + ret = poll(pollfds, nevents, (ConnectionStatus == QWDS_PKT_DATA_CONNECTED) ? 1000 : -1); + } while ((ret < 0) && (errno == EINTR)); + + if (ret == 0) + { + #if POLL_DATA_CALL_STATE_SECONDS + if (++slient_seconds >= POLL_DATA_CALL_STATE_SECONDS) + send_signo_to_main(SIGUSR2); + #endif + continue; + } + slient_seconds = 0; + + if (ret <= 0) { + dbg_time("%s poll=%d, errno: %d (%s)", __func__, ret, errno, strerror(errno)); + goto __main_quit; + } + + for (ne = 0; ne < nevents; ne++) { + int fd = pollfds[ne].fd; + short revents = pollfds[ne].revents; + + if (revents & (POLLERR | POLLHUP | POLLNVAL)) { + dbg_time("%s poll err/hup", __func__); + dbg_time("epoll fd = %d, events = 0x%04x", fd, revents); + main_send_event_to_qmidevice(RIL_REQUEST_QUIT); + if (revents & POLLHUP) + goto __main_quit; + } + + if ((revents & POLLIN) == 0) + continue; + + if (fd == signal_control_fd[1]) + { + if (read(fd, &signo, sizeof(signo)) == sizeof(signo)) + { + alarm(0); + switch (signo) + { + case SIGUSR1: + requestQueryDataCall(&ConnectionStatus); + if (QWDS_PKT_DATA_CONNECTED != ConnectionStatus) + { + usbnet_link_change(0, &profile); + requestRegistrationState(&PSAttachedState); + if (PSAttachedState == 1 && requestSetupDataCall(&profile) == 0) + { + //succssful setup data call + } + else + { +#ifdef CONFIG_EXIT_WHEN_DIAL_FAILED + kill(getpid(), SIGTERM); +#endif + alarm(5); //try to setup data call 5 seconds later + } + } + break; + + case SIGUSR2: + if (QWDS_PKT_DATA_CONNECTED == ConnectionStatus) + { + requestQueryDataCall(&ConnectionStatus); + } + if (QWDS_PKT_DATA_CONNECTED != ConnectionStatus || check_ipv4_address(&profile) == 0) //local ip is different with remote ip + { + requestDeactivateDefaultPDP(); + #if defined(ANDROID) || defined(LINUX_RIL_SHLIB) + kill(getpid(), SIGTERM); //android will setup data call again + #else + send_signo_to_main(SIGUSR1); + #endif + } + break; + + case SIGTERM: + case SIGHUP: + case SIGINT: + if (QWDS_PKT_DATA_CONNECTED == ConnectionStatus) + requestDeactivateDefaultPDP(); + usbnet_link_change(0, &profile); + if (!strncmp(profile.qmichannel, "/dev/cdc-wdm", strlen("/dev/cdc-wdm"))) + QmiWwanDeInit(); + main_send_event_to_qmidevice(RIL_REQUEST_QUIT); + goto __main_quit; + break; + + default: + break; + } + } + } + + if (fd == qmidevice_control_fd[0]) { + if (read(fd, &triger_event, sizeof(triger_event)) == sizeof(triger_event)) { + switch (triger_event) { + case RIL_INDICATE_DEVICE_DISCONNECTED: + usbnet_link_change(0, &profile); + if (main_loop) + { + if (pthread_join(gQmiThreadID, NULL)) { + dbg_time("%s Error joining to listener thread (%s)", __func__, strerror(errno)); + } + profile.qmichannel = NULL; + profile.usbnet_adapter = save_usbnet_adapter; + goto __main_loop; + } + goto __main_quit; + break; + + case RIL_UNSOL_RESPONSE_VOICE_NETWORK_STATE_CHANGED: + requestRegistrationState(&PSAttachedState); + if (PSAttachedState == 1 && QWDS_PKT_DATA_DISCONNECTED == ConnectionStatus) + send_signo_to_main(SIGUSR1); + break; + + case RIL_UNSOL_DATA_CALL_LIST_CHANGED: + { + #if defined(ANDROID) || defined(LINUX_RIL_SHLIB) + UCHAR oldConnectionStatus = ConnectionStatus; + #endif + requestQueryDataCall(&ConnectionStatus); + if (QWDS_PKT_DATA_CONNECTED != ConnectionStatus) + { + usbnet_link_change(0, &profile); + #if defined(ANDROID) || defined(LINUX_RIL_SHLIB) + if (oldConnectionStatus == QWDS_PKT_DATA_CONNECTED) //connected change to disconnect + kill(getpid(), SIGTERM); //android will setup data call again + #else + send_signo_to_main(SIGUSR1); + #endif + } else if (QWDS_PKT_DATA_CONNECTED == ConnectionStatus) { + requestGetIPAddress(&profile); + usbnet_link_change(1, &profile); + } + } + break; + + default: + break; + } + } + } + } + } + +__main_quit: + usbnet_link_change(0, &profile); + if (pthread_join(gQmiThreadID, NULL)) { + dbg_time("%s Error joining to listener thread (%s)", __func__, strerror(errno)); + } + close(signal_control_fd[0]); + close(signal_control_fd[1]); + close(qmidevice_control_fd[0]); + close(qmidevice_control_fd[1]); + dbg_time("%s exit", __func__); + if (logfilefp) + fclose(logfilefp); + +#ifdef CONFIG_PID_FILE_FORMAT + { + char cmd[255]; + sprintf(cmd, "rm " CONFIG_PID_FILE_FORMAT, profile.usbnet_adapter); + system(cmd); + } +#endif + + if(access("/var/meige_proc", F_OK) == 0){ + unlink("/var/meige_proc"); + } + + return 0; +} diff --git a/root/package/link4all/quectel-CM/src_fangge/udhcpc.c b/root/package/link4all/quectel-CM/src_fangge/udhcpc.c new file mode 100755 index 00000000..cbde8e0d --- /dev/null +++ b/root/package/link4all/quectel-CM/src_fangge/udhcpc.c @@ -0,0 +1,415 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include "QMIThread.h" + +static int ifc_ctl_sock = -1; +static int do_kill_first = 1; + +static int ifc_init(void) +{ + int ret; + if (ifc_ctl_sock == -1) { + ifc_ctl_sock = socket(AF_INET, SOCK_DGRAM, 0); + if (ifc_ctl_sock < 0) { + dbg_time("socket() failed: %s\n", strerror(errno)); + } + } + + ret = ifc_ctl_sock < 0 ? -1 : 0; + return ret; +} + +static void ifc_close(void) +{ + if (ifc_ctl_sock != -1) { + (void)close(ifc_ctl_sock); + ifc_ctl_sock = -1; + } +} + +static void ifc_init_ifr(const char *name, struct ifreq *ifr) +{ + memset(ifr, 0, sizeof(struct ifreq)); + strncpy(ifr->ifr_name, name, IFNAMSIZ); + ifr->ifr_name[IFNAMSIZ - 1] = 0; +} + +static int ifc_set_flags(const char *name, unsigned set, unsigned clr) +{ + struct ifreq ifr; + ifc_init_ifr(name, &ifr); + + if(ioctl(ifc_ctl_sock, SIOCGIFFLAGS, &ifr) < 0) return -1; + ifr.ifr_flags = (ifr.ifr_flags & (~clr)) | set; + return ioctl(ifc_ctl_sock, SIOCSIFFLAGS, &ifr); +} + +static int ifc_up(const char *name, int rawIP) +{ + int ret = ifc_set_flags(name, IFF_UP | (rawIP ? IFF_NOARP : 0), 0); + return ret; +} + +static int ifc_down(const char *name) +{ + int ret = ifc_set_flags(name, 0, IFF_UP); + return ret; +} + +static void init_sockaddr_in(struct sockaddr *sa, in_addr_t addr) +{ + struct sockaddr_in *sin = (struct sockaddr_in *) sa; + sin->sin_family = AF_INET; + sin->sin_port = 0; + sin->sin_addr.s_addr = addr; +} + +static int ifc_set_addr(const char *name, in_addr_t addr) +{ + struct ifreq ifr; + int ret; + + ifc_init_ifr(name, &ifr); + init_sockaddr_in(&ifr.ifr_addr, addr); + + ret = ioctl(ifc_ctl_sock, SIOCSIFADDR, &ifr); + return ret; +} + +static pthread_attr_t udhcpc_thread_attr; +static pthread_t udhcpc_thread_id; + +#ifdef ANDROID +void do_dhcp_request(PROFILE_T *profile); +static void* udhcpc_thread_function(void* arg) { + do_dhcp_request((PROFILE_T *)arg); + return NULL; +} +#else +#define USE_DHCLIENT +static pid_t udhcpc_pid = 0; +static FILE * ql_popen(const char *program, const char *type) +{ + FILE *iop; + int pdes[2]; + pid_t pid; + char *argv[20]; + int argc = 0; + char *dup_program = strdup(program); + char *pos = dup_program; + + while (*pos != '\0') + { + while (isblank(*pos)) *pos++ = '\0'; + if (*pos != '\0') + { + argv[argc++] = pos; + while (*pos != '\0' && !isblank(*pos)) pos++; + //dbg_time("argv[%d] = %s", argc-1, argv[argc-1]); + } + } + argv[argc++] = NULL; + + if (pipe(pdes) < 0) { + return (NULL); + } + + switch (pid = fork()) { + case -1: /* Error. */ + (void)close(pdes[0]); + (void)close(pdes[1]); + return (NULL); + /* NOTREACHED */ + case 0: /* Child. */ + { + if (*type == 'r') { + (void) close(pdes[0]); + if (pdes[1] != STDOUT_FILENO) { + (void)dup2(pdes[1], STDOUT_FILENO); + (void)close(pdes[1]); + } + } else { + (void)close(pdes[1]); + if (pdes[0] != STDIN_FILENO) { + (void)dup2(pdes[0], STDIN_FILENO); + (void)close(pdes[0]); + } + } +#if 1 //child do not use these fds + if (cdc_wdm_fd > 0) { + close(cdc_wdm_fd); + } else { + unsigned int i; + for (i = 0; i < sizeof(qmiclientId)/sizeof(qmiclientId[0]); i++) { + if (qmiclientId[i] != 0) + close(qmiclientId[i]); + } + } +#endif + execvp(argv[0], argv); + _exit(127); + /* NOTREACHED */ + } + break; + default: + udhcpc_pid = pid; + free(dup_program); + break; + } + + /* Parent; assume fdopen can't fail. */ + if (*type == 'r') { + iop = fdopen(pdes[0], type); + (void)close(pdes[1]); + } else { + iop = fdopen(pdes[1], type); + (void)close(pdes[0]); + } + + return (iop); +} + +static int ql_pclose(FILE *iop) +{ + (void)fclose(iop); + udhcpc_pid = 0; + return 0; +} + +static void* udhcpc_thread_function(void* arg) { + FILE * udhcpc_fp; + char udhcpc_cmd[128]; + PROFILE_T *profile = (PROFILE_T *)arg; + char *ifname = profile->usbnet_adapter; + int need_add_default_route = 0; + +#ifdef USE_DHCLIENT + snprintf(udhcpc_cmd, sizeof(udhcpc_cmd), "dhclient -%d -d --no-pid %s", profile->IPType, (char *)ifname); +#else + if (profile->IPType == 0x04) + { + // if (access("/usr/share/udhcpc/default.script", X_OK)) { + // dbg_time("Fail to access /usr/share/udhcpc/default.script, errno: %d (%s)", errno, strerror(errno)); + // } + + //-f,--foreground Run in foreground + //-b,--background Background if lease is not obtained + //-n,--now Exit if lease is not obtained + //-q,--quit Exit after obtaining lease + //-t,--retries N Send up to N discover packets (default 3) + snprintf(udhcpc_cmd, sizeof(udhcpc_cmd), "%s", "getip -i lte0 -s /etc/dhcp.bound -p /var/dhcp.pid"); + } + else + { + /* + DHCPv6: Dibbler - a portable DHCPv6 + 1. download from http://klub.com.pl/dhcpv6/ + 2. cross-compile + 2.1 ./configure --host=arm-linux-gnueabihf + 2.2 copy dibbler-client to your board + 3. mkdir -p /var/log/dibbler/ /var/lib/ on your board + 4. create /etc/dibbler/client.conf on your board, the content is + log-mode short + log-level 7 + iface wwan0 { + ia + option dns-server + } + 5. run "dibbler-client start" to get ipV6 address + 6. run "route -A inet6 add default dev wwan0" to add default route + */ + need_add_default_route = 1; + snprintf(udhcpc_cmd, sizeof(udhcpc_cmd), "dibbler-client run"); + } +#endif + + udhcpc_fp = ql_popen(udhcpc_cmd, "r"); + if (udhcpc_fp) { + char buf[0xff]; + + if (need_add_default_route) + { + snprintf(buf, sizeof(buf), "route %s add default %s", (profile->IPType == 0x04) ? "" : "-A inet6", ifname); + system(buf); + } + + while((fgets(buf, sizeof(buf), udhcpc_fp)) != NULL) { + if ((strlen(buf) > 1) && (buf[strlen(buf) - 1] == '\n')) + buf[strlen(buf) - 1] = '\0'; + dbg_time("%s", buf); + } + ql_pclose(udhcpc_fp); + } + + return NULL; +} +#endif + +void udhcpc_start(PROFILE_T *profile) { + const char *ifname = profile->usbnet_adapter; + + /* start ǰÏȵ÷ÓÃstop£¬È·±£Ô­À´µÄdhcpc½ø³ÌÍ˳ö*/ + if(do_kill_first)udhcpc_stop(profile); + +//because must use udhcpc to obtain IP when working on ETH mode, +//so it is better also use udhcpc to obtain IP when working on IP mode. +//use the same policy for all modules +#if 0 +#ifndef ANDROID + if (profile->rawIP && ((profile->IPType==0x04 && profile->ipv4.Address))) + { + char shell_cmd[128]; + unsigned char *ip = (unsigned char *)&profile->ipv4.Address; + unsigned char *gw = (unsigned char *)&profile->ipv4.Gateway; + unsigned char *netmask = (unsigned char *)&profile->ipv4.SubnetMask; + unsigned char *dns1 = (unsigned char *)&profile->ipv4.DnsPrimary; + unsigned char *dns2 = (unsigned char *)&profile->ipv4.DnsSecondary; + + snprintf(shell_cmd, sizeof(shell_cmd), "ifconfig %s %d.%d.%d.%d netmask %d.%d.%d.%d",ifname, + ip[3], ip[2], ip[1], ip[0], netmask[3], netmask[2], netmask[1], netmask[0]); + dbg_time("%s", shell_cmd); + system(shell_cmd); + + //Resetting default routes + snprintf(shell_cmd, sizeof(shell_cmd), "route del default gw 0.0.0.0 dev %s", ifname); + while(!system(shell_cmd)); + + snprintf(shell_cmd, sizeof(shell_cmd), "route add default gw %d.%d.%d.%d dev %s metric 0", + gw[3], gw[2], gw[1], gw[0], ifname); + dbg_time("%s", shell_cmd); + system(shell_cmd); + + #ifdef ANDROID + do_dhcp_request(profile); + return; + #endif + + //Adding DNS + if (profile->ipv4.DnsSecondary == 0) + profile->ipv4.DnsSecondary = profile->ipv4.DnsPrimary; + if (dns1[0]) + { + dbg_time("Adding DNS %d.%d.%d.%d %d.%d.%d.%d", dns1[3], dns1[2], dns1[1], dns1[0], dns2[3], dns2[2], dns2[1], dns2[0]); + snprintf(shell_cmd, sizeof(shell_cmd), "echo -n \"nameserver %d.%d.%d.%d\nnameserver %d.%d.%d.%d\n\" > /etc/resolv.conf", + dns1[3], dns1[2], dns1[1], dns1[0], dns2[3], dns2[2], dns2[1], dns2[0]); + system(shell_cmd); + } + + return; + } +#endif +#endif + + + ifc_init(); + if (ifc_set_addr(profile->usbnet_adapter, 0)) { + dbg_time("failed to set ip addr for %s to 0.0.0.0: %s\n", ifname, strerror(errno)); + return; + } + + if (ifc_up(profile->usbnet_adapter, profile->rawIP)) { + dbg_time("failed to bring up interface %s: %s\n", ifname, strerror(errno)); + return; + } + ifc_close(); + + pthread_attr_init(&udhcpc_thread_attr); + pthread_attr_setdetachstate(&udhcpc_thread_attr, PTHREAD_CREATE_DETACHED); + if(pthread_create(&udhcpc_thread_id, &udhcpc_thread_attr, udhcpc_thread_function, (void*)profile) !=0 ) { + dbg_time("failed to create udhcpc_thread for %s: %s\n", ifname, strerror(errno)); + } + pthread_attr_destroy(&udhcpc_thread_attr); + do_kill_first = 1; +} + +static int getDhcpcPid(void) +{ + int pid = 0; + char str[64] = {0}; + FILE* fs = NULL; + + fs = fopen("/var/dhcp.pid", "r"); + if ( fs != NULL ) + { + if ( fgets(str, 64, fs) > 0 ) + { + pid = atoi(str); + } + fclose(fs); + } + + return pid; +} + + +void udhcpc_stop(PROFILE_T *profile) { + const char *ifname = profile->usbnet_adapter; + do_kill_first = 0; + system("3g-mngr ipdown"); + sleep(1); + +#ifdef ANDROID +#else + pid_t pid = getDhcpcPid(); + dbg_time("%s kill %d/%d", __func__, pid, getpid()); + #if 0 + if (pid > 0 && !kill(pid, 0)) + { + int kill_time = 50; + do { + kill(pid, SIGTERM); + usleep(100*1000); + } while(kill_time-- && !kill(pid, 0)); //wait udhcpc quit + if (!kill(pid, 0)) + kill(pid, SIGKILL); + dbg_time("%s kill udhcpc_pid time=%d", __func__, (50 - kill_time) * 100); + } + #else + if(pid > 0){ + int kill_time = 20; + + kill(pid, SIGTERM); + + do { + usleep(100*1000); + } while((kill_time--) > 0 && udhcpc_pid != 0); //wait udhcpc quit + } + + unlink("/var/dhcp.pid"); //if kill is ok, just remove the file + #endif +#endif + ifc_init(); + ifc_set_addr(ifname, 0); + ifc_down(ifname); + ifc_close(); +} + +UINT ifc_get_addr(const char *ifname) { + int inet_sock; + struct ifreq ifr; + UINT addr = 0; + + inet_sock = socket(AF_INET, SOCK_DGRAM, 0); + strcpy(ifr.ifr_name, ifname); + + if (ioctl(inet_sock, SIOCGIFADDR, &ifr) < 0) { + goto error; + } + addr = ((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr.s_addr; +error: + close(inet_sock); + + return addr; +} diff --git a/root/package/link4all/quectel-CM/src_fangge/util.c b/root/package/link4all/quectel-CM/src_fangge/util.c new file mode 100755 index 00000000..baa76c26 --- /dev/null +++ b/root/package/link4all/quectel-CM/src_fangge/util.c @@ -0,0 +1,160 @@ +#include "QMIThread.h" +#include + +#if defined(__STDC__) +#include +#define __V(x) x +#else +#include +#define __V(x) (va_alist) va_dcl +#define const +#define volatile +#endif + +#ifdef ANDROID +#define LOG_TAG "NDIS" +#include "../ql-log.h" +#else +#include +#endif + +int debug = 0; + +#ifndef ANDROID //defined in atchannel.c +static void setTimespecRelative(struct timespec *p_ts, long long msec) +{ + struct timeval tv; + + gettimeofday(&tv, (struct timezone *) NULL); + + /* what's really funny about this is that I know + pthread_cond_timedwait just turns around and makes this + a relative time again */ + p_ts->tv_sec = tv.tv_sec + (msec / 1000); + p_ts->tv_nsec = (tv.tv_usec + (msec % 1000) * 1000L ) * 1000L; +} + +int pthread_cond_timeout_np(pthread_cond_t *cond, + pthread_mutex_t * mutex, + unsigned msecs) { + if (msecs != 0) { + struct timespec ts; + setTimespecRelative(&ts, msecs); + return pthread_cond_timedwait(cond, mutex, &ts); + } else { + return pthread_cond_wait(cond, mutex); + } +} +#endif + +static const char * get_time(void) { + static char time_buf[68] = {0}; + struct timeval tv; + time_t time; + suseconds_t millitm; + struct tm *ti; + + gettimeofday (&tv, NULL); + + time= tv.tv_sec; + millitm = (tv.tv_usec + 500) / 1000; + + if (millitm == 1000) { + ++time; + millitm = 0; + } + + ti = localtime(&time); + sprintf(time_buf, "[%02d-%02d_%02d:%02d:%02d:%03d]", ti->tm_mon+1, ti->tm_mday, ti->tm_hour, ti->tm_min, ti->tm_sec, (int)millitm); + return time_buf; +} + +FILE *logfilefp = NULL; +static pthread_mutex_t printfMutex = PTHREAD_MUTEX_INITIALIZER; +static char line[1024]; +void dbg_time (const char *fmt, ...) { + va_list args; + va_start(args, fmt); + + pthread_mutex_lock(&printfMutex); +#ifdef ANDROID + vsnprintf(line, sizeof(line), fmt, args); + RLOGD("%s", line); +#else +#ifdef LINUX_RIL_SHLIB + line[0] = '\0'; +#else + snprintf(line, sizeof(line), "%s ", get_time()); +#endif + vsnprintf(line + strlen(line), sizeof(line) - strlen(line), fmt, args); + fprintf(stdout, "%s\n", line); +#endif + if (logfilefp) { + fprintf(logfilefp, "%s\n", line); + } + pthread_mutex_unlock(&printfMutex); +} + +const int i = 1; +#define is_bigendian() ( (*(char*)&i) == 0 ) + +USHORT le16_to_cpu(USHORT v16) { + USHORT tmp = v16; + if (is_bigendian()) { + unsigned char *s = (unsigned char *)(&v16); + unsigned char *d = (unsigned char *)(&tmp); + d[0] = s[1]; + d[1] = s[0]; + } + return tmp; +} + +UINT le32_to_cpu (UINT v32) { + UINT tmp = v32; + if (is_bigendian()) { + unsigned char *s = (unsigned char *)(&v32); + unsigned char *d = (unsigned char *)(&tmp); + d[0] = s[3]; + d[1] = s[2]; + d[2] = s[1]; + d[3] = s[0]; + } + return tmp; +} + +UINT ql_swap32(UINT v32) { + UINT tmp = v32; + { + unsigned char *s = (unsigned char *)(&v32); + unsigned char *d = (unsigned char *)(&tmp); + d[0] = s[3]; + d[1] = s[2]; + d[2] = s[1]; + d[3] = s[0]; + } + return tmp; +} + +USHORT cpu_to_le16(USHORT v16) { + USHORT tmp = v16; + if (is_bigendian()) { + unsigned char *s = (unsigned char *)(&v16); + unsigned char *d = (unsigned char *)(&tmp); + d[0] = s[1]; + d[1] = s[0]; + } + return tmp; +} + +UINT cpu_to_le32 (UINT v32) { + UINT tmp = v32; + if (is_bigendian()) { + unsigned char *s = (unsigned char *)(&v32); + unsigned char *d = (unsigned char *)(&tmp); + d[0] = s[3]; + d[1] = s[2]; + d[2] = s[1]; + d[3] = s[0]; + } + return tmp; +} diff --git a/root/package/link4all/quectel-CM/src_fangge/util.h b/root/package/link4all/quectel-CM/src_fangge/util.h new file mode 100755 index 00000000..6b94d08e --- /dev/null +++ b/root/package/link4all/quectel-CM/src_fangge/util.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _UTILS_H_ +#define _UTILS_H_ + +#include + +struct listnode +{ + struct listnode *next; + struct listnode *prev; +}; + +#define node_to_item(node, container, member) \ + (container *) (((char*) (node)) - offsetof(container, member)) + +#define list_declare(name) \ + struct listnode name = { \ + .next = &name, \ + .prev = &name, \ + } + +#define list_for_each(node, list) \ + for (node = (list)->next; node != (list); node = node->next) + +#define list_for_each_reverse(node, list) \ + for (node = (list)->prev; node != (list); node = node->prev) + +void list_init(struct listnode *list); +void list_add_tail(struct listnode *list, struct listnode *item); +void list_add_head(struct listnode *head, struct listnode *item); +void list_remove(struct listnode *item); + +#define list_empty(list) ((list) == (list)->next) +#define list_head(list) ((list)->next) +#define list_tail(list) ((list)->prev) + +int epoll_register(int epoll_fd, int fd, unsigned int events); +int epoll_deregister(int epoll_fd, int fd); +#endif diff --git a/root/package/link4all/quectel-CM/src_quectel/GobiNetCM.c b/root/package/link4all/quectel-CM/src_quectel/GobiNetCM.c new file mode 100755 index 00000000..c81d8b90 --- /dev/null +++ b/root/package/link4all/quectel-CM/src_quectel/GobiNetCM.c @@ -0,0 +1,213 @@ +#include +#include +#include +#include +#include +#include "QMIThread.h" + +#ifdef CONFIG_GOBINET + +// 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 + +int GobiNetSendQMI(PQCQMIMSG pRequest) { + int ret, fd; + + fd = qmiclientId[pRequest->QMIHdr.QMIType]; + + if (fd <= 0) { + dbg_time("%s QMIType: %d has no clientID", __func__, pRequest->QMIHdr.QMIType); + return -ENODEV; + } + + // Always ready to write + if (1 == 1) { + ssize_t nwrites = le16_to_cpu(pRequest->QMIHdr.Length) + 1 - sizeof(QCQMI_HDR); + ret = write(fd, &pRequest->MUXMsg, nwrites); + if (ret == nwrites) { + ret = 0; + } else { + dbg_time("%s write=%d, errno: %d (%s)", __func__, ret, errno, strerror(errno)); + } + } else { + dbg_time("%s poll=%d, errno: %d (%s)", __func__, ret, errno, strerror(errno)); + } + + return ret; +} + +static int GobiNetGetClientID(const char *qcqmi, UCHAR QMIType) { + int ClientId; + ClientId = open(qcqmi, O_RDWR | O_NONBLOCK | O_NOCTTY); + if (ClientId == -1) { + dbg_time("failed to open %s, errno: %d (%s)", qcqmi, errno, strerror(errno)); + return -1; + } + if (ioctl(ClientId, IOCTL_QMI_GET_SERVICE_FILE, QMIType) != 0) { + dbg_time("failed to get ClientID for 0x%02x errno: %d (%s)", QMIType, errno, strerror(errno)); + close(ClientId); + ClientId = 0; + } + + qmiclientId[QMIType] = ClientId; + switch (QMIType) { + case QMUX_TYPE_WDS: dbg_time("Get clientWDS = %d", ClientId); break; + case QMUX_TYPE_DMS: dbg_time("Get clientDMS = %d", ClientId); break; + case QMUX_TYPE_NAS: dbg_time("Get clientNAS = %d", ClientId); break; + case QMUX_TYPE_QOS: dbg_time("Get clientQOS = %d", ClientId); break; + case QMUX_TYPE_WMS: dbg_time("Get clientWMS = %d", ClientId); break; + case QMUX_TYPE_PDS: dbg_time("Get clientPDS = %d", ClientId); break; + case QMUX_TYPE_UIM: dbg_time("Get clientUIM = %d", ClientId); break; + case QMUX_TYPE_WDS_ADMIN: dbg_time("Get clientWDA = %d", ClientId); + break; + default: break; + } + + return ClientId; +} + +int GobiNetDeInit(void) { + unsigned int i; + for (i = 0; i < sizeof(qmiclientId)/sizeof(qmiclientId[0]); i++) + { + if (qmiclientId[i] != 0) + { + close(qmiclientId[i]); + qmiclientId[i] = 0; + } + } + + return 0; +} + + +void * GobiNetThread(void *pData) { + const char *qcqmi = (const char *)pData; + GobiNetGetClientID(qcqmi, QMUX_TYPE_WDS); + GobiNetGetClientID(qcqmi, QMUX_TYPE_DMS); + GobiNetGetClientID(qcqmi, QMUX_TYPE_NAS); + GobiNetGetClientID(qcqmi, QMUX_TYPE_UIM); + GobiNetGetClientID(qcqmi, QMUX_TYPE_WDS_ADMIN); + + //donot check clientWDA, there is only one client for WDA, if quectel-CM is killed by SIGKILL, i cannot get client ID for WDA again! + if ((qmiclientId[QMUX_TYPE_WDS] == 0) /*|| (clientWDA == -1)*/) { + GobiNetDeInit(); + dbg_time("%s Failed to open %s, errno: %d (%s)", __func__, qcqmi, errno, strerror(errno)); + qmidevice_send_event_to_main(RIL_INDICATE_DEVICE_DISCONNECTED); + pthread_exit(NULL); + return NULL; + } + + qmidevice_send_event_to_main(RIL_INDICATE_DEVICE_CONNECTED); + + while (1) { + struct pollfd pollfds[16] = {{qmidevice_control_fd[1], POLLIN, 0}}; + int ne, ret, nevents = 1; + unsigned int i; + + for (i = 0; i < sizeof(qmiclientId)/sizeof(qmiclientId[0]); i++) + { + if (qmiclientId[i] != 0) + { + pollfds[nevents].fd = qmiclientId[i]; + pollfds[nevents].events = POLLIN; + pollfds[nevents].revents = 0; + nevents++; + } + } + + do { + ret = poll(pollfds, nevents, -1); + } while ((ret < 0) && (errno == EINTR)); + + if (ret <= 0) { + dbg_time("%s poll=%d, errno: %d (%s)", __func__, ret, errno, strerror(errno)); + break; + } + + for (ne = 0; ne < nevents; ne++) { + int fd = pollfds[ne].fd; + short revents = pollfds[ne].revents; + + if (revents & (POLLERR | POLLHUP | POLLNVAL)) { + dbg_time("%s poll err/hup/inval", __func__); + dbg_time("epoll fd = %d, events = 0x%04x", fd, revents); + if (fd == qmidevice_control_fd[1]) { + } else { + } + if (revents & (POLLERR | POLLHUP | POLLNVAL)) + goto __GobiNetThread_quit; + } + + if ((revents & POLLIN) == 0) + continue; + + if (fd == qmidevice_control_fd[1]) { + int triger_event; + if (read(fd, &triger_event, sizeof(triger_event)) == sizeof(triger_event)) { + //DBG("triger_event = 0x%x", triger_event); + switch (triger_event) { + case RIL_REQUEST_QUIT: + goto __GobiNetThread_quit; + break; + case SIGTERM: + case SIGHUP: + case SIGINT: + QmiThreadRecvQMI(NULL); + break; + default: + break; + } + } + continue; + } + + { + ssize_t nreads; + UCHAR QMIBuf[512]; + PQCQMIMSG pResponse = (PQCQMIMSG)QMIBuf; + + nreads = read(fd, &pResponse->MUXMsg, sizeof(QMIBuf) - sizeof(QCQMI_HDR)); + if (nreads <= 0) + { + dbg_time("%s read=%d errno: %d (%s)", __func__, (int)nreads, errno, strerror(errno)); + break; + } + + for (i = 0; i < sizeof(qmiclientId)/sizeof(qmiclientId[0]); i++) + { + if (qmiclientId[i] == fd) + { + pResponse->QMIHdr.QMIType = i; + } + } + + pResponse->QMIHdr.IFType = USB_CTL_MSG_TYPE_QMI; + pResponse->QMIHdr.Length = cpu_to_le16(nreads + sizeof(QCQMI_HDR) - 1); + pResponse->QMIHdr.CtlFlags = 0x00; + pResponse->QMIHdr.ClientId = fd & 0xFF; + + QmiThreadRecvQMI(pResponse); + } + } + } + +__GobiNetThread_quit: + GobiNetDeInit(); + qmidevice_send_event_to_main(RIL_INDICATE_DEVICE_DISCONNECTED); + QmiThreadRecvQMI(NULL); //main thread may pending on QmiThreadSendQMI() + dbg_time("%s exit", __func__); + pthread_exit(NULL); + return NULL; +} + +#else +int GobiNetSendQMI(PQCQMIMSG pRequest) {return -1;} +void * GobiNetThread(void *pData) {dbg_time("please set CONFIG_GOBINET"); return NULL;} +#endif diff --git a/root/package/link4all/quectel-CM/src_quectel/MPQCTL.h b/root/package/link4all/quectel-CM/src_quectel/MPQCTL.h new file mode 100755 index 00000000..77d8aee2 --- /dev/null +++ b/root/package/link4all/quectel-CM/src_quectel/MPQCTL.h @@ -0,0 +1,376 @@ +/*=========================================================================== + + M P Q C T L. H +DESCRIPTION: + + This module contains QMI QCTL module. + +INITIALIZATION AND SEQUENCING REQUIREMENTS: + +Copyright (C) 2011 by Qualcomm Technologies, Incorporated. All Rights Reserved. +===========================================================================*/ + +#ifndef MPQCTL_H +#define MPQCTL_H + +#include "MPQMI.h" + +#pragma pack(push, 1) + +// ================= QMICTL ================== + +// QMICTL Control Flags +#define QMICTL_CTL_FLAG_CMD 0x00 +#define QMICTL_CTL_FLAG_RSP 0x01 +#define QMICTL_CTL_FLAG_IND 0x02 + +#if 0 +typedef struct _QMICTL_TRANSACTION_ITEM +{ + LIST_ENTRY List; + UCHAR TransactionId; // QMICTL transaction id + PVOID Context; // Adapter or IocDev + PIRP Irp; +} QMICTL_TRANSACTION_ITEM, *PQMICTL_TRANSACTION_ITEM; +#endif + +typedef struct _QCQMICTL_MSG_HDR +{ + UCHAR CtlFlags; // 00-cmd, 01-rsp, 10-ind + UCHAR TransactionId; + USHORT QMICTLType; + USHORT Length; +} __attribute__ ((packed)) QCQMICTL_MSG_HDR, *PQCQMICTL_MSG_HDR; + +#define QCQMICTL_MSG_HDR_SIZE sizeof(QCQMICTL_MSG_HDR) + +typedef struct _QCQMICTL_MSG_HDR_RESP +{ + UCHAR CtlFlags; // 00-cmd, 01-rsp, 10-ind + UCHAR TransactionId; + USHORT QMICTLType; + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} __attribute__ ((packed)) QCQMICTL_MSG_HDR_RESP, *PQCQMICTL_MSG_HDR_RESP; + +typedef struct _QCQMICTL_MSG +{ + UCHAR CtlFlags; // 00-cmd, 01-rsp, 10-ind + UCHAR TransactionId; + USHORT QMICTLType; + USHORT Length; + UCHAR Payload; +} __attribute__ ((packed)) QCQMICTL_MSG, *PQCQMICTL_MSG; + +// TLV Header +typedef struct _QCQMICTL_TLV_HDR +{ + UCHAR TLVType; + USHORT TLVLength; +} __attribute__ ((packed)) QCQMICTL_TLV_HDR, *PQCQMICTL_TLV_HDR; + +#define QCQMICTL_TLV_HDR_SIZE sizeof(QCQMICTL_TLV_HDR) + +// QMICTL Type +#define QMICTL_SET_INSTANCE_ID_REQ 0x0020 +#define QMICTL_SET_INSTANCE_ID_RESP 0x0020 +#define QMICTL_GET_VERSION_REQ 0x0021 +#define QMICTL_GET_VERSION_RESP 0x0021 +#define QMICTL_GET_CLIENT_ID_REQ 0x0022 +#define QMICTL_GET_CLIENT_ID_RESP 0x0022 +#define QMICTL_RELEASE_CLIENT_ID_REQ 0x0023 +#define QMICTL_RELEASE_CLIENT_ID_RESP 0x0023 +#define QMICTL_REVOKE_CLIENT_ID_IND 0x0024 +#define QMICTL_INVALID_CLIENT_ID_IND 0x0025 +#define QMICTL_SET_DATA_FORMAT_REQ 0x0026 +#define QMICTL_SET_DATA_FORMAT_RESP 0x0026 +#define QMICTL_SYNC_REQ 0x0027 +#define QMICTL_SYNC_RESP 0x0027 +#define QMICTL_SYNC_IND 0x0027 + +#define QMICTL_FLAG_REQUEST 0x00 +#define QMICTL_FLAG_RESPONSE 0x01 +#define QMICTL_FLAG_INDICATION 0x02 + +// QMICTL Message Definitions + +typedef struct _QMICTL_SET_INSTANCE_ID_REQ_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_REQUEST + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_SET_INSTANCE_ID_REQ + USHORT Length; // 4 + UCHAR TLVType; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLVLength; // 1 + UCHAR Value; // Host-unique QMI instance for this device driver +} __attribute__ ((packed)) QMICTL_SET_INSTANCE_ID_REQ_MSG, *PQMICTL_SET_INSTANCE_ID_REQ_MSG; + +typedef struct _QMICTL_SET_INSTANCE_ID_RESP_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_RESPONSE + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_SET_INSTANCE_ID_RESP + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_RESULT_CODE + USHORT TLVLength; // 0x0004 + USHORT QMIResult; + USHORT QMIError; + UCHAR TLV2Type; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLV2Length; // 0x0002 + USHORT QMI_ID; // Upper byte is assigned by MSM, + // lower assigned by host +} __attribute__ ((packed)) QMICTL_SET_INSTANCE_ID_RESP_MSG, *PQMICTL_SET_INSTANCE_ID_RESP_MSG; + +typedef struct _QMICTL_GET_VERSION_REQ_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_REQUEST + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_GET_VERSION_REQ + USHORT Length; // 0 + UCHAR TLVType; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLVLength; // var + UCHAR QMUXTypes; // List of one byte QMUX_TYPE values + // 0xFF returns a list of versions for all + // QMUX_TYPEs implemented on the device +} __attribute__ ((packed)) QMICTL_GET_VERSION_REQ_MSG, *PQMICTL_GET_VERSION_REQ_MSG; + +typedef struct _QMUX_TYPE_VERSION_STRUCT +{ + UCHAR QMUXType; + USHORT MajorVersion; + USHORT MinorVersion; +} __attribute__ ((packed)) QMUX_TYPE_VERSION_STRUCT, *PQMUX_TYPE_VERSION_STRUCT; + +typedef struct _ADDENDUM_VERSION_PREAMBLE +{ + UCHAR LabelLength; + UCHAR Label; +} __attribute__ ((packed)) ADDENDUM_VERSION_PREAMBLE, *PADDENDUM_VERSION_PREAMBLE; + +#define QMICTL_GET_VERSION_RSP_TLV_TYPE_VERSION 0x01 +#define QMICTL_GET_VERSION_RSP_TLV_TYPE_ADD_VERSION 0x10 + +typedef struct _QMICTL_GET_VERSION_RESP_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_RESPONSE + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_GET_VERSION_RESP + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_RESULT_CODE + USHORT TLVLength; // 0x0004 + USHORT QMIResult; + USHORT QMIError; + UCHAR TLV2Type; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLV2Length; // var + UCHAR NumElements; // Num of QMUX_TYPE_VERSION_STRUCT + QMUX_TYPE_VERSION_STRUCT TypeVersion; +} __attribute__ ((packed)) QMICTL_GET_VERSION_RESP_MSG, *PQMICTL_GET_VERSION_RESP_MSG; + +typedef struct _QMICTL_GET_CLIENT_ID_REQ_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_REQUEST + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_GET_CLIENT_ID_REQ + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLVLength; // 1 + UCHAR QMIType; // QMUX type +} __attribute__ ((packed)) QMICTL_GET_CLIENT_ID_REQ_MSG, *PQMICTL_GET_CLIENT_ID_REQ_MSG; + +typedef struct _QMICTL_GET_CLIENT_ID_RESP_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_RESPONSE + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_GET_CLIENT_ID_RESP + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_RESULT_CODE + USHORT TLVLength; // 0x0004 + USHORT QMIResult; // result code + USHORT QMIError; // error code + UCHAR TLV2Type; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLV2Length; // 2 + UCHAR QMIType; + UCHAR ClientId; +} __attribute__ ((packed)) QMICTL_GET_CLIENT_ID_RESP_MSG, *PQMICTL_GET_CLIENT_ID_RESP_MSG; + +typedef struct _QMICTL_RELEASE_CLIENT_ID_REQ_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_REQUEST + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_RELEASE_CLIENT_ID_REQ + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLVLength; // 0x0002 + UCHAR QMIType; + UCHAR ClientId; +} __attribute__ ((packed)) QMICTL_RELEASE_CLIENT_ID_REQ_MSG, *PQMICTL_RELEASE_CLIENT_ID_REQ_MSG; + +typedef struct _QMICTL_RELEASE_CLIENT_ID_RESP_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_RESPONSE + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_RELEASE_CLIENT_ID_RESP + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_RESULT_CODE + USHORT TLVLength; // 0x0004 + USHORT QMIResult; // result code + USHORT QMIError; // error code + UCHAR TLV2Type; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLV2Length; // 2 + UCHAR QMIType; + UCHAR ClientId; +} __attribute__ ((packed)) QMICTL_RELEASE_CLIENT_ID_RESP_MSG, *PQMICTL_RELEASE_CLIENT_ID_RESP_MSG; + +typedef struct _QMICTL_REVOKE_CLIENT_ID_IND_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_INDICATION + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_REVOKE_CLIENT_ID_IND + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLVLength; // 0x0002 + UCHAR QMIType; + UCHAR ClientId; +} __attribute__ ((packed)) QMICTL_REVOKE_CLIENT_ID_IND_MSG, *PQMICTL_REVOKE_CLIENT_ID_IND_MSG; + +typedef struct _QMICTL_INVALID_CLIENT_ID_IND_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_INDICATION + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_REVOKE_CLIENT_ID_IND + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLVLength; // 0x0002 + UCHAR QMIType; + UCHAR ClientId; +} __attribute__ ((packed)) QMICTL_INVALID_CLIENT_ID_IND_MSG, *PQMICTL_INVALID_CLIENT_ID_IND_MSG; + +typedef struct _QMICTL_SET_DATA_FORMAT_REQ_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_REQUEST + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_SET_DATA_FORMAT_REQ + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_REQUIRED_PARAMETER + USHORT TLVLength; // 1 + UCHAR DataFormat; // 0-default; 1-QoS hdr present +} __attribute__ ((packed)) QMICTL_SET_DATA_FORMAT_REQ_MSG, *PQMICTL_SET_DATA_FORMAT_REQ_MSG; + +#ifdef QC_IP_MODE +#define SET_DATA_FORMAT_TLV_TYPE_LINK_PROTO 0x10 +#define SET_DATA_FORMAT_LINK_PROTO_ETH 0x0001 +#define SET_DATA_FORMAT_LINK_PROTO_IP 0x0002 +typedef struct _QMICTL_SET_DATA_FORMAT_TLV_LINK_PROT +{ + UCHAR TLVType; // Link-Layer Protocol + USHORT TLVLength; // 2 + USHORT LinkProt; // 0x1: ETH; 0x2: IP +} QMICTL_SET_DATA_FORMAT_TLV_LINK_PROT, *PQMICTL_SET_DATA_FORMAT_TLV_LINK_PROT; + +#ifdef QCMP_UL_TLP +#define SET_DATA_FORMAT_TLV_TYPE_UL_TLP 0x11 +typedef struct _QMICTL_SET_DATA_FORMAT_TLV_UL_TLP +{ + UCHAR TLVType; // 0x11, Uplink TLP Setting + USHORT TLVLength; // 1 + UCHAR UlTlpSetting; // 0x0: Disable; 0x01: Enable +} QMICTL_SET_DATA_FORMAT_TLV_UL_TLP, *PQMICTL_SET_DATA_FORMAT_TLV_UL_TLP; +#endif // QCMP_UL_TLP + +#ifdef QCMP_DL_TLP +#define SET_DATA_FORMAT_TLV_TYPE_DL_TLP 0x13 +typedef struct _QMICTL_SET_DATA_FORMAT_TLV_DL_TLP +{ + UCHAR TLVType; // 0x11, Uplink TLP Setting + USHORT TLVLength; // 1 + UCHAR DlTlpSetting; // 0x0: Disable; 0x01: Enable +} QMICTL_SET_DATA_FORMAT_TLV_DL_TLP, *PQMICTL_SET_DATA_FORMAT_TLV_DL_TLP; +#endif // QCMP_DL_TLP + +#endif // QC_IP_MODE + +#ifdef MP_QCQOS_ENABLED +#define SET_DATA_FORMAT_TLV_TYPE_QOS_SETTING 0x12 +typedef struct _QMICTL_SET_DATA_FORMAT_TLV_QOS_SETTING +{ + UCHAR TLVType; // 0x12, QoS setting + USHORT TLVLength; // 1 + UCHAR QosSetting; // 0x0: Disable; 0x01: Enable +} QMICTL_SET_DATA_FORMAT_TLV_QOS_SETTING, *PQMICTL_SET_DATA_FORMAT_TLV_QOS_SETTING; +#endif // MP_QCQOS_ENABLED + +typedef struct _QMICTL_SET_DATA_FORMAT_RESP_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_RESPONSE + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_SET_DATA_FORMAT_RESP + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_RESULT_CODE + USHORT TLVLength; // 0x0004 + USHORT QMIResult; // result code + USHORT QMIError; // error code +} __attribute__ ((packed)) QMICTL_SET_DATA_FORMAT_RESP_MSG, *PQMICTL_SET_DATA_FORMAT_RESP_MSG; + +typedef struct _QMICTL_SYNC_REQ_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_REQUEST + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_CTL_SYNC_REQ + USHORT Length; // 0 +} __attribute__ ((packed)) QMICTL_SYNC_REQ_MSG, *PQMICTL_SYNC_REQ_MSG; + +typedef struct _QMICTL_SYNC_RESP_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_RESPONSE + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_CTL_SYNC_RESP + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_RESULT_CODE + USHORT TLVLength; // 0x0004 + USHORT QMIResult; + USHORT QMIError; +} __attribute__ ((packed)) QMICTL_SYNC_RESP_MSG, *PQMICTL_SYNC_RESP_MSG; + +typedef struct _QMICTL_SYNC_IND_MSG +{ + UCHAR CtlFlags; // QMICTL_FLAG_INDICATION + UCHAR TransactionId; + USHORT QMICTLType; // QMICTL_REVOKE_CLIENT_ID_IND + USHORT Length; +} __attribute__ ((packed)) QMICTL_SYNC_IND_MSG, *PQMICTL_SYNC_IND_MSG; + +typedef struct _QMICTL_MSG +{ + union + { + // Message Header + QCQMICTL_MSG_HDR QMICTLMsgHdr; + QCQMICTL_MSG_HDR_RESP QMICTLMsgHdrRsp; + + // QMICTL Message + QMICTL_SET_INSTANCE_ID_REQ_MSG SetInstanceIdReq; + QMICTL_SET_INSTANCE_ID_RESP_MSG SetInstanceIdRsp; + QMICTL_GET_VERSION_REQ_MSG GetVersionReq; + QMICTL_GET_VERSION_RESP_MSG GetVersionRsp; + QMICTL_GET_CLIENT_ID_REQ_MSG GetClientIdReq; + QMICTL_GET_CLIENT_ID_RESP_MSG GetClientIdRsp; + QMICTL_RELEASE_CLIENT_ID_REQ_MSG ReleaseClientIdReq; + QMICTL_RELEASE_CLIENT_ID_RESP_MSG ReleaseClientIdRsp; + QMICTL_REVOKE_CLIENT_ID_IND_MSG RevokeClientIdInd; + QMICTL_INVALID_CLIENT_ID_IND_MSG InvalidClientIdInd; + QMICTL_SET_DATA_FORMAT_REQ_MSG SetDataFormatReq; + QMICTL_SET_DATA_FORMAT_RESP_MSG SetDataFormatRsp; + QMICTL_SYNC_REQ_MSG SyncReq; + QMICTL_SYNC_RESP_MSG SyncRsp; + QMICTL_SYNC_IND_MSG SyncInd; + }; +} __attribute__ ((packed)) QMICTL_MSG, *PQMICTL_MSG; + +#endif // MPQCTL_H diff --git a/root/package/link4all/quectel-CM/src_quectel/MPQMI.h b/root/package/link4all/quectel-CM/src_quectel/MPQMI.h new file mode 100755 index 00000000..ca457c4a --- /dev/null +++ b/root/package/link4all/quectel-CM/src_quectel/MPQMI.h @@ -0,0 +1,220 @@ +/*=========================================================================== + + M P Q M I. H +DESCRIPTION: + + This module contains forward references to the QMI module. + +INITIALIZATION AND SEQUENCING REQUIREMENTS: + +Copyright (C) 2011 by Qualcomm Technologies, Incorporated. All Rights Reserved. +===========================================================================*/ +/*=========================================================================== + + EDIT HISTORY FOR FILE + $Header: //depot/QMI/win/qcdrivers/ndis/MPQMI.h#3 $ + +when who what, where, why +-------- --- ---------------------------------------------------------- +11/20/04 hg Initial version. +===========================================================================*/ + +#ifndef USBQMI_H +#define USBQMI_H + +typedef char CHAR; +typedef unsigned char UCHAR; +typedef unsigned short USHORT; +typedef int INT; +typedef unsigned int UINT; +typedef long LONG; +typedef unsigned int ULONG; +typedef unsigned long long ULONG64; +typedef char *PCHAR; +typedef unsigned char *PUCHAR; +typedef int *PINT; +typedef int BOOL; + +#define TRUE (1 == 1) +#define FALSE (1 != 1) + +#define QMICTL_SUPPORTED_MAJOR_VERSION 1 +#define QMICTL_SUPPORTED_MINOR_VERSION 0 + +#pragma pack(push, 1) + +// ========= USB Control Message ========== + +#define USB_CTL_MSG_TYPE_QMI 0x01 + +// USB Control Message +typedef struct _QCUSB_CTL_MSG_HDR +{ + UCHAR IFType; +} __attribute__ ((packed)) QCUSB_CTL_MSG_HDR, *PQCUSB_CTL_MSG_HDR; + +#define QCUSB_CTL_MSG_HDR_SIZE sizeof(QCUSB_CTL_MSG_HDR) + +typedef struct _QCUSB_CTL_MSG +{ + UCHAR IFType; + UCHAR Message; +} __attribute__ ((packed)) QCUSB_CTL_MSG, *PQCUSB_CTL_MSG; + +#define QCTLV_TYPE_REQUIRED_PARAMETER 0x01 +#define QCTLV_TYPE_RESULT_CODE 0x02 + +// ================= QMI ================== + +// Define QMI Type +typedef enum _QMI_SERVICE_TYPE +{ + QMUX_TYPE_CTL = 0x00, + QMUX_TYPE_WDS = 0x01, + QMUX_TYPE_DMS = 0x02, + QMUX_TYPE_NAS = 0x03, + QMUX_TYPE_QOS = 0x04, + QMUX_TYPE_WMS = 0x05, + QMUX_TYPE_PDS = 0x06, + QMUX_TYPE_UIM = 0x0B, + QMUX_TYPE_WDS_ADMIN = 0x1A, + QMUX_TYPE_MAX = 0xFF, + QMUX_TYPE_ALL = 0xFF +} QMI_SERVICE_TYPE; + +typedef enum _QMI_RESULT_CODE_TYPE +{ + QMI_RESULT_SUCCESS = 0x0000, + QMI_RESULT_FAILURE = 0x0001 +} QMI_RESULT_CODE_TYPE; + +typedef enum _QMI_ERROR_CODE_TYPE +{ + QMI_ERR_NONE = 0x0000, + QMI_ERR_INTERNAL = 0x0003, + QMI_ERR_CLIENT_IDS_EXHAUSTED = 0x0005, + QMI_ERR_DENIED = 0x0006, + QMI_ERR_INVALID_CLIENT_IDS = 0x0007, + QMI_ERR_NO_BATTERY = 0x0008, + QMI_ERR_INVALID_HANDLE = 0x0009, + QMI_ERR_INVALID_PROFILE = 0x000A, + QMI_ERR_STORAGE_EXCEEDED = 0x000B, + QMI_ERR_INCORRECT_PIN = 0x000C, + QMI_ERR_NO_NETWORK = 0x000D, + QMI_ERR_PIN_LOCKED = 0x000E, + QMI_ERR_OUT_OF_CALL = 0x000F, + QMI_ERR_NOT_PROVISIONED = 0x0010, + QMI_ERR_ARG_TOO_LONG = 0x0013, + QMI_ERR_DEVICE_IN_USE = 0x0017, + QMI_ERR_OP_DEVICE_UNSUPPORTED = 0x0019, + QMI_ERR_NO_EFFECT = 0x001A, + QMI_ERR_INVALID_ARG = 0x0020, + QMI_ERR_NO_MEMORY = 0x0021, + QMI_ERR_PIN_BLOCKED = 0x0023, + QMI_ERR_PIN_PERM_BLOCKED = 0x0024, + QMI_ERR_INVALID_INDEX = 0x0031, + QMI_ERR_NO_ENTRY = 0x0032, + QMI_ERR_EXTENDED_INTERNAL = 0x0051, + QMI_ERR_ACCESS_DENIED = 0x0052 +} QMI_ERROR_CODE_TYPE; + +#define QCQMI_CTL_FLAG_SERVICE 0x80 +#define QCQMI_CTL_FLAG_CTL_POINT 0x00 + +typedef struct _QCQMI_HDR +{ + UCHAR IFType; + USHORT Length; + UCHAR CtlFlags; // reserved + UCHAR QMIType; + UCHAR ClientId; +} __attribute__ ((packed)) QCQMI_HDR, *PQCQMI_HDR; + +#define QCQMI_HDR_SIZE (sizeof(QCQMI_HDR)-1) + +typedef struct _QCQMI +{ + UCHAR IFType; + USHORT Length; + UCHAR CtlFlags; // reserved + UCHAR QMIType; + UCHAR ClientId; + UCHAR SDU; +} __attribute__ ((packed)) QCQMI, *PQCQMI; + +typedef struct _QMI_SERVICE_VERSION +{ + USHORT Major; + USHORT Minor; + USHORT AddendumMajor; + USHORT AddendumMinor; +} __attribute__ ((packed)) QMI_SERVICE_VERSION, *PQMI_SERVICE_VERSION; + +// ================= QMUX ================== + +#define QMUX_MSG_OVERHEAD_BYTES 4 // Type(USHORT) Length(USHORT) -- header + +#define QMUX_BROADCAST_CID 0xFF + +typedef struct _QCQMUX_HDR +{ + UCHAR CtlFlags; // 0: single QMUX Msg; 1: + USHORT TransactionId; +} __attribute__ ((packed)) QCQMUX_HDR, *PQCQMUX_HDR; + +typedef struct _QCQMUX +{ + UCHAR CtlFlags; // 0: single QMUX Msg; 1: + USHORT TransactionId; + UCHAR Message; // Type(2), Length(2), Value +} __attribute__ ((packed)) QCQMUX, *PQCQMUX; + +#define QCQMUX_HDR_SIZE sizeof(QCQMUX_HDR) + +typedef struct _QCQMUX_MSG_HDR +{ + USHORT Type; + USHORT Length; +} __attribute__ ((packed)) QCQMUX_MSG_HDR, *PQCQMUX_MSG_HDR; + +#define QCQMUX_MSG_HDR_SIZE sizeof(QCQMUX_MSG_HDR) + +typedef struct _QCQMUX_MSG_HDR_RESP +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} __attribute__ ((packed)) QCQMUX_MSG_HDR_RESP, *PQCQMUX_MSG_HDR_RESP; + +typedef struct _QCQMUX_TLV +{ + UCHAR Type; + USHORT Length; + UCHAR Value; +} __attribute__ ((packed)) QCQMUX_TLV, *PQCQMUX_TLV; + +typedef struct _QMI_TLV_HDR +{ + UCHAR TLVType; + USHORT TLVLength; +} __attribute__ ((packed)) QMI_TLV_HDR, *PQMI_TLV_HDR; + +// QMUX Message Definitions -- QMI SDU +#define QMUX_CTL_FLAG_SINGLE_MSG 0x00 +#define QMUX_CTL_FLAG_COMPOUND_MSG 0x01 +#define QMUX_CTL_FLAG_TYPE_CMD 0x00 +#define QMUX_CTL_FLAG_TYPE_RSP 0x02 +#define QMUX_CTL_FLAG_TYPE_IND 0x04 +#define QMUX_CTL_FLAG_MASK_COMPOUND 0x01 +#define QMUX_CTL_FLAG_MASK_TYPE 0x06 // 00-cmd, 01-rsp, 10-ind + +#pragma pack(pop) + +#endif // USBQMI_H diff --git a/root/package/link4all/quectel-CM/src_quectel/MPQMUX.c b/root/package/link4all/quectel-CM/src_quectel/MPQMUX.c new file mode 100755 index 00000000..b03ed408 --- /dev/null +++ b/root/package/link4all/quectel-CM/src_quectel/MPQMUX.c @@ -0,0 +1,422 @@ +#include "QMIThread.h" +static char line[1024]; +static pthread_mutex_t dumpQMIMutex = PTHREAD_MUTEX_INITIALIZER; +#undef dbg +#define dbg( format, arg... ) do {if (strlen(line) < sizeof(line)) snprintf(&line[strlen(line)], sizeof(line) - strlen(line), format, ## arg);} while (0) + +PQMI_TLV_HDR GetTLV (PQCQMUX_MSG_HDR pQMUXMsgHdr, int TLVType); + +typedef struct { + UINT type; + const char *name; +} QMI_NAME_T; + +#define qmi_name_item(type) {type, #type} + +static const QMI_NAME_T qmi_IFType[] = { +{USB_CTL_MSG_TYPE_QMI, "USB_CTL_MSG_TYPE_QMI"}, +}; + +static const QMI_NAME_T qmi_CtlFlags[] = { +qmi_name_item(QMICTL_CTL_FLAG_CMD), +qmi_name_item(QCQMI_CTL_FLAG_SERVICE), +}; + +static const QMI_NAME_T qmi_QMIType[] = { +qmi_name_item(QMUX_TYPE_CTL), +qmi_name_item(QMUX_TYPE_WDS), +qmi_name_item(QMUX_TYPE_DMS), +qmi_name_item(QMUX_TYPE_NAS), +qmi_name_item(QMUX_TYPE_QOS), +qmi_name_item(QMUX_TYPE_WMS), +qmi_name_item(QMUX_TYPE_PDS), +qmi_name_item(QMUX_TYPE_WDS_ADMIN), +}; + +static const QMI_NAME_T qmi_ctl_CtlFlags[] = { +qmi_name_item(QMICTL_FLAG_REQUEST), +qmi_name_item(QMICTL_FLAG_RESPONSE), +qmi_name_item(QMICTL_FLAG_INDICATION), +}; + +static const QMI_NAME_T qmux_ctl_QMICTLType[] = { +// QMICTL Type +qmi_name_item(QMICTL_SET_INSTANCE_ID_REQ), // 0x0020 +qmi_name_item(QMICTL_SET_INSTANCE_ID_RESP), // 0x0020 +qmi_name_item(QMICTL_GET_VERSION_REQ), // 0x0021 +qmi_name_item(QMICTL_GET_VERSION_RESP), // 0x0021 +qmi_name_item(QMICTL_GET_CLIENT_ID_REQ), // 0x0022 +qmi_name_item(QMICTL_GET_CLIENT_ID_RESP), // 0x0022 +qmi_name_item(QMICTL_RELEASE_CLIENT_ID_REQ), // 0x0023 +qmi_name_item(QMICTL_RELEASE_CLIENT_ID_RESP), // 0x0023 +qmi_name_item(QMICTL_REVOKE_CLIENT_ID_IND), // 0x0024 +qmi_name_item(QMICTL_INVALID_CLIENT_ID_IND), // 0x0025 +qmi_name_item(QMICTL_SET_DATA_FORMAT_REQ), // 0x0026 +qmi_name_item(QMICTL_SET_DATA_FORMAT_RESP), // 0x0026 +qmi_name_item(QMICTL_SYNC_REQ), // 0x0027 +qmi_name_item(QMICTL_SYNC_RESP), // 0x0027 +qmi_name_item(QMICTL_SYNC_IND), // 0x0027 +}; + +static const QMI_NAME_T qmux_CtlFlags[] = { +qmi_name_item(QMUX_CTL_FLAG_TYPE_CMD), +qmi_name_item(QMUX_CTL_FLAG_TYPE_RSP), +qmi_name_item(QMUX_CTL_FLAG_TYPE_IND), +}; + + +static const QMI_NAME_T qmux_wds_Type[] = { +qmi_name_item(QMIWDS_SET_EVENT_REPORT_REQ), // 0x0001 +qmi_name_item(QMIWDS_SET_EVENT_REPORT_RESP), // 0x0001 +qmi_name_item(QMIWDS_EVENT_REPORT_IND), // 0x0001 +qmi_name_item(QMIWDS_START_NETWORK_INTERFACE_REQ), // 0x0020 +qmi_name_item(QMIWDS_START_NETWORK_INTERFACE_RESP), // 0x0020 +qmi_name_item(QMIWDS_STOP_NETWORK_INTERFACE_REQ), // 0x0021 +qmi_name_item(QMIWDS_STOP_NETWORK_INTERFACE_RESP), // 0x0021 +qmi_name_item(QMIWDS_GET_PKT_SRVC_STATUS_REQ), // 0x0022 +qmi_name_item(QMIWDS_GET_PKT_SRVC_STATUS_RESP), // 0x0022 +qmi_name_item(QMIWDS_GET_PKT_SRVC_STATUS_IND), // 0x0022 +qmi_name_item(QMIWDS_GET_CURRENT_CHANNEL_RATE_REQ), // 0x0023 +qmi_name_item(QMIWDS_GET_CURRENT_CHANNEL_RATE_RESP), // 0x0023 +qmi_name_item(QMIWDS_GET_PKT_STATISTICS_REQ), // 0x0024 +qmi_name_item(QMIWDS_GET_PKT_STATISTICS_RESP), // 0x0024 +qmi_name_item(QMIWDS_MODIFY_PROFILE_SETTINGS_REQ), // 0x0028 +qmi_name_item(QMIWDS_MODIFY_PROFILE_SETTINGS_RESP), // 0x0028 +qmi_name_item(QMIWDS_GET_PROFILE_SETTINGS_REQ), // 0x002B +qmi_name_item(QMIWDS_GET_PROFILE_SETTINGS_RESP), // 0x002BD +qmi_name_item(QMIWDS_GET_DEFAULT_SETTINGS_REQ), // 0x002C +qmi_name_item(QMIWDS_GET_DEFAULT_SETTINGS_RESP), // 0x002C +qmi_name_item(QMIWDS_GET_RUNTIME_SETTINGS_REQ), // 0x002D +qmi_name_item(QMIWDS_GET_RUNTIME_SETTINGS_RESP), // 0x002D +qmi_name_item(QMIWDS_GET_MIP_MODE_REQ), // 0x002F +qmi_name_item(QMIWDS_GET_MIP_MODE_RESP), // 0x002F +qmi_name_item(QMIWDS_GET_DATA_BEARER_REQ), // 0x0037 +qmi_name_item(QMIWDS_GET_DATA_BEARER_RESP), // 0x0037 +qmi_name_item(QMIWDS_DUN_CALL_INFO_REQ), // 0x0038 +qmi_name_item(QMIWDS_DUN_CALL_INFO_RESP), // 0x0038 +qmi_name_item(QMIWDS_DUN_CALL_INFO_IND), // 0x0038 +qmi_name_item(QMIWDS_SET_CLIENT_IP_FAMILY_PREF_REQ), // 0x004D +qmi_name_item(QMIWDS_SET_CLIENT_IP_FAMILY_PREF_RESP), // 0x004D +qmi_name_item(QMIWDS_BIND_MUX_DATA_PORT_REQ), // 0x00A2 +qmi_name_item(QMIWDS_BIND_MUX_DATA_PORT_RESP), // 0x00A2 +}; + +static const QMI_NAME_T qmux_dms_Type[] = { +// ======================= DMS ============================== +qmi_name_item(QMIDMS_SET_EVENT_REPORT_REQ), // 0x0001 +qmi_name_item(QMIDMS_SET_EVENT_REPORT_RESP), // 0x0001 +qmi_name_item(QMIDMS_EVENT_REPORT_IND), // 0x0001 +qmi_name_item(QMIDMS_GET_DEVICE_CAP_REQ), // 0x0020 +qmi_name_item(QMIDMS_GET_DEVICE_CAP_RESP), // 0x0020 +qmi_name_item(QMIDMS_GET_DEVICE_MFR_REQ), // 0x0021 +qmi_name_item(QMIDMS_GET_DEVICE_MFR_RESP), // 0x0021 +qmi_name_item(QMIDMS_GET_DEVICE_MODEL_ID_REQ), // 0x0022 +qmi_name_item(QMIDMS_GET_DEVICE_MODEL_ID_RESP), // 0x0022 +qmi_name_item(QMIDMS_GET_DEVICE_REV_ID_REQ), // 0x0023 +qmi_name_item(QMIDMS_GET_DEVICE_REV_ID_RESP), // 0x0023 +qmi_name_item(QMIDMS_GET_MSISDN_REQ), // 0x0024 +qmi_name_item(QMIDMS_GET_MSISDN_RESP), // 0x0024 +qmi_name_item(QMIDMS_GET_DEVICE_SERIAL_NUMBERS_REQ), // 0x0025 +qmi_name_item(QMIDMS_GET_DEVICE_SERIAL_NUMBERS_RESP), // 0x0025 +qmi_name_item(QMIDMS_UIM_SET_PIN_PROTECTION_REQ), // 0x0027 +qmi_name_item(QMIDMS_UIM_SET_PIN_PROTECTION_RESP), // 0x0027 +qmi_name_item(QMIDMS_UIM_VERIFY_PIN_REQ), // 0x0028 +qmi_name_item(QMIDMS_UIM_VERIFY_PIN_RESP), // 0x0028 +qmi_name_item(QMIDMS_UIM_UNBLOCK_PIN_REQ), // 0x0029 +qmi_name_item(QMIDMS_UIM_UNBLOCK_PIN_RESP), // 0x0029 +qmi_name_item(QMIDMS_UIM_CHANGE_PIN_REQ), // 0x002A +qmi_name_item(QMIDMS_UIM_CHANGE_PIN_RESP), // 0x002A +qmi_name_item(QMIDMS_UIM_GET_PIN_STATUS_REQ), // 0x002B +qmi_name_item(QMIDMS_UIM_GET_PIN_STATUS_RESP), // 0x002B +qmi_name_item(QMIDMS_GET_DEVICE_HARDWARE_REV_REQ), // 0x002C +qmi_name_item(QMIDMS_GET_DEVICE_HARDWARE_REV_RESP), // 0x002C +qmi_name_item(QMIDMS_GET_OPERATING_MODE_REQ), // 0x002D +qmi_name_item(QMIDMS_GET_OPERATING_MODE_RESP), // 0x002D +qmi_name_item(QMIDMS_SET_OPERATING_MODE_REQ), // 0x002E +qmi_name_item(QMIDMS_SET_OPERATING_MODE_RESP), // 0x002E +qmi_name_item(QMIDMS_GET_ACTIVATED_STATUS_REQ), // 0x0031 +qmi_name_item(QMIDMS_GET_ACTIVATED_STATUS_RESP), // 0x0031 +qmi_name_item(QMIDMS_ACTIVATE_AUTOMATIC_REQ), // 0x0032 +qmi_name_item(QMIDMS_ACTIVATE_AUTOMATIC_RESP), // 0x0032 +qmi_name_item(QMIDMS_ACTIVATE_MANUAL_REQ), // 0x0033 +qmi_name_item(QMIDMS_ACTIVATE_MANUAL_RESP), // 0x0033 +qmi_name_item(QMIDMS_UIM_GET_ICCID_REQ), // 0x003C +qmi_name_item(QMIDMS_UIM_GET_ICCID_RESP), // 0x003C +qmi_name_item(QMIDMS_UIM_GET_CK_STATUS_REQ), // 0x0040 +qmi_name_item(QMIDMS_UIM_GET_CK_STATUS_RESP), // 0x0040 +qmi_name_item(QMIDMS_UIM_SET_CK_PROTECTION_REQ), // 0x0041 +qmi_name_item(QMIDMS_UIM_SET_CK_PROTECTION_RESP), // 0x0041 +qmi_name_item(QMIDMS_UIM_UNBLOCK_CK_REQ), // 0x0042 +qmi_name_item(QMIDMS_UIM_UNBLOCK_CK_RESP), // 0x0042 +qmi_name_item(QMIDMS_UIM_GET_IMSI_REQ), // 0x0043 +qmi_name_item(QMIDMS_UIM_GET_IMSI_RESP), // 0x0043 +qmi_name_item(QMIDMS_UIM_GET_STATE_REQ), // 0x0044 +qmi_name_item(QMIDMS_UIM_GET_STATE_RESP), // 0x0044 +qmi_name_item(QMIDMS_GET_BAND_CAP_REQ), // 0x0045 +qmi_name_item(QMIDMS_GET_BAND_CAP_RESP), // 0x0045 +}; + +static const QMI_NAME_T qmux_nas_Type[] = { +// ======================= NAS ============================== +qmi_name_item(QMINAS_SET_EVENT_REPORT_REQ), // 0x0002 +qmi_name_item(QMINAS_SET_EVENT_REPORT_RESP), // 0x0002 +qmi_name_item(QMINAS_EVENT_REPORT_IND), // 0x0002 +qmi_name_item(QMINAS_GET_SIGNAL_STRENGTH_REQ), // 0x0020 +qmi_name_item(QMINAS_GET_SIGNAL_STRENGTH_RESP), // 0x0020 +qmi_name_item(QMINAS_PERFORM_NETWORK_SCAN_REQ), // 0x0021 +qmi_name_item(QMINAS_PERFORM_NETWORK_SCAN_RESP), // 0x0021 +qmi_name_item(QMINAS_INITIATE_NW_REGISTER_REQ), // 0x0022 +qmi_name_item(QMINAS_INITIATE_NW_REGISTER_RESP), // 0x0022 +qmi_name_item(QMINAS_INITIATE_ATTACH_REQ), // 0x0023 +qmi_name_item(QMINAS_INITIATE_ATTACH_RESP), // 0x0023 +qmi_name_item(QMINAS_GET_SERVING_SYSTEM_REQ), // 0x0024 +qmi_name_item(QMINAS_GET_SERVING_SYSTEM_RESP), // 0x0024 +qmi_name_item(QMINAS_SERVING_SYSTEM_IND), // 0x0024 +qmi_name_item(QMINAS_GET_HOME_NETWORK_REQ), // 0x0025 +qmi_name_item(QMINAS_GET_HOME_NETWORK_RESP), // 0x0025 +qmi_name_item(QMINAS_GET_PREFERRED_NETWORK_REQ), // 0x0026 +qmi_name_item(QMINAS_GET_PREFERRED_NETWORK_RESP), // 0x0026 +qmi_name_item(QMINAS_SET_PREFERRED_NETWORK_REQ), // 0x0027 +qmi_name_item(QMINAS_SET_PREFERRED_NETWORK_RESP), // 0x0027 +qmi_name_item(QMINAS_GET_FORBIDDEN_NETWORK_REQ), // 0x0028 +qmi_name_item(QMINAS_GET_FORBIDDEN_NETWORK_RESP), // 0x0028 +qmi_name_item(QMINAS_SET_FORBIDDEN_NETWORK_REQ), // 0x0029 +qmi_name_item(QMINAS_SET_FORBIDDEN_NETWORK_RESP), // 0x0029 +qmi_name_item(QMINAS_SET_TECHNOLOGY_PREF_REQ), // 0x002A +qmi_name_item(QMINAS_SET_TECHNOLOGY_PREF_RESP), // 0x002A +qmi_name_item(QMINAS_GET_RF_BAND_INFO_REQ), // 0x0031 +qmi_name_item(QMINAS_GET_RF_BAND_INFO_RESP), // 0x0031 +qmi_name_item(QMINAS_GET_PLMN_NAME_REQ), // 0x0044 +qmi_name_item(QMINAS_GET_PLMN_NAME_RESP), // 0x0044 +qmi_name_item(QUECTEL_PACKET_TRANSFER_START_IND), // 0X100 +qmi_name_item(QUECTEL_PACKET_TRANSFER_END_IND), // 0X101 +qmi_name_item(QMINAS_GET_SYS_INFO_REQ), // 0x004D +qmi_name_item(QMINAS_GET_SYS_INFO_RESP), // 0x004D +qmi_name_item(QMINAS_SYS_INFO_IND), // 0x004D +}; + +static const QMI_NAME_T qmux_wms_Type[] = { +// ======================= WMS ============================== +qmi_name_item(QMIWMS_SET_EVENT_REPORT_REQ), // 0x0001 +qmi_name_item(QMIWMS_SET_EVENT_REPORT_RESP), // 0x0001 +qmi_name_item(QMIWMS_EVENT_REPORT_IND), // 0x0001 +qmi_name_item(QMIWMS_RAW_SEND_REQ), // 0x0020 +qmi_name_item(QMIWMS_RAW_SEND_RESP), // 0x0020 +qmi_name_item(QMIWMS_RAW_WRITE_REQ), // 0x0021 +qmi_name_item(QMIWMS_RAW_WRITE_RESP), // 0x0021 +qmi_name_item(QMIWMS_RAW_READ_REQ), // 0x0022 +qmi_name_item(QMIWMS_RAW_READ_RESP), // 0x0022 +qmi_name_item(QMIWMS_MODIFY_TAG_REQ), // 0x0023 +qmi_name_item(QMIWMS_MODIFY_TAG_RESP), // 0x0023 +qmi_name_item(QMIWMS_DELETE_REQ), // 0x0024 +qmi_name_item(QMIWMS_DELETE_RESP), // 0x0024 +qmi_name_item(QMIWMS_GET_MESSAGE_PROTOCOL_REQ), // 0x0030 +qmi_name_item(QMIWMS_GET_MESSAGE_PROTOCOL_RESP), // 0x0030 +qmi_name_item(QMIWMS_LIST_MESSAGES_REQ), // 0x0031 +qmi_name_item(QMIWMS_LIST_MESSAGES_RESP), // 0x0031 +qmi_name_item(QMIWMS_GET_SMSC_ADDRESS_REQ), // 0x0034 +qmi_name_item(QMIWMS_GET_SMSC_ADDRESS_RESP), // 0x0034 +qmi_name_item(QMIWMS_SET_SMSC_ADDRESS_REQ), // 0x0035 +qmi_name_item(QMIWMS_SET_SMSC_ADDRESS_RESP), // 0x0035 +qmi_name_item(QMIWMS_GET_STORE_MAX_SIZE_REQ), // 0x0036 +qmi_name_item(QMIWMS_GET_STORE_MAX_SIZE_RESP), // 0x0036 +}; + +static const QMI_NAME_T qmux_wds_admin_Type[] = { +qmi_name_item(QMIWDS_ADMIN_SET_DATA_FORMAT_REQ), // 0x0020 +qmi_name_item(QMIWDS_ADMIN_SET_DATA_FORMAT_RESP), // 0x0020 +qmi_name_item(QMIWDS_ADMIN_GET_DATA_FORMAT_REQ), // 0x0021 +qmi_name_item(QMIWDS_ADMIN_GET_DATA_FORMAT_RESP), // 0x0021 +qmi_name_item(QMIWDS_ADMIN_SET_QMAP_SETTINGS_REQ), // 0x002B +qmi_name_item(QMIWDS_ADMIN_SET_QMAP_SETTINGS_RESP), // 0x002B +qmi_name_item(QMIWDS_ADMIN_GET_QMAP_SETTINGS_REQ), // 0x002C +qmi_name_item(QMIWDS_ADMIN_GET_QMAP_SETTINGS_RESP), // 0x002C +}; + +static const QMI_NAME_T qmux_uim_Type[] = { +qmi_name_item( QMIUIM_READ_TRANSPARENT_REQ), // 0x0020 +qmi_name_item( QMIUIM_READ_TRANSPARENT_RESP), // 0x0020 +qmi_name_item( QMIUIM_READ_TRANSPARENT_IND), // 0x0020 +qmi_name_item( QMIUIM_READ_RECORD_REQ), // 0x0021 +qmi_name_item( QMIUIM_READ_RECORD_RESP), // 0x0021 +qmi_name_item( QMIUIM_READ_RECORD_IND), // 0x0021 +qmi_name_item( QMIUIM_WRITE_TRANSPARENT_REQ), // 0x0022 +qmi_name_item( QMIUIM_WRITE_TRANSPARENT_RESP), // 0x0022 +qmi_name_item( QMIUIM_WRITE_TRANSPARENT_IND), // 0x0022 +qmi_name_item( QMIUIM_WRITE_RECORD_REQ), // 0x0023 +qmi_name_item( QMIUIM_WRITE_RECORD_RESP), // 0x0023 +qmi_name_item( QMIUIM_WRITE_RECORD_IND), // 0x0023 +qmi_name_item( QMIUIM_SET_PIN_PROTECTION_REQ), // 0x0025 +qmi_name_item( QMIUIM_SET_PIN_PROTECTION_RESP), // 0x0025 +qmi_name_item( QMIUIM_SET_PIN_PROTECTION_IND), // 0x0025 +qmi_name_item( QMIUIM_VERIFY_PIN_REQ), // 0x0026 +qmi_name_item( QMIUIM_VERIFY_PIN_RESP), // 0x0026 +qmi_name_item( QMIUIM_VERIFY_PIN_IND), // 0x0026 +qmi_name_item( QMIUIM_UNBLOCK_PIN_REQ), // 0x0027 +qmi_name_item( QMIUIM_UNBLOCK_PIN_RESP), // 0x0027 +qmi_name_item( QMIUIM_UNBLOCK_PIN_IND), // 0x0027 +qmi_name_item( QMIUIM_CHANGE_PIN_REQ), // 0x0028 +qmi_name_item( QMIUIM_CHANGE_PIN_RESP), // 0x0028 +qmi_name_item( QMIUIM_CHANGE_PIN_IND), // 0x0028 +qmi_name_item( QMIUIM_DEPERSONALIZATION_REQ), // 0x0029 +qmi_name_item( QMIUIM_DEPERSONALIZATION_RESP), // 0x0029 +qmi_name_item( QMIUIM_EVENT_REG_REQ), // 0x002E +qmi_name_item( QMIUIM_EVENT_REG_RESP), // 0x002E +qmi_name_item( QMIUIM_GET_CARD_STATUS_REQ), // 0x002F +qmi_name_item( QMIUIM_GET_CARD_STATUS_RESP), // 0x002F +qmi_name_item( QMIUIM_STATUS_CHANGE_IND), // 0x0032 +}; + +static const char * qmi_name_get(const QMI_NAME_T *table, size_t size, int type, const char *tag) { + static char unknow[40]; + size_t i; + + if (qmux_CtlFlags == table) { + if (!strcmp(tag, "_REQ")) + tag = "_CMD"; + else if (!strcmp(tag, "_RESP")) + tag = "_RSP"; + } + + for (i = 0; i < size; i++) { + if (table[i].type == (UINT)type) { + if (!tag || (strstr(table[i].name, tag))) + return table[i].name; + } + } + sprintf(unknow, "unknow_%x", type); + return unknow; +} + +#define QMI_NAME(table, type) qmi_name_get(table, sizeof(table) / sizeof(table[0]), type, 0) +#define QMUX_NAME(table, type, tag) qmi_name_get(table, sizeof(table) / sizeof(table[0]), type, tag) + +void dump_tlv(PQCQMUX_MSG_HDR pQMUXMsgHdr) { + int TLVFind = 0; + int i; + //dbg("QCQMUX_TLV-----------------------------------\n"); + //dbg("{Type,\tLength,\tValue}\n"); + + while (1) { + PQMI_TLV_HDR TLVHdr = GetTLV(pQMUXMsgHdr, 0x1000 + (++TLVFind)); + if (TLVHdr == NULL) + break; + + //if ((TLVHdr->TLVType == 0x02) && ((USHORT *)(TLVHdr+1))[0]) + { + dbg("{%02x,\t%04x,\t", TLVHdr->TLVType, le16_to_cpu(TLVHdr->TLVLength)); + for (i = 0; i < le16_to_cpu(TLVHdr->TLVLength); i++) { + dbg("%02x ", ((UCHAR *)(TLVHdr+1))[i]); + } + dbg("}\n"); + } + } // while +} + +void dump_ctl(PQCQMICTL_MSG_HDR CTLHdr) { + const char *tag; + + //dbg("QCQMICTL_MSG--------------------------------------------\n"); + //dbg("CtlFlags: %02x\t\t%s\n", CTLHdr->CtlFlags, QMI_NAME(qmi_ctl_CtlFlags, CTLHdr->CtlFlags)); + dbg("TransactionId: %02x\n", CTLHdr->TransactionId); + switch (CTLHdr->CtlFlags) { + case QMICTL_FLAG_REQUEST: tag = "_REQ"; break; + case QMICTL_FLAG_RESPONSE: tag = "_RESP"; break; + case QMICTL_FLAG_INDICATION: tag = "_IND"; break; + default: tag = 0; break; + } + dbg("QMICTLType: %04x\t%s\n", le16_to_cpu(CTLHdr->QMICTLType), + QMUX_NAME(qmux_ctl_QMICTLType, le16_to_cpu(CTLHdr->QMICTLType), tag)); + dbg("Length: %04x\n", le16_to_cpu(CTLHdr->Length)); + + dump_tlv((PQCQMUX_MSG_HDR)(&CTLHdr->QMICTLType)); +} + +int dump_qmux(QMI_SERVICE_TYPE serviceType, PQCQMUX_HDR QMUXHdr) { + PQCQMUX_MSG_HDR QMUXMsgHdr = (PQCQMUX_MSG_HDR) (QMUXHdr + 1); + CHAR *tag; + + //dbg("QCQMUX--------------------------------------------\n"); + switch (QMUXHdr->CtlFlags&QMUX_CTL_FLAG_MASK_TYPE) { + case QMUX_CTL_FLAG_TYPE_CMD: tag = "_REQ"; break; + case QMUX_CTL_FLAG_TYPE_RSP: tag = "_RESP"; break; + case QMUX_CTL_FLAG_TYPE_IND: tag = "_IND"; break; + default: tag = 0; break; + } + //dbg("CtlFlags: %02x\t\t%s\n", QMUXHdr->CtlFlags, QMUX_NAME(qmux_CtlFlags, QMUXHdr->CtlFlags, tag)); + dbg("TransactionId: %04x\n", le16_to_cpu(QMUXHdr->TransactionId)); + + //dbg("QCQMUX_MSG_HDR-----------------------------------\n"); + switch (serviceType) { + case QMUX_TYPE_DMS: + dbg("Type: %04x\t%s\n", le16_to_cpu(QMUXMsgHdr->Type), + QMUX_NAME(qmux_dms_Type, le16_to_cpu(QMUXMsgHdr->Type), tag)); + break; + case QMUX_TYPE_NAS: + dbg("Type: %04x\t%s\n", le16_to_cpu(QMUXMsgHdr->Type), + QMUX_NAME(qmux_nas_Type, le16_to_cpu(QMUXMsgHdr->Type), tag)); + break; + case QMUX_TYPE_WDS: + dbg("Type: %04x\t%s\n", le16_to_cpu(QMUXMsgHdr->Type), + QMUX_NAME(qmux_wds_Type, le16_to_cpu(QMUXMsgHdr->Type), tag)); + break; + case QMUX_TYPE_WMS: + dbg("Type: %04x\t%s\n", le16_to_cpu(QMUXMsgHdr->Type), + QMUX_NAME(qmux_wms_Type, le16_to_cpu(QMUXMsgHdr->Type), tag)); + break; + case QMUX_TYPE_WDS_ADMIN: + dbg("Type: %04x\t%s\n", le16_to_cpu(QMUXMsgHdr->Type), + QMUX_NAME(qmux_wds_admin_Type, le16_to_cpu(QMUXMsgHdr->Type), tag)); + break; + case QMUX_TYPE_UIM: + dbg("Type: %04x\t%s\n", le16_to_cpu(QMUXMsgHdr->Type), + QMUX_NAME(qmux_uim_Type, le16_to_cpu(QMUXMsgHdr->Type), tag)); + break; + case QMUX_TYPE_PDS: + case QMUX_TYPE_QOS: + case QMUX_TYPE_CTL: + default: + dbg("Type: %04x\t%s\n", le16_to_cpu(QMUXMsgHdr->Type), "PDS/QOS/CTL/unknown!"); + break; + } + dbg("Length: %04x\n", le16_to_cpu(QMUXMsgHdr->Length)); + + dump_tlv(QMUXMsgHdr); + + return 0; +} + +void dump_qmi(void *dataBuffer, int dataLen) +{ + PQCQMI_HDR QMIHdr = (PQCQMI_HDR)dataBuffer; + PQCQMUX_HDR QMUXHdr = (PQCQMUX_HDR) (QMIHdr + 1); + PQCQMICTL_MSG_HDR CTLHdr = (PQCQMICTL_MSG_HDR) (QMIHdr + 1); + + int i; + + if (!debug_qmi) + return; + + pthread_mutex_lock(&dumpQMIMutex); + line[0] = 0; + for (i = 0; i < dataLen; i++) { + dbg("%02x ", ((unsigned char *)dataBuffer)[i]); + } + dbg_time("%s", line); + line[0] = 0; + + //dbg("QCQMI_HDR-----------------------------------------"); + //dbg("IFType: %02x\t\t%s", QMIHdr->IFType, QMI_NAME(qmi_IFType, QMIHdr->IFType)); + //dbg("Length: %04x", le16_to_cpu(QMIHdr->Length)); + //dbg("CtlFlags: %02x\t\t%s", QMIHdr->CtlFlags, QMI_NAME(qmi_CtlFlags, QMIHdr->CtlFlags)); + //dbg("QMIType: %02x\t\t%s", QMIHdr->QMIType, QMI_NAME(qmi_QMIType, QMIHdr->QMIType)); + //dbg("ClientId: %02x", QMIHdr->ClientId); + + if ((QMIHdr->QMIType == QMUX_TYPE_CTL) ) { + dump_ctl(CTLHdr); + } else { + dump_qmux(QMIHdr->QMIType, QMUXHdr); + } + dbg_time("%s", line); + pthread_mutex_unlock(&dumpQMIMutex); +} diff --git a/root/package/link4all/quectel-CM/src_quectel/MPQMUX.h b/root/package/link4all/quectel-CM/src_quectel/MPQMUX.h new file mode 100755 index 00000000..a15cad53 --- /dev/null +++ b/root/package/link4all/quectel-CM/src_quectel/MPQMUX.h @@ -0,0 +1,3244 @@ +/*=========================================================================== + + M P Q M U X. H +DESCRIPTION: + + This file provides support for QMUX. + +INITIALIZATION AND SEQUENCING REQUIREMENTS: + +Copyright (C) 2011 by Qualcomm Technologies, Incorporated. All Rights Reserved. +===========================================================================*/ + +#ifndef MPQMUX_H +#define MPQMUX_H + +#include "MPQMI.h" + +#pragma pack(push, 1) + +#define QMIWDS_SET_EVENT_REPORT_REQ 0x0001 +#define QMIWDS_SET_EVENT_REPORT_RESP 0x0001 +#define QMIWDS_EVENT_REPORT_IND 0x0001 +#define QMIWDS_START_NETWORK_INTERFACE_REQ 0x0020 +#define QMIWDS_START_NETWORK_INTERFACE_RESP 0x0020 +#define QMIWDS_STOP_NETWORK_INTERFACE_REQ 0x0021 +#define QMIWDS_STOP_NETWORK_INTERFACE_RESP 0x0021 +#define QMIWDS_GET_PKT_SRVC_STATUS_REQ 0x0022 +#define QMIWDS_GET_PKT_SRVC_STATUS_RESP 0x0022 +#define QMIWDS_GET_PKT_SRVC_STATUS_IND 0x0022 +#define QMIWDS_GET_CURRENT_CHANNEL_RATE_REQ 0x0023 +#define QMIWDS_GET_CURRENT_CHANNEL_RATE_RESP 0x0023 +#define QMIWDS_GET_PKT_STATISTICS_REQ 0x0024 +#define QMIWDS_GET_PKT_STATISTICS_RESP 0x0024 +#define QMIWDS_MODIFY_PROFILE_SETTINGS_REQ 0x0028 +#define QMIWDS_MODIFY_PROFILE_SETTINGS_RESP 0x0028 +#define QMIWDS_GET_PROFILE_SETTINGS_REQ 0x002B +#define QMIWDS_GET_PROFILE_SETTINGS_RESP 0x002B +#define QMIWDS_GET_DEFAULT_SETTINGS_REQ 0x002C +#define QMIWDS_GET_DEFAULT_SETTINGS_RESP 0x002C +#define QMIWDS_GET_RUNTIME_SETTINGS_REQ 0x002D +#define QMIWDS_GET_RUNTIME_SETTINGS_RESP 0x002D +#define QMIWDS_GET_MIP_MODE_REQ 0x002F +#define QMIWDS_GET_MIP_MODE_RESP 0x002F +#define QMIWDS_GET_DATA_BEARER_REQ 0x0037 +#define QMIWDS_GET_DATA_BEARER_RESP 0x0037 +#define QMIWDS_DUN_CALL_INFO_REQ 0x0038 +#define QMIWDS_DUN_CALL_INFO_RESP 0x0038 +#define QMIWDS_DUN_CALL_INFO_IND 0x0038 +#define QMIWDS_SET_CLIENT_IP_FAMILY_PREF_REQ 0x004D +#define QMIWDS_SET_CLIENT_IP_FAMILY_PREF_RESP 0x004D +#define QMIWDS_BIND_MUX_DATA_PORT_REQ 0x00A2 +#define QMIWDS_BIND_MUX_DATA_PORT_RESP 0x00A2 + + +// Stats masks +#define QWDS_STAT_MASK_TX_PKT_OK 0x00000001 +#define QWDS_STAT_MASK_RX_PKT_OK 0x00000002 +#define QWDS_STAT_MASK_TX_PKT_ER 0x00000004 +#define QWDS_STAT_MASK_RX_PKT_ER 0x00000008 +#define QWDS_STAT_MASK_TX_PKT_OF 0x00000010 +#define QWDS_STAT_MASK_RX_PKT_OF 0x00000020 + +// TLV Types for xfer statistics +#define TLV_WDS_TX_GOOD_PKTS 0x10 +#define TLV_WDS_RX_GOOD_PKTS 0x11 +#define TLV_WDS_TX_ERROR 0x12 +#define TLV_WDS_RX_ERROR 0x13 +#define TLV_WDS_TX_OVERFLOW 0x14 +#define TLV_WDS_RX_OVERFLOW 0x15 +#define TLV_WDS_CHANNEL_RATE 0x16 +#define TLV_WDS_DATA_BEARER 0x17 +#define TLV_WDS_DORMANCY_STATUS 0x18 + +#define QWDS_PKT_DATA_DISCONNECTED 0x01 +#define QWDS_PKT_DATA_CONNECTED 0x02 +#define QWDS_PKT_DATA_SUSPENDED 0x03 +#define QWDS_PKT_DATA_AUTHENTICATING 0x04 + +#define QMIWDS_ADMIN_SET_DATA_FORMAT_REQ 0x0020 +#define QMIWDS_ADMIN_SET_DATA_FORMAT_RESP 0x0020 +#define QMIWDS_ADMIN_GET_DATA_FORMAT_REQ 0x0021 +#define QMIWDS_ADMIN_GET_DATA_FORMAT_RESP 0x0021 +#define QMIWDS_ADMIN_SET_QMAP_SETTINGS_REQ 0x002B +#define QMIWDS_ADMIN_SET_QMAP_SETTINGS_RESP 0x002B +#define QMIWDS_ADMIN_GET_QMAP_SETTINGS_REQ 0x002C +#define QMIWDS_ADMIN_GET_QMAP_SETTINGS_RESP 0x002C + +#define NETWORK_DESC_ENCODING_OCTET 0x00 +#define NETWORK_DESC_ENCODING_EXTPROTOCOL 0x01 +#define NETWORK_DESC_ENCODING_7BITASCII 0x02 +#define NETWORK_DESC_ENCODING_IA5 0x03 +#define NETWORK_DESC_ENCODING_UNICODE 0x04 +#define NETWORK_DESC_ENCODING_SHIFTJIS 0x05 +#define NETWORK_DESC_ENCODING_KOREAN 0x06 +#define NETWORK_DESC_ENCODING_LATINH 0x07 +#define NETWORK_DESC_ENCODING_LATIN 0x08 +#define NETWORK_DESC_ENCODING_GSM7BIT 0x09 +#define NETWORK_DESC_ENCODING_GSMDATA 0x0A +#define NETWORK_DESC_ENCODING_UNKNOWN 0xFF + +typedef struct _QMIWDS_ADMIN_SET_DATA_FORMAT +{ + USHORT Type; // QMUX type 0x0000 + USHORT Length; +} __attribute__ ((packed)) QMIWDS_ADMIN_SET_DATA_FORMAT, *PQMIWDS_ADMIN_SET_DATA_FORMAT; + +typedef struct _QMIWDS_ADMIN_SET_DATA_FORMAT_TLV_QOS +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR QOSSetting; +} __attribute__ ((packed)) QMIWDS_ADMIN_SET_DATA_FORMAT_TLV_QOS, *PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV_QOS; + +typedef struct _QMIWDS_ADMIN_SET_DATA_FORMAT_TLV +{ + UCHAR TLVType; + USHORT TLVLength; + ULONG Value; +} __attribute__ ((packed)) QMIWDS_ADMIN_SET_DATA_FORMAT_TLV, *PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV; + +#if 0 +typedef struct _QMIWDS_ENDPOINT_TLV +{ + UCHAR TLVType; + USHORT TLVLength; + ULONG ep_type; + ULONG iface_id; +} QMIWDS_ENDPOINT_TLV, *PQMIWDS_ENDPOINT_TLV; + +typedef enum _QMI_RETURN_CODES { + QMI_SUCCESS = 0, + QMI_SUCCESS_NOT_COMPLETE, + QMI_FAILURE +}QMI_RETURN_CODES; + +typedef struct _QMIWDS_GET_PKT_SRVC_STATUS_REQ_MSG +{ + USHORT Type; // 0x0022 + USHORT Length; // 0x0000 +} QMIWDS_GET_PKT_SRVC_STATUS_REQ_MSG, *PQMIWDS_GET_PKT_SRVC_STATUS_REQ_MSG; + +typedef struct _QMIWDS_GET_PKT_SRVC_STATUS_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLVType2; + USHORT TLVLength2; + UCHAR ConnectionStatus; // 0x01: QWDS_PKT_DATAC_DISCONNECTED + // 0x02: QWDS_PKT_DATA_CONNECTED + // 0x03: QWDS_PKT_DATA_SUSPENDED + // 0x04: QWDS_PKT_DATA_AUTHENTICATING +} QMIWDS_GET_PKT_SRVC_STATUS_RESP_MSG, *PQMIWDS_GET_PKT_SRVC_STATUS_RESP_MSG; + +typedef struct _QMIWDS_GET_PKT_SRVC_STATUS_IND_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR ConnectionStatus; // 0x01: QWDS_PKT_DATAC_DISCONNECTED + // 0x02: QWDS_PKT_DATA_CONNECTED + // 0x03: QWDS_PKT_DATA_SUSPENDED + UCHAR ReconfigRequired; // 0x00: No need to reconfigure + // 0x01: Reconfiguration required +} QMIWDS_GET_PKT_SRVC_STATUS_IND_MSG, *PQMIWDS_GET_PKT_SRVC_STATUS_IND_MSG; + +typedef struct _WDS_PKT_SRVC_IP_FAMILY_TLV +{ + UCHAR TLVType; // 0x12 + USHORT TLVLength; // 1 + UCHAR IpFamily; // IPV4-0x04, IPV6-0x06 +} WDS_PKT_SRVC_IP_FAMILY_TLV, *PWDS_PKT_SRVC_IP_FAMILY_TLV; + +typedef struct _QMIWDS_DUN_CALL_INFO_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + ULONG Mask; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR ReportConnectionStatus; +} QMIWDS_DUN_CALL_INFO_REQ_MSG, *PQMIWDS_DUN_CALL_INFO_REQ_MSG; + +typedef struct _QMIWDS_DUN_CALL_INFO_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} QMIWDS_DUN_CALL_INFO_RESP_MSG, *PQMIWDS_DUN_CALL_INFO_RESP_MSG; + +typedef struct _QMIWDS_DUN_CALL_INFO_IND_MSG +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR ConnectionStatus; +} QMIWDS_DUN_CALL_INFO_IND_MSG, *PQMIWDS_DUN_CALL_INFO_IND_MSG; + +typedef struct _QMIWDS_GET_CURRENT_CHANNEL_RATE_REQ_MSG +{ + USHORT Type; // QMUX type 0x0040 + USHORT Length; +} QMIWDS_GET_CURRENT_CHANNEL_RATE_REQ_MSG, *PQMIWDS_GET_CURRENT_CHANNEL_RATE_REQ_MSG; + +typedef struct _QMIWDS_GET_CURRENT_CHANNEL_RATE_RESP_MSG +{ + USHORT Type; // QMUX type 0x0040 + USHORT Length; + UCHAR TLVType; // 0x02 + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + + UCHAR TLV2Type; // 0x01 + USHORT TLV2Length; // 16 + //ULONG CallHandle; // Context corresponding to reported channel + ULONG CurrentTxRate; // bps + ULONG CurrentRxRate; // bps + ULONG ServingSystemTxRate; // bps + ULONG ServingSystemRxRate; // bps + +} QMIWDS_GET_CURRENT_CHANNEL_RATE_RESP_MSG, *PQMIWDS_GET_CURRENT_CHANNEL_RATE_RESP; + +#define QWDS_EVENT_REPORT_MASK_RATES 0x01 +#define QWDS_EVENT_REPORT_MASK_STATS 0x02 + +#ifdef QCUSB_MUX_PROTOCOL +#error code not present +#endif // QCUSB_MUX_PROTOCOL + +typedef struct _QMIWDS_SET_EVENT_REPORT_REQ_MSG +{ + USHORT Type; // QMUX type 0x0042 + USHORT Length; + + UCHAR TLVType; // 0x10 -- current channel rate indicator + USHORT TLVLength; // 1 + UCHAR Mode; // 0-do not report; 1-report when rate changes + + UCHAR TLV2Type; // 0x11 + USHORT TLV2Length; // 5 + UCHAR StatsPeriod; // seconds between reports; 0-do not report + ULONG StatsMask; // + + UCHAR TLV3Type; // 0x12 -- current data bearer indicator + USHORT TLV3Length; // 1 + UCHAR Mode3; // 0-do not report; 1-report when changes + + UCHAR TLV4Type; // 0x13 -- dormancy status indicator + USHORT TLV4Length; // 1 + UCHAR DormancyStatus; // 0-do not report; 1-report when changes +} QMIWDS_SET_EVENT_REPORT_REQ_MSG, *PQMIWDS_SET_EVENT_REPORT_REQ_MSG; + +typedef struct _QMIWDS_SET_EVENT_REPORT_RESP_MSG +{ + USHORT Type; // QMUX type 0x0042 + USHORT Length; + + UCHAR TLVType; // 0x02 result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_NO_BATTERY + // QMI_ERR_FAULT +} QMIWDS_SET_EVENT_REPORT_RESP_MSG, *PQMIWDS_SET_EVENT_REPORT_RESP_MSG; + +typedef struct _QMIWDS_EVENT_REPORT_IND_MSG +{ + USHORT Type; // QMUX type 0x0001 + USHORT Length; +} QMIWDS_EVENT_REPORT_IND_MSG, *PQMIWDS_EVENT_REPORT_IND_MSG; + +// PQCTLV_PKT_STATISTICS + +typedef struct _QMIWDS_EVENT_REPORT_IND_CHAN_RATE_TLV +{ + UCHAR Type; + USHORT Length; // 8 + ULONG TxRate; + ULONG RxRate; +} QMIWDS_EVENT_REPORT_IND_CHAN_RATE_TLV, *PQMIWDS_EVENT_REPORT_IND_CHAN_RATE_TLV; + +#ifdef QCUSB_MUX_PROTOCOL +#error code not present +#endif // QCUSB_MUX_PROTOCOL + +typedef struct _QMIWDS_GET_PKT_STATISTICS_REQ_MSG +{ + USHORT Type; // QMUX type 0x0041 + USHORT Length; + UCHAR TLVType; // 0x01 + USHORT TLVLength; // 4 + ULONG StateMask; // 0x00000001 tx success packets + // 0x00000002 rx success packets + // 0x00000004 rx packet errors (checksum) + // 0x00000008 rx packets dropped (memory) + +} QMIWDS_GET_PKT_STATISTICS_REQ_MSG, *PQMIWDS_GET_PKT_STATISTICS_REQ_MSG; + +typedef struct _QMIWDS_GET_PKT_STATISTICS_RESP_MSG +{ + USHORT Type; // QMUX type 0x0041 + USHORT Length; + UCHAR TLVType; // 0x02 + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} QMIWDS_GET_PKT_STATISTICS_RESP_MSG, *PQMIWDS_GET_PKT_STATISTICS_RESP_MSG; + +// optional TLV for stats +typedef struct _QCTLV_PKT_STATISTICS +{ + UCHAR TLVType; // see above definitions for TLV types + USHORT TLVLength; // 4 + ULONG Count; +} QCTLV_PKT_STATISTICS, *PQCTLV_PKT_STATISTICS; +#endif + +//#ifdef QC_IP_MODE + +#define QMIWDS_GET_RUNTIME_SETTINGS_MASK_IPV4DNS_ADDR 0x0010 +#define QMIWDS_GET_RUNTIME_SETTINGS_MASK_IPV4_ADDR 0x0100 +#define QMIWDS_GET_RUNTIME_SETTINGS_MASK_IPV4GATEWAY_ADDR 0x0200 +#define QMIWDS_GET_RUNTIME_SETTINGS_MASK_MTU 0x2000 + +typedef struct _QMIWDS_GET_RUNTIME_SETTINGS_REQ_MSG +{ + USHORT Type; // QMIWDS_GET_RUNTIME_SETTINGS_REQ + USHORT Length; + UCHAR TLVType; // 0x10 + USHORT TLVLength; // 0x0004 + ULONG Mask; // mask, bit 8: IP addr -- 0x0100 +} __attribute__ ((packed)) QMIWDS_GET_RUNTIME_SETTINGS_REQ_MSG, *PQMIWDS_GET_RUNTIME_SETTINGS_REQ_MSG; + +typedef struct _QMIWDS_BIND_MUX_DATA_PORT_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + ULONG ep_type; + ULONG iface_id; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR MuxId; + UCHAR TLV3Type; + USHORT TLV3Length; + ULONG client_type; +} __attribute__ ((packed)) QMIWDS_BIND_MUX_DATA_PORT_REQ_MSG, *PQMIWDS_BIND_MUX_DATA_PORT_REQ_MSG; + +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4PRIMARYDNS 0x15 +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4SECONDARYDNS 0x16 +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4 0x1E +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4GATEWAY 0x20 +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4SUBNET 0x21 + +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6 0x25 +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6GATEWAY 0x26 +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6PRIMARYDNS 0x27 +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6SECONDARYDNS 0x28 +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_MTU 0x29 + +typedef struct _QMIWDS_GET_RUNTIME_SETTINGS_TLV_MTU +{ + UCHAR TLVType; // QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_MTU + USHORT TLVLength; // 4 + ULONG Mtu; // MTU +} __attribute__ ((packed)) QMIWDS_GET_RUNTIME_SETTINGS_TLV_MTU, *PQMIWDS_GET_RUNTIME_SETTINGS_TLV_MTU; + +typedef struct _QMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR +{ + UCHAR TLVType; // QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4 + USHORT TLVLength; // 4 + ULONG IPV4Address; // address +} __attribute__ ((packed)) QMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR, *PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR; + +typedef struct _QMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV6_ADDR +{ + UCHAR TLVType; // QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6 + USHORT TLVLength; // 16 + UCHAR IPV6Address[16]; // address + UCHAR PrefixLength; // prefix length +} __attribute__ ((packed)) QMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV6_ADDR, *PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV6_ADDR; + +typedef struct _QMIWDS_GET_RUNTIME_SETTINGS_RESP_MSG +{ + USHORT Type; // QMIWDS_GET_RUNTIME_SETTINGS_RESP + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_RESULT_CODE + USHORT TLVLength; // 0x0004 + USHORT QMUXResult; // result code + USHORT QMUXError; // error code +} __attribute__ ((packed)) QMIWDS_GET_RUNTIME_SETTINGS_RESP_MSG, *PQMIWDS_GET_RUNTIME_SETTINGS_RESP_MSG; + +//#endif // QC_IP_MODE + +typedef struct _QMIWDS_IP_FAMILY_TLV +{ + UCHAR TLVType; // 0x12 + USHORT TLVLength; // 1 + UCHAR IpFamily; // IPV4-0x04, IPV6-0x06 +} __attribute__ ((packed)) QMIWDS_IP_FAMILY_TLV, *PQMIWDS_IP_FAMILY_TLV; + +typedef struct _QMIWDS_PKT_SRVC_TLV +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR ConnectionStatus; + UCHAR ReconfigReqd; +} __attribute__ ((packed)) QMIWDS_PKT_SRVC_TLV, *PQMIWDS_PKT_SRVC_TLV; + +#if 0 +typedef struct _QMIWDS_CALL_END_REASON_TLV +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT CallEndReason; +} QMIWDS_CALL_END_REASON_TLV, *PQMIWDS_CALL_END_REASON_TLV; + +typedef struct _QMIWDS_CALL_END_REASON_V_TLV +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT CallEndReasonType; + USHORT CallEndReason; +} QMIWDS_CALL_END_REASON_V_TLV, *PQMIWDS_CALL_END_REASON_V_TLV; + +typedef struct _QMIWDS_SET_CLIENT_IP_FAMILY_PREF_REQ_MSG +{ + USHORT Type; // QMUX type 0x004D + USHORT Length; + UCHAR TLVType; // 0x01 + USHORT TLVLength; // 1 + UCHAR IpPreference; // IPV4-0x04, IPV6-0x06 +} QMIWDS_SET_CLIENT_IP_FAMILY_PREF_REQ_MSG, *PQMIWDS_SET_CLIENT_IP_FAMILY_PREF_REQ_MSG; + +typedef struct _QMIWDS_SET_CLIENT_IP_FAMILY_PREF_RESP_MSG +{ + USHORT Type; // QMUX type 0x0037 + USHORT Length; + UCHAR TLVType; // 0x02 + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS, QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INTERNAL, QMI_ERR_MALFORMED_MSG, QMI_ERR_INVALID_ARG +} QMIWDS_SET_CLIENT_IP_FAMILY_PREF_RESP_MSG, *PQMIWDS_SET_CLIENT_IP_FAMILY_PREF_RESP_MSG; + +typedef struct _QMIWDS_GET_MIP_MODE_REQ_MSG +{ + USHORT Type; // QMUX type 0x0040 + USHORT Length; +} QMIWDS_GET_MIP_MODE_REQ_MSG, *PQMIWDS_GET_MIP_MODE_REQ_MSG; + +typedef struct _QMIWDS_GET_MIP_MODE_RESP_MSG +{ + USHORT Type; // QMUX type 0x0040 + USHORT Length; + UCHAR TLVType; // 0x02 + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + + UCHAR TLV2Type; // 0x01 + USHORT TLV2Length; // 20 + UCHAR MipMode; // +} QMIWDS_GET_MIP_MODE_RESP_MSG, *PQMIWDS_GET_MIP_MODE_RESP_MSG; +#endif + +typedef struct _QMIWDS_TECHNOLOGY_PREFERECE +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR TechPreference; +} __attribute__ ((packed)) QMIWDS_TECHNOLOGY_PREFERECE, *PQMIWDS_TECHNOLOGY_PREFERECE; + +typedef struct _QMIWDS_PROFILE_IDENTIFIER +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR ProfileIndex; +} __attribute__ ((packed)) QMIWDS_PROFILE_IDENTIFIER, *PQMIWDS_PROFILE_IDENTIFIER; + +#if 0 +typedef struct _QMIWDS_IPADDRESS +{ + UCHAR TLVType; + USHORT TLVLength; + ULONG IPv4Address; +}QMIWDS_IPADDRESS, *PQMIWDS_IPADDRESS; + +/* +typedef struct _QMIWDS_UMTS_QOS +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR TrafficClass; + ULONG MaxUplinkBitRate; + ULONG MaxDownlinkBitRate; + ULONG GuarUplinkBitRate; + ULONG GuarDownlinkBitRate; + UCHAR QOSDevOrder; + ULONG MAXSDUSize; + UCHAR SDUErrorRatio; + UCHAR ResidualBerRatio; + UCHAR DeliveryErrorSDUs; + ULONG TransferDelay; + ULONG TrafficHndPri; +}QMIWDS_UMTS_QOS, *PQMIWDS_UMTS_QOS; + +typedef struct _QMIWDS_GPRS_QOS +{ + UCHAR TLVType; + USHORT TLVLength; + ULONG PrecedenceClass; + ULONG DelayClass; + ULONG ReliabilityClass; + ULONG PeekThroClass; + ULONG MeanThroClass; +}QMIWDS_GPRS_QOS, *PQMIWDS_GPRS_QOS; +*/ +#endif + +typedef struct _QMIWDS_PROFILENAME +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR ProfileName; +} __attribute__ ((packed)) QMIWDS_PROFILENAME, *PQMIWDS_PROFILENAME; + +typedef struct _QMIWDS_PDPTYPE +{ + UCHAR TLVType; + USHORT TLVLength; +// 0 ¨C PDP-IP (IPv4) +// 1 ¨C PDP-PPP +// 2 ¨C PDP-IPv6 +// 3 ¨C PDP-IPv4v6 + UCHAR PdpType; +} __attribute__ ((packed)) QMIWDS_PDPTYPE, *PQMIWDS_PDPTYPE; + +typedef struct _QMIWDS_USERNAME +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR UserName; +} __attribute__ ((packed)) QMIWDS_USERNAME, *PQMIWDS_USERNAME; + +typedef struct _QMIWDS_PASSWD +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR Passwd; +} __attribute__ ((packed)) QMIWDS_PASSWD, *PQMIWDS_PASSWD; + +typedef struct _QMIWDS_AUTH_PREFERENCE +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR AuthPreference; +} __attribute__ ((packed)) QMIWDS_AUTH_PREFERENCE, *PQMIWDS_AUTH_PREFERENCE; + +typedef struct _QMIWDS_APNNAME +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR ApnName; +} __attribute__ ((packed)) QMIWDS_APNNAME, *PQMIWDS_APNNAME; + +typedef struct _QMIWDS_AUTOCONNECT +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR AutoConnect; +} __attribute__ ((packed)) QMIWDS_AUTOCONNECT, *PQMIWDS_AUTOCONNECT; + +typedef struct _QMIWDS_START_NETWORK_INTERFACE_REQ_MSG +{ + USHORT Type; + USHORT Length; +} __attribute__ ((packed)) QMIWDS_START_NETWORK_INTERFACE_REQ_MSG, *PQMIWDS_START_NETWORK_INTERFACE_REQ_MSG; + +typedef struct _QMIWDS_CALLENDREASON +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT Reason; +}__attribute__ ((packed)) QMIWDS_CALLENDREASON, *PQMIWDS_CALLENDREASON; + +typedef struct _QMIWDS_START_NETWORK_INTERFACE_RESP_MSG +{ + USHORT Type; // QMUX type 0x0040 + USHORT Length; + UCHAR TLVType; // 0x02 + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + + UCHAR TLV2Type; // 0x01 + USHORT TLV2Length; // 20 + ULONG Handle; // +} __attribute__ ((packed)) QMIWDS_START_NETWORK_INTERFACE_RESP_MSG, *PQMIWDS_START_NETWORK_INTERFACE_RESP_MSG; + +typedef struct _QMIWDS_STOP_NETWORK_INTERFACE_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + ULONG Handle; +} __attribute__ ((packed)) QMIWDS_STOP_NETWORK_INTERFACE_REQ_MSG, *PQMIWDS_STOP_NETWORK_INTERFACE_REQ_MSG; + +typedef struct _QMIWDS_STOP_NETWORK_INTERFACE_RESP_MSG +{ + USHORT Type; // QMUX type 0x0040 + USHORT Length; + UCHAR TLVType; // 0x02 + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + +} __attribute__ ((packed)) QMIWDS_STOP_NETWORK_INTERFACE_RESP_MSG, *PQMIWDS_STOP_NETWORK_INTERFACE_RESP_MSG; + +typedef struct _QMIWDS_GET_DEFAULT_SETTINGS_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR ProfileType; +} __attribute__ ((packed)) QMIWDS_GET_DEFAULT_SETTINGS_REQ_MSG, *PQMIWDS_GET_DEFAULT_SETTINGS_REQ_MSG; + +typedef struct _QMIWDS_GET_DEFAULT_SETTINGS_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} __attribute__ ((packed)) QMIWDS_GET_DEFAULT_SETTINGS_RESP_MSG, *PQMIWDS_GET_DEFAULT_SETTINGS_RESP_MSG; + +typedef struct _QMIWDS_MODIFY_PROFILE_SETTINGS_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR ProfileType; + UCHAR ProfileIndex; +} __attribute__ ((packed)) QMIWDS_MODIFY_PROFILE_SETTINGS_REQ_MSG, *PQMIWDS_MODIFY_PROFILE_SETTINGS_REQ_MSG; + +typedef struct _QMIWDS_MODIFY_PROFILE_SETTINGS_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} __attribute__ ((packed)) QMIWDS_MODIFY_PROFILE_SETTINGS_RESP_MSG, *PQMIWDS_MODIFY_PROFILE_SETTINGS_RESP_MSG; + +typedef struct _QMIWDS_GET_PROFILE_SETTINGS_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR ProfileType; + UCHAR ProfileIndex; +} __attribute__ ((packed)) QMIWDS_GET_PROFILE_SETTINGS_REQ_MSG, *PQMIWDS_GET_PROFILE_SETTINGS_REQ_MSG; + +#if 0 +typedef struct _QMIWDS_EVENT_REPORT_IND_DATA_BEARER_TLV +{ + UCHAR Type; + USHORT Length; + UCHAR DataBearer; +} QMIWDS_EVENT_REPORT_IND_DATA_BEARER_TLV, *PQMIWDS_EVENT_REPORT_IND_DATA_BEARER_TLV; + +typedef struct _QMIWDS_EVENT_REPORT_IND_DORMANCY_STATUS_TLV +{ + UCHAR Type; + USHORT Length; + UCHAR DormancyStatus; +} QMIWDS_EVENT_REPORT_IND_DORMANCY_STATUS_TLV, *PQMIWDS_EVENT_REPORT_IND_DORMANCY_STATUS_TLV; + + +typedef struct _QMIWDS_GET_DATA_BEARER_REQ_MSG +{ + USHORT Type; // QMUX type 0x0037 + USHORT Length; +} QMIWDS_GET_DATA_BEARER_REQ_MSG, *PQMIWDS_GET_DATA_BEARER_REQ_MSG; + +typedef struct _QMIWDS_GET_DATA_BEARER_RESP_MSG +{ + USHORT Type; // QMUX type 0x0037 + USHORT Length; + UCHAR TLVType; // 0x02 + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INTERNAL + // QMI_ERR_MALFORMED_MSG + // QMI_ERR_NO_MEMORY + // QMI_ERR_OUT_OF_CALL + // QMI_ERR_INFO_UNAVAILABLE + UCHAR TLV2Type; // 0x01 + USHORT TLV2Length; // + UCHAR Technology; // +} QMIWDS_GET_DATA_BEARER_RESP_MSG, *PQMIWDS_GET_DATA_BEARER_RESP_MSG; +#endif + +// ======================= DMS ============================== +#define QMIDMS_SET_EVENT_REPORT_REQ 0x0001 +#define QMIDMS_SET_EVENT_REPORT_RESP 0x0001 +#define QMIDMS_EVENT_REPORT_IND 0x0001 +#define QMIDMS_GET_DEVICE_CAP_REQ 0x0020 +#define QMIDMS_GET_DEVICE_CAP_RESP 0x0020 +#define QMIDMS_GET_DEVICE_MFR_REQ 0x0021 +#define QMIDMS_GET_DEVICE_MFR_RESP 0x0021 +#define QMIDMS_GET_DEVICE_MODEL_ID_REQ 0x0022 +#define QMIDMS_GET_DEVICE_MODEL_ID_RESP 0x0022 +#define QMIDMS_GET_DEVICE_REV_ID_REQ 0x0023 +#define QMIDMS_GET_DEVICE_REV_ID_RESP 0x0023 +#define QMIDMS_GET_MSISDN_REQ 0x0024 +#define QMIDMS_GET_MSISDN_RESP 0x0024 +#define QMIDMS_GET_DEVICE_SERIAL_NUMBERS_REQ 0x0025 +#define QMIDMS_GET_DEVICE_SERIAL_NUMBERS_RESP 0x0025 +#define QMIDMS_UIM_SET_PIN_PROTECTION_REQ 0x0027 +#define QMIDMS_UIM_SET_PIN_PROTECTION_RESP 0x0027 +#define QMIDMS_UIM_VERIFY_PIN_REQ 0x0028 +#define QMIDMS_UIM_VERIFY_PIN_RESP 0x0028 +#define QMIDMS_UIM_UNBLOCK_PIN_REQ 0x0029 +#define QMIDMS_UIM_UNBLOCK_PIN_RESP 0x0029 +#define QMIDMS_UIM_CHANGE_PIN_REQ 0x002A +#define QMIDMS_UIM_CHANGE_PIN_RESP 0x002A +#define QMIDMS_UIM_GET_PIN_STATUS_REQ 0x002B +#define QMIDMS_UIM_GET_PIN_STATUS_RESP 0x002B +#define QMIDMS_GET_DEVICE_HARDWARE_REV_REQ 0x002C +#define QMIDMS_GET_DEVICE_HARDWARE_REV_RESP 0x002C +#define QMIDMS_GET_OPERATING_MODE_REQ 0x002D +#define QMIDMS_GET_OPERATING_MODE_RESP 0x002D +#define QMIDMS_SET_OPERATING_MODE_REQ 0x002E +#define QMIDMS_SET_OPERATING_MODE_RESP 0x002E +#define QMIDMS_GET_ACTIVATED_STATUS_REQ 0x0031 +#define QMIDMS_GET_ACTIVATED_STATUS_RESP 0x0031 +#define QMIDMS_ACTIVATE_AUTOMATIC_REQ 0x0032 +#define QMIDMS_ACTIVATE_AUTOMATIC_RESP 0x0032 +#define QMIDMS_ACTIVATE_MANUAL_REQ 0x0033 +#define QMIDMS_ACTIVATE_MANUAL_RESP 0x0033 +#define QMIDMS_UIM_GET_ICCID_REQ 0x003C +#define QMIDMS_UIM_GET_ICCID_RESP 0x003C +#define QMIDMS_UIM_GET_CK_STATUS_REQ 0x0040 +#define QMIDMS_UIM_GET_CK_STATUS_RESP 0x0040 +#define QMIDMS_UIM_SET_CK_PROTECTION_REQ 0x0041 +#define QMIDMS_UIM_SET_CK_PROTECTION_RESP 0x0041 +#define QMIDMS_UIM_UNBLOCK_CK_REQ 0x0042 +#define QMIDMS_UIM_UNBLOCK_CK_RESP 0x0042 +#define QMIDMS_UIM_GET_IMSI_REQ 0x0043 +#define QMIDMS_UIM_GET_IMSI_RESP 0x0043 +#define QMIDMS_UIM_GET_STATE_REQ 0x0044 +#define QMIDMS_UIM_GET_STATE_RESP 0x0044 +#define QMIDMS_GET_BAND_CAP_REQ 0x0045 +#define QMIDMS_GET_BAND_CAP_RESP 0x0045 + +#if 0 +typedef struct _QMIDMS_GET_DEVICE_MFR_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; +} QMIDMS_GET_DEVICE_MFR_REQ_MSG, *PQMIDMS_GET_DEVICE_MFR_REQ_MSG; + +typedef struct _QMIDMS_GET_DEVICE_MFR_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR TLV2Type; // 0x01 - required parameter + USHORT TLV2Length; // length of the mfr string + UCHAR DeviceManufacturer; // first byte of string +} QMIDMS_GET_DEVICE_MFR_RESP_MSG, *PQMIDMS_GET_DEVICE_MFR_RESP_MSG; + +typedef struct _QMIDMS_GET_DEVICE_MODEL_ID_REQ_MSG +{ + USHORT Type; // QMUX type 0x0004 + USHORT Length; +} QMIDMS_GET_DEVICE_MODEL_ID_REQ_MSG, *PQMIDMS_GET_DEVICE_MODEL_ID_REQ_MSG; + +typedef struct _QMIDMS_GET_DEVICE_MODEL_ID_RESP_MSG +{ + USHORT Type; // QMUX type 0x0004 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR TLV2Type; // 0x01 - required parameter + USHORT TLV2Length; // length of the modem id string + UCHAR DeviceModelID; // device model id +} QMIDMS_GET_DEVICE_MODEL_ID_RESP_MSG, *PQMIDMS_GET_DEVICE_MODEL_ID_RESP_MSG; +#endif + +typedef struct _QMIDMS_GET_DEVICE_REV_ID_REQ_MSG +{ + USHORT Type; // QMUX type 0x0005 + USHORT Length; +} __attribute__ ((packed)) QMIDMS_GET_DEVICE_REV_ID_REQ_MSG, *PQMIDMS_GET_DEVICE_REV_ID_REQ_MSG; + +typedef struct _DEVICE_REV_ID +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR RevisionID; +} __attribute__ ((packed)) DEVICE_REV_ID, *PDEVICE_REV_ID; + +#if 0 +typedef struct _QMIDMS_GET_DEVICE_REV_ID_RESP_MSG +{ + USHORT Type; // QMUX type 0x0023 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} QMIDMS_GET_DEVICE_REV_ID_RESP_MSG, *PQMIDMS_GET_DEVICE_REV_ID_RESP_MSG; + +typedef struct _QMIDMS_GET_MSISDN_REQ_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; +} QMIDMS_GET_MSISDN_REQ_MSG, *PQMIDMS_GET_MSISDN_REQ_MSG; + +typedef struct _QCTLV_DEVICE_VOICE_NUMBERS +{ + UCHAR TLVType; // as defined above + USHORT TLVLength; // 4/7/7 + UCHAR VoideNumberString; // ESN, IMEI, or MEID + +} QCTLV_DEVICE_VOICE_NUMBERS, *PQCTLV_DEVICE_VOICE_NUMBERS; + + +typedef struct _QMIDMS_GET_MSISDN_RESP_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG +} QMIDMS_GET_MSISDN_RESP_MSG, *PQMIDMS_GET_MSISDN_RESP_MSG; +#endif + +typedef struct _QMIDMS_UIM_GET_IMSI_REQ_MSG +{ + USHORT Type; + USHORT Length; +} __attribute__ ((packed)) QMIDMS_UIM_GET_IMSI_REQ_MSG, *PQMIDMS_UIM_GET_IMSI_REQ_MSG; + +typedef struct _QMIDMS_UIM_GET_IMSI_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR IMSI; +} __attribute__ ((packed)) QMIDMS_UIM_GET_IMSI_RESP_MSG, *PQMIDMS_UIM_GET_IMSI_RESP_MSG; + +#if 0 +typedef struct _QMIDMS_GET_DEVICE_SERIAL_NUMBERS_REQ_MSG +{ + USHORT Type; // QMUX type 0x0007 + USHORT Length; +} QMIDMS_GET_DEVICE_SERIAL_NUMBERS_REQ_MSG, *PQMIDMS_GET_DEVICE_SERIAL_NUMBERS_REQ_MSG; + +#define QCTLV_TYPE_SER_NUM_ESN 0x10 +#define QCTLV_TYPE_SER_NUM_IMEI 0x11 +#define QCTLV_TYPE_SER_NUM_MEID 0x12 + +typedef struct _QCTLV_DEVICE_SERIAL_NUMBER +{ + UCHAR TLVType; // as defined above + USHORT TLVLength; // 4/7/7 + UCHAR SerialNumberString; // ESN, IMEI, or MEID + +} QCTLV_DEVICE_SERIAL_NUMBER, *PQCTLV_DEVICE_SERIAL_NUMBER; + +typedef struct _QMIDMS_GET_DEVICE_SERIAL_NUMBERS_RESP_MSG +{ + USHORT Type; // QMUX type 0x0007 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + // followed by optional TLV +} QMIDMS_GET_DEVICE_SERIAL_NUMBERS_RESP_MSG, *PQMIDMS_GET_DEVICE_SERIAL_NUMBERS_RESP; + +typedef struct _QMIDMS_GET_DMS_BAND_CAP +{ + USHORT Type; + USHORT Length; +} QMIDMS_GET_BAND_CAP_REQ_MSG, *PQMIDMS_GET_BAND_CAP_REQ_MSG; + +typedef struct _QMIDMS_GET_BAND_CAP_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_NONE + // QMI_ERR_INTERNAL + // QMI_ERR_MALFORMED_MSG + // QMI_ERR_NO_MEMORY + + UCHAR TLV2Type; // 0x01 + USHORT TLV2Length; // 2 + ULONG64 BandCap; +} QMIDMS_GET_BAND_CAP_RESP_MSG, *PQMIDMS_GET_BAND_CAP_RESP; + +typedef struct _QMIDMS_GET_DEVICE_CAP_REQ_MSG +{ + USHORT Type; // QMUX type 0x0002 + USHORT Length; +} QMIDMS_GET_DEVICE_CAP_REQ_MSG, *PQMIDMS_GET_DEVICE_CAP_REQ_MSG; + +typedef struct _QMIDMS_GET_DEVICE_CAP_RESP_MSG +{ + USHORT Type; // QMUX type 0x0002 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMUX_RESULT_SUCCESS + // QMUX_RESULT_FAILURE + USHORT QMUXError; // QMUX_ERR_INVALID_ARG + // QMUX_ERR_NO_MEMORY + // QMUX_ERR_INTERNAL + // QMUX_ERR_FAULT + UCHAR TLV2Type; // 0x01 + USHORT TLV2Length; // 2 + + ULONG MaxTxChannelRate; + ULONG MaxRxChannelRate; + UCHAR VoiceCap; + UCHAR SimCap; + + UCHAR RadioIfListCnt; // #elements in radio interface list + UCHAR RadioIfList; // N 1-byte elements +} QMIDMS_GET_DEVICE_CAP_RESP_MSG, *PQMIDMS_GET_DEVICE_CAP_RESP_MSG; + +typedef struct _QMIDMS_GET_ACTIVATED_STATUS_REQ_MSG +{ + USHORT Type; // QMUX type 0x0002 + USHORT Length; +} QMIDMS_GET_ACTIVATED_STATUS_REQ_MSG, *PQMIDMS_GET_ACTIVATES_STATUD_REQ_MSG; + +typedef struct _QMIDMS_GET_ACTIVATED_STATUS_RESP_MSG +{ + USHORT Type; // QMUX type 0x0002 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMUX_RESULT_SUCCESS + // QMUX_RESULT_FAILURE + USHORT QMUXError; // QMUX_ERR_INVALID_ARG + // QMUX_ERR_NO_MEMORY + // QMUX_ERR_INTERNAL + // QMUX_ERR_FAULT + UCHAR TLV2Type; // 0x01 + USHORT TLV2Length; // 2 + + USHORT ActivatedStatus; +} QMIDMS_GET_ACTIVATED_STATUS_RESP_MSG, *PQMIDMS_GET_ACTIVATED_STATUS_RESP_MSG; + +typedef struct _QMIDMS_GET_OPERATING_MODE_REQ_MSG +{ + USHORT Type; // QMUX type 0x0002 + USHORT Length; +} QMIDMS_GET_OPERATING_MODE_REQ_MSG, *PQMIDMS_GET_OPERATING_MODE_REQ_MSG; + +typedef struct _OFFLINE_REASON +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT OfflineReason; +} OFFLINE_REASON, *POFFLINE_REASON; + +typedef struct _HARDWARE_RESTRICTED_MODE +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR HardwareControlledMode; +} HARDWARE_RESTRICTED_MODE, *PHARDWARE_RESTRICTED_MODE; + +typedef struct _QMIDMS_GET_OPERATING_MODE_RESP_MSG +{ + USHORT Type; // QMUX type 0x0002 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMUX_RESULT_SUCCESS + // QMUX_RESULT_FAILURE + USHORT QMUXError; // QMUX_ERR_INVALID_ARG + // QMUX_ERR_NO_MEMORY + // QMUX_ERR_INTERNAL + // QMUX_ERR_FAULT + UCHAR TLV2Type; // 0x01 + USHORT TLV2Length; // 2 + + UCHAR OperatingMode; +} QMIDMS_GET_OPERATING_MODE_RESP_MSG, *PQMIDMS_GET_OPERATING_MODE_RESP_MSG; + +typedef struct _QMIDMS_UIM_GET_ICCID_REQ_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; +} QMIDMS_UIM_GET_ICCID_REQ_MSG, *PQMIDMS_UIM_GET_ICCID_REQ_MSG; + +typedef struct _QMIDMS_UIM_GET_ICCID_RESP_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR TLV2Type; // 0x01 - required parameter + USHORT TLV2Length; // var + UCHAR ICCID; // String of voice number +} QMIDMS_UIM_GET_ICCID_RESP_MSG, *PQMIDMS_UIM_GET_ICCID_RESP_MSG; + +typedef struct _QMIDMS_SET_OPERATING_MODE_REQ_MSG +{ + USHORT Type; // QMUX type 0x0002 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + UCHAR OperatingMode; +} QMIDMS_SET_OPERATING_MODE_REQ_MSG, *PQMIDMS_SET_OPERATING_MODE_REQ_MSG; + +typedef struct _QMIDMS_SET_OPERATING_MODE_RESP_MSG +{ + USHORT Type; // QMUX type 0x0002 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMUX_RESULT_SUCCESS + // QMUX_RESULT_FAILURE + USHORT QMUXError; // QMUX_ERR_INVALID_ARG + // QMUX_ERR_NO_MEMORY + // QMUX_ERR_INTERNAL + // QMUX_ERR_FAULT +} QMIDMS_SET_OPERATING_MODE_RESP_MSG, *PQMIDMS_SET_OPERATING_MODE_RESP_MSG; + +typedef struct _QMIDMS_ACTIVATE_AUTOMATIC_REQ_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // + UCHAR ActivateCodelen; + UCHAR ActivateCode; +} QMIDMS_ACTIVATE_AUTOMATIC_REQ_MSG, *PQMIDMS_ACTIVATE_AUTOMATIC_REQ_MSG; + +typedef struct _QMIDMS_ACTIVATE_AUTOMATIC_RESP_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} QMIDMS_ACTIVATE_AUTOMATIC_RESP_MSG, *PQMIDMS_ACTIVATE_AUTOMATIC_RESP_MSG; + + +typedef struct _SPC_MSG +{ + UCHAR SPC[6]; + USHORT SID; +} SPC_MSG, *PSPC_MSG; + +typedef struct _MDN_MSG +{ + UCHAR MDNLEN; + UCHAR MDN; +} MDN_MSG, *PMDN_MSG; + +typedef struct _MIN_MSG +{ + UCHAR MINLEN; + UCHAR MIN; +} MIN_MSG, *PMIN_MSG; + +typedef struct _PRL_MSG +{ + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // + USHORT PRLLEN; + UCHAR PRL; +} PRL_MSG, *PPRL_MSG; + +typedef struct _MN_HA_KEY_MSG +{ + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // + UCHAR MN_HA_KEY_LEN; + UCHAR MN_HA_KEY; +} MN_HA_KEY_MSG, *PMN_HA_KEY_MSG; + +typedef struct _MN_AAA_KEY_MSG +{ + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // + UCHAR MN_AAA_KEY_LEN; + UCHAR MN_AAA_KEY; +} MN_AAA_KEY_MSG, *PMN_AAA_KEY_MSG; + +typedef struct _QMIDMS_ACTIVATE_MANUAL_REQ_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // + UCHAR Value; +} QMIDMS_ACTIVATE_MANUAL_REQ_MSG, *PQMIDMS_ACTIVATE_MANUAL_REQ_MSG; + +typedef struct _QMIDMS_ACTIVATE_MANUAL_RESP_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} QMIDMS_ACTIVATE_MANUAL_RESP_MSG, *PQMIDMS_ACTIVATE_MANUAL_RESP_MSG; +#endif + +typedef struct _QMIDMS_UIM_GET_STATE_REQ_MSG +{ + USHORT Type; + USHORT Length; +} __attribute__ ((packed)) QMIDMS_UIM_GET_STATE_REQ_MSG, *PQMIDMS_UIM_GET_STATE_REQ_MSG; + +typedef struct _QMIDMS_UIM_GET_STATE_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR UIMState; +} __attribute__ ((packed)) QMIDMS_UIM_GET_STATE_RESP_MSG, *PQMIDMS_UIM_GET_STATE_RESP_MSG; + +typedef struct _QMIDMS_UIM_GET_PIN_STATUS_REQ_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; +} __attribute__ ((packed)) QMIDMS_UIM_GET_PIN_STATUS_REQ_MSG, *PQMIDMS_UIM_GET_PIN_STATUS_REQ_MSG; + +typedef struct _QMIDMS_UIM_PIN_STATUS +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR PINStatus; + UCHAR PINVerifyRetriesLeft; + UCHAR PINUnblockRetriesLeft; +} __attribute__ ((packed)) QMIDMS_UIM_PIN_STATUS, *PQMIDMS_UIM_PIN_STATUS; + +#define QMI_PIN_STATUS_NOT_INIT 0 +#define QMI_PIN_STATUS_NOT_VERIF 1 +#define QMI_PIN_STATUS_VERIFIED 2 +#define QMI_PIN_STATUS_DISABLED 3 +#define QMI_PIN_STATUS_BLOCKED 4 +#define QMI_PIN_STATUS_PERM_BLOCKED 5 +#define QMI_PIN_STATUS_UNBLOCKED 6 +#define QMI_PIN_STATUS_CHANGED 7 + + +typedef struct _QMIDMS_UIM_GET_PIN_STATUS_RESP_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR PinStatus; +} __attribute__ ((packed)) QMIDMS_UIM_GET_PIN_STATUS_RESP_MSG, *PQMIDMS_UIM_GET_PIN_STATUS_RESP_MSG; + +#if 0 +typedef struct _QMIDMS_UIM_GET_CK_STATUS_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR Facility; +} QMIDMS_UIM_GET_CK_STATUS_REQ_MSG, *PQMIDMS_UIM_GET_CK_STATUS_REQ_MSG; + + +typedef struct _QMIDMS_UIM_CK_STATUS +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR FacilityStatus; + UCHAR FacilityVerifyRetriesLeft; + UCHAR FacilityUnblockRetriesLeft; +} QMIDMS_UIM_CK_STATUS, *PQMIDMS_UIM_CK_STATUS; + +typedef struct _QMIDMS_UIM_CK_OPERATION_STATUS +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR OperationBlocking; +} QMIDMS_UIM_CK_OPERATION_STATUS, *PQMIDMS_UIM_CK_OPERATION_STATUS; + +typedef struct _QMIDMS_UIM_GET_CK_STATUS_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR CkStatus; +} QMIDMS_UIM_GET_CK_STATUS_RESP_MSG, *PQMIDMS_UIM_GET_CK_STATUS_RESP_MSG; +#endif + +typedef struct _QMIDMS_UIM_VERIFY_PIN_REQ_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + UCHAR PINID; + UCHAR PINLen; + UCHAR PINValue; +} __attribute__ ((packed)) QMIDMS_UIM_VERIFY_PIN_REQ_MSG, *PQMIDMS_UIM_VERIFY_PIN_REQ_MSG; + +typedef struct _QMIDMS_UIM_VERIFY_PIN_RESP_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR PINVerifyRetriesLeft; + UCHAR PINUnblockRetriesLeft; +} __attribute__ ((packed)) QMIDMS_UIM_VERIFY_PIN_RESP_MSG, *PQMIDMS_UIM_VERIFY_PIN_RESP_MSG; + +#if 0 +typedef struct _QMIDMS_UIM_SET_PIN_PROTECTION_REQ_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + UCHAR PINID; + UCHAR ProtectionSetting; + UCHAR PINLen; + UCHAR PINValue; +} QMIDMS_UIM_SET_PIN_PROTECTION_REQ_MSG, *PQMIDMS_UIM_SET_PIN_PROTECTION_REQ_MSG; + +typedef struct _QMIDMS_UIM_SET_PIN_PROTECTION_RESP_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR PINVerifyRetriesLeft; + UCHAR PINUnblockRetriesLeft; +} QMIDMS_UIM_SET_PIN_PROTECTION_RESP_MSG, *PQMIDMS_UIM_SET_PIN_PROTECTION_RESP_MSG; + +typedef struct _QMIDMS_UIM_SET_CK_PROTECTION_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR Facility; + UCHAR FacilityState; + UCHAR FacliltyLen; + UCHAR FacliltyValue; +} QMIDMS_UIM_SET_CK_PROTECTION_REQ_MSG, *PQMIDMS_UIM_SET_CK_PROTECTION_REQ_MSG; + +typedef struct _QMIDMS_UIM_SET_CK_PROTECTION_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR FacilityRetriesLeft; +} QMIDMS_UIM_SET_CK_PROTECTION_RESP_MSG, *PQMIDMS_UIM_SET_CK_PROTECTION_RESP_MSG; + + +typedef struct _UIM_PIN +{ + UCHAR PinLength; + UCHAR PinValue; +} UIM_PIN, *PUIM_PIN; + +typedef struct _QMIDMS_UIM_CHANGE_PIN_REQ_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + UCHAR PINID; + UCHAR PinDetails; +} QMIDMS_UIM_CHANGE_PIN_REQ_MSG, *PQMIDMS_UIM_CHANGE_PIN_REQ_MSG; + +typedef struct QMIDMS_UIM_CHANGE_PIN_RESP_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR PINVerifyRetriesLeft; + UCHAR PINUnblockRetriesLeft; +} QMIDMS_UIM_CHANGE_PIN_RESP_MSG, *PQMIDMS_UIM_CHANGE_PIN_RESP_MSG; + +typedef struct _UIM_PUK +{ + UCHAR PukLength; + UCHAR PukValue; +} UIM_PUK, *PUIM_PUK; + +typedef struct _QMIDMS_UIM_UNBLOCK_PIN_REQ_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + UCHAR PINID; + UCHAR PinDetails; +} QMIDMS_UIM_UNBLOCK_PIN_REQ_MSG, *PQMIDMS_UIM_BLOCK_PIN_REQ_MSG; + +typedef struct QMIDMS_UIM_UNBLOCK_PIN_RESP_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR PINVerifyRetriesLeft; + UCHAR PINUnblockRetriesLeft; +} QMIDMS_UIM_UNBLOCK_PIN_RESP_MSG, *PQMIDMS_UIM_UNBLOCK_PIN_RESP_MSG; + +typedef struct _QMIDMS_UIM_UNBLOCK_CK_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR Facility; + UCHAR FacliltyUnblockLen; + UCHAR FacliltyUnblockValue; +} QMIDMS_UIM_UNBLOCK_CK_REQ_MSG, *PQMIDMS_UIM_BLOCK_CK_REQ_MSG; + +typedef struct QMIDMS_UIM_UNBLOCK_CK_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR FacilityUnblockRetriesLeft; +} QMIDMS_UIM_UNBLOCK_CK_RESP_MSG, *PQMIDMS_UIM_UNBLOCK_CK_RESP_MSG; + +typedef struct _QMIDMS_SET_EVENT_REPORT_REQ_MSG +{ + USHORT Type; + USHORT Length; +} QMIDMS_SET_EVENT_REPORT_REQ_MSG, *PQMIDMS_SET_EVENT_REPORT_REQ_MSG; + +typedef struct _QMIDMS_SET_EVENT_REPORT_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG +} QMIDMS_SET_EVENT_REPORT_RESP_MSG, *PQMIDMS_SET_EVENT_REPORT_RESP_MSG; + +typedef struct _PIN_STATUS +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR ReportPinState; +} PIN_STATUS, *PPIN_STATUS; + +typedef struct _POWER_STATUS +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR PowerStatus; + UCHAR BatteryLvl; +} POWER_STATUS, *PPOWER_STATUS; + +typedef struct _ACTIVATION_STATE +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT ActivationState; +} ACTIVATION_STATE, *PACTIVATION_STATE; + +typedef struct _ACTIVATION_STATE_REQ +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR ActivationState; +} ACTIVATION_STATE_REQ, *PACTIVATION_STATE_REQ; + +typedef struct _OPERATING_MODE +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR OperatingMode; +} OPERATING_MODE, *POPERATING_MODE; + +typedef struct _UIM_STATE +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR UIMState; +} UIM_STATE, *PUIM_STATE; + +typedef struct _WIRELESS_DISABLE_STATE +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR WirelessDisableState; +} WIRELESS_DISABLE_STATE, *PWIRELESS_DISABLE_STATE; + +typedef struct _QMIDMS_EVENT_REPORT_IND_MSG +{ + USHORT Type; + USHORT Length; +} QMIDMS_EVENT_REPORT_IND_MSG, *PQMIDMS_EVENT_REPORT_IND_MSG; +#endif + +// ============================ END OF DMS =============================== + +// ======================= QOS ============================== +typedef struct _MPIOC_DEV_INFO MPIOC_DEV_INFO, *PMPIOC_DEV_INFO; + +#define QMI_QOS_SET_EVENT_REPORT_REQ 0x0001 +#define QMI_QOS_SET_EVENT_REPORT_RESP 0x0001 +#define QMI_QOS_EVENT_REPORT_IND 0x0001 + +#if 0 +typedef struct _QMI_QOS_SET_EVENT_REPORT_REQ_MSG +{ + USHORT Type; // QMUX type 0x0001 + USHORT Length; + // UCHAR TLVType; // 0x01 - physical link state + // USHORT TLVLength; // 1 + // UCHAR PhyLinkStatusRpt; // 0-enable; 1-disable + UCHAR TLVType2; // 0x02 = global flow reporting + USHORT TLVLength2; // 1 + UCHAR GlobalFlowRpt; // 1-enable; 0-disable +} QMI_QOS_SET_EVENT_REPORT_REQ_MSG, *PQMI_QOS_SET_EVENT_REPORT_REQ_MSG; + +typedef struct _QMI_QOS_SET_EVENT_REPORT_RESP_MSG +{ + USHORT Type; // QMUX type 0x0010 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMUX_RESULT_SUCCESS + // QMUX_RESULT_FAILURE + USHORT QMUXError; // QMUX_ERR_INVALID_ARG + // QMUX_ERR_NO_MEMORY + // QMUX_ERR_INTERNAL + // QMUX_ERR_FAULT +} QMI_QOS_SET_EVENT_REPORT_RESP_MSG, *PQMI_QOS_SET_EVENT_REPORT_RESP_MSG; + +typedef struct _QMI_QOS_EVENT_REPORT_IND_MSG +{ + USHORT Type; // QMUX type 0x0001 + USHORT Length; + UCHAR TLVs; +} QMI_QOS_EVENT_REPORT_IND_MSG, *PQMI_QOS_EVENT_REPORT_IND_MSG; + +#define QOS_EVENT_RPT_IND_FLOW_ACTIVATED 0x01 +#define QOS_EVENT_RPT_IND_FLOW_MODIFIED 0x02 +#define QOS_EVENT_RPT_IND_FLOW_DELETED 0x03 +#define QOS_EVENT_RPT_IND_FLOW_SUSPENDED 0x04 +#define QOS_EVENT_RPT_IND_FLOW_ENABLED 0x05 +#define QOS_EVENT_RPT_IND_FLOW_DISABLED 0x06 + +#define QOS_EVENT_RPT_IND_TLV_PHY_LINK_STATE_TYPE 0x01 +#define QOS_EVENT_RPT_IND_TLV_GLOBAL_FL_RPT_STATE 0x10 +#define QOS_EVENT_RPT_IND_TLV_GLOBAL_FL_RPT_TYPE 0x10 +#define QOS_EVENT_RPT_IND_TLV_TX_FLOW_TYPE 0x11 +#define QOS_EVENT_RPT_IND_TLV_RX_FLOW_TYPE 0x12 +#define QOS_EVENT_RPT_IND_TLV_TX_FILTER_TYPE 0x13 +#define QOS_EVENT_RPT_IND_TLV_RX_FILTER_TYPE 0x14 +#define QOS_EVENT_RPT_IND_TLV_FLOW_SPEC 0x10 +#define QOS_EVENT_RPT_IND_TLV_FILTER_SPEC 0x10 + +typedef struct _QOS_EVENT_RPT_IND_TLV_PHY_LINK_STATE +{ + UCHAR TLVType; // 0x01 + USHORT TLVLength; // 1 + UCHAR PhyLinkState; // 0-dormant, 1-active +} QOS_EVENT_RPT_IND_TLV_PHY_LINK_STATE, *PQOS_EVENT_RPT_IND_TLV_PHY_LINK_STATE; + +typedef struct _QOS_EVENT_RPT_IND_TLV_GLOBAL_FL_RPT +{ + UCHAR TLVType; // 0x10 + USHORT TLVLength; // 6 + ULONG QosId; + UCHAR NewFlow; // 1: newly added flow; 0: existing flow + UCHAR StateChange; // 1: activated; 2: modified; 3: deleted; + // 4: suspended(delete); 5: enabled; 6: disabled +} QOS_EVENT_RPT_IND_TLV_GLOBAL_FL_RPT, *PQOS_EVENT_RPT_IND_TLV_GLOBAL_FL_RPT; + +// QOS Flow + +typedef struct _QOS_EVENT_RPT_IND_TLV_FLOW +{ + UCHAR TLVType; // 0x10-TX flow; 0x11-RX flow + USHORT TLVLength; // var + // embedded TLV's +} QOS_EVENT_RPT_IND_TLV_TX_FLOW, *PQOS_EVENT_RPT_IND_TLV_TX_FLOW; + +#define QOS_FLOW_TLV_IP_FLOW_IDX_TYPE 0x10 +#define QOS_FLOW_TLV_IP_FLOW_TRAFFIC_CLASS_TYPE 0x11 +#define QOS_FLOW_TLV_IP_FLOW_DATA_RATE_MIN_MAX_TYPE 0x12 +#define QOS_FLOW_TLV_IP_FLOW_DATA_RATE_TOKEN_BUCKET_TYPE 0x13 +#define QOS_FLOW_TLV_IP_FLOW_LATENCY_TYPE 0x14 +#define QOS_FLOW_TLV_IP_FLOW_JITTER_TYPE 0x15 +#define QOS_FLOW_TLV_IP_FLOW_PKT_ERR_RATE_TYPE 0x16 +#define QOS_FLOW_TLV_IP_FLOW_MIN_PKT_SIZE_TYPE 0x17 +#define QOS_FLOW_TLV_IP_FLOW_MAX_PKT_SIZE_TYPE 0x18 +#define QOS_FLOW_TLV_IP_FLOW_3GPP_BIT_ERR_RATE_TYPE 0x19 +#define QOS_FLOW_TLV_IP_FLOW_3GPP_TRAF_PRIORITY_TYPE 0x1A +#define QOS_FLOW_TLV_IP_FLOW_3GPP2_PROFILE_ID_TYPE 0x1B + +typedef struct _QOS_FLOW_TLV_IP_FLOW_IDX +{ + UCHAR TLVType; // 0x10 + USHORT TLVLength; // 1 + UCHAR IpFlowIndex; +} QOS_FLOW_TLV_IP_FLOW_IDX, *PQOS_FLOW_TLV_IP_FLOW_IDX; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_TRAFFIC_CLASS +{ + UCHAR TLVType; // 0x11 + USHORT TLVLength; // 1 + UCHAR TrafficClass; +} QOS_FLOW_TLV_IP_FLOW_TRAFFIC_CLASS, *PQOS_FLOW_TLV_IP_FLOW_TRAFFIC_CLASS; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_DATA_RATE_MIN_MAX +{ + UCHAR TLVType; // 0x12 + USHORT TLVLength; // 8 + ULONG DataRateMax; + ULONG GuaranteedRate; +} QOS_FLOW_TLV_IP_FLOW_DATA_RATE_MIN_MAX, *PQOS_FLOW_TLV_IP_FLOW_DATA_RATE_MIN_MAX; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_DATA_RATE_TOKEN_BUCKET +{ + UCHAR TLVType; // 0x13 + USHORT TLVLength; // 12 + ULONG PeakRate; + ULONG TokenRate; + ULONG BucketSize; +} QOS_FLOW_TLV_IP_FLOW_DATA_RATE_TOKEN_BUCKET, *PQOS_FLOW_TLV_IP_FLOW_DATA_RATE_TOKEN_BUCKET; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_LATENCY +{ + UCHAR TLVType; // 0x14 + USHORT TLVLength; // 4 + ULONG IpFlowLatency; +} QOS_FLOW_TLV_IP_FLOW_LATENCY, *PQOS_FLOW_TLV_IP_FLOW_LATENCY; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_JITTER +{ + UCHAR TLVType; // 0x15 + USHORT TLVLength; // 4 + ULONG IpFlowJitter; +} QOS_FLOW_TLV_IP_FLOW_JITTER, *PQOS_FLOW_TLV_IP_FLOW_JITTER; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_PKT_ERR_RATE +{ + UCHAR TLVType; // 0x16 + USHORT TLVLength; // 4 + USHORT ErrRateMultiplier; + USHORT ErrRateExponent; +} QOS_FLOW_TLV_IP_FLOW_PKT_ERR_RATE, *PQOS_FLOW_TLV_IP_FLOW_PKT_ERR_RATE; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_MIN_PKT_SIZE +{ + UCHAR TLVType; // 0x17 + USHORT TLVLength; // 4 + ULONG MinPolicedPktSize; +} QOS_FLOW_TLV_IP_FLOW_MIN_PKT_SIZE, *PQOS_FLOW_TLV_IP_FLOW_MIN_PKT_SIZE; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_MAX_PKT_SIZE +{ + UCHAR TLVType; // 0x18 + USHORT TLVLength; // 4 + ULONG MaxAllowedPktSize; +} QOS_FLOW_TLV_IP_FLOW_MAX_PKT_SIZE, *PQOS_FLOW_TLV_IP_FLOW_MAX_PKT_SIZE; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_3GPP_BIT_ERR_RATE +{ + UCHAR TLVType; // 0x19 + USHORT TLVLength; // 1 + UCHAR ResidualBitErrorRate; +} QOS_FLOW_TLV_IP_FLOW_3GPP_BIT_ERR_RATE, *PQOS_FLOW_TLV_IP_FLOW_3GPP_BIT_ERR_RATE; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_3GPP_TRAF_PRIORITY +{ + UCHAR TLVType; // 0x1A + USHORT TLVLength; // 1 + UCHAR TrafficHandlingPriority; +} QOS_FLOW_TLV_IP_FLOW_3GPP_TRAF_PRIORITY, *PQOS_FLOW_TLV_IP_FLOW_3GPP_TRAF_PRIORITY; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_3GPP2_PROFILE_ID +{ + UCHAR TLVType; // 0x1B + USHORT TLVLength; // 2 + USHORT ProfileId; +} QOS_FLOW_TLV_IP_FLOW_3GPP2_PROFILE_ID, *PQOS_FLOW_TLV_IP_FLOW_3GPP2_PROFILE_ID; + +// QOS Filter + +#define QOS_FILTER_TLV_IP_FILTER_IDX_TYPE 0x10 +#define QOS_FILTER_TLV_IP_VERSION_TYPE 0x11 +#define QOS_FILTER_TLV_IPV4_SRC_ADDR_TYPE 0x12 +#define QOS_FILTER_TLV_IPV4_DEST_ADDR_TYPE 0x13 +#define QOS_FILTER_TLV_NEXT_HDR_PROTOCOL_TYPE 0x14 +#define QOS_FILTER_TLV_IPV4_TYPE_OF_SERVICE_TYPE 0x15 +#define QOS_FILTER_TLV_TCP_UDP_PORT_SRC_TCP_TYPE 0x1B +#define QOS_FILTER_TLV_TCP_UDP_PORT_DEST_TCP_TYPE 0x1C +#define QOS_FILTER_TLV_TCP_UDP_PORT_SRC_UDP_TYPE 0x1D +#define QOS_FILTER_TLV_TCP_UDP_PORT_DEST_UDP_TYPE 0x1E +#define QOS_FILTER_TLV_ICMP_FILTER_MSG_TYPE_TYPE 0x1F +#define QOS_FILTER_TLV_ICMP_FILTER_MSG_CODE_TYPE 0x20 +#define QOS_FILTER_TLV_TCP_UDP_PORT_SRC_TYPE 0x24 +#define QOS_FILTER_TLV_TCP_UDP_PORT_DEST_TYPE 0x25 + +typedef struct _QOS_EVENT_RPT_IND_TLV_FILTER +{ + UCHAR TLVType; // 0x12-TX filter; 0x13-RX filter + USHORT TLVLength; // var + // embedded TLV's +} QOS_EVENT_RPT_IND_TLV_RX_FILTER, *PQOS_EVENT_RPT_IND_TLV_RX_FILTER; + +typedef struct _QOS_FILTER_TLV_IP_FILTER_IDX +{ + UCHAR TLVType; // 0x10 + USHORT TLVLength; // 1 + UCHAR IpFilterIndex; +} QOS_FILTER_TLV_IP_FILTER_IDX, *PQOS_FILTER_TLV_IP_FILTER_IDX; + +typedef struct _QOS_FILTER_TLV_IP_VERSION +{ + UCHAR TLVType; // 0x11 + USHORT TLVLength; // 1 + UCHAR IpVersion; +} QOS_FILTER_TLV_IP_VERSION, *PQOS_FILTER_TLV_IP_VERSION; + +typedef struct _QOS_FILTER_TLV_IPV4_SRC_ADDR +{ + UCHAR TLVType; // 0x12 + USHORT TLVLength; // 8 + ULONG IpSrcAddr; + ULONG IpSrcSubnetMask; +} QOS_FILTER_TLV_IPV4_SRC_ADDR, *PQOS_FILTER_TLV_IPV4_SRC_ADDR; + +typedef struct _QOS_FILTER_TLV_IPV4_DEST_ADDR +{ + UCHAR TLVType; // 0x13 + USHORT TLVLength; // 8 + ULONG IpDestAddr; + ULONG IpDestSubnetMask; +} QOS_FILTER_TLV_IPV4_DEST_ADDR, *PQOS_FILTER_TLV_IPV4_DEST_ADDR; + +typedef struct _QOS_FILTER_TLV_NEXT_HDR_PROTOCOL +{ + UCHAR TLVType; // 0x14 + USHORT TLVLength; // 1 + UCHAR NextHdrProtocol; +} QOS_FILTER_TLV_NEXT_HDR_PROTOCOL, *PQOS_FILTER_TLV_NEXT_HDR_PROTOCOL; + +typedef struct _QOS_FILTER_TLV_IPV4_TYPE_OF_SERVICE +{ + UCHAR TLVType; // 0x15 + USHORT TLVLength; // 2 + UCHAR Ipv4TypeOfService; + UCHAR Ipv4TypeOfServiceMask; +} QOS_FILTER_TLV_IPV4_TYPE_OF_SERVICE, *PQOS_FILTER_TLV_IPV4_TYPE_OF_SERVICE; + +typedef struct _QOS_FILTER_TLV_TCP_UDP_PORT +{ + UCHAR TLVType; // source port: 0x1B-TCP; 0x1D-UDP + // dest port: 0x1C-TCP; 0x1E-UDP + USHORT TLVLength; // 4 + USHORT FilterPort; + USHORT FilterPortRange; +} QOS_FILTER_TLV_TCP_UDP_PORT, *PQOS_FILTER_TLV_TCP_UDP_PORT; + +typedef struct _QOS_FILTER_TLV_ICMP_FILTER_MSG_TYPE +{ + UCHAR TLVType; // 0x1F + USHORT TLVLength; // 1 + UCHAR IcmpFilterMsgType; +} QOS_FILTER_TLV_ICMP_FILTER_MSG_TYPE, *PQOS_FILTER_TLV_ICMP_FILTER_MSG_TYPE; + +typedef struct _QOS_FILTER_TLV_ICMP_FILTER_MSG_CODE +{ + UCHAR TLVType; // 0x20 + USHORT TLVLength; // 1 + UCHAR IcmpFilterMsgCode; +} QOS_FILTER_TLV_ICMP_FILTER_MSG_CODE, *PQOS_FILTER_TLV_ICMP_FILTER_MSG_CODE; + +#define QOS_FILTER_PRECEDENCE_INVALID 256 +#define QOS_FILTER_TLV_PRECEDENCE_TYPE 0x22 +#define QOS_FILTER_TLV_ID_TYPE 0x23 + +typedef struct _QOS_FILTER_TLV_PRECEDENCE +{ + UCHAR TLVType; // 0x22 + USHORT TLVLength; // 2 + USHORT Precedence; // precedence of the filter +} QOS_FILTER_TLV_PRECEDENCE, *PQOS_FILTER_TLV_PRECEDENCE; + +typedef struct _QOS_FILTER_TLV_ID +{ + UCHAR TLVType; // 0x23 + USHORT TLVLength; // 2 + USHORT FilterId; // filter ID +} QOS_FILTER_TLV_ID, *PQOS_FILTER_TLV_ID; + +#ifdef QCQOS_IPV6 + +#define QOS_FILTER_TLV_IPV6_SRC_ADDR_TYPE 0x16 +#define QOS_FILTER_TLV_IPV6_DEST_ADDR_TYPE 0x17 +#define QOS_FILTER_TLV_IPV6_NEXT_HDR_PROTOCOL_TYPE 0x14 // same as IPV4 +#define QOS_FILTER_TLV_IPV6_TRAFFIC_CLASS_TYPE 0x19 +#define QOS_FILTER_TLV_IPV6_FLOW_LABEL_TYPE 0x1A + +typedef struct _QOS_FILTER_TLV_IPV6_SRC_ADDR +{ + UCHAR TLVType; // 0x16 + USHORT TLVLength; // 17 + UCHAR IpSrcAddr[16]; + UCHAR IpSrcAddrPrefixLen; // [0..128] +} QOS_FILTER_TLV_IPV6_SRC_ADDR, *PQOS_FILTER_TLV_IPV6_SRC_ADDR; + +typedef struct _QOS_FILTER_TLV_IPV6_DEST_ADDR +{ + UCHAR TLVType; // 0x17 + USHORT TLVLength; // 17 + UCHAR IpDestAddr[16]; + UCHAR IpDestAddrPrefixLen; // [0..128] +} QOS_FILTER_TLV_IPV6_DEST_ADDR, *PQOS_FILTER_TLV_IPV6_DEST_ADDR; + +#define QOS_FILTER_IPV6_NEXT_HDR_PROTOCOL_TCP 0x06 +#define QOS_FILTER_IPV6_NEXT_HDR_PROTOCOL_UDP 0x11 + +typedef struct _QOS_FILTER_TLV_IPV6_TRAFFIC_CLASS +{ + UCHAR TLVType; // 0x19 + USHORT TLVLength; // 2 + UCHAR TrafficClass; + UCHAR TrafficClassMask; // compare the first 6 bits only +} QOS_FILTER_TLV_IPV6_TRAFFIC_CLASS, *PQOS_FILTER_TLV_IPV6_TRAFFIC_CLASS; + +typedef struct _QOS_FILTER_TLV_IPV6_FLOW_LABEL +{ + UCHAR TLVType; // 0x1A + USHORT TLVLength; // 4 + ULONG FlowLabel; +} QOS_FILTER_TLV_IPV6_FLOW_LABEL, *PQOS_FILTER_TLV_IPV6_FLOW_LABEL; + +#endif // QCQOS_IPV6 +#endif + +// ======================= WMS ============================== +#define QMIWMS_SET_EVENT_REPORT_REQ 0x0001 +#define QMIWMS_SET_EVENT_REPORT_RESP 0x0001 +#define QMIWMS_EVENT_REPORT_IND 0x0001 +#define QMIWMS_RAW_SEND_REQ 0x0020 +#define QMIWMS_RAW_SEND_RESP 0x0020 +#define QMIWMS_RAW_WRITE_REQ 0x0021 +#define QMIWMS_RAW_WRITE_RESP 0x0021 +#define QMIWMS_RAW_READ_REQ 0x0022 +#define QMIWMS_RAW_READ_RESP 0x0022 +#define QMIWMS_MODIFY_TAG_REQ 0x0023 +#define QMIWMS_MODIFY_TAG_RESP 0x0023 +#define QMIWMS_DELETE_REQ 0x0024 +#define QMIWMS_DELETE_RESP 0x0024 +#define QMIWMS_GET_MESSAGE_PROTOCOL_REQ 0x0030 +#define QMIWMS_GET_MESSAGE_PROTOCOL_RESP 0x0030 +#define QMIWMS_LIST_MESSAGES_REQ 0x0031 +#define QMIWMS_LIST_MESSAGES_RESP 0x0031 +#define QMIWMS_GET_SMSC_ADDRESS_REQ 0x0034 +#define QMIWMS_GET_SMSC_ADDRESS_RESP 0x0034 +#define QMIWMS_SET_SMSC_ADDRESS_REQ 0x0035 +#define QMIWMS_SET_SMSC_ADDRESS_RESP 0x0035 +#define QMIWMS_GET_STORE_MAX_SIZE_REQ 0x0036 +#define QMIWMS_GET_STORE_MAX_SIZE_RESP 0x0036 + + +#define WMS_MESSAGE_PROTOCOL_CDMA 0x00 +#define WMS_MESSAGE_PROTOCOL_WCDMA 0x01 + +#if 0 +typedef struct _QMIWMS_GET_MESSAGE_PROTOCOL_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; +} QMIWMS_GET_MESSAGE_PROTOCOL_REQ_MSG, *PQMIWMS_GET_MESSAGE_PROTOCOL_REQ_MSG; + +typedef struct _QMIWMS_GET_MESSAGE_PROTOCOL_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR MessageProtocol; +} QMIWMS_GET_MESSAGE_PROTOCOL_RESP_MSG, *PQMIWMS_GET_MESSAGE_PROTOCOL_RESP_MSG; + +typedef struct _QMIWMS_GET_STORE_MAX_SIZE_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR StorageType; +} QMIWMS_GET_STORE_MAX_SIZE_REQ_MSG, *PQMIWMS_GET_STORE_MAX_SIZE_REQ_MSG; + +typedef struct _QMIWMS_GET_STORE_MAX_SIZE_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLV2Type; + USHORT TLV2Length; + ULONG MemStoreMaxSize; +} QMIWMS_GET_STORE_MAX_SIZE_RESP_MSG, *PQMIWMS_GET_STORE_MAX_SIZE_RESP_MSG; + +typedef struct _REQUEST_TAG +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR TagType; +} REQUEST_TAG, *PREQUEST_TAG; + +typedef struct _QMIWMS_LIST_MESSAGES_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR StorageType; +} QMIWMS_LIST_MESSAGES_REQ_MSG, *PQMIWMS_LIST_MESSAGES_REQ_MSG; + +typedef struct _QMIWMS_MESSAGE +{ + ULONG MessageIndex; + UCHAR TagType; +} QMIWMS_MESSAGE, *PQMIWMS_MESSAGE; + +typedef struct _QMIWMS_LIST_MESSAGES_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLV2Type; + USHORT TLV2Length; + ULONG NumMessages; +} QMIWMS_LIST_MESSAGES_RESP_MSG, *PQMIWMS_LIST_MESSAGES_RESP_MSG; + +typedef struct _QMIWMS_RAW_READ_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR StorageType; + ULONG MemoryIndex; +} QMIWMS_RAW_READ_REQ_MSG, *PQMIWMS_RAW_READ_REQ_MSG; + +typedef struct _QMIWMS_RAW_READ_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR TagType; + UCHAR Format; + USHORT MessageLength; + UCHAR Message; +} QMIWMS_RAW_READ_RESP_MSG, *PQMIWMS_RAW_READ_RESP_MSG; + +typedef struct _QMIWMS_MODIFY_TAG_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR StorageType; + ULONG MemoryIndex; + UCHAR TagType; +} QMIWMS_MODIFY_TAG_REQ_MSG, *PQMIWMS_MODIFY_TAG_REQ_MSG; + +typedef struct _QMIWMS_MODIFY_TAG_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} QMIWMS_MODIFY_TAG_RESP_MSG, *PQMIWMS_MODIFY_TAG_RESP_MSG; + +typedef struct _QMIWMS_RAW_SEND_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR SmsFormat; + USHORT SmsLength; + UCHAR SmsMessage; +} QMIWMS_RAW_SEND_REQ_MSG, *PQMIWMS_RAW_SEND_REQ_MSG; + +typedef struct _RAW_SEND_CAUSE_CODE +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT CauseCode; +} RAW_SEND_CAUSE_CODE, *PRAW_SEND_CAUSE_CODE; + + +typedef struct _QMIWMS_RAW_SEND_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} QMIWMS_RAW_SEND_RESP_MSG, *PQMIWMS_RAW_SEND_RESP_MSG; + + +typedef struct _WMS_DELETE_MESSAGE_INDEX +{ + UCHAR TLVType; + USHORT TLVLength; + ULONG MemoryIndex; +} WMS_DELETE_MESSAGE_INDEX, *PWMS_DELETE_MESSAGE_INDEX; + +typedef struct _WMS_DELETE_MESSAGE_TAG +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR MessageTag; +} WMS_DELETE_MESSAGE_TAG, *PWMS_DELETE_MESSAGE_TAG; + +typedef struct _QMIWMS_DELETE_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR StorageType; +} QMIWMS_DELETE_REQ_MSG, *PQMIWMS_DELETE_REQ_MSG; + +typedef struct _QMIWMS_DELETE_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} QMIWMS_DELETE_RESP_MSG, *PQMIWMS_DELETE_RESP_MSG; + + +typedef struct _QMIWMS_GET_SMSC_ADDRESS_REQ_MSG +{ + USHORT Type; + USHORT Length; +} QMIWMS_GET_SMSC_ADDRESS_REQ_MSG, *PQMIWMS_GET_SMSC_ADDRESS_REQ_MSG; + +typedef struct _QMIWMS_SMSC_ADDRESS +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR SMSCAddressType[3]; + UCHAR SMSCAddressLength; + UCHAR SMSCAddressDigits; +} QMIWMS_SMSC_ADDRESS, *PQMIWMS_SMSC_ADDRESS; + + +typedef struct _QMIWMS_GET_SMSC_ADDRESS_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR SMSCAddress; +} QMIWMS_GET_SMSC_ADDRESS_RESP_MSG, *PQMIWMS_GET_SMSC_ADDRESS_RESP_MSG; + +typedef struct _QMIWMS_SET_SMSC_ADDRESS_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR SMSCAddress; +} QMIWMS_SET_SMSC_ADDRESS_REQ_MSG, *PQMIWMS_SET_SMSC_ADDRESS_REQ_MSG; + +typedef struct _QMIWMS_SET_SMSC_ADDRESS_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} QMIWMS_SET_SMSC_ADDRESS_RESP_MSG, *PQMIWMS_SET_SMSC_ADDRESS_RESP_MSG; + +typedef struct _QMIWMS_SET_EVENT_REPORT_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR ReportNewMessage; +} QMIWMS_SET_EVENT_REPORT_REQ_MSG, *PQMIWMS_SET_EVENT_REPORT_REQ_MSG; + +typedef struct _QMIWMS_SET_EVENT_REPORT_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} QMIWMS_SET_EVENT_REPORT_RESP_MSG, *PQMIWMS_SET_EVENT_REPORT_RESP_MSG; + +typedef struct _QMIWMS_EVENT_REPORT_IND_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR StorageType; + ULONG StorageIndex; +} QMIWMS_EVENT_REPORT_IND_MSG, *PQMIWMS_EVENT_REPORT_IND_MSG; +#endif + +// ======================= End of WMS ============================== + + +// ======================= NAS ============================== +#define QMINAS_SET_EVENT_REPORT_REQ 0x0002 +#define QMINAS_SET_EVENT_REPORT_RESP 0x0002 +#define QMINAS_EVENT_REPORT_IND 0x0002 +#define QMINAS_GET_SIGNAL_STRENGTH_REQ 0x0020 +#define QMINAS_GET_SIGNAL_STRENGTH_RESP 0x0020 +#define QMINAS_PERFORM_NETWORK_SCAN_REQ 0x0021 +#define QMINAS_PERFORM_NETWORK_SCAN_RESP 0x0021 +#define QMINAS_INITIATE_NW_REGISTER_REQ 0x0022 +#define QMINAS_INITIATE_NW_REGISTER_RESP 0x0022 +#define QMINAS_INITIATE_ATTACH_REQ 0x0023 +#define QMINAS_INITIATE_ATTACH_RESP 0x0023 +#define QMINAS_GET_SERVING_SYSTEM_REQ 0x0024 +#define QMINAS_GET_SERVING_SYSTEM_RESP 0x0024 +#define QMINAS_SERVING_SYSTEM_IND 0x0024 +#define QMINAS_GET_HOME_NETWORK_REQ 0x0025 +#define QMINAS_GET_HOME_NETWORK_RESP 0x0025 +#define QMINAS_GET_PREFERRED_NETWORK_REQ 0x0026 +#define QMINAS_GET_PREFERRED_NETWORK_RESP 0x0026 +#define QMINAS_SET_PREFERRED_NETWORK_REQ 0x0027 +#define QMINAS_SET_PREFERRED_NETWORK_RESP 0x0027 +#define QMINAS_GET_FORBIDDEN_NETWORK_REQ 0x0028 +#define QMINAS_GET_FORBIDDEN_NETWORK_RESP 0x0028 +#define QMINAS_SET_FORBIDDEN_NETWORK_REQ 0x0029 +#define QMINAS_SET_FORBIDDEN_NETWORK_RESP 0x0029 +#define QMINAS_SET_TECHNOLOGY_PREF_REQ 0x002A +#define QMINAS_SET_TECHNOLOGY_PREF_RESP 0x002A +#define QMINAS_GET_RF_BAND_INFO_REQ 0x0031 +#define QMINAS_GET_RF_BAND_INFO_RESP 0x0031 +#define QMINAS_GET_PLMN_NAME_REQ 0x0044 +#define QMINAS_GET_PLMN_NAME_RESP 0x0044 +#define QUECTEL_PACKET_TRANSFER_START_IND 0X100 +#define QUECTEL_PACKET_TRANSFER_END_IND 0X101 +#define QMINAS_GET_SYS_INFO_REQ 0x004D +#define QMINAS_GET_SYS_INFO_RESP 0x004D +#define QMINAS_SYS_INFO_IND 0x004D + +typedef struct _QMINAS_GET_HOME_NETWORK_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; +} __attribute__ ((packed)) QMINAS_GET_HOME_NETWORK_REQ_MSG, *PQMINAS_GET_HOME_NETWORK_REQ_MSG; + +typedef struct _HOME_NETWORK_SYSTEMID +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT SystemID; + USHORT NetworkID; +} __attribute__ ((packed)) HOME_NETWORK_SYSTEMID, *PHOME_NETWORK_SYSTEMID; + +typedef struct _HOME_NETWORK +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT MobileCountryCode; + USHORT MobileNetworkCode; + UCHAR NetworkDesclen; + UCHAR NetworkDesc; +} __attribute__ ((packed)) HOME_NETWORK, *PHOME_NETWORK; + +#if 0 +typedef struct _HOME_NETWORK_EXT +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT MobileCountryCode; + USHORT MobileNetworkCode; + UCHAR NetworkDescDisp; + UCHAR NetworkDescEncoding; + UCHAR NetworkDesclen; + UCHAR NetworkDesc; +} HOME_NETWORK_EXT, *PHOME_NETWORK_EXT; + +typedef struct _QMINAS_GET_HOME_NETWORK_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} QMINAS_GET_HOME_NETWORK_RESP_MSG, *PQMINAS_GET_HOME_NETWORK_RESP_MSG; + +typedef struct _QMINAS_GET_PREFERRED_NETWORK_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; +} QMINAS_GET_PREFERRED_NETWORK_REQ_MSG, *PQMINAS_GET_PREFERRED_NETWORK_REQ_MSG; + + +typedef struct _PREFERRED_NETWORK +{ + USHORT MobileCountryCode; + USHORT MobileNetworkCode; + USHORT RadioAccess; +} PREFERRED_NETWORK, *PPREFERRED_NETWORK; + +typedef struct _QMINAS_GET_PREFERRED_NETWORK_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR TLV2Type; // 0x01 - required parameter + USHORT TLV2Length; // length of the mfr string + USHORT NumPreferredNetwork; +} QMINAS_GET_PREFERRED_NETWORK_RESP_MSG, *PQMINAS_GET_PREFERRED_NETWORK_RESP_MSG; + +typedef struct _QMINAS_GET_FORBIDDEN_NETWORK_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; +} QMINAS_GET_FORBIDDEN_NETWORK_REQ_MSG, *PQMINAS_GET_FORBIDDEN_NETWORK_REQ_MSG; + +typedef struct _FORBIDDEN_NETWORK +{ + USHORT MobileCountryCode; + USHORT MobileNetworkCode; +} FORBIDDEN_NETWORK, *PFORBIDDEN_NETWORK; + +typedef struct _QMINAS_GET_FORBIDDEN_NETWORK_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR TLV2Type; // 0x01 - required parameter + USHORT TLV2Length; // length of the mfr string + USHORT NumForbiddenNetwork; +} QMINAS_GET_FORBIDDEN_NETWORK_RESP_MSG, *PQMINAS_GET_FORBIDDEN_NETWORK_RESP_MSG; + +typedef struct _QMINAS_GET_SERVING_SYSTEM_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; +} QMINAS_GET_SERVING_SYSTEM_REQ_MSG, *PQMINAS_GET_SERVING_SYSTEM_REQ_MSG; + +typedef struct _QMINAS_ROAMING_INDICATOR_MSG +{ + UCHAR TLVType; // 0x01 - required parameter + USHORT TLVLength; // length of the mfr string + UCHAR RoamingIndicator; +} QMINAS_ROAMING_INDICATOR_MSG, *PQMINAS_ROAMING_INDICATOR_MSG; +#endif + +typedef struct _QMINAS_DATA_CAP +{ + UCHAR TLVType; // 0x01 - required parameter + USHORT TLVLength; // length of the mfr string + UCHAR DataCapListLen; + UCHAR DataCap; +} __attribute__ ((packed)) QMINAS_DATA_CAP, *PQMINAS_DATA_CAP; + +typedef struct _QMINAS_CURRENT_PLMN_MSG +{ + UCHAR TLVType; // 0x01 - required parameter + USHORT TLVLength; // length of the mfr string + USHORT MobileCountryCode; + USHORT MobileNetworkCode; + UCHAR NetworkDesclen; + UCHAR NetworkDesc; +} __attribute__ ((packed)) QMINAS_CURRENT_PLMN_MSG, *PQMINAS_CURRENT_PLMN_MSG; + +typedef struct _QMINAS_GET_SERVING_SYSTEM_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} __attribute__ ((packed)) QMINAS_GET_SERVING_SYSTEM_RESP_MSG, *PQMINAS_GET_SERVING_SYSTEM_RESP_MSG; + +typedef struct _SERVING_SYSTEM +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR RegistrationState; + UCHAR CSAttachedState; + UCHAR PSAttachedState; + UCHAR RegistredNetwork; + UCHAR InUseRadioIF; + UCHAR RadioIF; +} __attribute__ ((packed)) SERVING_SYSTEM, *PSERVING_SYSTEM; + +typedef struct _QMINAS_GET_SYS_INFO_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} __attribute__ ((packed)) QMINAS_GET_SYS_INFO_RESP_MSG, *PQMINAS_GET_SYS_INFO_RESP_MSG; + +typedef struct _QMINAS_SYS_INFO_IND_MSG +{ + USHORT Type; + USHORT Length; +} __attribute__ ((packed)) QMINAS_SYS_INFO_IND_MSG, *PQMINAS_SYS_INFO_IND_MSG; + +typedef struct _SERVICE_STATUS_INFO +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR SrvStatus; + UCHAR IsPrefDataPath; +} __attribute__ ((packed)) SERVICE_STATUS_INFO, *PSERVICE_STATUS_INFO; + +typedef struct _CDMA_SYSTEM_INFO +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR SrvDomainValid; + UCHAR SrvDomain; + UCHAR SrvCapabilityValid; + UCHAR SrvCapability; + UCHAR RoamStatusValid; + UCHAR RoamStatus; + UCHAR IsSysForbiddenValid; + UCHAR IsSysForbidden; + UCHAR IsSysPrlMatchValid; + UCHAR IsSysPrlMatch; + UCHAR PRevInUseValid; + UCHAR PRevInUse; + UCHAR BSPRevValid; + UCHAR BSPRev; + UCHAR CCSSupportedValid; + UCHAR CCSSupported; + UCHAR CDMASysIdValid; + USHORT SID; + USHORT NID; + UCHAR BSInfoValid; + USHORT BaseID; + ULONG BaseLAT; + ULONG BaseLONG; + UCHAR PacketZoneValid; + USHORT PacketZone; + UCHAR NetworkIdValid; + UCHAR MCC[3]; + UCHAR MNC[3]; +} __attribute__ ((packed)) CDMA_SYSTEM_INFO, *PCDMA_SYSTEM_INFO; + +typedef struct _HDR_SYSTEM_INFO +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR SrvDomainValid; + UCHAR SrvDomain; + UCHAR SrvCapabilityValid; + UCHAR SrvCapability; + UCHAR RoamStatusValid; + UCHAR RoamStatus; + UCHAR IsSysForbiddenValid; + UCHAR IsSysForbidden; + UCHAR IsSysPrlMatchValid; + UCHAR IsSysPrlMatch; + UCHAR HdrPersonalityValid; + UCHAR HdrPersonality; + UCHAR HdrActiveProtValid; + UCHAR HdrActiveProt; + UCHAR is856SysIdValid; + UCHAR is856SysId[16]; +} __attribute__ ((packed)) HDR_SYSTEM_INFO, *PHDR_SYSTEM_INFO; + +typedef struct _GSM_SYSTEM_INFO +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR SrvDomainValid; + UCHAR SrvDomain; + UCHAR SrvCapabilityValid; + UCHAR SrvCapability; + UCHAR RoamStatusValid; + UCHAR RoamStatus; + UCHAR IsSysForbiddenValid; + UCHAR IsSysForbidden; + UCHAR LacValid; + USHORT Lac; + UCHAR CellIdValid; + ULONG CellId; + UCHAR RegRejectInfoValid; + UCHAR RejectSrvDomain; + UCHAR RejCause; + UCHAR NetworkIdValid; + UCHAR MCC[3]; + UCHAR MNC[3]; + UCHAR EgprsSuppValid; + UCHAR EgprsSupp; + UCHAR DtmSuppValid; + UCHAR DtmSupp; +} __attribute__ ((packed)) GSM_SYSTEM_INFO, *PGSM_SYSTEM_INFO; + +typedef struct _WCDMA_SYSTEM_INFO +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR SrvDomainValid; + UCHAR SrvDomain; + UCHAR SrvCapabilityValid; + UCHAR SrvCapability; + UCHAR RoamStatusValid; + UCHAR RoamStatus; + UCHAR IsSysForbiddenValid; + UCHAR IsSysForbidden; + UCHAR LacValid; + USHORT Lac; + UCHAR CellIdValid; + ULONG CellId; + UCHAR RegRejectInfoValid; + UCHAR RejectSrvDomain; + UCHAR RejCause; + UCHAR NetworkIdValid; + UCHAR MCC[3]; + UCHAR MNC[3]; + UCHAR HsCallStatusValid; + UCHAR HsCallStatus; + UCHAR HsIndValid; + UCHAR HsInd; + UCHAR PscValid; + UCHAR Psc; +} __attribute__ ((packed)) WCDMA_SYSTEM_INFO, *PWCDMA_SYSTEM_INFO; + +typedef struct _LTE_SYSTEM_INFO +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR SrvDomainValid; + UCHAR SrvDomain; + UCHAR SrvCapabilityValid; + UCHAR SrvCapability; + UCHAR RoamStatusValid; + UCHAR RoamStatus; + UCHAR IsSysForbiddenValid; + UCHAR IsSysForbidden; + UCHAR LacValid; + USHORT Lac; + UCHAR CellIdValid; + ULONG CellId; + UCHAR RegRejectInfoValid; + UCHAR RejectSrvDomain; + UCHAR RejCause; + UCHAR NetworkIdValid; + UCHAR MCC[3]; + UCHAR MNC[3]; + UCHAR TacValid; + USHORT Tac; +} __attribute__ ((packed)) LTE_SYSTEM_INFO, *PLTE_SYSTEM_INFO; + +typedef struct _TDSCDMA_SYSTEM_INFO +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR SrvDomainValid; + UCHAR SrvDomain; + UCHAR SrvCapabilityValid; + UCHAR SrvCapability; + UCHAR RoamStatusValid; + UCHAR RoamStatus; + UCHAR IsSysForbiddenValid; + UCHAR IsSysForbidden; + UCHAR LacValid; + USHORT Lac; + UCHAR CellIdValid; + ULONG CellId; + UCHAR RegRejectInfoValid; + UCHAR RejectSrvDomain; + UCHAR RejCause; + UCHAR NetworkIdValid; + UCHAR MCC[3]; + UCHAR MNC[3]; + UCHAR HsCallStatusValid; + UCHAR HsCallStatus; + UCHAR HsIndValid; + UCHAR HsInd; + UCHAR CellParameterIdValid; + USHORT CellParameterId; + UCHAR CellBroadcastCapValid; + ULONG CellBroadcastCap; + UCHAR CsBarStatusValid; + ULONG CsBarStatus; + UCHAR PsBarStatusValid; + ULONG PsBarStatus; + UCHAR CipherDomainValid; + UCHAR CipherDomain; +} __attribute__ ((packed)) TDSCDMA_SYSTEM_INFO, *PTDSCDMA_SYSTEM_INFO; + +#if 0 +typedef struct _QMINAS_SERVING_SYSTEM_IND_MSG +{ + USHORT Type; + USHORT Length; +} QMINAS_SERVING_SYSTEM_IND_MSG, *PQMINAS_SERVING_SYSTEM_IND_MSG; + +typedef struct _QMINAS_SET_PREFERRED_NETWORK_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT NumPreferredNetwork; + USHORT MobileCountryCode; + USHORT MobileNetworkCode; + USHORT RadioAccess; +} QMINAS_SET_PREFERRED_NETWORK_REQ_MSG, *PQMINAS_SET_PREFERRED_NETWORK_REQ_MSG; + +typedef struct _QMINAS_SET_PREFERRED_NETWORK_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} QMINAS_SET_PREFERRED_NETWORK_RESP_MSG, *PQMINAS_SET_PREFERRED_NETWORK_RESP_MSG; + +typedef struct _QMINAS_SET_FORBIDDEN_NETWORK_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT NumForbiddenNetwork; + USHORT MobileCountryCode; + USHORT MobileNetworkCode; +} QMINAS_SET_FORBIDDEN_NETWORK_REQ_MSG, *PQMINAS_SET_FORBIDDEN_NETWORK_REQ_MSG; + +typedef struct _QMINAS_SET_FORBIDDEN_NETWORK_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} QMINAS_SET_FORBIDDEN_NETWORK_RESP_MSG, *PQMINAS_SET_FORBIDDEN_NETWORK_RESP_MSG; + +typedef struct _QMINAS_PERFORM_NETWORK_SCAN_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; +} QMINAS_PERFORM_NETWORK_SCAN_REQ_MSG, *PQMINAS_PERFORM_NETWORK_SCAN_REQ_MSG; + +typedef struct _VISIBLE_NETWORK +{ + USHORT MobileCountryCode; + USHORT MobileNetworkCode; + UCHAR NetworkStatus; + UCHAR NetworkDesclen; +} VISIBLE_NETWORK, *PVISIBLE_NETWORK; + +typedef struct _QMINAS_PERFORM_NETWORK_SCAN_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} QMINAS_PERFORM_NETWORK_SCAN_RESP_MSG, *PQMINAS_PERFORM_NETWORK_SCAN_RESP_MSG; + +typedef struct _QMINAS_PERFORM_NETWORK_SCAN_NETWORK_INFO +{ + UCHAR TLVType; // 0x010 - required parameter + USHORT TLVLength; // length + USHORT NumNetworkInstances; +} QMINAS_PERFORM_NETWORK_SCAN_NETWORK_INFO, *PQMINAS_PERFORM_NETWORK_SCAN_NETWORK_INFO; + +typedef struct _QMINAS_PERFORM_NETWORK_SCAN_RAT_INFO +{ + UCHAR TLVType; // 0x011 - required parameter + USHORT TLVLength; // length + USHORT NumInst; +} QMINAS_PERFORM_NETWORK_SCAN_RAT_INFO, *PQMINAS_PERFORM_NETWORK_SCAN_RAT_INFO; + +typedef struct _QMINAS_PERFORM_NETWORK_SCAN_RAT +{ + USHORT MCC; + USHORT MNC; + UCHAR RAT; +} QMINAS_PERFORM_NETWORK_SCAN_RAT, *PQMINAS_PERFORM_NETWORK_SCAN_RAT; + + +typedef struct _QMINAS_MANUAL_NW_REGISTER +{ + UCHAR TLV2Type; // 0x02 - result code + USHORT TLV2Length; // 4 + USHORT MobileCountryCode; + USHORT MobileNetworkCode; + UCHAR RadioAccess; +} QMINAS_MANUAL_NW_REGISTER, *PQMINAS_MANUAL_NW_REGISTER; + +typedef struct _QMINAS_INITIATE_NW_REGISTER_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + UCHAR RegisterAction; +} QMINAS_INITIATE_NW_REGISTER_REQ_MSG, *PQMINAS_INITIATE_NW_REGISTER_REQ_MSG; + +typedef struct _QMINAS_INITIATE_NW_REGISTER_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} QMINAS_INITIATE_NW_REGISTER_RESP_MSG, *PQMINAS_INITIATE_NW_REGISTER_RESP_MSG; + +typedef struct _QMINAS_SET_TECHNOLOGY_PREF_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT TechPref; + UCHAR Duration; +} QMINAS_SET_TECHNOLOGY_PREF_REQ_MSG, *PQMINAS_SET_TECHNOLOGY_PREF_REQ_MSG; + +typedef struct _QMINAS_SET_TECHNOLOGY_PREF_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} QMINAS_SET_TECHNOLOGY_PREF_RESP_MSG, *PQMINAS_SET_TECHNOLOGY_PREF_RESP_MSG; + +typedef struct _QMINAS_GET_SIGNAL_STRENGTH_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; +} QMINAS_GET_SIGNAL_STRENGTH_REQ_MSG, *PQMINAS_GET_SIGNAL_STRENGTH_REQ_MSG; + +typedef struct _QMINAS_SIGNAL_STRENGTH +{ + CHAR SigStrength; + UCHAR RadioIf; +} QMINAS_SIGNAL_STRENGTH, *PQMINAS_SIGNAL_STRENGTH; + +typedef struct _QMINAS_SIGNAL_STRENGTH_LIST +{ + UCHAR TLV3Type; + USHORT TLV3Length; + USHORT NumInstance; +} QMINAS_SIGNAL_STRENGTH_LIST, *PQMINAS_SIGNAL_STRENGTH_LIST; + + +typedef struct _QMINAS_GET_SIGNAL_STRENGTH_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR TLV2Type; + USHORT TLV2Length; + CHAR SignalStrength; + UCHAR RadioIf; +} QMINAS_GET_SIGNAL_STRENGTH_RESP_MSG, *PQMINAS_GET_SIGNAL_STRENGTH_RESP_MSG; + + +typedef struct _QMINAS_SET_EVENT_REPORT_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR ReportSigStrength; + UCHAR NumTresholds; + CHAR TresholdList[2]; +} QMINAS_SET_EVENT_REPORT_REQ_MSG, *PQMINAS_SET_EVENT_REPORT_REQ_MSG; + +typedef struct _QMINAS_SET_EVENT_REPORT_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} QMINAS_SET_EVENT_REPORT_RESP_MSG, *PQMINAS_SET_EVENT_REPORT_RESP_MSG; + +typedef struct _QMINAS_SIGNAL_STRENGTH_TLV +{ + UCHAR TLVType; + USHORT TLVLength; + CHAR SigStrength; + UCHAR RadioIf; +} QMINAS_SIGNAL_STRENGTH_TLV, *PQMINAS_SIGNAL_STRENGTH_TLV; + +typedef struct _QMINAS_REJECT_CAUSE_TLV +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR ServiceDomain; + USHORT RejectCause; +} QMINAS_REJECT_CAUSE_TLV, *PQMINAS_REJECT_CAUSE_TLV; + +typedef struct _QMINAS_EVENT_REPORT_IND_MSG +{ + USHORT Type; + USHORT Length; +} QMINAS_EVENT_REPORT_IND_MSG, *PQMINAS_EVENT_REPORT_IND_MSG; + +typedef struct _QMINAS_GET_RF_BAND_INFO_REQ_MSG +{ + USHORT Type; + USHORT Length; +} QMINAS_GET_RF_BAND_INFO_REQ_MSG, *PQMINAS_GET_RF_BAND_INFO_REQ_MSG; + +typedef struct _QMINASRF_BAND_INFO +{ + UCHAR RadioIf; + USHORT ActiveBand; + USHORT ActiveChannel; +} QMINASRF_BAND_INFO, *PQMINASRF_BAND_INFO; + +typedef struct _QMINAS_GET_RF_BAND_INFO_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR NumInstances; +} QMINAS_GET_RF_BAND_INFO_RESP_MSG, *PQMINAS_GET_RF_BAND_INFO_RESP_MSG; + + +typedef struct _QMINAS_GET_PLMN_NAME_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT MCC; + USHORT MNC; +} QMINAS_GET_PLMN_NAME_REQ_MSG, *PQMINAS_GET_PLMN_NAME_REQ_MSG; + +typedef struct _QMINAS_GET_PLMN_NAME_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} QMINAS_GET_PLMN_NAME_RESP_MSG, *PQMINAS_GET_PLMN_NAME_RESP_MSG; + +typedef struct _QMINAS_GET_PLMN_NAME_SPN +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR SPN_Enc; + UCHAR SPN_Len; +} QMINAS_GET_PLMN_NAME_SPN, *PQMINAS_GET_PLMN_NAME_SPN; + +typedef struct _QMINAS_GET_PLMN_NAME_PLMN +{ + UCHAR PLMN_Enc; + UCHAR PLMN_Ci; + UCHAR PLMN_SpareBits; + UCHAR PLMN_Len; +} QMINAS_GET_PLMN_NAME_PLMN, *PQMINAS_GET_PLMN_NAME_PLMN; + +typedef struct _QMINAS_INITIATE_ATTACH_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR PsAttachAction; +} QMINAS_INITIATE_ATTACH_REQ_MSG, *PQMINAS_INITIATE_ATTACH_REQ_MSG; + +typedef struct _QMINAS_INITIATE_ATTACH_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG + // QMI_ERR_NO_MEMORY + // QMI_ERR_INTERNAL + // QMI_ERR_FAULT +} QMINAS_INITIATE_ATTACH_RESP_MSG, *PQMINAS_INITIATE_ATTACH_RESP_MSG; +#endif +// ======================= End of NAS ============================== + +// ======================= UIM ============================== +#define QMIUIM_READ_TRANSPARENT_REQ 0x0020 +#define QMIUIM_READ_TRANSPARENT_RESP 0x0020 +#define QMIUIM_READ_TRANSPARENT_IND 0x0020 +#define QMIUIM_READ_RECORD_REQ 0x0021 +#define QMIUIM_READ_RECORD_RESP 0x0021 +#define QMIUIM_READ_RECORD_IND 0x0021 +#define QMIUIM_WRITE_TRANSPARENT_REQ 0x0022 +#define QMIUIM_WRITE_TRANSPARENT_RESP 0x0022 +#define QMIUIM_WRITE_TRANSPARENT_IND 0x0022 +#define QMIUIM_WRITE_RECORD_REQ 0x0023 +#define QMIUIM_WRITE_RECORD_RESP 0x0023 +#define QMIUIM_WRITE_RECORD_IND 0x0023 +#define QMIUIM_SET_PIN_PROTECTION_REQ 0x0025 +#define QMIUIM_SET_PIN_PROTECTION_RESP 0x0025 +#define QMIUIM_SET_PIN_PROTECTION_IND 0x0025 +#define QMIUIM_VERIFY_PIN_REQ 0x0026 +#define QMIUIM_VERIFY_PIN_RESP 0x0026 +#define QMIUIM_VERIFY_PIN_IND 0x0026 +#define QMIUIM_UNBLOCK_PIN_REQ 0x0027 +#define QMIUIM_UNBLOCK_PIN_RESP 0x0027 +#define QMIUIM_UNBLOCK_PIN_IND 0x0027 +#define QMIUIM_CHANGE_PIN_REQ 0x0028 +#define QMIUIM_CHANGE_PIN_RESP 0x0028 +#define QMIUIM_CHANGE_PIN_IND 0x0028 +#define QMIUIM_DEPERSONALIZATION_REQ 0x0029 +#define QMIUIM_DEPERSONALIZATION_RESP 0x0029 +#define QMIUIM_EVENT_REG_REQ 0x002E +#define QMIUIM_EVENT_REG_RESP 0x002E +#define QMIUIM_GET_CARD_STATUS_REQ 0x002F +#define QMIUIM_GET_CARD_STATUS_RESP 0x002F +#define QMIUIM_STATUS_CHANGE_IND 0x0032 + + +typedef struct _QMIUIM_GET_CARD_STATUS_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} __attribute__ ((packed)) QMIUIM_GET_CARD_STATUS_RESP_MSG, *PQMIUIM_GET_CARD_STATUS_RESP_MSG; + +typedef struct _QMIUIM_CARD_STATUS +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT IndexGWPri; + USHORT Index1XPri; + USHORT IndexGWSec; + USHORT Index1XSec; + UCHAR NumSlot; + UCHAR CardState; + UCHAR UPINState; + UCHAR UPINRetries; + UCHAR UPUKRetries; + UCHAR ErrorCode; + UCHAR NumApp; + UCHAR AppType; + UCHAR AppState; + UCHAR PersoState; + UCHAR PersoFeature; + UCHAR PersoRetries; + UCHAR PersoUnblockRetries; + UCHAR AIDLength; +} __attribute__ ((packed)) QMIUIM_CARD_STATUS, *PQMIUIM_CARD_STATUS; + +typedef struct _QMIUIM_PIN_STATE +{ + UCHAR UnivPIN; + UCHAR PIN1State; + UCHAR PIN1Retries; + UCHAR PUK1Retries; + UCHAR PIN2State; + UCHAR PIN2Retries; + UCHAR PUK2Retries; +} __attribute__ ((packed)) QMIUIM_PIN_STATE, *PQMIUIM_PIN_STATE; + +typedef struct _QMIUIM_VERIFY_PIN_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR Session_Type; + UCHAR Aid_Len; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR PINID; + UCHAR PINLen; + UCHAR PINValue; +} __attribute__ ((packed)) QMIUIM_VERIFY_PIN_REQ_MSG, *PQMIUIM_VERIFY_PIN_REQ_MSG; + +typedef struct _QMIUIM_VERIFY_PIN_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR PINVerifyRetriesLeft; + UCHAR PINUnblockRetriesLeft; +} __attribute__ ((packed)) QMIUIM_VERIFY_PIN_RESP_MSG, *PQMIUIM_VERIFY_PIN_RESP_MSG; + + +typedef struct _QMUX_MSG +{ + QCQMUX_HDR QMUXHdr; + union + { + // Message Header + QCQMUX_MSG_HDR QMUXMsgHdr; + QCQMUX_MSG_HDR_RESP QMUXMsgHdrResp; + + // QMIWDS Message +#if 0 + QMIWDS_GET_PKT_SRVC_STATUS_REQ_MSG PacketServiceStatusReq; + QMIWDS_GET_PKT_SRVC_STATUS_RESP_MSG PacketServiceStatusRsp; + QMIWDS_GET_PKT_SRVC_STATUS_IND_MSG PacketServiceStatusInd; + QMIWDS_EVENT_REPORT_IND_MSG EventReportInd; + QMIWDS_GET_CURRENT_CHANNEL_RATE_REQ_MSG GetCurrChannelRateReq; + QMIWDS_GET_CURRENT_CHANNEL_RATE_RESP_MSG GetCurrChannelRateRsp; + QMIWDS_GET_PKT_STATISTICS_REQ_MSG GetPktStatsReq; + QMIWDS_GET_PKT_STATISTICS_RESP_MSG GetPktStatsRsp; + QMIWDS_SET_EVENT_REPORT_REQ_MSG EventReportReq; + QMIWDS_SET_EVENT_REPORT_RESP_MSG EventReportRsp; +#endif + //#ifdef QC_IP_MODE + QMIWDS_GET_RUNTIME_SETTINGS_REQ_MSG GetRuntimeSettingsReq; + QMIWDS_GET_RUNTIME_SETTINGS_RESP_MSG GetRuntimeSettingsRsp; + //#endif // QC_IP_MODE +#if 0 + QMIWDS_SET_CLIENT_IP_FAMILY_PREF_REQ_MSG SetClientIpFamilyPrefReq; + QMIWDS_SET_CLIENT_IP_FAMILY_PREF_RESP_MSG SetClientIpFamilyPrefResp; + QMIWDS_GET_MIP_MODE_REQ_MSG GetMipModeReq; + QMIWDS_GET_MIP_MODE_RESP_MSG GetMipModeResp; +#endif + QMIWDS_START_NETWORK_INTERFACE_REQ_MSG StartNwInterfaceReq; + QMIWDS_START_NETWORK_INTERFACE_RESP_MSG StartNwInterfaceResp; + QMIWDS_STOP_NETWORK_INTERFACE_REQ_MSG StopNwInterfaceReq; + QMIWDS_STOP_NETWORK_INTERFACE_RESP_MSG StopNwInterfaceResp; + QMIWDS_GET_DEFAULT_SETTINGS_REQ_MSG GetDefaultSettingsReq; + QMIWDS_GET_DEFAULT_SETTINGS_RESP_MSG GetDefaultSettingsResp; + QMIWDS_MODIFY_PROFILE_SETTINGS_REQ_MSG ModifyProfileSettingsReq; + QMIWDS_MODIFY_PROFILE_SETTINGS_RESP_MSG ModifyProfileSettingsResp; + QMIWDS_GET_PROFILE_SETTINGS_REQ_MSG GetProfileSettingsReq; +#if 0 + QMIWDS_GET_DATA_BEARER_REQ_MSG GetDataBearerReq; + QMIWDS_GET_DATA_BEARER_RESP_MSG GetDataBearerResp; + QMIWDS_DUN_CALL_INFO_REQ_MSG DunCallInfoReq; + QMIWDS_DUN_CALL_INFO_RESP_MSG DunCallInfoResp; + QMIWDS_BIND_MUX_DATA_PORT_REQ_MSG BindMuxDataPortReq; +#endif + + // QMIDMS Messages +#if 0 + QMIDMS_GET_DEVICE_MFR_REQ_MSG GetDeviceMfrReq; + QMIDMS_GET_DEVICE_MFR_RESP_MSG GetDeviceMfrRsp; + QMIDMS_GET_DEVICE_MODEL_ID_REQ_MSG GetDeviceModeIdReq; + QMIDMS_GET_DEVICE_MODEL_ID_RESP_MSG GetDeviceModeIdRsp; + QMIDMS_GET_DEVICE_REV_ID_REQ_MSG GetDeviceRevIdReq; + QMIDMS_GET_DEVICE_REV_ID_RESP_MSG GetDeviceRevIdRsp; + QMIDMS_GET_MSISDN_REQ_MSG GetMsisdnReq; + QMIDMS_GET_MSISDN_RESP_MSG GetMsisdnRsp; + QMIDMS_GET_DEVICE_SERIAL_NUMBERS_REQ_MSG GetDeviceSerialNumReq; + QMIDMS_GET_DEVICE_SERIAL_NUMBERS_RESP_MSG GetDeviceSerialNumRsp; + QMIDMS_GET_DEVICE_CAP_REQ_MSG GetDeviceCapReq; + QMIDMS_GET_DEVICE_CAP_RESP_MSG GetDeviceCapResp; + QMIDMS_GET_BAND_CAP_REQ_MSG GetBandCapReq; + QMIDMS_GET_BAND_CAP_RESP_MSG GetBandCapRsp; + QMIDMS_GET_ACTIVATED_STATUS_REQ_MSG GetActivatedStatusReq; + QMIDMS_GET_ACTIVATED_STATUS_RESP_MSG GetActivatedStatusResp; + QMIDMS_GET_OPERATING_MODE_REQ_MSG GetOperatingModeReq; + QMIDMS_GET_OPERATING_MODE_RESP_MSG GetOperatingModeResp; + QMIDMS_SET_OPERATING_MODE_REQ_MSG SetOperatingModeReq; + QMIDMS_SET_OPERATING_MODE_RESP_MSG SetOperatingModeResp; + QMIDMS_UIM_GET_ICCID_REQ_MSG GetICCIDReq; + QMIDMS_UIM_GET_ICCID_RESP_MSG GetICCIDResp; + QMIDMS_ACTIVATE_AUTOMATIC_REQ_MSG ActivateAutomaticReq; + QMIDMS_ACTIVATE_AUTOMATIC_RESP_MSG ActivateAutomaticResp; + QMIDMS_ACTIVATE_MANUAL_REQ_MSG ActivateManualReq; + QMIDMS_ACTIVATE_MANUAL_RESP_MSG ActivateManualResp; +#endif + QMIDMS_UIM_GET_PIN_STATUS_REQ_MSG UIMGetPinStatusReq; + QMIDMS_UIM_GET_PIN_STATUS_RESP_MSG UIMGetPinStatusResp; + QMIDMS_UIM_VERIFY_PIN_REQ_MSG UIMVerifyPinReq; + QMIDMS_UIM_VERIFY_PIN_RESP_MSG UIMVerifyPinResp; +#if 0 + QMIDMS_UIM_SET_PIN_PROTECTION_REQ_MSG UIMSetPinProtectionReq; + QMIDMS_UIM_SET_PIN_PROTECTION_RESP_MSG UIMSetPinProtectionResp; + QMIDMS_UIM_CHANGE_PIN_REQ_MSG UIMChangePinReq; + QMIDMS_UIM_CHANGE_PIN_RESP_MSG UIMChangePinResp; + QMIDMS_UIM_UNBLOCK_PIN_REQ_MSG UIMUnblockPinReq; + QMIDMS_UIM_UNBLOCK_PIN_RESP_MSG UIMUnblockPinResp; + QMIDMS_SET_EVENT_REPORT_REQ_MSG DmsSetEventReportReq; + QMIDMS_SET_EVENT_REPORT_RESP_MSG DmsSetEventReportResp; + QMIDMS_EVENT_REPORT_IND_MSG DmsEventReportInd; +#endif + QMIDMS_UIM_GET_STATE_REQ_MSG UIMGetStateReq; + QMIDMS_UIM_GET_STATE_RESP_MSG UIMGetStateResp; + QMIDMS_UIM_GET_IMSI_REQ_MSG UIMGetIMSIReq; + QMIDMS_UIM_GET_IMSI_RESP_MSG UIMGetIMSIResp; +#if 0 + QMIDMS_UIM_GET_CK_STATUS_REQ_MSG UIMGetCkStatusReq; + QMIDMS_UIM_GET_CK_STATUS_RESP_MSG UIMGetCkStatusResp; + QMIDMS_UIM_SET_CK_PROTECTION_REQ_MSG UIMSetCkProtectionReq; + QMIDMS_UIM_SET_CK_PROTECTION_RESP_MSG UIMSetCkProtectionResp; + QMIDMS_UIM_UNBLOCK_CK_REQ_MSG UIMUnblockCkReq; + QMIDMS_UIM_UNBLOCK_CK_RESP_MSG UIMUnblockCkResp; +#endif + + // QMIQOS Messages +#if 0 + QMI_QOS_SET_EVENT_REPORT_REQ_MSG QosSetEventReportReq; + QMI_QOS_SET_EVENT_REPORT_RESP_MSG QosSetEventReportRsp; + QMI_QOS_EVENT_REPORT_IND_MSG QosEventReportInd; +#endif + + // QMIWMS Messages +#if 0 + QMIWMS_GET_MESSAGE_PROTOCOL_REQ_MSG GetMessageProtocolReq; + QMIWMS_GET_MESSAGE_PROTOCOL_RESP_MSG GetMessageProtocolResp; + QMIWMS_GET_SMSC_ADDRESS_REQ_MSG GetSMSCAddressReq; + QMIWMS_GET_SMSC_ADDRESS_RESP_MSG GetSMSCAddressResp; + QMIWMS_SET_SMSC_ADDRESS_REQ_MSG SetSMSCAddressReq; + QMIWMS_SET_SMSC_ADDRESS_RESP_MSG SetSMSCAddressResp; + QMIWMS_GET_STORE_MAX_SIZE_REQ_MSG GetStoreMaxSizeReq; + QMIWMS_GET_STORE_MAX_SIZE_RESP_MSG GetStoreMaxSizeResp; + QMIWMS_LIST_MESSAGES_REQ_MSG ListMessagesReq; + QMIWMS_LIST_MESSAGES_RESP_MSG ListMessagesResp; + QMIWMS_RAW_READ_REQ_MSG RawReadMessagesReq; + QMIWMS_RAW_READ_RESP_MSG RawReadMessagesResp; + QMIWMS_SET_EVENT_REPORT_REQ_MSG WmsSetEventReportReq; + QMIWMS_SET_EVENT_REPORT_RESP_MSG WmsSetEventReportResp; + QMIWMS_EVENT_REPORT_IND_MSG WmsEventReportInd; + QMIWMS_DELETE_REQ_MSG WmsDeleteReq; + QMIWMS_DELETE_RESP_MSG WmsDeleteResp; + QMIWMS_RAW_SEND_REQ_MSG RawSendMessagesReq; + QMIWMS_RAW_SEND_RESP_MSG RawSendMessagesResp; + QMIWMS_MODIFY_TAG_REQ_MSG WmsModifyTagReq; + QMIWMS_MODIFY_TAG_RESP_MSG WmsModifyTagResp; +#endif + + // QMINAS Messages +#if 0 + QMINAS_GET_HOME_NETWORK_REQ_MSG GetHomeNetworkReq; + QMINAS_GET_HOME_NETWORK_RESP_MSG GetHomeNetworkResp; + QMINAS_GET_PREFERRED_NETWORK_REQ_MSG GetPreferredNetworkReq; + QMINAS_GET_PREFERRED_NETWORK_RESP_MSG GetPreferredNetworkResp; + QMINAS_GET_FORBIDDEN_NETWORK_REQ_MSG GetForbiddenNetworkReq; + QMINAS_GET_FORBIDDEN_NETWORK_RESP_MSG GetForbiddenNetworkResp; + QMINAS_GET_SERVING_SYSTEM_REQ_MSG GetServingSystemReq; +#endif + QMINAS_GET_SERVING_SYSTEM_RESP_MSG GetServingSystemResp; + QMINAS_GET_SYS_INFO_RESP_MSG GetSysInfoResp; + QMINAS_SYS_INFO_IND_MSG NasSysInfoInd; +#if 0 + QMINAS_SERVING_SYSTEM_IND_MSG NasServingSystemInd; + QMINAS_SET_PREFERRED_NETWORK_REQ_MSG SetPreferredNetworkReq; + QMINAS_SET_PREFERRED_NETWORK_RESP_MSG SetPreferredNetworkResp; + QMINAS_SET_FORBIDDEN_NETWORK_REQ_MSG SetForbiddenNetworkReq; + QMINAS_SET_FORBIDDEN_NETWORK_RESP_MSG SetForbiddenNetworkResp; + QMINAS_PERFORM_NETWORK_SCAN_REQ_MSG PerformNetworkScanReq; + QMINAS_PERFORM_NETWORK_SCAN_RESP_MSG PerformNetworkScanResp; + QMINAS_INITIATE_NW_REGISTER_REQ_MSG InitiateNwRegisterReq; + QMINAS_INITIATE_NW_REGISTER_RESP_MSG InitiateNwRegisterResp; + QMINAS_SET_TECHNOLOGY_PREF_REQ_MSG SetTechnologyPrefReq; + QMINAS_SET_TECHNOLOGY_PREF_RESP_MSG SetTechnologyPrefResp; + QMINAS_GET_SIGNAL_STRENGTH_REQ_MSG GetSignalStrengthReq; + QMINAS_GET_SIGNAL_STRENGTH_RESP_MSG GetSignalStrengthResp; + QMINAS_SET_EVENT_REPORT_REQ_MSG SetEventReportReq; + QMINAS_SET_EVENT_REPORT_RESP_MSG SetEventReportResp; + QMINAS_EVENT_REPORT_IND_MSG NasEventReportInd; + QMINAS_GET_RF_BAND_INFO_REQ_MSG GetRFBandInfoReq; + QMINAS_GET_RF_BAND_INFO_RESP_MSG GetRFBandInfoResp; + QMINAS_INITIATE_ATTACH_REQ_MSG InitiateAttachReq; + QMINAS_INITIATE_ATTACH_RESP_MSG InitiateAttachResp; + QMINAS_GET_PLMN_NAME_REQ_MSG GetPLMNNameReq; + QMINAS_GET_PLMN_NAME_RESP_MSG GetPLMNNameResp; +#endif + + // QMIUIM Messages + QMIUIM_GET_CARD_STATUS_RESP_MSG UIMGetCardStatus; + QMIUIM_VERIFY_PIN_REQ_MSG UIMUIMVerifyPinReq; + QMIUIM_VERIFY_PIN_RESP_MSG UIMUIMVerifyPinResp; +#if 0 + QMIUIM_SET_PIN_PROTECTION_REQ_MSG UIMUIMSetPinProtectionReq; + QMIUIM_SET_PIN_PROTECTION_RESP_MSG UIMUIMSetPinProtectionResp; + QMIUIM_CHANGE_PIN_REQ_MSG UIMUIMChangePinReq; + QMIUIM_CHANGE_PIN_RESP_MSG UIMUIMChangePinResp; + QMIUIM_UNBLOCK_PIN_REQ_MSG UIMUIMUnblockPinReq; + QMIUIM_UNBLOCK_PIN_RESP_MSG UIMUIMUnblockPinResp; + QMIUIM_READ_TRANSPARENT_REQ_MSG UIMUIMReadTransparentReq; + QMIUIM_READ_TRANSPARENT_RESP_MSG UIMUIMReadTransparentResp; +#endif + + }; +} __attribute__ ((packed)) QMUX_MSG, *PQMUX_MSG; + +#pragma pack(pop) + +#endif // MPQMUX_H diff --git a/root/package/link4all/quectel-CM/src_quectel/Makefile b/root/package/link4all/quectel-CM/src_quectel/Makefile new file mode 100755 index 00000000..aaf8e4ed --- /dev/null +++ b/root/package/link4all/quectel-CM/src_quectel/Makefile @@ -0,0 +1,6 @@ +quectel-CM:clean + $(CC) -Wall -s QmiWwanCM.c GobiNetCM.c main.c MPQMUX.c QMIThread.c util.c udhcpc.c -o quectel-CM -lpthread + +clean: + rm -rf quectel-CM *~ + diff --git a/root/package/link4all/quectel-CM/src_quectel/QMIThread.c b/root/package/link4all/quectel-CM/src_quectel/QMIThread.c new file mode 100755 index 00000000..1ed3e59b --- /dev/null +++ b/root/package/link4all/quectel-CM/src_quectel/QMIThread.c @@ -0,0 +1,1739 @@ +#include "QMIThread.h" +extern char *strndup (const char *__string, size_t __n); + +int qmiclientId[QMUX_TYPE_WDS_ADMIN + 1]; //GobiNet use fd to indicate client ID, so type of qmiclientId must be int +static UINT WdsConnectionIPv4Handle = 0; +static int s_is_cdma = 0; +static int s_hdr_personality = 0; // 0x01-HRPD, 0x02-eHRPD +static char *qstrcpy(char *to, const char *from) { //no __strcpy_chk + char *save = to; + for (; (*to = *from) != '\0'; ++from, ++to); + return(save); +} + +static int s_9x07 = 0; + +typedef USHORT (*CUSTOMQMUX)(PQMUX_MSG pMUXMsg, void *arg); + +// To retrieve the ith (Index) TLV +PQMI_TLV_HDR GetTLV (PQCQMUX_MSG_HDR pQMUXMsgHdr, int TLVType) { + int TLVFind = 0; + USHORT Length = le16_to_cpu(pQMUXMsgHdr->Length); + PQMI_TLV_HDR pTLVHdr = (PQMI_TLV_HDR)(pQMUXMsgHdr + 1); + + while (Length >= sizeof(QMI_TLV_HDR)) { + TLVFind++; + if (TLVType > 0x1000) { + if ((TLVFind + 0x1000) == TLVType) + return pTLVHdr; + } else if (pTLVHdr->TLVType == TLVType) { + return pTLVHdr; + } + + Length -= (le16_to_cpu((pTLVHdr->TLVLength)) + sizeof(QMI_TLV_HDR)); + pTLVHdr = (PQMI_TLV_HDR)(((UCHAR *)pTLVHdr) + le16_to_cpu(pTLVHdr->TLVLength) + sizeof(QMI_TLV_HDR)); + } + + return NULL; +} + +static USHORT GetQMUXTransactionId(void) { + static int TransactionId = 0; + if (++TransactionId > 0xFFFF) + TransactionId = 1; + return TransactionId; +} + +static PQCQMIMSG ComposeQMUXMsg(UCHAR QMIType, USHORT Type, CUSTOMQMUX customQmuxMsgFunction, void *arg) { + UCHAR QMIBuf[WDM_DEFAULT_BUFSIZE]; + PQCQMIMSG pRequest = (PQCQMIMSG)QMIBuf; + int Length; + + memset(QMIBuf, 0x00, sizeof(QMIBuf)); + pRequest->QMIHdr.IFType = USB_CTL_MSG_TYPE_QMI; + pRequest->QMIHdr.CtlFlags = 0x00; + pRequest->QMIHdr.QMIType = QMIType; + pRequest->QMIHdr.ClientId = qmiclientId[pRequest->QMIHdr.QMIType] & 0xFF; + + if (pRequest->QMIHdr.ClientId == 0) { + dbg_time("QMIType %d has no clientID", pRequest->QMIHdr.QMIType); + return NULL; + } + + pRequest->MUXMsg.QMUXHdr.CtlFlags = QMUX_CTL_FLAG_SINGLE_MSG | QMUX_CTL_FLAG_TYPE_CMD; + pRequest->MUXMsg.QMUXHdr.TransactionId = cpu_to_le16(GetQMUXTransactionId()); + pRequest->MUXMsg.QMUXMsgHdr.Type = cpu_to_le16(Type); + if (customQmuxMsgFunction) + pRequest->MUXMsg.QMUXMsgHdr.Length = cpu_to_le16(customQmuxMsgFunction(&pRequest->MUXMsg, arg) - sizeof(QCQMUX_MSG_HDR)); + else + pRequest->MUXMsg.QMUXMsgHdr.Length = cpu_to_le16(0x0000); + + pRequest->QMIHdr.Length = cpu_to_le16(le16_to_cpu(pRequest->MUXMsg.QMUXMsgHdr.Length) + sizeof(QCQMUX_MSG_HDR) + sizeof(QCQMUX_HDR) + + sizeof(QCQMI_HDR) - 1); + Length = le16_to_cpu(pRequest->QMIHdr.Length) + 1; + + pRequest = (PQCQMIMSG)malloc(Length); + if (pRequest == NULL) { + dbg_time("%s fail to malloc", __func__); + } else { + memcpy(pRequest, QMIBuf, Length); + } + + return pRequest; +} + +#if 0 +static USHORT NasSetEventReportReq(PQMUX_MSG pMUXMsg, void *arg) { + pMUXMsg->SetEventReportReq.TLVType = 0x10; + pMUXMsg->SetEventReportReq.TLVLength = 0x04; + pMUXMsg->SetEventReportReq.ReportSigStrength = 0x00; + pMUXMsg->SetEventReportReq.NumTresholds = 2; + pMUXMsg->SetEventReportReq.TresholdList[0] = -113; + pMUXMsg->SetEventReportReq.TresholdList[1] = -50; + return sizeof(QMINAS_SET_EVENT_REPORT_REQ_MSG); +} + +static USHORT WdsSetEventReportReq(PQMUX_MSG pMUXMsg, void *arg) { + pMUXMsg->EventReportReq.TLVType = 0x10; // 0x10 -- current channel rate indicator + pMUXMsg->EventReportReq.TLVLength = 0x0001; // 1 + pMUXMsg->EventReportReq.Mode = 0x00; // 0-do not report; 1-report when rate changes + + pMUXMsg->EventReportReq.TLV2Type = 0x11; // 0x11 + pMUXMsg->EventReportReq.TLV2Length = 0x0005; // 5 + pMUXMsg->EventReportReq.StatsPeriod = 0x00; // seconds between reports; 0-do not report + pMUXMsg->EventReportReq.StatsMask = 0x000000ff; // + + pMUXMsg->EventReportReq.TLV3Type = 0x12; // 0x12 -- current data bearer indicator + pMUXMsg->EventReportReq.TLV3Length = 0x0001; // 1 + pMUXMsg->EventReportReq.Mode3 = 0x01; // 0-do not report; 1-report when changes + + pMUXMsg->EventReportReq.TLV4Type = 0x13; // 0x13 -- dormancy status indicator + pMUXMsg->EventReportReq.TLV4Length = 0x0001; // 1 + pMUXMsg->EventReportReq.DormancyStatus = 0x00; // 0-do not report; 1-report when changes + return sizeof(QMIWDS_SET_EVENT_REPORT_REQ_MSG); +} + +static USHORT DmsSetEventReportReq(PQMUX_MSG pMUXMsg) { + PPIN_STATUS pPinState = (PPIN_STATUS)(&pMUXMsg->DmsSetEventReportReq + 1); + PUIM_STATE pUimState = (PUIM_STATE)(pPinState + 1); + // Pin State + pPinState->TLVType = 0x12; + pPinState->TLVLength = 0x01; + pPinState->ReportPinState = 0x01; + // UIM State + pUimState->TLVType = 0x15; + pUimState->TLVLength = 0x01; + pUimState->UIMState = 0x01; + return sizeof(QMIDMS_SET_EVENT_REPORT_REQ_MSG) + sizeof(PIN_STATUS) + sizeof(UIM_STATE); +} +#endif + +static USHORT WdsStartNwInterfaceReq(PQMUX_MSG pMUXMsg, void *arg) { + PQMIWDS_TECHNOLOGY_PREFERECE pTechPref; + PQMIWDS_AUTH_PREFERENCE pAuthPref; + PQMIWDS_USERNAME pUserName; + PQMIWDS_PASSWD pPasswd; + PQMIWDS_APNNAME pApnName; + PQMIWDS_IP_FAMILY_TLV pIpFamily; + USHORT TLVLength = 0; + UCHAR *pTLV; + PROFILE_T *profile = (PROFILE_T *)arg; + + pTLV = (UCHAR *)(&pMUXMsg->StartNwInterfaceReq + 1); + pMUXMsg->StartNwInterfaceReq.Length = 0; + + // Set technology Preferece + pTechPref = (PQMIWDS_TECHNOLOGY_PREFERECE)(pTLV + TLVLength); + pTechPref->TLVType = 0x30; + pTechPref->TLVLength = cpu_to_le16(0x01); + if (s_is_cdma == 0) + pTechPref->TechPreference = 0x01; + else + pTechPref->TechPreference = 0x02; + TLVLength +=(le16_to_cpu(pTechPref->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + + // Set APN Name + if (profile->apn) { + pApnName = (PQMIWDS_APNNAME)(pTLV + TLVLength); + pApnName->TLVType = 0x14; + pApnName->TLVLength = cpu_to_le16(strlen(profile->apn)); + qstrcpy((char *)&pApnName->ApnName, profile->apn); + TLVLength +=(le16_to_cpu(pApnName->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + // Set User Name + if (profile->user) { + pUserName = (PQMIWDS_USERNAME)(pTLV + TLVLength); + pUserName->TLVType = 0x17; + pUserName->TLVLength = cpu_to_le16(strlen(profile->user)); + qstrcpy((char *)&pUserName->UserName, profile->user); + TLVLength += (le16_to_cpu(pUserName->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + // Set Password + if (profile->password) { + pPasswd = (PQMIWDS_PASSWD)(pTLV + TLVLength); + pPasswd->TLVType = 0x18; + pPasswd->TLVLength = cpu_to_le16(strlen(profile->password)); + qstrcpy((char *)&pPasswd->Passwd, profile->password); + TLVLength +=(le16_to_cpu(pPasswd->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + // Set Auth Protocol + if (profile->user && profile->password) { + pAuthPref = (PQMIWDS_AUTH_PREFERENCE)(pTLV + TLVLength); + pAuthPref->TLVType = 0x16; + pAuthPref->TLVLength = cpu_to_le16(0x01); + pAuthPref->AuthPreference = profile->auth; // 0 ~ None, 1 ~ Pap, 2 ~ Chap, 3 ~ MsChapV2 + TLVLength +=(le16_to_cpu(pAuthPref->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + // Add IP Family Preference + pIpFamily = (PQMIWDS_IP_FAMILY_TLV)(pTLV + TLVLength); + pIpFamily->TLVType = 0x19; + pIpFamily->TLVLength = cpu_to_le16(0x01); + pIpFamily->IpFamily = profile->IPType; + TLVLength += (le16_to_cpu(pIpFamily->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + + //Set Profile Index + if (profile->pdp) { + PQMIWDS_PROFILE_IDENTIFIER pProfileIndex = (PQMIWDS_PROFILE_IDENTIFIER)(pTLV + TLVLength); + pProfileIndex->TLVLength = cpu_to_le16(0x01); + pProfileIndex->TLVType = 0x31; + pProfileIndex->ProfileIndex = profile->pdp; + if (s_is_cdma && s_hdr_personality == 0x02) { + pProfileIndex->TLVType = 0x32; //profile_index_3gpp2 + pProfileIndex->ProfileIndex = 101; + } + TLVLength += (le16_to_cpu(pProfileIndex->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + return sizeof(QMIWDS_START_NETWORK_INTERFACE_REQ_MSG) + TLVLength; +} + +static USHORT WdsStopNwInterfaceReq(PQMUX_MSG pMUXMsg, void *arg) { + pMUXMsg->StopNwInterfaceReq.TLVType = 0x01; + pMUXMsg->StopNwInterfaceReq.TLVLength = cpu_to_le16(0x04); + pMUXMsg->StopNwInterfaceReq.Handle = cpu_to_le32(WdsConnectionIPv4Handle); + return sizeof(QMIWDS_STOP_NETWORK_INTERFACE_REQ_MSG); +} + +static USHORT WdaSetDataFormat(PQMUX_MSG pMUXMsg, void *arg) { + PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV_QOS pWdsAdminQosTlv; + PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV linkProto; + PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV dlTlp; + + pWdsAdminQosTlv = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV_QOS)(&pMUXMsg->QMUXMsgHdr + 1); + pWdsAdminQosTlv->TLVType = 0x10; + pWdsAdminQosTlv->TLVLength = cpu_to_le16(0x0001); + pWdsAdminQosTlv->QOSSetting = 0; /* no-QOS header */ + + linkProto = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV)(pWdsAdminQosTlv + 1); + linkProto->TLVType = 0x11; + linkProto->TLVLength = cpu_to_le16(4); + linkProto->Value = cpu_to_le32(0x01); /* Set Ethernet mode */ + + dlTlp = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV)(linkProto + 1);; + dlTlp->TLVType = 0x13; + dlTlp->TLVLength = cpu_to_le16(4); + dlTlp->Value = cpu_to_le32(0x00); + + if (sizeof(*linkProto) != 7 ) + dbg_time("%s sizeof(*linkProto) = %d, is not 7!", __func__, sizeof(*linkProto) ); + + return sizeof(QCQMUX_MSG_HDR) + sizeof(*pWdsAdminQosTlv) + sizeof(*linkProto) + sizeof(*dlTlp); +} + +#ifdef CONFIG_SIM +static USHORT DmsUIMVerifyPinReqSend(PQMUX_MSG pMUXMsg, void *arg) { + pMUXMsg->UIMVerifyPinReq.TLVType = 0x01; + pMUXMsg->UIMVerifyPinReq.PINID = 0x01; //Pin1, not Puk + pMUXMsg->UIMVerifyPinReq.PINLen = strlen((const char *)arg); + qstrcpy((PCHAR)&pMUXMsg->UIMVerifyPinReq.PINValue, ((const char *)arg)); + pMUXMsg->UIMVerifyPinReq.TLVLength = cpu_to_le16(2 + strlen((const char *)arg)); + return sizeof(QMIDMS_UIM_VERIFY_PIN_REQ_MSG) + (strlen((const char *)arg) - 1); +} + +static USHORT UimVerifyPinReqSend(PQMUX_MSG pMUXMsg, void *arg) +{ + pMUXMsg->UIMUIMVerifyPinReq.TLVType = 0x01; + pMUXMsg->UIMUIMVerifyPinReq.TLVLength = cpu_to_le16(0x02); + pMUXMsg->UIMUIMVerifyPinReq.Session_Type = 0x00; + pMUXMsg->UIMUIMVerifyPinReq.Aid_Len = 0x00; + pMUXMsg->UIMUIMVerifyPinReq.TLV2Type = 0x02; + pMUXMsg->UIMUIMVerifyPinReq.TLV2Length = cpu_to_le16(2 + strlen((const char *)arg)); + pMUXMsg->UIMUIMVerifyPinReq.PINID = 0x01; //Pin1, not Puk + pMUXMsg->UIMUIMVerifyPinReq.PINLen= strlen((const char *)arg); + qstrcpy((PCHAR)&pMUXMsg->UIMUIMVerifyPinReq.PINValue, ((const char *)arg)); + return sizeof(QMIUIM_VERIFY_PIN_REQ_MSG) + (strlen((const char *)arg) - 1); +} +#endif + +#ifdef CONFIG_APN +static USHORT WdsGetProfileSettingsReqSend(PQMUX_MSG pMUXMsg, void *arg) { + PROFILE_T *profile = (PROFILE_T *)arg; + pMUXMsg->GetProfileSettingsReq.Length = cpu_to_le16(sizeof(QMIWDS_GET_PROFILE_SETTINGS_REQ_MSG) - 4); + pMUXMsg->GetProfileSettingsReq.TLVType = 0x01; + pMUXMsg->GetProfileSettingsReq.TLVLength = cpu_to_le16(0x02); + pMUXMsg->GetProfileSettingsReq.ProfileType = 0x00; // 0 ~ 3GPP, 1 ~ 3GPP2 + pMUXMsg->GetProfileSettingsReq.ProfileIndex = profile->pdp; + return sizeof(QMIWDS_GET_PROFILE_SETTINGS_REQ_MSG); +} + +static USHORT WdsModifyProfileSettingsReq(PQMUX_MSG pMUXMsg, void *arg) { + USHORT TLVLength = 0; + UCHAR *pTLV; + PROFILE_T *profile = (PROFILE_T *)arg; + PQMIWDS_PDPTYPE pPdpType; + + pMUXMsg->ModifyProfileSettingsReq.Length = cpu_to_le16(sizeof(QMIWDS_MODIFY_PROFILE_SETTINGS_REQ_MSG) - 4); + pMUXMsg->ModifyProfileSettingsReq.TLVType = 0x01; + pMUXMsg->ModifyProfileSettingsReq.TLVLength = cpu_to_le16(0x02); + pMUXMsg->ModifyProfileSettingsReq.ProfileType = 0x00; // 0 ~ 3GPP, 1 ~ 3GPP2 + pMUXMsg->ModifyProfileSettingsReq.ProfileIndex = profile->pdp; + + pTLV = (UCHAR *)(&pMUXMsg->ModifyProfileSettingsReq + 1); + + pPdpType = (PQMIWDS_PDPTYPE)(pTLV + TLVLength); + pPdpType->TLVType = 0x11; + pPdpType->TLVLength = cpu_to_le16(0x01); +// 0 ¨C PDP-IP (IPv4) +// 1 ¨C PDP-PPP +// 2 ¨C PDP-IPv6 +// 3 ¨C PDP-IPv4v6 + pPdpType->PdpType = 3; + TLVLength +=(le16_to_cpu(pPdpType->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + + // Set APN Name + if (profile->apn) { + PQMIWDS_APNNAME pApnName = (PQMIWDS_APNNAME)(pTLV + TLVLength); + pApnName->TLVType = 0x14; + pApnName->TLVLength = cpu_to_le16(strlen(profile->apn)); + qstrcpy((char *)&pApnName->ApnName, profile->apn); + TLVLength +=(le16_to_cpu(pApnName->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + // Set User Name + if (profile->user) { + PQMIWDS_USERNAME pUserName = (PQMIWDS_USERNAME)(pTLV + TLVLength); + pUserName->TLVType = 0x1B; + pUserName->TLVLength = cpu_to_le16(strlen(profile->user)); + qstrcpy((char *)&pUserName->UserName, profile->user); + TLVLength += (le16_to_cpu(pUserName->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + // Set Password + if (profile->password) { + PQMIWDS_PASSWD pPasswd = (PQMIWDS_PASSWD)(pTLV + TLVLength); + pPasswd->TLVType = 0x1C; + pPasswd->TLVLength = cpu_to_le16(strlen(profile->password)); + qstrcpy((char *)&pPasswd->Passwd, profile->password); + TLVLength +=(le16_to_cpu(pPasswd->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + // Set Auth Protocol + if (profile->user && profile->password) { + PQMIWDS_AUTH_PREFERENCE pAuthPref = (PQMIWDS_AUTH_PREFERENCE)(pTLV + TLVLength); + pAuthPref->TLVType = 0x1D; + pAuthPref->TLVLength = cpu_to_le16(0x01); + pAuthPref->AuthPreference = profile->auth; // 0 ~ None, 1 ~ Pap, 2 ~ Chap, 3 ~ MsChapV2 + TLVLength += (le16_to_cpu(pAuthPref->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + return sizeof(QMIWDS_MODIFY_PROFILE_SETTINGS_REQ_MSG) + TLVLength; +} +#endif + +static USHORT WdsGetRuntimeSettingReq(PQMUX_MSG pMUXMsg, void *arg) { + pMUXMsg->GetRuntimeSettingsReq.TLVType = 0x10; + pMUXMsg->GetRuntimeSettingsReq.TLVLength = cpu_to_le16(0x04); + // the following mask also applies to IPV6 + pMUXMsg->GetRuntimeSettingsReq.Mask = cpu_to_le32(QMIWDS_GET_RUNTIME_SETTINGS_MASK_IPV4DNS_ADDR | + QMIWDS_GET_RUNTIME_SETTINGS_MASK_IPV4_ADDR | + QMIWDS_GET_RUNTIME_SETTINGS_MASK_MTU | + QMIWDS_GET_RUNTIME_SETTINGS_MASK_IPV4GATEWAY_ADDR); // | + // QMIWDS_GET_RUNTIME_SETTINGS_MASK_PCSCF_SV_ADDR | + // QMIWDS_GET_RUNTIME_SETTINGS_MASK_PCSCF_DOM_NAME; + + return sizeof(QMIWDS_GET_RUNTIME_SETTINGS_REQ_MSG); +} + +static PQCQMIMSG s_pRequest; +static PQCQMIMSG s_pResponse; +static pthread_mutex_t s_commandmutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t s_commandcond = PTHREAD_COND_INITIALIZER; + +static int is_response(const PQCQMIMSG pRequest, const PQCQMIMSG pResponse) { + if ((pRequest->QMIHdr.QMIType == pResponse->QMIHdr.QMIType) + && (pRequest->QMIHdr.ClientId == pResponse->QMIHdr.ClientId)) { + USHORT requestTID, responseTID; + if (pRequest->QMIHdr.QMIType == QMUX_TYPE_CTL) { + requestTID = pRequest->CTLMsg.QMICTLMsgHdr.TransactionId; + responseTID = pResponse->CTLMsg.QMICTLMsgHdr.TransactionId; + } else { + requestTID = le16_to_cpu(pRequest->MUXMsg.QMUXHdr.TransactionId); + responseTID = le16_to_cpu(pResponse->MUXMsg.QMUXHdr.TransactionId); + } + return (requestTID == responseTID); + } + return 0; +} + +int QmiThreadSendQMITimeout(PQCQMIMSG pRequest, PQCQMIMSG *ppResponse, unsigned msecs) { + int ret; + + if (!pRequest) + { + return -EINVAL; + } + + pthread_mutex_lock(&s_commandmutex); + + if (ppResponse) + *ppResponse = NULL; + + dump_qmi(pRequest, le16_to_cpu(pRequest->QMIHdr.Length) + 1); + + s_pRequest = pRequest; + s_pResponse = NULL; + + if (!strncmp(qmichannel, "/dev/qcqmi", strlen("/dev/qcqmi"))) + ret = GobiNetSendQMI(pRequest); + else + ret = QmiWwanSendQMI(pRequest); + + if (ret == 0) { + ret = pthread_cond_timeout_np(&s_commandcond, &s_commandmutex, msecs); + if (!ret) { + if (s_pResponse && ppResponse) { + *ppResponse = s_pResponse; + } else { + if (s_pResponse) { + free(s_pResponse); + s_pResponse = NULL; + } + } + } else { + dbg_time("%s pthread_cond_timeout_np=%d, errno: %d (%s)", __func__, ret, errno, strerror(errno)); + } + } + + pthread_mutex_unlock(&s_commandmutex); + + return ret; +} + +int QmiThreadSendQMI(PQCQMIMSG pRequest, PQCQMIMSG *ppResponse) { + return QmiThreadSendQMITimeout(pRequest, ppResponse, 30 * 1000); +} + +void QmiThreadRecvQMI(PQCQMIMSG pResponse) { + pthread_mutex_lock(&s_commandmutex); + if (pResponse == NULL) { + if (s_pRequest) { + free(s_pRequest); + s_pRequest = NULL; + s_pResponse = NULL; + pthread_cond_signal(&s_commandcond); + } + pthread_mutex_unlock(&s_commandmutex); + return; + } + dump_qmi(pResponse, le16_to_cpu(pResponse->QMIHdr.Length) + 1); + if (s_pRequest && is_response(s_pRequest, pResponse)) { + free(s_pRequest); + s_pRequest = NULL; + s_pResponse = malloc(le16_to_cpu(pResponse->QMIHdr.Length) + 1); + if (s_pResponse != NULL) { + memcpy(s_pResponse, pResponse, le16_to_cpu(pResponse->QMIHdr.Length) + 1); + } + pthread_cond_signal(&s_commandcond); + } else if ((pResponse->QMIHdr.QMIType == QMUX_TYPE_NAS) + && (le16_to_cpu(pResponse->MUXMsg.QMUXMsgHdrResp.Type) == QMINAS_SERVING_SYSTEM_IND)) { + qmidevice_send_event_to_main(RIL_UNSOL_RESPONSE_VOICE_NETWORK_STATE_CHANGED); + } else if ((pResponse->QMIHdr.QMIType == QMUX_TYPE_WDS) + && (le16_to_cpu(pResponse->MUXMsg.QMUXMsgHdrResp.Type) == QMIWDS_GET_PKT_SRVC_STATUS_IND)) { + qmidevice_send_event_to_main(RIL_UNSOL_DATA_CALL_LIST_CHANGED); + } else if ((pResponse->QMIHdr.QMIType == QMUX_TYPE_NAS) + && (le16_to_cpu(pResponse->MUXMsg.QMUXMsgHdrResp.Type) == QMINAS_SYS_INFO_IND)) { + qmidevice_send_event_to_main(RIL_UNSOL_RESPONSE_VOICE_NETWORK_STATE_CHANGED); + } else { + if (debug_qmi) + dbg_time("nobody care this qmi msg!!"); + } + pthread_mutex_unlock(&s_commandmutex); +} + +int requestSetEthMode(PROFILE_T *profile) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV linkProto; + + pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS_ADMIN, QMIWDS_ADMIN_SET_DATA_FORMAT_REQ, WdaSetDataFormat, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + linkProto = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x11); + if (linkProto != NULL) { + profile->rawIP = (le32_to_cpu(linkProto->Value) == 2); + s_9x07 = profile->rawIP; + } + + + free(pResponse); + return 0; +} + +#ifdef CONFIG_SIM +int requestGetPINStatus(SIM_Status *pSIMStatus) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + PQMIDMS_UIM_PIN_STATUS pPin1Status = NULL; + //PQMIDMS_UIM_PIN_STATUS pPin2Status = NULL; + + if (s_9x07 && qmiclientId[QMUX_TYPE_UIM]) + pRequest = ComposeQMUXMsg(QMUX_TYPE_UIM, QMIUIM_GET_CARD_STATUS_REQ, NULL, NULL); + else + pRequest = ComposeQMUXMsg(QMUX_TYPE_DMS, QMIDMS_UIM_GET_PIN_STATUS_REQ, NULL, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + pPin1Status = (PQMIDMS_UIM_PIN_STATUS)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x11); + //pPin2Status = (PQMIDMS_UIM_PIN_STATUS)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x12); + + if (pPin1Status != NULL) { + if (pPin1Status->PINStatus == QMI_PIN_STATUS_NOT_VERIF) { + *pSIMStatus = SIM_PIN; + } else if (pPin1Status->PINStatus == QMI_PIN_STATUS_BLOCKED) { + *pSIMStatus = SIM_PUK; + } else if (pPin1Status->PINStatus == QMI_PIN_STATUS_PERM_BLOCKED) { + *pSIMStatus = SIM_BAD; + } + } + + free(pResponse); + return 0; +} + +int requestGetSIMStatus(SIM_Status *pSIMStatus) { //RIL_REQUEST_GET_SIM_STATUS + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + const char * SIM_Status_String[] = { + "SIM_ABSENT", + "SIM_NOT_READY", + "SIM_READY", /* SIM_READY means the radio state is RADIO_STATE_SIM_READY */ + "SIM_PIN", + "SIM_PUK", + "SIM_NETWORK_PERSONALIZATION" + }; + + +__requestGetSIMStatus: + if (s_9x07 && qmiclientId[QMUX_TYPE_UIM]) + pRequest = ComposeQMUXMsg(QMUX_TYPE_UIM, QMIUIM_GET_CARD_STATUS_REQ, NULL, NULL); + else + pRequest = ComposeQMUXMsg(QMUX_TYPE_DMS, QMIDMS_UIM_GET_STATE_REQ, NULL, NULL); + + err = QmiThreadSendQMI(pRequest, &pResponse); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + if (QMI_ERR_OP_DEVICE_UNSUPPORTED == le16_to_cpu(pResponse->MUXMsg.QMUXMsgHdrResp.QMUXError)) { + sleep(1); + goto __requestGetSIMStatus; + } + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + *pSIMStatus = SIM_ABSENT; + if (s_9x07 && qmiclientId[QMUX_TYPE_UIM]) + { + PQMIUIM_CARD_STATUS pCardStatus = NULL; + PQMIUIM_PIN_STATE pPINState = NULL; + UCHAR CardState; + UCHAR PIN1State; + //UCHAR PIN1Retries; + //UCHAR PUK1Retries; + //UCHAR PIN2State; + //UCHAR PIN2Retries; + //UCHAR PUK2Retries; + + pCardStatus = (PQMIUIM_CARD_STATUS)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x10); + if (pCardStatus != NULL) + { + pPINState = (PQMIUIM_PIN_STATE)((PUCHAR)pCardStatus + sizeof(QMIUIM_CARD_STATUS) + pCardStatus->AIDLength); + CardState = pCardStatus->CardState; + if (pPINState->UnivPIN == 1) + { + PIN1State = pCardStatus->UPINState; + //PIN1Retries = pCardStatus->UPINRetries; + //PUK1Retries = pCardStatus->UPUKRetries; + } + else + { + PIN1State = pPINState->PIN1State; + //PIN1Retries = pPINState->PIN1Retries; + //PUK1Retries = pPINState->PUK1Retries; + } + //PIN2State = pPINState->PIN2State; + //PIN2Retries = pPINState->PIN2Retries; + //PUK2Retries = pPINState->PUK2Retries; + } + + *pSIMStatus = SIM_ABSENT; + if ((CardState == 0x01) && ((PIN1State == QMI_PIN_STATUS_VERIFIED)|| (PIN1State == QMI_PIN_STATUS_DISABLED))) + { + *pSIMStatus = SIM_READY; + } + else if (CardState == 0x01) + { + if (PIN1State == QMI_PIN_STATUS_NOT_VERIF) + { + *pSIMStatus = SIM_PIN; + } + if ( PIN1State == QMI_PIN_STATUS_BLOCKED) + { + *pSIMStatus = SIM_PUK; + } + else if (PIN1State == QMI_PIN_STATUS_PERM_BLOCKED) + { + *pSIMStatus = SIM_BAD; + } + else if (PIN1State == QMI_PIN_STATUS_NOT_INIT || PIN1State == QMI_PIN_STATUS_VERIFIED || PIN1State == QMI_PIN_STATUS_DISABLED) + { + *pSIMStatus = SIM_READY; + } + } + else if (CardState == 0x00 || CardState == 0x02) + { + } + else + { + } + } + else + { + //UIM state. Values: + // 0x00 UIM initialization completed + // 0x01 UIM is locked or the UIM failed + // 0x02 UIM is not present + // 0x03 Reserved + // 0xFF UIM state is currently + //unavailable + if (pResponse->MUXMsg.UIMGetStateResp.UIMState == 0x00) { + *pSIMStatus = SIM_READY; + } else if (pResponse->MUXMsg.UIMGetStateResp.UIMState == 0x01) { + *pSIMStatus = SIM_ABSENT; + err = requestGetPINStatus(pSIMStatus); + } else if ((pResponse->MUXMsg.UIMGetStateResp.UIMState == 0x02) || (pResponse->MUXMsg.UIMGetStateResp.UIMState == 0xFF)) { + *pSIMStatus = SIM_ABSENT; + } else { + *pSIMStatus = SIM_ABSENT; + } + } + dbg_time("%s SIMStatus: %s", __func__, SIM_Status_String[*pSIMStatus]); + + free(pResponse); + return 0; +} + +int requestEnterSimPin(const CHAR *pPinCode) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + + if (s_9x07 && qmiclientId[QMUX_TYPE_UIM]) + pRequest = ComposeQMUXMsg(QMUX_TYPE_UIM, QMIUIM_VERIFY_PIN_REQ, UimVerifyPinReqSend, (void *)pPinCode); + else + pRequest = ComposeQMUXMsg(QMUX_TYPE_DMS, QMIDMS_UIM_VERIFY_PIN_REQ, DmsUIMVerifyPinReqSend, (void *)pPinCode); + err = QmiThreadSendQMI(pRequest, &pResponse); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + free(pResponse); + return 0; +} +#endif + +#if 1 +static void quectel_convert_cdma_mcc_2_ascii_mcc( USHORT *p_mcc, USHORT mcc ) +{ + unsigned int d1, d2, d3, buf = mcc + 111; + + if ( mcc == 0x3FF ) // wildcard + { + *p_mcc = 3; + } + else + { + d3 = buf % 10; + buf = ( d3 == 0 ) ? (buf-10)/10 : buf/10; + + d2 = buf % 10; + buf = ( d2 == 0 ) ? (buf-10)/10 : buf/10; + + d1 = ( buf == 10 ) ? 0 : buf; + +//dbg_time("d1:%d, d2:%d,d3:%d",d1,d2,d3); + if ( d1<10 && d2<10 && d3<10 ) + { + *p_mcc = d1*100+d2*10+d3; +#if 0 + *(p_mcc+0) = '0' + d1; + *(p_mcc+1) = '0' + d2; + *(p_mcc+2) = '0' + d3; +#endif + } + else + { + //dbg_time( "invalid digits %d %d %d", d1, d2, d3 ); + *p_mcc = 0; + } + } +} + +static void quectel_convert_cdma_mnc_2_ascii_mnc( USHORT *p_mnc, USHORT imsi_11_12) +{ + unsigned int d1, d2, buf = imsi_11_12 + 11; + + if ( imsi_11_12 == 0x7F ) // wildcard + { + *p_mnc = 7; + } + else + { + d2 = buf % 10; + buf = ( d2 == 0 ) ? (buf-10)/10 : buf/10; + + d1 = ( buf == 10 ) ? 0 : buf; + + if ( d1<10 && d2<10 ) + { + *p_mnc = d1*10 + d2; + } + else + { + //dbg_time( "invalid digits %d %d", d1, d2, 0 ); + *p_mnc = 0; + } + } +} + +int requestGetHomeNetwork(USHORT *p_mcc, USHORT *p_mnc, USHORT *p_sid, USHORT *p_nid) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + PHOME_NETWORK pHomeNetwork; + PHOME_NETWORK_SYSTEMID pHomeNetworkSystemID; + + pRequest = ComposeQMUXMsg(QMUX_TYPE_NAS, QMINAS_GET_HOME_NETWORK_REQ, NULL, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + pHomeNetwork = (PHOME_NETWORK)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x01); + if (pHomeNetwork && p_mcc && p_mnc ) { + *p_mcc = le16_to_cpu(pHomeNetwork->MobileCountryCode); + *p_mnc = le16_to_cpu(pHomeNetwork->MobileNetworkCode); + //dbg_time("%s MobileCountryCode: %d, MobileNetworkCode: %d", __func__, *pMobileCountryCode, *pMobileNetworkCode); + } + + pHomeNetworkSystemID = (PHOME_NETWORK_SYSTEMID)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x10); + if (pHomeNetworkSystemID && p_sid && p_nid) { + *p_sid = le16_to_cpu(pHomeNetworkSystemID->SystemID); //china-hefei: sid 14451 + *p_nid = le16_to_cpu(pHomeNetworkSystemID->NetworkID); + //dbg_time("%s SystemID: %d, NetworkID: %d", __func__, *pSystemID, *pNetworkID); + } + + free(pResponse); + + return 0; +} +#endif + +#if 0 +// Lookup table for carriers known to produce SIMs which incorrectly indicate MNC length. +static const char * MCCMNC_CODES_HAVING_3DIGITS_MNC[] = { + "302370", "302720", "310260", + "405025", "405026", "405027", "405028", "405029", "405030", "405031", "405032", + "405033", "405034", "405035", "405036", "405037", "405038", "405039", "405040", + "405041", "405042", "405043", "405044", "405045", "405046", "405047", "405750", + "405751", "405752", "405753", "405754", "405755", "405756", "405799", "405800", + "405801", "405802", "405803", "405804", "405805", "405806", "405807", "405808", + "405809", "405810", "405811", "405812", "405813", "405814", "405815", "405816", + "405817", "405818", "405819", "405820", "405821", "405822", "405823", "405824", + "405825", "405826", "405827", "405828", "405829", "405830", "405831", "405832", + "405833", "405834", "405835", "405836", "405837", "405838", "405839", "405840", + "405841", "405842", "405843", "405844", "405845", "405846", "405847", "405848", + "405849", "405850", "405851", "405852", "405853", "405875", "405876", "405877", + "405878", "405879", "405880", "405881", "405882", "405883", "405884", "405885", + "405886", "405908", "405909", "405910", "405911", "405912", "405913", "405914", + "405915", "405916", "405917", "405918", "405919", "405920", "405921", "405922", + "405923", "405924", "405925", "405926", "405927", "405928", "405929", "405930", + "405931", "405932", "502142", "502143", "502145", "502146", "502147", "502148" +}; + +static const char * MCC_CODES_HAVING_3DIGITS_MNC[] = { + "302", //Canada + "310", //United States of America + "311", //United States of America + "312", //United States of America + "313", //United States of America + "314", //United States of America + "315", //United States of America + "316", //United States of America + "334", //Mexico + "338", //Jamaica + "342", //Barbados + "344", //Antigua and Barbuda + "346", //Cayman Islands + "348", //British Virgin Islands + "365", //Anguilla + "708", //Honduras (Republic of) + "722", //Argentine Republic + "732" //Colombia (Republic of) +}; + +int requestGetIMSI(const char **pp_imsi, USHORT *pMobileCountryCode, USHORT *pMobileNetworkCode) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + + if (pp_imsi) *pp_imsi = NULL; + if (pMobileCountryCode) *pMobileCountryCode = 0; + if (pMobileNetworkCode) *pMobileNetworkCode = 0; + + pRequest = ComposeQMUXMsg(QMUX_TYPE_DMS, QMIDMS_UIM_GET_IMSI_REQ, NULL, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + if (pMUXMsg->UIMGetIMSIResp.TLV2Type == 0x01 && le16_to_cpu(pMUXMsg->UIMGetIMSIResp.TLV2Length) >= 5) { + int mnc_len = 2; + unsigned i; + char tmp[4]; + + if (pp_imsi) *pp_imsi = strndup((const char *)(&pMUXMsg->UIMGetIMSIResp.IMSI), le16_to_cpu(pMUXMsg->UIMGetIMSIResp.TLV2Length)); + + for (i = 0; i < sizeof(MCCMNC_CODES_HAVING_3DIGITS_MNC)/sizeof(MCCMNC_CODES_HAVING_3DIGITS_MNC[0]); i++) { + if (!strncmp((const char *)(&pMUXMsg->UIMGetIMSIResp.IMSI), MCCMNC_CODES_HAVING_3DIGITS_MNC[i], 6)) { + mnc_len = 3; + break; + } + } + if (mnc_len == 2) { + for (i = 0; i < sizeof(MCC_CODES_HAVING_3DIGITS_MNC)/sizeof(MCC_CODES_HAVING_3DIGITS_MNC[0]); i++) { + if (!strncmp((const char *)(&pMUXMsg->UIMGetIMSIResp.IMSI), MCC_CODES_HAVING_3DIGITS_MNC[i], 3)) { + mnc_len = 3; + break; + } + } + } + + tmp[0] = (&pMUXMsg->UIMGetIMSIResp.IMSI)[0]; + tmp[1] = (&pMUXMsg->UIMGetIMSIResp.IMSI)[1]; + tmp[2] = (&pMUXMsg->UIMGetIMSIResp.IMSI)[2]; + tmp[3] = 0; + if (pMobileCountryCode) *pMobileCountryCode = atoi(tmp); + tmp[0] = (&pMUXMsg->UIMGetIMSIResp.IMSI)[3]; + tmp[1] = (&pMUXMsg->UIMGetIMSIResp.IMSI)[4]; + tmp[2] = 0; + if (mnc_len == 3) { + tmp[2] = (&pMUXMsg->UIMGetIMSIResp.IMSI)[6]; + } + if (pMobileNetworkCode) *pMobileNetworkCode = atoi(tmp); + } + + free(pResponse); + + return 0; +} +#endif + +struct wwan_data_class_str class2str[] = { + {WWAN_DATA_CLASS_NONE, "UNKNOWN"}, + {WWAN_DATA_CLASS_GPRS, "GPRS"}, + {WWAN_DATA_CLASS_EDGE, "EDGE"}, + {WWAN_DATA_CLASS_UMTS, "UMTS"}, + {WWAN_DATA_CLASS_HSDPA, "HSDPA"}, + {WWAN_DATA_CLASS_HSUPA, "HSUPA"}, + {WWAN_DATA_CLASS_LTE, "LTE"}, + {WWAN_DATA_CLASS_1XRTT, "1XRTT"}, + {WWAN_DATA_CLASS_1XEVDO, "1XEVDO"}, + {WWAN_DATA_CLASS_1XEVDO_REVA, "1XEVDO_REVA"}, + {WWAN_DATA_CLASS_1XEVDV, "1XEVDV"}, + {WWAN_DATA_CLASS_3XRTT, "3XRTT"}, + {WWAN_DATA_CLASS_1XEVDO_REVB, "1XEVDO_REVB"}, + {WWAN_DATA_CLASS_UMB, "UMB"}, + {WWAN_DATA_CLASS_CUSTOM, "CUSTOM"}, +}; + +CHAR *wwan_data_class2str(ULONG class) +{ + int i = 0; + for (i = 0; i < sizeof(class2str)/sizeof(class2str[0]); i++) { + if (class2str[i].class == class) { + return class2str[i].str; + } + } + return "UNKNOWN"; +} + +int requestRegistrationState2(UCHAR *pPSAttachedState) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + USHORT MobileCountryCode = 0; + USHORT MobileNetworkCode = 0; + const char *pDataCapStr = "UNKNOW"; + LONG remainingLen; + PSERVICE_STATUS_INFO pServiceStatusInfo; + int is_lte = 0; + PCDMA_SYSTEM_INFO pCdmaSystemInfo; + PHDR_SYSTEM_INFO pHdrSystemInfo; + PGSM_SYSTEM_INFO pGsmSystemInfo; + PWCDMA_SYSTEM_INFO pWcdmaSystemInfo; + PLTE_SYSTEM_INFO pLteSystemInfo; + PTDSCDMA_SYSTEM_INFO pTdscdmaSystemInfo; + UCHAR DeviceClass = 0; + ULONG DataCapList = 0; + + pRequest = ComposeQMUXMsg(QMUX_TYPE_NAS, QMINAS_GET_SYS_INFO_REQ, NULL, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + pServiceStatusInfo = (PSERVICE_STATUS_INFO)(((PCHAR)&pMUXMsg->GetSysInfoResp) + QCQMUX_MSG_HDR_SIZE); + remainingLen = le16_to_cpu(pMUXMsg->GetSysInfoResp.Length); + + s_is_cdma = 0; + while (remainingLen > 0) { + switch (pServiceStatusInfo->TLVType) { + case 0x10: // CDMA + if (pServiceStatusInfo->SrvStatus == 0x02) { + DataCapList = WWAN_DATA_CLASS_1XRTT| + WWAN_DATA_CLASS_1XEVDO| + WWAN_DATA_CLASS_1XEVDO_REVA| + WWAN_DATA_CLASS_1XEVDV| + WWAN_DATA_CLASS_1XEVDO_REVB; + DeviceClass = DEVICE_CLASS_CDMA; + s_is_cdma = (0 == is_lte); + } + break; + case 0x11: // HDR + if (pServiceStatusInfo->SrvStatus == 0x02) { + DataCapList = WWAN_DATA_CLASS_3XRTT| + WWAN_DATA_CLASS_UMB; + DeviceClass = DEVICE_CLASS_CDMA; + s_is_cdma = (0 == is_lte); + } + break; + case 0x12: // GSM + if (pServiceStatusInfo->SrvStatus == 0x02) { + DataCapList = WWAN_DATA_CLASS_GPRS| + WWAN_DATA_CLASS_EDGE; + DeviceClass = DEVICE_CLASS_GSM; + } + break; + case 0x13: // WCDMA + if (pServiceStatusInfo->SrvStatus == 0x02) { + DataCapList = WWAN_DATA_CLASS_UMTS; + DeviceClass = DEVICE_CLASS_GSM; + } + break; + case 0x14: // LTE + if (pServiceStatusInfo->SrvStatus == 0x02) { + DataCapList = WWAN_DATA_CLASS_LTE; + DeviceClass = DEVICE_CLASS_GSM; + is_lte = 1; + s_is_cdma = 0; + } + break; + case 0x24: // TDSCDMA + if (pServiceStatusInfo->SrvStatus == 0x02) { + pDataCapStr = "TD-SCDMA"; + } + break; + case 0x15: // CDMA + // CDMA_SYSTEM_INFO + pCdmaSystemInfo = (PCDMA_SYSTEM_INFO)pServiceStatusInfo; + if (pCdmaSystemInfo->SrvDomainValid == 0x01) { + *pPSAttachedState = 0; + if (pCdmaSystemInfo->SrvDomain & 0x02) { + *pPSAttachedState = 1; + s_is_cdma = (0 == is_lte); + } + } + if (pCdmaSystemInfo->SrvCapabilityValid == 0x01) { + *pPSAttachedState = 0; + if (pCdmaSystemInfo->SrvCapability & 0x02) { + *pPSAttachedState = 1; + s_is_cdma = (0 == is_lte); + } + } + if (pCdmaSystemInfo->NetworkIdValid == 0x01) { + int i; + CHAR temp[10]; + strncpy(temp, (CHAR *)pCdmaSystemInfo->MCC, 3); + temp[3] = '\0'; + for (i = 0; i < 4; i++) { + if ((UCHAR)temp[i] == 0xFF) { + temp[i] = '\0'; + } + } + MobileCountryCode = (USHORT)atoi(temp); + + strncpy(temp, (CHAR *)pCdmaSystemInfo->MNC, 3); + temp[3] = '\0'; + for (i = 0; i < 4; i++) { + if ((UCHAR)temp[i] == 0xFF) { + temp[i] = '\0'; + } + } + MobileNetworkCode = (USHORT)atoi(temp); + } + break; + case 0x16: // HDR + // HDR_SYSTEM_INFO + pHdrSystemInfo = (PHDR_SYSTEM_INFO)pServiceStatusInfo; + if (pHdrSystemInfo->SrvDomainValid == 0x01) { + *pPSAttachedState = 0; + if (pHdrSystemInfo->SrvDomain & 0x02) { + *pPSAttachedState = 1; + s_is_cdma = (0 == is_lte); + } + } + if (pHdrSystemInfo->SrvCapabilityValid == 0x01) { + *pPSAttachedState = 0; + if (pHdrSystemInfo->SrvCapability & 0x02) { + *pPSAttachedState = 1; + s_is_cdma = (0 == is_lte); + } + } + USHORT cmda_mcc = 0, cdma_mnc = 0; + if(!requestGetHomeNetwork(&cmda_mcc, &cdma_mnc,NULL, NULL) && cmda_mcc) { + quectel_convert_cdma_mcc_2_ascii_mcc(&MobileCountryCode, cmda_mcc); + quectel_convert_cdma_mnc_2_ascii_mnc(&MobileNetworkCode, cdma_mnc); + } + break; + case 0x17: // GSM + // GSM_SYSTEM_INFO + pGsmSystemInfo = (PGSM_SYSTEM_INFO)pServiceStatusInfo; + if (pGsmSystemInfo->SrvDomainValid == 0x01) { + *pPSAttachedState = 0; + if (pGsmSystemInfo->SrvDomain & 0x02) { + *pPSAttachedState = 1; + } + } + if (pGsmSystemInfo->SrvCapabilityValid == 0x01) { + *pPSAttachedState = 0; + if (pGsmSystemInfo->SrvCapability & 0x02) { + *pPSAttachedState = 1; + } + } + if (pGsmSystemInfo->NetworkIdValid == 0x01) { + int i; + CHAR temp[10]; + strncpy(temp, (CHAR *)pGsmSystemInfo->MCC, 3); + temp[3] = '\0'; + for (i = 0; i < 4; i++) { + if ((UCHAR)temp[i] == 0xFF) { + temp[i] = '\0'; + } + } + MobileCountryCode = (USHORT)atoi(temp); + + strncpy(temp, (CHAR *)pGsmSystemInfo->MNC, 3); + temp[3] = '\0'; + for (i = 0; i < 4; i++) { + if ((UCHAR)temp[i] == 0xFF) { + temp[i] = '\0'; + } + } + MobileNetworkCode = (USHORT)atoi(temp); + } + break; + case 0x18: // WCDMA + // WCDMA_SYSTEM_INFO + pWcdmaSystemInfo = (PWCDMA_SYSTEM_INFO)pServiceStatusInfo; + if (pWcdmaSystemInfo->SrvDomainValid == 0x01) { + *pPSAttachedState = 0; + if (pWcdmaSystemInfo->SrvDomain & 0x02) { + *pPSAttachedState = 1; + } + } + if (pWcdmaSystemInfo->SrvCapabilityValid == 0x01) { + *pPSAttachedState = 0; + if (pWcdmaSystemInfo->SrvCapability & 0x02) { + *pPSAttachedState = 1; + } + } + if (pWcdmaSystemInfo->NetworkIdValid == 0x01) { + int i; + CHAR temp[10]; + strncpy(temp, (CHAR *)pWcdmaSystemInfo->MCC, 3); + temp[3] = '\0'; + for (i = 0; i < 4; i++) { + if ((UCHAR)temp[i] == 0xFF) { + temp[i] = '\0'; + } + } + MobileCountryCode = (USHORT)atoi(temp); + + strncpy(temp, (CHAR *)pWcdmaSystemInfo->MNC, 3); + temp[3] = '\0'; + for (i = 0; i < 4; i++) { + if ((UCHAR)temp[i] == 0xFF) { + temp[i] = '\0'; + } + } + MobileNetworkCode = (USHORT)atoi(temp); + } + break; + case 0x19: // LTE_SYSTEM_INFO + // LTE_SYSTEM_INFO + pLteSystemInfo = (PLTE_SYSTEM_INFO)pServiceStatusInfo; + if (pLteSystemInfo->SrvDomainValid == 0x01) { + *pPSAttachedState = 0; + if (pLteSystemInfo->SrvDomain & 0x02) { + *pPSAttachedState = 1; + is_lte = 1; + s_is_cdma = 0; + } + } + if (pLteSystemInfo->SrvCapabilityValid == 0x01) { + *pPSAttachedState = 0; + if (pLteSystemInfo->SrvCapability & 0x02) { + *pPSAttachedState = 1; + is_lte = 1; + s_is_cdma = 0; + } + } + if (pLteSystemInfo->NetworkIdValid == 0x01) { + int i; + CHAR temp[10]; + strncpy(temp, (CHAR *)pLteSystemInfo->MCC, 3); + temp[3] = '\0'; + for (i = 0; i < 4; i++) { + if ((UCHAR)temp[i] == 0xFF) { + temp[i] = '\0'; + } + } + MobileCountryCode = (USHORT)atoi(temp); + + strncpy(temp, (CHAR *)pLteSystemInfo->MNC, 3); + temp[3] = '\0'; + for (i = 0; i < 4; i++) { + if ((UCHAR)temp[i] == 0xFF) { + temp[i] = '\0'; + } + } + MobileNetworkCode = (USHORT)atoi(temp); + } + break; + case 0x25: // TDSCDMA + // TDSCDMA_SYSTEM_INFO + pTdscdmaSystemInfo = (PTDSCDMA_SYSTEM_INFO)pServiceStatusInfo; + if (pTdscdmaSystemInfo->SrvDomainValid == 0x01) { + *pPSAttachedState = 0; + if (pTdscdmaSystemInfo->SrvDomain & 0x02) { + *pPSAttachedState = 1; + } + } + if (pTdscdmaSystemInfo->SrvCapabilityValid == 0x01) { + *pPSAttachedState = 0; + if (pTdscdmaSystemInfo->SrvCapability & 0x02) { + *pPSAttachedState = 1; + } + } + if (pTdscdmaSystemInfo->NetworkIdValid == 0x01) { + int i; + CHAR temp[10]; + strncpy(temp, (CHAR *)pTdscdmaSystemInfo->MCC, 3); + temp[3] = '\0'; + for (i = 0; i < 4; i++) { + if ((UCHAR)temp[i] == 0xFF) { + temp[i] = '\0'; + } + } + MobileCountryCode = (USHORT)atoi(temp); + + strncpy(temp, (CHAR *)pTdscdmaSystemInfo->MNC, 3); + temp[3] = '\0'; + for (i = 0; i < 4; i++) { + if ((UCHAR)temp[i] == 0xFF) { + temp[i] = '\0'; + } + } + MobileNetworkCode = (USHORT)atoi(temp); + } + break; + default: + break; + } /* switch (pServiceStatusInfo->TLYType) */ + remainingLen -= (le16_to_cpu(pServiceStatusInfo->TLVLength) + 3); + pServiceStatusInfo = (PSERVICE_STATUS_INFO)((PCHAR)&pServiceStatusInfo->TLVLength + le16_to_cpu(pServiceStatusInfo->TLVLength) + sizeof(USHORT)); + } /* while (remainingLen > 0) */ + + if (DeviceClass == DEVICE_CLASS_CDMA) { + if (DataCapList & WWAN_DATA_CLASS_1XEVDO_REVB) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_1XEVDO_REVB); + } else if (DataCapList & WWAN_DATA_CLASS_1XEVDO_REVA) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_1XEVDO_REVA); + } else if (DataCapList & WWAN_DATA_CLASS_1XEVDO) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_1XEVDO); + } else if (DataCapList & WWAN_DATA_CLASS_1XRTT) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_1XRTT); + } else if (DataCapList & WWAN_DATA_CLASS_3XRTT) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_3XRTT); + } else if (DataCapList & WWAN_DATA_CLASS_UMB) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_UMB); + } + } else { + if (DataCapList & WWAN_DATA_CLASS_LTE) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_LTE); + } else if ((DataCapList & WWAN_DATA_CLASS_HSDPA) && (DataCapList & WWAN_DATA_CLASS_HSUPA)) { + pDataCapStr = "HSDPA_HSUPA"; + } else if (DataCapList & WWAN_DATA_CLASS_HSDPA) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_HSDPA); + } else if (DataCapList & WWAN_DATA_CLASS_HSUPA) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_HSUPA); + } else if (DataCapList & WWAN_DATA_CLASS_UMTS) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_UMTS); + } else if (DataCapList & WWAN_DATA_CLASS_EDGE) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_EDGE); + } else if (DataCapList & WWAN_DATA_CLASS_GPRS) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_GPRS); + } + } + + dbg_time("%s MCC: %d, MNC: %d, PS: %s, DataCap: %s", __func__, + MobileCountryCode, MobileNetworkCode, (*pPSAttachedState == 1) ? "Attached" : "Detached" , pDataCapStr); + + free(pResponse); + + return 0; +} + +int requestRegistrationState(UCHAR *pPSAttachedState) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + PQMINAS_CURRENT_PLMN_MSG pCurrentPlmn; + PSERVING_SYSTEM pServingSystem; + PQMINAS_DATA_CAP pDataCap; + USHORT MobileCountryCode = 0; + USHORT MobileNetworkCode = 0; + const char *pDataCapStr = "UNKNOW"; + + if (s_9x07) { + return requestRegistrationState2(pPSAttachedState); + } + + pRequest = ComposeQMUXMsg(QMUX_TYPE_NAS, QMINAS_GET_SERVING_SYSTEM_REQ, NULL, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + pCurrentPlmn = (PQMINAS_CURRENT_PLMN_MSG)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x12); + if (pCurrentPlmn) { + MobileCountryCode = le16_to_cpu(pCurrentPlmn->MobileCountryCode); + MobileNetworkCode = le16_to_cpu(pCurrentPlmn->MobileNetworkCode); + } + + *pPSAttachedState = 0; + pServingSystem = (PSERVING_SYSTEM)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x01); + if (pServingSystem) { + //Packet-switched domain attach state of the mobile. + //0x00 PS_UNKNOWN ?Unknown or not applicable + //0x01 PS_ATTACHED ?Attached + //0x02 PS_DETACHED ?Detached + *pPSAttachedState = pServingSystem->RegistrationState; + if (pServingSystem->RegistrationState == 0x01) //0x01 ¨C REGISTERED ¨C Registered with a network + *pPSAttachedState = pServingSystem->PSAttachedState; + else { + //MobileCountryCode = MobileNetworkCode = 0; + *pPSAttachedState = 0x02; + } + } + + pDataCap = (PQMINAS_DATA_CAP)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x11); + if (pDataCap && pDataCap->DataCapListLen) { + UCHAR *DataCap = &pDataCap->DataCap; + if (pDataCap->DataCapListLen == 2) { + if ((DataCap[0] == 0x06) && ((DataCap[1] == 0x08) || (DataCap[1] == 0x0A))) + DataCap[0] = DataCap[1]; + } + switch (DataCap[0]) { + case 0x01: pDataCapStr = "GPRS"; break; + case 0x02: pDataCapStr = "EDGE"; break; + case 0x03: pDataCapStr = "HSDPA"; break; + case 0x04: pDataCapStr = "HSUPA"; break; + case 0x05: pDataCapStr = "UMTS"; break; + case 0x06: pDataCapStr = "1XRTT"; break; + case 0x07: pDataCapStr = "1XEVDO"; break; + case 0x08: pDataCapStr = "1XEVDO_REVA"; break; + case 0x09: pDataCapStr = "GPRS"; break; + case 0x0A: pDataCapStr = "1XEVDO_REVB"; break; + case 0x0B: pDataCapStr = "LTE"; break; + case 0x0C: pDataCapStr = "HSDPA"; break; + case 0x0D: pDataCapStr = "HSDPA"; break; + default: pDataCapStr = "UNKNOW"; break; + } + } + + if (pServingSystem && pServingSystem->RegistrationState == 0x01 && pServingSystem->InUseRadioIF && pServingSystem->RadioIF == 0x09) { + pDataCapStr = "TD-SCDMA"; + } + + s_is_cdma = 0; + if (pServingSystem && pServingSystem->RegistrationState == 0x01 && pServingSystem->InUseRadioIF && (pServingSystem->RadioIF == 0x01 || pServingSystem->RadioIF == 0x02)) { + USHORT cmda_mcc = 0, cdma_mnc = 0; + s_is_cdma = 1; + if(!requestGetHomeNetwork(&cmda_mcc, &cdma_mnc,NULL, NULL) && cmda_mcc) { + quectel_convert_cdma_mcc_2_ascii_mcc(&MobileCountryCode, cmda_mcc); + quectel_convert_cdma_mnc_2_ascii_mnc(&MobileNetworkCode, cdma_mnc); + } + if (1) { + PQCQMUX_TLV pTLV = (PQCQMUX_TLV)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x23); + if (pTLV) + s_hdr_personality = pTLV->Value; + else + s_hdr_personality = 0; + if (s_hdr_personality == 2) + pDataCapStr = "eHRPD"; + } + } + + dbg_time("%s MCC: %d, MNC: %d, PS: %s, DataCap: %s", __func__, + MobileCountryCode, MobileNetworkCode, (*pPSAttachedState == 1) ? "Attached" : "Detached" , pDataCapStr); + + free(pResponse); + + return 0; +} + +int requestQueryDataCall(UCHAR *pConnectionStatus) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + PQMIWDS_PKT_SRVC_TLV pPktSrvc; + UCHAR oldConnectionStatus = *pConnectionStatus; + + pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS, QMIWDS_GET_PKT_SRVC_STATUS_REQ, NULL, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + *pConnectionStatus = QWDS_PKT_DATA_DISCONNECTED; + pPktSrvc = (PQMIWDS_PKT_SRVC_TLV)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x01); + if (pPktSrvc) { + *pConnectionStatus = pPktSrvc->ConnectionStatus; + if ((le16_to_cpu(pPktSrvc->TLVLength) == 2) && (pPktSrvc->ReconfigReqd == 0x01)) + *pConnectionStatus = QWDS_PKT_DATA_DISCONNECTED; + } + + if (*pConnectionStatus == QWDS_PKT_DATA_DISCONNECTED) + WdsConnectionIPv4Handle = 0; + + if (oldConnectionStatus != *pConnectionStatus || debug_qmi) + dbg_time("%s ConnectionStatus: %s", __func__, (*pConnectionStatus == QWDS_PKT_DATA_CONNECTED) ? "CONNECTED" : "DISCONNECTED"); + + free(pResponse); + return 0; +} + +#if 0 +BOOLEAN QCMAIN_IsDualIPSupported(PMP_ADAPTER pAdapter) +{ + return (pAdapter->QMUXVersion[QMUX_TYPE_WDS].Major >= 1 && pAdapter->QMUXVersion[QMUX_TYPE_WDS].Minor >= 9); +} // QCMAIN_IsDualIPSupported +#endif + +int requestSetupDataCall(PROFILE_T *profile) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + + +//DualIPSupported means can get ipv4 & ipv6 address at the same time, one wds for ipv4, the other wds for ipv6 + profile->IPType = 0x04; //ipv4 first +__requestSetupDataCall: + pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS, QMIWDS_START_NETWORK_INTERFACE_REQ, WdsStartNwInterfaceReq, profile); + err = QmiThreadSendQMITimeout(pRequest, &pResponse, 120 * 1000); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) + { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + + if (!access("/proc/net/if_inet6", R_OK) && QMI_ERR_PIN_LOCKED == le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError) && profile->IPType == 0x04) + { + free(pResponse); + profile->IPType = 0x06; //ipv6 + goto __requestSetupDataCall; + } + + profile->IPType = 0x04; //reset to ipv4 first + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + WdsConnectionIPv4Handle = le32_to_cpu(pResponse->MUXMsg.StartNwInterfaceResp.Handle); + dbg_time("%s %s: 0x%08x", __func__, + (profile->IPType == 0x04) ? "WdsConnectionIPv4Handle" : "WdsConnectionIPv6Handle", WdsConnectionIPv4Handle); + + free(pResponse); + return 0; +} + +int requestDeactivateDefaultPDP(void) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + + pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS, QMIWDS_STOP_NETWORK_INTERFACE_REQ , WdsStopNwInterfaceReq, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + WdsConnectionIPv4Handle = 0; + free(pResponse); + return 0; +} + +int requestGetIPAddress(PROFILE_T *profile) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR pIpv4Addr; + PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV6_ADDR pIpv6Addr = NULL; + PQMIWDS_GET_RUNTIME_SETTINGS_TLV_MTU pMtu; + IPV4_T *pIpv4 = &profile->ipv4; + IPV6_T *pIpv6 = &profile->ipv6; + + if (profile->IPType == 0x04) + memset(pIpv4, 0x00, sizeof(IPV4_T)); + else + memset(pIpv6, 0x00, sizeof(IPV6_T)); + + pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS, QMIWDS_GET_RUNTIME_SETTINGS_REQ, WdsGetRuntimeSettingReq, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + pIpv4Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4PRIMARYDNS); + if (pIpv4Addr) { + pIpv4->DnsPrimary = pIpv4Addr->IPV4Address; + } + + pIpv4Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4SECONDARYDNS); + if (pIpv4Addr) { + pIpv4->DnsSecondary = pIpv4Addr->IPV4Address; + } + + pIpv4Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4GATEWAY); + if (pIpv4Addr) { + pIpv4->Gateway = pIpv4Addr->IPV4Address; + } + + pIpv4Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4SUBNET); + if (pIpv4Addr) { + pIpv4->SubnetMask = pIpv4Addr->IPV4Address; + } + + pIpv4Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4); + if (pIpv4Addr) { + pIpv4->Address = pIpv4Addr->IPV4Address; + } + + pIpv6Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV6_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6PRIMARYDNS); + if (pIpv6Addr) { + memcpy(pIpv6->DnsPrimary, pIpv6Addr->IPV6Address, 16); + } + + pIpv6Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV6_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6SECONDARYDNS); + if (pIpv6Addr) { + memcpy(pIpv6->DnsSecondary, pIpv6Addr->IPV6Address, 16); + } + + pIpv6Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV6_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6GATEWAY); + if (pIpv6Addr) { + memcpy(pIpv6->Gateway, pIpv6Addr->IPV6Address, 16); + pIpv6->PrefixLengthGateway = pIpv6Addr->PrefixLength; + } + + pIpv6Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV6_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6); + if (pIpv6Addr) { + memcpy(pIpv6->Address, pIpv6Addr->IPV6Address, 16); + pIpv6->PrefixLengthIPAddr = pIpv6Addr->PrefixLength; + } + + pMtu = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_MTU)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_MTU); + if (pMtu) { + if (profile->IPType == 0x04) + pIpv4->Mtu = le32_to_cpu(pMtu->Mtu); + else + pIpv6->Mtu = le32_to_cpu(pMtu->Mtu); + } + + free(pResponse); + return 0; +} + +#ifdef CONFIG_APN +int requestSetProfile(PROFILE_T *profile) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + + dbg_time("%s[%d] %s/%s/%s/%d", __func__, profile->pdp, profile->apn, profile->user, profile->password, profile->auth); + pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS, QMIWDS_MODIFY_PROFILE_SETTINGS_REQ, WdsModifyProfileSettingsReq, profile); + err = QmiThreadSendQMI(pRequest, &pResponse); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + free(pResponse); + return 0; +} + +int requestGetProfile(PROFILE_T *profile) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + char *apn = NULL; + char *user = NULL; + char *password = NULL; + int auth = 0; + PQMIWDS_APNNAME pApnName; + PQMIWDS_USERNAME pUserName; + PQMIWDS_PASSWD pPassWd; + PQMIWDS_AUTH_PREFERENCE pAuthPref; + + pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS, QMIWDS_GET_PROFILE_SETTINGS_REQ, WdsGetProfileSettingsReqSend, profile); + err = QmiThreadSendQMI(pRequest, &pResponse); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + pApnName = (PQMIWDS_APNNAME)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x14); + pUserName = (PQMIWDS_USERNAME)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x1B); + pPassWd = (PQMIWDS_PASSWD)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x1C); + pAuthPref = (PQMIWDS_AUTH_PREFERENCE)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x1D); + + if (pApnName/* && le16_to_cpu(pApnName->TLVLength)*/) + apn = strndup((const char *)(&pApnName->ApnName), le16_to_cpu(pApnName->TLVLength)); + if (pUserName/* && pUserName->UserName*/) + user = strndup((const char *)(&pUserName->UserName), le16_to_cpu(pUserName->TLVLength)); + if (pPassWd/* && le16_to_cpu(pPassWd->TLVLength)*/) + password = strndup((const char *)(&pPassWd->Passwd), le16_to_cpu(pPassWd->TLVLength)); + if (pAuthPref/* && le16_to_cpu(pAuthPref->TLVLength)*/) { + auth = pAuthPref->AuthPreference; + } + +#if 0 + if (profile) { + profile->apn = apn; + profile->user = user; + profile->password = password; + profile->auth = auth; + } +#endif + + dbg_time("%s[%d] %s/%s/%s/%d", __func__, profile->pdp, apn, user, password, auth); + + free(pResponse); + return 0; +} +#endif + +#ifdef CONFIG_VERSION +int requestBaseBandVersion(const char **pp_reversion) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + PDEVICE_REV_ID revId; + int err; + + if (pp_reversion) *pp_reversion = NULL; + + pRequest = ComposeQMUXMsg(QMUX_TYPE_DMS, QMIDMS_GET_DEVICE_REV_ID_REQ, NULL, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + return le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + } + + revId = (PDEVICE_REV_ID)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x01); + + if (revId && le16_to_cpu(revId->TLVLength)) + { + char *DeviceRevisionID = strndup((const char *)(&revId->RevisionID), le16_to_cpu(revId->TLVLength)); + dbg_time("%s %s", __func__, DeviceRevisionID); + s_9x07 = (!strncmp(DeviceRevisionID, "EC21", 4) || !strncmp(DeviceRevisionID, "EC25", 4) + || !strncmp(DeviceRevisionID, "EC20CEF", 7)); //may fail to get QMUX_TYPE_WDS_ADMIN + if (pp_reversion) *pp_reversion = DeviceRevisionID; + } + + free(pResponse); + return 0; +} +#endif diff --git a/root/package/link4all/quectel-CM/src_quectel/QMIThread.h b/root/package/link4all/quectel-CM/src_quectel/QMIThread.h new file mode 100755 index 00000000..7685d14f --- /dev/null +++ b/root/package/link4all/quectel-CM/src_quectel/QMIThread.h @@ -0,0 +1,164 @@ +#ifndef __QMI_THREAD_H__ +#define __QMI_THREAD_H__ + +#define CONFIG_GOBINET +#define CONFIG_QMIWWAN +#define CONFIG_SIM +#define CONFIG_APN +#define CONFIG_VERSION +#define CONFIG_DEFAULT_PDP 1 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "MPQMI.h" +#include "MPQCTL.h" +#include "MPQMUX.h" + +#define DEVICE_CLASS_UNKNOWN 0 +#define DEVICE_CLASS_CDMA 1 +#define DEVICE_CLASS_GSM 2 + +#define WWAN_DATA_CLASS_NONE 0x00000000 +#define WWAN_DATA_CLASS_GPRS 0x00000001 +#define WWAN_DATA_CLASS_EDGE 0x00000002 /* EGPRS */ +#define WWAN_DATA_CLASS_UMTS 0x00000004 +#define WWAN_DATA_CLASS_HSDPA 0x00000008 +#define WWAN_DATA_CLASS_HSUPA 0x00000010 +#define WWAN_DATA_CLASS_LTE 0x00000020 +#define WWAN_DATA_CLASS_1XRTT 0x00010000 +#define WWAN_DATA_CLASS_1XEVDO 0x00020000 +#define WWAN_DATA_CLASS_1XEVDO_REVA 0x00040000 +#define WWAN_DATA_CLASS_1XEVDV 0x00080000 +#define WWAN_DATA_CLASS_3XRTT 0x00100000 +#define WWAN_DATA_CLASS_1XEVDO_REVB 0x00200000 /* for future use */ +#define WWAN_DATA_CLASS_UMB 0x00400000 +#define WWAN_DATA_CLASS_CUSTOM 0x80000000 + +struct wwan_data_class_str { + ULONG class; + CHAR *str; +}; + +#pragma pack(push, 1) + +typedef struct _QCQMIMSG { + QCQMI_HDR QMIHdr; + union { + QMICTL_MSG CTLMsg; + QMUX_MSG MUXMsg; + }; +} __attribute__ ((packed)) QCQMIMSG, *PQCQMIMSG; + +#pragma pack(pop) + +typedef struct __IPV4 { + ULONG Address; + ULONG Gateway; + ULONG SubnetMask; + ULONG DnsPrimary; + ULONG DnsSecondary; + ULONG Mtu; +} IPV4_T; + +typedef struct __IPV6 { + UCHAR Address[16]; + UCHAR Gateway[16]; + UCHAR SubnetMask[16]; + UCHAR DnsPrimary[16]; + UCHAR DnsSecondary[16]; + UCHAR PrefixLengthIPAddr; + UCHAR PrefixLengthGateway; + ULONG Mtu; +} IPV6_T; + +typedef struct __PROFILE { + char * qmichannel; + char * usbnet_adapter; + const char *apn; + const char *user; + const char *password; + const char *pincode; + int auth; + int pdp; + int IPType; + int rawIP; + IPV4_T ipv4; + IPV6_T ipv6; +} PROFILE_T; + +typedef enum { + SIM_ABSENT = 0, + SIM_NOT_READY = 1, + SIM_READY = 2, /* SIM_READY means the radio state is RADIO_STATE_SIM_READY */ + SIM_PIN = 3, + SIM_PUK = 4, + SIM_NETWORK_PERSONALIZATION = 5, + SIM_BAD = 6, +} SIM_Status; + +#define WDM_DEFAULT_BUFSIZE 256 +#define RIL_REQUEST_QUIT 0x1000 +#define RIL_INDICATE_DEVICE_CONNECTED 0x1002 +#define RIL_INDICATE_DEVICE_DISCONNECTED 0x1003 +#define RIL_UNSOL_RESPONSE_VOICE_NETWORK_STATE_CHANGED 0x1004 +#define RIL_UNSOL_DATA_CALL_LIST_CHANGED 0x1005 + +extern int pthread_cond_timeout_np(pthread_cond_t *cond, pthread_mutex_t * mutex, unsigned msecs); +extern int QmiThreadSendQMI(PQCQMIMSG pRequest, PQCQMIMSG *ppResponse); +extern int QmiThreadSendQMITimeout(PQCQMIMSG pRequest, PQCQMIMSG *ppResponse, unsigned msecs); +extern void QmiThreadRecvQMI(PQCQMIMSG pResponse); +extern int QmiWwanInit(void); +extern int QmiWwanDeInit(void); +extern int QmiWwanSendQMI(PQCQMIMSG pRequest); +extern void * QmiWwanThread(void *pData); +extern int GobiNetSendQMI(PQCQMIMSG pRequest); +extern void * GobiNetThread(void *pData); +extern void udhcpc_start(PROFILE_T *profile); +extern void udhcpc_stop(PROFILE_T *profile); +extern void dump_qmi(void *dataBuffer, int dataLen); +extern void qmidevice_send_event_to_main(int triger_event); +extern int requestSetEthMode(PROFILE_T *profile); +extern int requestGetSIMStatus(SIM_Status *pSIMStatus); +extern int requestEnterSimPin(const CHAR *pPinCode); +extern int requestRegistrationState(UCHAR *pPSAttachedState); +extern int requestQueryDataCall(UCHAR *pConnectionStatus); +extern int requestSetupDataCall(PROFILE_T *profile); +extern int requestDeactivateDefaultPDP(void); +extern int requestSetProfile(PROFILE_T *profile); +extern int requestGetProfile(PROFILE_T *profile); +extern int requestBaseBandVersion(const char **pp_reversion); +extern int requestGetIPAddress(PROFILE_T *profile); + +extern FILE *logfilefp; +extern int debug_qmi; +extern char * qmichannel; +extern int qmidevice_control_fd[2]; +extern int qmiclientId[QMUX_TYPE_WDS_ADMIN + 1]; +extern int cdc_wdm_fd; +extern void dbg_time (const char *fmt, ...); +extern USHORT le16_to_cpu(USHORT v16); +extern UINT le32_to_cpu (UINT v32); +extern UINT ql_swap32(UINT v32); +extern USHORT cpu_to_le16(USHORT v16); +extern UINT cpu_to_le32(UINT v32); +#endif diff --git a/root/package/link4all/quectel-CM/src_quectel/QmiWwanCM.c b/root/package/link4all/quectel-CM/src_quectel/QmiWwanCM.c new file mode 100755 index 00000000..694aca3b --- /dev/null +++ b/root/package/link4all/quectel-CM/src_quectel/QmiWwanCM.c @@ -0,0 +1,274 @@ +#include +#include +#include +#include +#include +#include "QMIThread.h" + +#ifdef CONFIG_QMIWWAN +int cdc_wdm_fd = -1; +static UCHAR GetQCTLTransactionId(void) { + static int TransactionId = 0; + if (++TransactionId > 0xFF) + TransactionId = 1; + return TransactionId; +} + +typedef USHORT (*CUSTOMQCTL)(PQMICTL_MSG pCTLMsg, void *arg); + +static PQCQMIMSG ComposeQCTLMsg(USHORT QMICTLType, CUSTOMQCTL customQctlMsgFunction, void *arg) { + UCHAR QMIBuf[WDM_DEFAULT_BUFSIZE]; + PQCQMIMSG pRequest = (PQCQMIMSG)QMIBuf; + int Length; + + pRequest->QMIHdr.IFType = USB_CTL_MSG_TYPE_QMI; + pRequest->QMIHdr.CtlFlags = 0x00; + pRequest->QMIHdr.QMIType = QMUX_TYPE_CTL; + pRequest->QMIHdr.ClientId= 0x00; + + pRequest->CTLMsg.QMICTLMsgHdr.CtlFlags = QMICTL_FLAG_REQUEST; + pRequest->CTLMsg.QMICTLMsgHdr.TransactionId = GetQCTLTransactionId(); + pRequest->CTLMsg.QMICTLMsgHdr.QMICTLType = cpu_to_le16(QMICTLType); + if (customQctlMsgFunction) + pRequest->CTLMsg.QMICTLMsgHdr.Length = cpu_to_le16(customQctlMsgFunction(&pRequest->CTLMsg, arg) - sizeof(QCQMICTL_MSG_HDR)); + else + pRequest->CTLMsg.QMICTLMsgHdr.Length = cpu_to_le16(0x0000); + + pRequest->QMIHdr.Length = cpu_to_le16(le16_to_cpu(pRequest->CTLMsg.QMICTLMsgHdr.Length) + sizeof(QCQMICTL_MSG_HDR) + sizeof(QCQMI_HDR) - 1); + Length = le16_to_cpu(pRequest->QMIHdr.Length) + 1; + + pRequest = (PQCQMIMSG)malloc(Length); + if (pRequest == NULL) { + dbg_time("%s fail to malloc", __func__); + } else { + memcpy(pRequest, QMIBuf, Length); + } + + return pRequest; +} + +static USHORT CtlGetVersionReq(PQMICTL_MSG QCTLMsg, void *arg) { + QCTLMsg->GetVersionReq.TLVType = QCTLV_TYPE_REQUIRED_PARAMETER; + QCTLMsg->GetVersionReq.TLVLength = cpu_to_le16(0x0001); + QCTLMsg->GetVersionReq.QMUXTypes = QMUX_TYPE_ALL; + return sizeof(QMICTL_GET_VERSION_REQ_MSG); +} + +static USHORT CtlGetClientIdReq(PQMICTL_MSG QCTLMsg, void *arg) { + QCTLMsg->GetClientIdReq.TLVType = QCTLV_TYPE_REQUIRED_PARAMETER; + QCTLMsg->GetClientIdReq.TLVLength = cpu_to_le16(0x0001); + QCTLMsg->GetClientIdReq.QMIType = ((UCHAR *)arg)[0]; + return sizeof(QMICTL_GET_CLIENT_ID_REQ_MSG); +} + +static USHORT CtlReleaseClientIdReq(PQMICTL_MSG QCTLMsg, void *arg) { + QCTLMsg->ReleaseClientIdReq.TLVType = QCTLV_TYPE_REQUIRED_PARAMETER; + QCTLMsg->ReleaseClientIdReq.TLVLength = cpu_to_le16(0x0002); + QCTLMsg->ReleaseClientIdReq.QMIType = ((UCHAR *)arg)[0]; + QCTLMsg->ReleaseClientIdReq.ClientId = ((UCHAR *)arg)[1] ; + return sizeof(QMICTL_RELEASE_CLIENT_ID_REQ_MSG); +} + +int QmiWwanSendQMI(PQCQMIMSG pRequest) { + struct pollfd pollfds[]= {{cdc_wdm_fd, POLLOUT, 0}}; + int ret; + + if (cdc_wdm_fd == -1) { + dbg_time("%s cdc_wdm_fd = -1", __func__); + return -ENODEV; + } + + do { + ret = poll(pollfds, sizeof(pollfds)/sizeof(pollfds[0]), 5000); + } while ((ret < 0) && (errno == EINTR)); + + if (pollfds[0].revents & POLLOUT) { + ssize_t nwrites = le16_to_cpu(pRequest->QMIHdr.Length) + 1; + ret = write(cdc_wdm_fd, pRequest, nwrites); + if (ret == nwrites) { + ret = 0; + } else { + dbg_time("%s write=%d, errno: %d (%s)", __func__, ret, errno, strerror(errno)); + } + } else { + dbg_time("%s poll=%d, revents = 0x%x, errno: %d (%s)", __func__, ret, pollfds[0].revents, errno, strerror(errno)); + } + + return ret; +} + +static int QmiWwanGetClientID(UCHAR QMIType) { + PQCQMIMSG pResponse; + + QmiThreadSendQMI(ComposeQCTLMsg(QMICTL_GET_CLIENT_ID_REQ, CtlGetClientIdReq, &QMIType), &pResponse); + + if (pResponse) { + USHORT QMUXResult = cpu_to_le16(pResponse->CTLMsg.QMICTLMsgHdrRsp.QMUXResult); // QMI_RESULT_SUCCESS + USHORT QMUXError = cpu_to_le16(pResponse->CTLMsg.QMICTLMsgHdrRsp.QMUXError); // QMI_ERR_INVALID_ARG + //UCHAR QMIType = pResponse->CTLMsg.GetClientIdRsp.QMIType; + UCHAR ClientId = pResponse->CTLMsg.GetClientIdRsp.ClientId; + + if (!QMUXResult && !QMUXError && (QMIType == pResponse->CTLMsg.GetClientIdRsp.QMIType)) { + qmiclientId[QMIType] = ClientId; + switch (QMIType) { + case QMUX_TYPE_WDS: dbg_time("Get clientWDS = %d", ClientId); break; + case QMUX_TYPE_DMS: dbg_time("Get clientDMS = %d", ClientId); break; + case QMUX_TYPE_NAS: dbg_time("Get clientNAS = %d", ClientId); break; + case QMUX_TYPE_QOS: dbg_time("Get clientQOS = %d", ClientId); break; + case QMUX_TYPE_WMS: dbg_time("Get clientWMS = %d", ClientId); break; + case QMUX_TYPE_PDS: dbg_time("Get clientPDS = %d", ClientId); break; + case QMUX_TYPE_UIM: dbg_time("Get clientUIM = %d", ClientId); break; + case QMUX_TYPE_WDS_ADMIN: dbg_time("Get clientWDA = %d", ClientId); + break; + default: break; + } + } + } + return 0; +} + +static int QmiWwanReleaseClientID(QMI_SERVICE_TYPE QMIType, UCHAR ClientId) { + UCHAR argv[] = {QMIType, ClientId}; + QmiThreadSendQMI(ComposeQCTLMsg(QMICTL_RELEASE_CLIENT_ID_REQ, CtlReleaseClientIdReq, argv), NULL); + return 0; +} + +int QmiWwanInit(void) { + unsigned i; + int ret; + PQCQMIMSG pResponse; + + for (i = 0; i < 10; i++) { + ret = QmiThreadSendQMITimeout(ComposeQCTLMsg(QMICTL_SYNC_REQ, NULL, NULL), NULL, 1 * 1000); + if (!ret) + break; + sleep(1); + } + if (ret) + return ret; + + QmiThreadSendQMI(ComposeQCTLMsg(QMICTL_GET_VERSION_REQ, CtlGetVersionReq, NULL), &pResponse); + if (pResponse) free(pResponse); + QmiWwanGetClientID(QMUX_TYPE_WDS); + QmiWwanGetClientID(QMUX_TYPE_DMS); + QmiWwanGetClientID(QMUX_TYPE_NAS); + QmiWwanGetClientID(QMUX_TYPE_UIM); + QmiWwanGetClientID(QMUX_TYPE_WDS_ADMIN); + return 0; +} + +int QmiWwanDeInit(void) { + unsigned int i; + for (i = 0; i < sizeof(qmiclientId)/sizeof(qmiclientId[0]); i++) + { + if (qmiclientId[i] != 0) + { + QmiWwanReleaseClientID(i, qmiclientId[i]); + qmiclientId[i] = 0; + } + } + + return 0; +} + +void * QmiWwanThread(void *pData) { + const char *cdc_wdm = (const char *)pData; + cdc_wdm_fd = open(cdc_wdm, O_RDWR | O_NONBLOCK | O_NOCTTY); + if (cdc_wdm_fd == -1) { + dbg_time("%s Failed to open %s, errno: %d (%s)", __func__, cdc_wdm, errno, strerror(errno)); + qmidevice_send_event_to_main(RIL_INDICATE_DEVICE_DISCONNECTED); + pthread_exit(NULL); + return NULL; + } + dbg_time("cdc_wdm_fd = %d", cdc_wdm_fd); + + qmidevice_send_event_to_main(RIL_INDICATE_DEVICE_CONNECTED); + + while (1) { + struct pollfd pollfds[] = {{qmidevice_control_fd[1], POLLIN, 0}, {cdc_wdm_fd, POLLIN, 0}}; + int ne, ret, nevents = sizeof(pollfds)/sizeof(pollfds[0]); + + do { + ret = poll(pollfds, nevents, -1); + } while ((ret < 0) && (errno == EINTR)); + + if (ret <= 0) { + dbg_time("%s poll=%d, errno: %d (%s)", __func__, ret, errno, strerror(errno)); + break; + } + + for (ne = 0; ne < nevents; ne++) { + int fd = pollfds[ne].fd; + short revents = pollfds[ne].revents; + + //dbg_time("{%d, %x, %x}", pollfds[ne].fd, pollfds[ne].events, pollfds[ne].revents); + + if (revents & (POLLERR | POLLHUP | POLLNVAL)) { + dbg_time("%s poll err/hup/inval", __func__); + dbg_time("poll fd = %d, events = 0x%04x", fd, revents); + if (fd == cdc_wdm_fd) { + } else { + } + if (revents & (POLLHUP | POLLNVAL)) //EC20 bug, Can get POLLERR + goto __QmiWwanThread_quit; + } + + if ((revents & POLLIN) == 0) + continue; + + if (fd == qmidevice_control_fd[1]) { + int triger_event; + if (read(fd, &triger_event, sizeof(triger_event)) == sizeof(triger_event)) { + //DBG("triger_event = 0x%x", triger_event); + switch (triger_event) { + case RIL_REQUEST_QUIT: + goto __QmiWwanThread_quit; + break; + case SIGTERM: + case SIGHUP: + case SIGINT: + QmiThreadRecvQMI(NULL); + break; + default: + break; + } + } + } + + if (fd == cdc_wdm_fd) { + ssize_t nreads; + UCHAR QMIBuf[512]; + PQCQMIMSG pResponse = (PQCQMIMSG)QMIBuf; + + nreads = read(fd, QMIBuf, sizeof(QMIBuf)); + //dbg_time("%s read=%d errno: %d (%s)", __func__, (int)nreads, errno, strerror(errno)); + if (nreads <= 0) { + dbg_time("%s read=%d errno: %d (%s)", __func__, (int)nreads, errno, strerror(errno)); + break; + } + + if (nreads != (le16_to_cpu(pResponse->QMIHdr.Length) + 1)) { + dbg_time("%s nreads=%d, pQCQMI->QMIHdr.Length = %d", __func__, (int)nreads, le16_to_cpu(pResponse->QMIHdr.Length)); + continue; + } + + QmiThreadRecvQMI(pResponse); + } + } + } + +__QmiWwanThread_quit: + if (cdc_wdm_fd != -1) { close(cdc_wdm_fd); cdc_wdm_fd = -1; } + qmidevice_send_event_to_main(RIL_INDICATE_DEVICE_DISCONNECTED); + QmiThreadRecvQMI(NULL); //main thread may pending on QmiThreadSendQMI() + dbg_time("%s exit", __func__); + pthread_exit(NULL); + return NULL; +} + +#else +int QmiWwanSendQMI(PQCQMIMSG pRequest) {return -1;} +int QmiWwanInit(void) {return -1;} +int QmiWwanDeInit(void) {return -1;} +void * QmiWwanThread(void *pData) {dbg_time("please set CONFIG_QMIWWAN"); return NULL;} +#endif diff --git a/root/package/link4all/quectel-CM/src_quectel/dhcpclient.c b/root/package/link4all/quectel-CM/src_quectel/dhcpclient.c new file mode 100755 index 00000000..e5aafd22 --- /dev/null +++ b/root/package/link4all/quectel-CM/src_quectel/dhcpclient.c @@ -0,0 +1,90 @@ +#ifdef ANDROID +/* + * Copyright 2008, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "QMIThread.h" +#ifdef USE_NDK +extern int (*ifc_init)(void); +extern void (*ifc_close)(void); +extern int (*do_dhcp)(const char *iname); +extern void (*get_dhcp_info)(uint32_t *ipaddr, uint32_t *gateway, uint32_t *prefixLength, + uint32_t *dns1, uint32_t *dns2, uint32_t *server, + uint32_t *lease); +extern int (*property_set)(const char *key, const char *value); +#else +#include +#include +extern int do_dhcp(const char *iname); +extern void get_dhcp_info(uint32_t *ipaddr, uint32_t *gateway, uint32_t *prefixLength, + uint32_t *dns1, uint32_t *dns2, uint32_t *server, + uint32_t *lease); +#endif + +static const char *ipaddr_to_string(in_addr_t addr) +{ + struct in_addr in_addr; + + in_addr.s_addr = addr; + return inet_ntoa(in_addr); +} + +void do_dhcp_request(PROFILE_T *profile) { +#ifdef USE_NDK + if (!ifc_init ||!ifc_close ||!do_dhcp || !get_dhcp_info || !property_set) { + return; + } +#endif + + char *ifname = profile->usbnet_adapter; + uint32_t ipaddr, gateway, prefixLength, dns1, dns2, server, lease; + char propKey[128]; + +#if 0 + if (profile->rawIP && ((profile->IPType==0x04 && profile->ipv4.Address))) + { + snprintf(propKey, sizeof(propKey), "net.%s.dns1", ifname); + property_set(propKey, profile->ipv4.DnsPrimary ? ipaddr_to_string(ql_swap32(profile->ipv4.DnsPrimary)) : "8.8.8.8"); + snprintf(propKey, sizeof(propKey), "net.%s.dns2", ifname); + property_set(propKey, profile->ipv4.DnsSecondary ? ipaddr_to_string(ql_swap32(profile->ipv4.DnsSecondary)) : "8.8.8.8"); + snprintf(propKey, sizeof(propKey), "net.%s.gw", ifname); + property_set(propKey, profile->ipv4.Gateway ? ipaddr_to_string(ql_swap32(profile->ipv4.Gateway)) : "0.0.0.0"); + return; + } +#endif + + if(ifc_init()) { + dbg_time("failed to ifc_init(%s): %s\n", ifname, strerror(errno)); + } + + if (do_dhcp(ifname) < 0) { + dbg_time("failed to do_dhcp(%s): %s\n", ifname, strerror(errno)); + } + + ifc_close(); + + get_dhcp_info(&ipaddr, &gateway, &prefixLength, &dns1, &dns2, &server, &lease); + snprintf(propKey, sizeof(propKey), "net.%s.gw", ifname); + property_set(propKey, gateway ? ipaddr_to_string(gateway) : "0.0.0.0"); +} +#endif diff --git a/root/package/link4all/quectel-CM/src_quectel/main.c b/root/package/link4all/quectel-CM/src_quectel/main.c new file mode 100755 index 00000000..5938d670 --- /dev/null +++ b/root/package/link4all/quectel-CM/src_quectel/main.c @@ -0,0 +1,677 @@ +#include "QMIThread.h" +#include +#include +#include + +#define POLL_DATA_CALL_STATE_SECONDS 15 //poll data call state, for qmi ind maybe not work well + +int debug_qmi = 0; +int main_loop = 0; +char * qmichannel; +int qmidevice_control_fd[2]; +static int signal_control_fd[2]; + +#ifdef LINUX_RIL_SHLIB +UINT ifc_get_addr(const char *ifname); +static int check_ipv4_address(PROFILE_T *profile); +#endif + +static void usbnet_link_change(int link, PROFILE_T *profile) { + static int s_link = -1; + + if (s_link == link) + return; + + s_link = link; + + if (link) + udhcpc_start(profile); + else + udhcpc_stop(profile); + +#ifdef LINUX_RIL_SHLIB + if (link) { + while (ifc_get_addr(profile->usbnet_adapter) == 0) { + sleep(1); + } + } + + if (link && requestGetIPAddress(profile) == 0) { + unsigned char *r; + + dbg_time("Using interface %s", profile->usbnet_adapter); + r = (unsigned char *)&profile->ipv4.Address; + dbg_time("local IP address %d.%d.%d.%d", r[3], r[2], r[1], r[0]); + r = (unsigned char *)&profile->ipv4.Gateway; + dbg_time("remote IP address %d.%d.%d.%d", r[3], r[2], r[1], r[0]); + r = (unsigned char *)&profile->ipv4.DnsPrimary; + dbg_time("primary DNS address %d.%d.%d.%d", r[3], r[2], r[1], r[0]); + r = (unsigned char *)&profile->ipv4.DnsSecondary; + dbg_time("secondary DNS address %d.%d.%d.%d", r[3], r[2], r[1], r[0]); + } +#endif +} + +UINT ifc_get_addr(const char *ifname); +static int check_ipv4_address(PROFILE_T *profile) { + if (requestGetIPAddress(profile) == 0) { + UINT localIP = ifc_get_addr(profile->usbnet_adapter); + UINT remoteIP = ql_swap32(profile->ipv4.Address); + unsigned char *l = (unsigned char *)&localIP; + unsigned char *r = (unsigned char *)&remoteIP; + if (remoteIP != remoteIP || debug_qmi) + dbg_time("localIP: %d.%d.%d.%d VS remoteIP: %d.%d.%d.%d", + l[0], l[1], l[2], l[3], r[0], r[1], r[2], r[3]); + if (profile->IPType == 0x04) + return (localIP == remoteIP); + } + return 0; +} + +static void main_send_event_to_qmidevice(int triger_event) { + write(qmidevice_control_fd[0], &triger_event, sizeof(triger_event)); +} + +static void send_signo_to_main(int signo) { + write(signal_control_fd[0], &signo, sizeof(signo)); +} + +void qmidevice_send_event_to_main(int triger_event) { + write(qmidevice_control_fd[1], &triger_event, sizeof(triger_event)); +} + +#define MAX_PATH 256 + +static int ls_dir(const char *dir, int (*match)(const char *dir, const char *file, void *argv[]), void *argv[]) +{ + DIR *pDir; + struct dirent* ent = NULL; + int match_times = 0; + + pDir = opendir(dir); + if (pDir == NULL) { + dbg_time("Cannot open directory: %s, errno: %d (%s)", dir, errno, strerror(errno)); + return 0; + } + + while ((ent = readdir(pDir)) != NULL) { + match_times += match(dir, ent->d_name, argv); + } + closedir(pDir); + + return match_times; +} + +static int is_same_linkfile(const char *dir, const char *file, void *argv[]) +{ + const char *qmichannel = (const char *)argv[1]; + char linkname[MAX_PATH]; + char filename[MAX_PATH]; + int linksize; + + snprintf(linkname, MAX_PATH, "%s/%s", dir, file); + linksize = readlink(linkname, filename, MAX_PATH); + if (linksize <= 0) + return 0; + + filename[linksize] = 0; + if (strcmp(filename, qmichannel)) + return 0; + + dbg_time("%s -> %s", linkname, filename); + return 1; +} + +static int is_brother_process(const char *dir, const char *file, void *argv[]) +{ + //const char *myself = (const char *)argv[0]; + char linkname[MAX_PATH]; + char filename[MAX_PATH]; + int linksize; + int i = 0, kill_timeout = 15; + pid_t pid; + + //dbg_time("%s", file); + while (file[i]) { + if (!isdigit(file[i])) + break; + i++; + } + + if (file[i]) { + //dbg_time("%s not digit", file); + return 0; + } + + snprintf(linkname, MAX_PATH, "%s/%s/exe", dir, file); + linksize = readlink(linkname, filename, MAX_PATH); + if (linksize <= 0) + return 0; + + filename[linksize] = 0; +#if 0 //check all process + if (strcmp(filename, myself)) + return 0; +#endif + + pid = atoi(file); + if (pid == getpid()) + return 0; + + snprintf(linkname, MAX_PATH, "%s/%s/fd", dir, file); + if (!ls_dir(linkname, is_same_linkfile, argv)) + return 0; + + dbg_time("%s/%s/exe -> %s", dir, file, filename); + while (kill_timeout-- && !kill(pid, 0)) + { + kill(pid, SIGTERM); + sleep(1); + } + if (!kill(pid, 0)) + { + dbg_time("force kill %s/%s/exe -> %s", dir, file, filename); + kill(pid, SIGKILL); + sleep(1); + } + + return 1; +} + +static int kill_brothers(const char *qmichannel) +{ + char myself[MAX_PATH]; + int filenamesize; + void *argv[2] = {myself, (void *)qmichannel}; + + filenamesize = readlink("/proc/self/exe", myself, MAX_PATH); + if (filenamesize <= 0) + return 0; + myself[filenamesize] = 0; + + if (ls_dir("/proc", is_brother_process, argv)) + sleep(1); + + return 0; +} + +static void ql_sigaction(int signo) { + if (SIGCHLD == signo) + waitpid(-1, NULL, WNOHANG); + else if (SIGALRM == signo) + send_signo_to_main(SIGUSR1); + else + { + if (SIGTERM == signo || SIGHUP == signo || SIGINT == signo) + main_loop = 0; + send_signo_to_main(signo); + main_send_event_to_qmidevice(signo); //main may be wating qmi response + } +} + +pthread_t gQmiThreadID; + +static int usage(const char *progname) { + dbg_time("Usage: %s [-s [apn [user password auth]]] [-p pincode] [-f logfilename] ", progname); + dbg_time("-s [apn [user password auth]] Set apn/user/password/auth get from your network provider"); + dbg_time("-p pincode Verify sim card pin if sim card is locked"); + dbg_time("-f logfilename Save log message of this program to file"); + dbg_time("Example 1: %s ", progname); + dbg_time("Example 2: %s -s 3gnet ", progname); + dbg_time("Example 3: %s -s 3gnet carl 1234 0 -p 1234 -f gobinet_log.txt", progname); + return 0; +} + +static int qmidevice_detect(char **pp_qmichannel, char **pp_usbnet_adapter) { + struct dirent* ent = NULL; + DIR *pDir; +#if 0 //ndef ANDROID + int osmaj, osmin, ospatch; + static struct utsname utsname; /* for the kernel version */ + static int kernel_version; +#define KVERSION(j,n,p) ((j)*1000000 + (n)*1000 + (p)) + + /* get the kernel version now, since we are called before sys_init */ + uname(&utsname); + osmaj = osmin = ospatch = 0; + sscanf(utsname.release, "%d.%d.%d", &osmaj, &osmin, &ospatch); + kernel_version = KVERSION(osmaj, osmin, ospatch); +#endif + + if ((pDir = opendir("/dev")) == NULL) { + dbg_time("Cannot open directory: %s, errno:%d (%s)", "/dev", errno, strerror(errno)); + return -ENODEV; + } + + while ((ent = readdir(pDir)) != NULL) { + if ((strncmp(ent->d_name, "cdc-wdm", strlen("cdc-wdm")) == 0) || (strncmp(ent->d_name, "qcqmi", strlen("qcqmi")) == 0)) { + char net_path[64]; + + *pp_qmichannel = (char *)malloc(32); + sprintf(*pp_qmichannel, "/dev/%s", ent->d_name); + dbg_time("Find qmichannel = %s", *pp_qmichannel); + + if (strncmp(ent->d_name, "cdc-wdm", strlen("cdc-wdm")) == 0) + sprintf(net_path, "/sys/class/net/wwan%s", &ent->d_name[strlen("cdc-wdm")]); + else + { + sprintf(net_path, "/sys/class/net/usb%s", &ent->d_name[strlen("qcqmi")]); + #if 0//ndef ANDROID + if (kernel_version >= KVERSION( 2,6,39 )) + sprintf(net_path, "/sys/class/net/eth%s", &ent->d_name[strlen("qcqmi")]); + #else + if (access(net_path, R_OK) && errno == ENOENT) + sprintf(net_path, "/sys/class/net/eth%s", &ent->d_name[strlen("qcqmi")]); + #endif + } + + if (access(net_path, R_OK) == 0) + { + if (*pp_usbnet_adapter && strcmp(*pp_usbnet_adapter, (net_path + strlen("/sys/class/net/")))) + { + free(*pp_qmichannel); *pp_qmichannel = NULL; + continue; + } + *pp_usbnet_adapter = strdup(net_path + strlen("/sys/class/net/")); + dbg_time("Find usbnet_adapter = %s", *pp_usbnet_adapter); + break; + } + else + { + dbg_time("Failed to access %s, errno:%d (%s)", net_path, errno, strerror(errno)); + free(*pp_qmichannel); *pp_qmichannel = NULL; + } + } + } + closedir(pDir); + + return (*pp_qmichannel && *pp_usbnet_adapter); +} + +#if defined(ANDROID) || defined(LINUX_RIL_SHLIB) +int quectel_CM(int argc, char *argv[]) +#else +int main(int argc, char *argv[]) +#endif +{ + int triger_event = 0; + int opt = 1; + int signo; +#ifdef CONFIG_SIM + SIM_Status SIMStatus; +#endif + UCHAR PSAttachedState; + UCHAR ConnectionStatus = 0xff; //unknow state + char * save_usbnet_adapter = NULL; + PROFILE_T profile; + int slient_seconds = 0; + + memset(&profile, 0x00, sizeof(profile)); +#if CONFIG_DEFAULT_PDP + profile.pdp = CONFIG_DEFAULT_PDP; +#else + profile.pdp = 1; +#endif + profile.IPType = 0x04; //ipv4 first + + if (!strcmp(argv[argc-1], "&")) + argc--; + + opt = 1; + while (opt < argc) + { + if (argv[opt][0] != '-') + return usage(argv[0]); + + switch (argv[opt++][1]) + { +#define has_more_argv() ((opt < argc) && (argv[opt][0] != '-')) + case 's': + profile.apn = profile.user = profile.password = ""; + if (has_more_argv()) + profile.apn = argv[opt++]; + if (has_more_argv()) + profile.user = argv[opt++]; + if (has_more_argv()) + { + profile.password = argv[opt++]; + if (profile.password && profile.password[0]) + profile.auth = 2; //default chap, customers may miss auth + } + if (has_more_argv()) + profile.auth = argv[opt++][0] - '0'; + break; + + case 'p': + if (has_more_argv()) + profile.pincode = argv[opt++]; + break; + + case 'n': + if (has_more_argv()) + profile.pdp = argv[opt++][0] - '0'; + break; + + case 'f': + if (has_more_argv()) + { + const char * filename = argv[opt++]; + logfilefp = fopen(filename, "a+"); + if (!logfilefp) { + dbg_time("Fail to open %s, errno: %d(%s)", filename, errno, strerror(errno)); + } + } + break; + + case 'i': + if (has_more_argv()) + profile.usbnet_adapter = save_usbnet_adapter = argv[opt++]; + break; + + case 'v': + debug_qmi = 1; + break; + + case 'l': + main_loop = 1; + break; + + default: + return usage(argv[0]); + break; + } + } + + dbg_time("Quectel_Linux_ConnectManager_SR01A01V21"); + dbg_time("%s profile[%d] = %s/%s/%s/%d, pincode = %s", argv[0], profile.pdp, profile.apn, profile.user, profile.password, profile.auth, profile.pincode); + + signal(SIGUSR1, ql_sigaction); + signal(SIGUSR2, ql_sigaction); + signal(SIGINT, ql_sigaction); + signal(SIGTERM, ql_sigaction); + signal(SIGHUP, ql_sigaction); + signal(SIGCHLD, ql_sigaction); + signal(SIGALRM, ql_sigaction); + + if (socketpair( AF_LOCAL, SOCK_STREAM, 0, signal_control_fd) < 0 ) { + dbg_time("%s Faild to create main_control_fd: %d (%s)", __func__, errno, strerror(errno)); + return -1; + } + + if ( socketpair( AF_LOCAL, SOCK_STREAM, 0, qmidevice_control_fd ) < 0 ) { + dbg_time("%s Failed to create thread control socket pair: %d (%s)", __func__, errno, strerror(errno)); + return 0; + } + +//sudo apt-get install udhcpc +//sudo apt-get remove ModemManager +__main_loop: + while (!profile.qmichannel) + { + if (qmidevice_detect(&profile.qmichannel, &profile.usbnet_adapter)) + break; + if (main_loop) + { + int wait_for_device = 3000; + dbg_time("Wait for Quectel modules connect"); + while (wait_for_device && main_loop) { + wait_for_device -= 100; + usleep(100*1000); + } + continue; + } + dbg_time("Cannot find qmichannel(%s) usbnet_adapter(%s) for Quectel modules", profile.qmichannel, profile.usbnet_adapter); + return -ENODEV; + } + + if (access(profile.qmichannel, R_OK | W_OK)) { + dbg_time("Fail to access %s, errno: %d (%s)", profile.qmichannel, errno, strerror(errno)); + return errno; + } + +#if 0 //for test only, make fd > 255 +{ + int max_dup = 255; + while (max_dup--) + dup(0); +} +#endif + + kill_brothers(profile.qmichannel); + + qmichannel = profile.qmichannel; + if (!strncmp(profile.qmichannel, "/dev/qcqmi", strlen("/dev/qcqmi"))) + { + if (pthread_create( &gQmiThreadID, 0, GobiNetThread, (void *)profile.qmichannel) != 0) + { + dbg_time("%s Failed to create GobiNetThread: %d (%s)", __func__, errno, strerror(errno)); + return 0; + } + } + else + { + if (pthread_create( &gQmiThreadID, 0, QmiWwanThread, (void *)profile.qmichannel) != 0) + { + dbg_time("%s Failed to create QmiWwanThread: %d (%s)", __func__, errno, strerror(errno)); + return 0; + } + } + + if ((read(qmidevice_control_fd[0], &triger_event, sizeof(triger_event)) != sizeof(triger_event)) + || (triger_event != RIL_INDICATE_DEVICE_CONNECTED)) { + dbg_time("%s Failed to init QMIThread: %d (%s)", __func__, errno, strerror(errno)); + return 0; + } + + if (!strncmp(profile.qmichannel, "/dev/cdc-wdm", strlen("/dev/cdc-wdm"))) { + if (QmiWwanInit()) { + dbg_time("%s Failed to QmiWwanInit: %d (%s)", __func__, errno, strerror(errno)); + return 0; + } + } + +#ifdef CONFIG_VERSION + requestBaseBandVersion(NULL); +#endif + requestSetEthMode(&profile); + if (profile.rawIP && !strncmp(profile.qmichannel, "/dev/cdc-wdm", strlen("/dev/cdc-wdm"))) { + char raw_ip_switch[128] = {0}; + sprintf(raw_ip_switch, "/sys/class/net/%s/qmi/raw_ip", profile.usbnet_adapter); + if (!access(raw_ip_switch, R_OK)) { + int raw_ip_fd = -1; + raw_ip_fd = open(raw_ip_switch, O_RDWR); + if (raw_ip_fd >= 0) { + write(raw_ip_fd, "1", strlen("1")); + close(raw_ip_fd); + raw_ip_fd = -1; + } else { + dbg_time("open %s failed, errno = %d(%s)\n", raw_ip_switch, errno, strerror(errno)); + } + } + } +#ifdef CONFIG_SIM + requestGetSIMStatus(&SIMStatus); + if ((SIMStatus == SIM_PIN) && profile.pincode) { + requestEnterSimPin(profile.pincode); + } +#endif +#ifdef CONFIG_APN + if (profile.apn || profile.user || profile.password) { + requestSetProfile(&profile); + } + requestGetProfile(&profile); +#endif + requestRegistrationState(&PSAttachedState); + + if (!requestQueryDataCall(&ConnectionStatus) && (QWDS_PKT_DATA_CONNECTED == ConnectionStatus)) + usbnet_link_change(1, &profile); + else + usbnet_link_change(0, &profile); + + send_signo_to_main(SIGUSR1); + + while (1) + { + struct pollfd pollfds[] = {{signal_control_fd[1], POLLIN, 0}, {qmidevice_control_fd[0], POLLIN, 0}}; + int ne, ret, nevents = sizeof(pollfds)/sizeof(pollfds[0]); + + do { + ret = poll(pollfds, nevents, (ConnectionStatus == QWDS_PKT_DATA_CONNECTED) ? 1000 : -1); + } while ((ret < 0) && (errno == EINTR)); + + if (ret == 0) + { + #if POLL_DATA_CALL_STATE_SECONDS + if (++slient_seconds >= POLL_DATA_CALL_STATE_SECONDS) + send_signo_to_main(SIGUSR2); + #endif + continue; + } + slient_seconds = 0; + + if (ret <= 0) { + dbg_time("%s poll=%d, errno: %d (%s)", __func__, ret, errno, strerror(errno)); + goto __main_quit; + } + + for (ne = 0; ne < nevents; ne++) { + int fd = pollfds[ne].fd; + short revents = pollfds[ne].revents; + + if (revents & (POLLERR | POLLHUP | POLLNVAL)) { + dbg_time("%s poll err/hup", __func__); + dbg_time("epoll fd = %d, events = 0x%04x", fd, revents); + main_send_event_to_qmidevice(RIL_REQUEST_QUIT); + if (revents & POLLHUP) + goto __main_quit; + } + + if ((revents & POLLIN) == 0) + continue; + + if (fd == signal_control_fd[1]) + { + if (read(fd, &signo, sizeof(signo)) == sizeof(signo)) + { + alarm(0); + switch (signo) + { + case SIGUSR1: + requestQueryDataCall(&ConnectionStatus); + if (QWDS_PKT_DATA_CONNECTED != ConnectionStatus) + { + usbnet_link_change(0, &profile); + requestRegistrationState(&PSAttachedState); + if (PSAttachedState == 1 && requestSetupDataCall(&profile) == 0) + { + //succssful setup data call + } + else + { + alarm(5); //try to setup data call 5 seconds later + } + } + break; + + case SIGUSR2: + if (QWDS_PKT_DATA_CONNECTED == ConnectionStatus) + { + requestQueryDataCall(&ConnectionStatus); + } + if (QWDS_PKT_DATA_CONNECTED != ConnectionStatus || check_ipv4_address(&profile) == 0) //local ip is different with remote ip + { + requestDeactivateDefaultPDP(); + #if defined(ANDROID) || defined(LINUX_RIL_SHLIB) + kill(getpid(), SIGTERM); //android will setup data call again + #else + send_signo_to_main(SIGUSR1); + #endif + } + break; + + case SIGTERM: + case SIGHUP: + case SIGINT: + if (QWDS_PKT_DATA_CONNECTED == ConnectionStatus) + requestDeactivateDefaultPDP(); + usbnet_link_change(0, &profile); + if (!strncmp(profile.qmichannel, "/dev/cdc-wdm", strlen("/dev/cdc-wdm"))) + QmiWwanDeInit(); + main_send_event_to_qmidevice(RIL_REQUEST_QUIT); + goto __main_quit; + break; + + default: + break; + } + } + } + + if (fd == qmidevice_control_fd[0]) { + if (read(fd, &triger_event, sizeof(triger_event)) == sizeof(triger_event)) { + switch (triger_event) { + case RIL_INDICATE_DEVICE_DISCONNECTED: + usbnet_link_change(0, &profile); + if (main_loop) + { + if (pthread_join(gQmiThreadID, NULL)) { + dbg_time("%s Error joining to listener thread (%s)", __func__, strerror(errno)); + } + profile.qmichannel = NULL; + profile.usbnet_adapter = save_usbnet_adapter; + goto __main_loop; + } + goto __main_quit; + break; + + case RIL_UNSOL_RESPONSE_VOICE_NETWORK_STATE_CHANGED: + requestRegistrationState(&PSAttachedState); + if (PSAttachedState == 1 && QWDS_PKT_DATA_DISCONNECTED == ConnectionStatus) + send_signo_to_main(SIGUSR1); + break; + + case RIL_UNSOL_DATA_CALL_LIST_CHANGED: + { + #if defined(ANDROID) || defined(LINUX_RIL_SHLIB) + UCHAR oldConnectionStatus = ConnectionStatus; + #endif + requestQueryDataCall(&ConnectionStatus); + if (QWDS_PKT_DATA_CONNECTED != ConnectionStatus) + { + usbnet_link_change(0, &profile); + #if defined(ANDROID) || defined(LINUX_RIL_SHLIB) + if (oldConnectionStatus == QWDS_PKT_DATA_CONNECTED) //connected change to disconnect + kill(getpid(), SIGTERM); //android will setup data call again + #else + send_signo_to_main(SIGUSR1); + #endif + } else if (QWDS_PKT_DATA_CONNECTED == ConnectionStatus) { + requestGetIPAddress(&profile); + usbnet_link_change(1, &profile); + } + } + break; + + default: + break; + } + } + } + } + } + +__main_quit: + usbnet_link_change(0, &profile); + if (pthread_join(gQmiThreadID, NULL)) { + dbg_time("%s Error joining to listener thread (%s)", __func__, strerror(errno)); + } + close(signal_control_fd[0]); + close(signal_control_fd[1]); + close(qmidevice_control_fd[0]); + close(qmidevice_control_fd[1]); + dbg_time("%s exit", __func__); + if (logfilefp) + fclose(logfilefp); + + return 0; +} diff --git a/root/package/link4all/quectel-CM/src_quectel/udhcpc.c b/root/package/link4all/quectel-CM/src_quectel/udhcpc.c new file mode 100755 index 00000000..25c9b337 --- /dev/null +++ b/root/package/link4all/quectel-CM/src_quectel/udhcpc.c @@ -0,0 +1,372 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include "QMIThread.h" + +static int ifc_ctl_sock = -1; + +static int ifc_init(void) +{ + int ret; + if (ifc_ctl_sock == -1) { + ifc_ctl_sock = socket(AF_INET, SOCK_DGRAM, 0); + if (ifc_ctl_sock < 0) { + dbg_time("socket() failed: %s\n", strerror(errno)); + } + } + + ret = ifc_ctl_sock < 0 ? -1 : 0; + return ret; +} + +static void ifc_close(void) +{ + if (ifc_ctl_sock != -1) { + (void)close(ifc_ctl_sock); + ifc_ctl_sock = -1; + } +} + +static void ifc_init_ifr(const char *name, struct ifreq *ifr) +{ + memset(ifr, 0, sizeof(struct ifreq)); + strncpy(ifr->ifr_name, name, IFNAMSIZ); + ifr->ifr_name[IFNAMSIZ - 1] = 0; +} + +static int ifc_set_flags(const char *name, unsigned set, unsigned clr) +{ + struct ifreq ifr; + ifc_init_ifr(name, &ifr); + + if(ioctl(ifc_ctl_sock, SIOCGIFFLAGS, &ifr) < 0) return -1; + ifr.ifr_flags = (ifr.ifr_flags & (~clr)) | set; + return ioctl(ifc_ctl_sock, SIOCSIFFLAGS, &ifr); +} + +static int ifc_up(const char *name, int rawIP) +{ + int ret = ifc_set_flags(name, IFF_UP | (rawIP ? IFF_NOARP : 0), 0); + return ret; +} + +static int ifc_down(const char *name) +{ + int ret = ifc_set_flags(name, 0, IFF_UP); + return ret; +} + +static void init_sockaddr_in(struct sockaddr *sa, in_addr_t addr) +{ + struct sockaddr_in *sin = (struct sockaddr_in *) sa; + sin->sin_family = AF_INET; + sin->sin_port = 0; + sin->sin_addr.s_addr = addr; +} + +static int ifc_set_addr(const char *name, in_addr_t addr) +{ + struct ifreq ifr; + int ret; + + ifc_init_ifr(name, &ifr); + init_sockaddr_in(&ifr.ifr_addr, addr); + + ret = ioctl(ifc_ctl_sock, SIOCSIFADDR, &ifr); + return ret; +} + +static pthread_attr_t udhcpc_thread_attr; +static pthread_t udhcpc_thread_id; + +#ifdef ANDROID +void do_dhcp_request(PROFILE_T *profile); +static void* udhcpc_thread_function(void* arg) { + do_dhcp_request((PROFILE_T *)arg); + return NULL; +} +#else +//#define USE_DHCLIENT +static pid_t udhcpc_pid = 0; +static FILE * ql_popen(const char *program, const char *type) +{ + FILE *iop; + int pdes[2]; + pid_t pid; + char *argv[20]; + int argc = 0; + char *dup_program = strdup(program); + char *pos = dup_program; + + while (*pos != '\0') + { + while (isblank(*pos)) *pos++ = '\0'; + if (*pos != '\0') + { + argv[argc++] = pos; + while (*pos != '\0' && !isblank(*pos)) pos++; + //dbg_time("argv[%d] = %s", argc-1, argv[argc-1]); + } + } + argv[argc++] = NULL; + + if (pipe(pdes) < 0) { + return (NULL); + } + + switch (pid = fork()) { + case -1: /* Error. */ + (void)close(pdes[0]); + (void)close(pdes[1]); + return (NULL); + /* NOTREACHED */ + case 0: /* Child. */ + { + if (*type == 'r') { + (void) close(pdes[0]); + if (pdes[1] != STDOUT_FILENO) { + (void)dup2(pdes[1], STDOUT_FILENO); + (void)close(pdes[1]); + } + } else { + (void)close(pdes[1]); + if (pdes[0] != STDIN_FILENO) { + (void)dup2(pdes[0], STDIN_FILENO); + (void)close(pdes[0]); + } + } +#if 1 //child do not use these fds + if (cdc_wdm_fd > 0) { + close(cdc_wdm_fd); + } else { + unsigned int i; + for (i = 0; i < sizeof(qmiclientId)/sizeof(qmiclientId[0]); i++) { + if (qmiclientId[i] != 0) + close(qmiclientId[i]); + } + } +#endif + execvp(argv[0], argv); + _exit(127); + /* NOTREACHED */ + } + break; + default: + udhcpc_pid = pid; + free(dup_program); + break; + } + + /* Parent; assume fdopen can't fail. */ + if (*type == 'r') { + iop = fdopen(pdes[0], type); + (void)close(pdes[1]); + } else { + iop = fdopen(pdes[1], type); + (void)close(pdes[0]); + } + + return (iop); +} + +static int ql_pclose(FILE *iop) +{ + (void)fclose(iop); + udhcpc_pid = 0; + return 0; +} + +static void* udhcpc_thread_function(void* arg) { + FILE * udhcpc_fp; + char udhcpc_cmd[128]; + PROFILE_T *profile = (PROFILE_T *)arg; + char *ifname = profile->usbnet_adapter; + int need_add_default_route = 0; + +#ifdef USE_DHCLIENT + snprintf(udhcpc_cmd, sizeof(udhcpc_cmd), "dhclient -%d -d --no-pid %s", profile->IPType, (char *)ifname); +#else + if (profile->IPType == 0x04) + { + if (access("/usr/share/udhcpc/default.script", X_OK)) { + dbg_time("Fail to access /usr/share/udhcpc/default.script, errno: %d (%s)", errno, strerror(errno)); + } + + //-f,--foreground Run in foreground + //-b,--background Background if lease is not obtained + //-n,--now Exit if lease is not obtained + //-q,--quit Exit after obtaining lease + //-t,--retries N Send up to N discover packets (default 3) + snprintf(udhcpc_cmd, sizeof(udhcpc_cmd), "busybox udhcpc -f -n -q -t 5 -i %s", (char *)ifname); + } + else + { + /* + DHCPv6: Dibbler - a portable DHCPv6 + 1. download from http://klub.com.pl/dhcpv6/ + 2. cross-compile + 2.1 ./configure --host=arm-linux-gnueabihf + 2.2 copy dibbler-client to your board + 3. mkdir -p /var/log/dibbler/ /var/lib/ on your board + 4. create /etc/dibbler/client.conf on your board, the content is + log-mode short + log-level 7 + iface wwan0 { + ia + option dns-server + } + 5. run "dibbler-client start" to get ipV6 address + 6. run "route -A inet6 add default dev wwan0" to add default route + */ + need_add_default_route = 1; + snprintf(udhcpc_cmd, sizeof(udhcpc_cmd), "dibbler-client run"); + } +#endif + + udhcpc_fp = ql_popen(udhcpc_cmd, "r"); + if (udhcpc_fp) { + char buf[0xff]; + + if (need_add_default_route) + { + snprintf(buf, sizeof(buf), "route %s add default %s", (profile->IPType == 0x04) ? "" : "-A inet6", ifname); + system(buf); + } + + while((fgets(buf, sizeof(buf), udhcpc_fp)) != NULL) { + if ((strlen(buf) > 1) && (buf[strlen(buf) - 1] == '\n')) + buf[strlen(buf) - 1] = '\0'; + dbg_time("%s", buf); + } + ql_pclose(udhcpc_fp); + } + + return NULL; +} +#endif + +void udhcpc_start(PROFILE_T *profile) { + const char *ifname = profile->usbnet_adapter; + +//because must use udhcpc to obtain IP when working on ETH mode, +//so it is better also use udhcpc to obtain IP when working on IP mode. +//use the same policy for all modules +#if 0 +#ifndef ANDROID + if (profile->rawIP && ((profile->IPType==0x04 && profile->ipv4.Address))) + { + char shell_cmd[128]; + unsigned char *ip = (unsigned char *)&profile->ipv4.Address; + unsigned char *gw = (unsigned char *)&profile->ipv4.Gateway; + unsigned char *netmask = (unsigned char *)&profile->ipv4.SubnetMask; + unsigned char *dns1 = (unsigned char *)&profile->ipv4.DnsPrimary; + unsigned char *dns2 = (unsigned char *)&profile->ipv4.DnsSecondary; + + snprintf(shell_cmd, sizeof(shell_cmd), "ifconfig %s %d.%d.%d.%d netmask %d.%d.%d.%d",ifname, + ip[3], ip[2], ip[1], ip[0], netmask[3], netmask[2], netmask[1], netmask[0]); + dbg_time("%s", shell_cmd); + system(shell_cmd); + + //Resetting default routes + snprintf(shell_cmd, sizeof(shell_cmd), "route del default gw 0.0.0.0 dev %s", ifname); + while(!system(shell_cmd)); + + snprintf(shell_cmd, sizeof(shell_cmd), "route add default gw %d.%d.%d.%d dev %s metric 0", + gw[3], gw[2], gw[1], gw[0], ifname); + dbg_time("%s", shell_cmd); + system(shell_cmd); + + #ifdef ANDROID + do_dhcp_request(profile); + return; + #endif + + //Adding DNS + if (profile->ipv4.DnsSecondary == 0) + profile->ipv4.DnsSecondary = profile->ipv4.DnsPrimary; + if (dns1[0]) + { + dbg_time("Adding DNS %d.%d.%d.%d %d.%d.%d.%d", dns1[3], dns1[2], dns1[1], dns1[0], dns2[3], dns2[2], dns2[1], dns2[0]); + snprintf(shell_cmd, sizeof(shell_cmd), "echo -n \"nameserver %d.%d.%d.%d\nnameserver %d.%d.%d.%d\n\" > /etc/resolv.conf", + dns1[3], dns1[2], dns1[1], dns1[0], dns2[3], dns2[2], dns2[1], dns2[0]); + system(shell_cmd); + } + + return; + } +#endif +#endif + + ifc_init(); + if (ifc_set_addr(profile->usbnet_adapter, 0)) { + dbg_time("failed to set ip addr for %s to 0.0.0.0: %s\n", ifname, strerror(errno)); + return; + } + + if (ifc_up(profile->usbnet_adapter, profile->rawIP)) { + dbg_time("failed to bring up interface %s: %s\n", ifname, strerror(errno)); + return; + } + ifc_close(); + + pthread_attr_init(&udhcpc_thread_attr); + pthread_attr_setdetachstate(&udhcpc_thread_attr, PTHREAD_CREATE_DETACHED); + if(pthread_create(&udhcpc_thread_id, &udhcpc_thread_attr, udhcpc_thread_function, (void*)profile) !=0 ) { + dbg_time("failed to create udhcpc_thread for %s: %s\n", ifname, strerror(errno)); + } + pthread_attr_destroy(&udhcpc_thread_attr); +} + +void udhcpc_stop(PROFILE_T *profile) { + const char *ifname = profile->usbnet_adapter; + +#ifdef ANDROID +#else + pid_t pid = udhcpc_pid; + //dbg_time("%s kill %d/%d", __func__, pid, getpid()); + if (pid > 0 && !kill(pid, 0)) + { + int kill_time = 50; + do { + kill(pid, SIGTERM); + usleep(100*1000); + } while(kill_time-- && !kill(pid, 0)); //wait udhcpc quit + if (!kill(pid, 0)) + kill(pid, SIGKILL); + //dbg_time("%s kill udhcpc_pid time=%d", __func__, (50 - kill_time) * 100); + } +#endif + ifc_init(); + ifc_set_addr(ifname, 0); + ifc_down(ifname); + ifc_close(); +} + +UINT ifc_get_addr(const char *ifname) { + int inet_sock; + struct ifreq ifr; + UINT addr = 0; + + inet_sock = socket(AF_INET, SOCK_DGRAM, 0); + strcpy(ifr.ifr_name, ifname); + + if (ioctl(inet_sock, SIOCGIFADDR, &ifr) < 0) { + goto error; + } + addr = ((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr.s_addr; +error: + close(inet_sock); + + return addr; +} diff --git a/root/package/link4all/quectel-CM/src_quectel/util.c b/root/package/link4all/quectel-CM/src_quectel/util.c new file mode 100755 index 00000000..b6db4108 --- /dev/null +++ b/root/package/link4all/quectel-CM/src_quectel/util.c @@ -0,0 +1,158 @@ +#include "QMIThread.h" +#include + +#if defined(__STDC__) +#include +#define __V(x) x +#else +#include +#define __V(x) (va_alist) va_dcl +#define const +#define volatile +#endif + +#ifdef ANDROID +#define LOG_TAG "NDIS" +#include "../ql-log.h" +#else +#include +#endif + +#ifndef ANDROID //defined in atchannel.c +static void setTimespecRelative(struct timespec *p_ts, long long msec) +{ + struct timeval tv; + + gettimeofday(&tv, (struct timezone *) NULL); + + /* what's really funny about this is that I know + pthread_cond_timedwait just turns around and makes this + a relative time again */ + p_ts->tv_sec = tv.tv_sec + (msec / 1000); + p_ts->tv_nsec = (tv.tv_usec + (msec % 1000) * 1000L ) * 1000L; +} + +int pthread_cond_timeout_np(pthread_cond_t *cond, + pthread_mutex_t * mutex, + unsigned msecs) { + if (msecs != 0) { + struct timespec ts; + setTimespecRelative(&ts, msecs); + return pthread_cond_timedwait(cond, mutex, &ts); + } else { + return pthread_cond_wait(cond, mutex); + } +} +#endif + +static const char * get_time(void) { + static char time_buf[50]; + struct timeval tv; + time_t time; + suseconds_t millitm; + struct tm *ti; + + gettimeofday (&tv, NULL); + + time= tv.tv_sec; + millitm = (tv.tv_usec + 500) / 1000; + + if (millitm == 1000) { + ++time; + millitm = 0; + } + + ti = localtime(&time); + sprintf(time_buf, "[%02d-%02d_%02d:%02d:%02d:%03d]", ti->tm_mon+1, ti->tm_mday, ti->tm_hour, ti->tm_min, ti->tm_sec, (int)millitm); + return time_buf; +} + +FILE *logfilefp = NULL; +static pthread_mutex_t printfMutex = PTHREAD_MUTEX_INITIALIZER; +static char line[1024]; +void dbg_time (const char *fmt, ...) { + va_list args; + va_start(args, fmt); + + pthread_mutex_lock(&printfMutex); +#ifdef ANDROID + vsnprintf(line, sizeof(line), fmt, args); + RLOGD("%s", line); +#else +#ifdef LINUX_RIL_SHLIB + line[0] = '\0'; +#else + snprintf(line, sizeof(line), "%s ", get_time()); +#endif + vsnprintf(line + strlen(line), sizeof(line) - strlen(line), fmt, args); + fprintf(stdout, "%s\n", line); +#endif + if (logfilefp) { + fprintf(logfilefp, "%s\n", line); + } + pthread_mutex_unlock(&printfMutex); +} + +const int i = 1; +#define is_bigendian() ( (*(char*)&i) == 0 ) + +USHORT le16_to_cpu(USHORT v16) { + USHORT tmp = v16; + if (is_bigendian()) { + unsigned char *s = (unsigned char *)(&v16); + unsigned char *d = (unsigned char *)(&tmp); + d[0] = s[1]; + d[1] = s[0]; + } + return tmp; +} + +UINT le32_to_cpu (UINT v32) { + UINT tmp = v32; + if (is_bigendian()) { + unsigned char *s = (unsigned char *)(&v32); + unsigned char *d = (unsigned char *)(&tmp); + d[0] = s[3]; + d[1] = s[2]; + d[2] = s[1]; + d[3] = s[0]; + } + return tmp; +} + +UINT ql_swap32(UINT v32) { + UINT tmp = v32; + { + unsigned char *s = (unsigned char *)(&v32); + unsigned char *d = (unsigned char *)(&tmp); + d[0] = s[3]; + d[1] = s[2]; + d[2] = s[1]; + d[3] = s[0]; + } + return tmp; +} + +USHORT cpu_to_le16(USHORT v16) { + USHORT tmp = v16; + if (is_bigendian()) { + unsigned char *s = (unsigned char *)(&v16); + unsigned char *d = (unsigned char *)(&tmp); + d[0] = s[1]; + d[1] = s[0]; + } + return tmp; +} + +UINT cpu_to_le32 (UINT v32) { + UINT tmp = v32; + if (is_bigendian()) { + unsigned char *s = (unsigned char *)(&v32); + unsigned char *d = (unsigned char *)(&tmp); + d[0] = s[3]; + d[1] = s[2]; + d[2] = s[1]; + d[3] = s[0]; + } + return tmp; +} diff --git a/root/package/link4all/quectel-CM/src_quectel/util.h b/root/package/link4all/quectel-CM/src_quectel/util.h new file mode 100755 index 00000000..cc35ef3a --- /dev/null +++ b/root/package/link4all/quectel-CM/src_quectel/util.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _UTILS_H_ +#define _UTILS_H_ + +#include + +struct listnode +{ + struct listnode *next; + struct listnode *prev; +}; + +#define node_to_item(node, container, member) \ + (container *) (((char*) (node)) - offsetof(container, member)) + +#define list_declare(name) \ + struct listnode name = { \ + .next = &name, \ + .prev = &name, \ + } + +#define list_for_each(node, list) \ + for (node = (list)->next; node != (list); node = node->next) + +#define list_for_each_reverse(node, list) \ + for (node = (list)->prev; node != (list); node = node->prev) + +void list_init(struct listnode *list); +void list_add_tail(struct listnode *list, struct listnode *item); +void list_add_head(struct listnode *head, struct listnode *item); +void list_remove(struct listnode *item); + +#define list_empty(list) ((list) == (list)->next) +#define list_head(list) ((list)->next) +#define list_tail(list) ((list)->prev) + +int epoll_register(int epoll_fd, int fd, unsigned int events); +int epoll_deregister(int epoll_fd, int fd); +#endif diff --git a/root/package/link4all/rm500q/Makefile b/root/package/link4all/rm500q/Makefile new file mode 100755 index 00000000..4d1a05d0 --- /dev/null +++ b/root/package/link4all/rm500q/Makefile @@ -0,0 +1,55 @@ +# +# Copyright (C) 2008-2012 OpenWrt.org +# +# This is free software, licensed under the GNU General Public License v2. +# See /LICENSE for more information. +# + +include $(TOPDIR)/rules.mk +include $(INCLUDE_DIR)/kernel.mk + +PKG_NAME:=rm500q +PKG_RELEASE:=3 +PKG_LICENSE:=GPL-2.0 + +include $(INCLUDE_DIR)/package.mk + +define KernelPackage/rm500q + CATEGORY:=LINK4ALL + DEPENDS:=+kmod-usb-net +kmod-usb-wdm + TITLE:=rm500q driver + FILES:=$(PKG_BUILD_DIR)/rm500q.ko + AUTOLOAD:=$(call AutoLoad,81,rm500q,1) + KCONFIG:= +endef + +define KernelPackage/rm500q/description + rm500q driver +endef + + +define Build/Prepare + $(CP) src/* $(PKG_BUILD_DIR) + $(call Build/Prepare/Default) +endef + +# define Build/Compile +# $(MAKE) -C "$(LINUX_DIR)" \ +# CROSS_COMPILE="$(TARGET_CROSS)" \ +# ARCH="$(LINUX_KARCH)" \ +# SUBDIRS="$(PKG_BUILD_DIR)" \ +# EXTRA_CFLAGS="$(EXTRA_CFLAGS)" \ +# modules +# endef + +MAKE_OPTS:= \ + $(KERNEL_MAKE_FLAGS) \ + M="$(PKG_BUILD_DIR)" + +define Build/Compile + $(MAKE) -C "$(LINUX_DIR)" \ + $(MAKE_OPTS) \ + modules +endef + +$(eval $(call KernelPackage,rm500q)) diff --git a/root/package/link4all/rm500q/log/how_to_use_bridge.txt b/root/package/link4all/rm500q/log/how_to_use_bridge.txt new file mode 100644 index 00000000..05735dcb --- /dev/null +++ b/root/package/link4all/rm500q/log/how_to_use_bridge.txt @@ -0,0 +1,68 @@ +1. Enable QUECTEL_BRIDGE_MODE in qmi_wwan_q.c + +2. Guide to use .... +Welcome to Buildroot for the Orange Pi Zero +OrangePi_Zero login: root +# insmod qmi_wwan_q.ko +[ 90.591841] qmi_wwan_q 3-1:1.4: cdc-wdm0: USB WDM device +[ 90.597185] qmi_wwan_q 3-1:1.4: Quectel EC25&EC21&EG91&EG95&EG06&EP06&EM06&EG12&EP12&EM12&EG16&EG18&BG96&AG35 work on RawIP mode +[ 90.610176] qmi_wwan_q 3-1:1.4: rx_urb_size = 32768 +[ 90.620589] qmi_wwan_q 3-1:1.4 wwan0: register 'qmi_wwan_q' at usb-1c1b000.usb-1, WWAN/QMI device, 96:42:59:a9:f5:e4 +[ 90.631293] usbcore: registered new interface driver qmi_wwan_q +# brctl addbr br0 +# brctl addif br0 eth0 +[ 100.413071] br0: port 1(eth0) entered blocking state +[ 100.418081] br0: port 1(eth0) entered disabled state +[ 100.423356] device eth0 entered promiscuous mode +# brctl addif br0 wwan0 +[ 102.696724] br0: port 2(wwan0) entered blocking state +[ 102.701823] br0: port 2(wwan0) entered disabled state +[ 102.707182] device wwan0 entered promiscuous mode +# ifconfig br0 up +[ 110.405561] br0: port 1(eth0) entered blocking state +[ 110.410567] br0: port 1(eth0) entered forwarding state +# brctl show +bridge name bridge id STP enabled interfaces +br0 8000.0242b22e80d8 no eth0 + wwan0 +# ./quectel-CM & +# [01-01_06:37:02:386] Quectel_QConnectManager_Linux_V1.4.3 +[01-01_06:37:02:388] Find /sys/bus/usb/devices/3-1 idVendor=0x2c7c idProduct=0x512 +[01-01_06:37:02:388] Auto find qmichannel = /dev/cdc-wdm0 +[01-01_06:37:02:388] Auto find usbnet_adapter = wwan0 +[01-01_06:37:02:389] qmap_mode = 1, muxid = 0x81, qmap_netcard = wwan0 +[01-01_06:37:02:389] Modem works in QMI mode +[01-01_06:37:02:389] qmap_mode = 1, muxid = 0x81, qmap_netcard = wwan0 +[01-01_06:37:02:394] cdc_wdm_fd = 7 +[01-01_06:37:02:561] Get clientWDS = 18 +[01-01_06:37:02:633] Get clientDMS = 1 +[01-01_06:37:02:689] Get clientNAS = 2 +[01-01_06:37:02:753] Get clientUIM = 1 +[01-01_06:37:02:817] Get clientWDA = 1 +[01-01_06:37:02:881] requestBaseBandVersion EM12GBATE1127 +[01-01_06:37:02:945] qmap_settings.rx_urb_size = 16384 +[01-01_06:37:03:201] requestGetSIMStatus SIMStatus: SIM_READY +[01-01_06:37:03:265] requestGetProfile[1] ctnet///0 +[01-01_06:37:03:329] requestRegistrationState2 MCC: 460, MNC: 11, PS: Attached, DataCap: LTE +[01-01_06:37:03:393] requestQueryDataCall IPv4ConnectionStatus: DISCONNECTED +[01-01_06:37:03:457] requestSetupDataCall WdsConnectionIPv4Handle: 0x192a5ed0 +[01-01_06:37:03:717] ifconfig wwan0 up +[01-01_06:37:03:747] echo '0x64b69855' > /sys/module/qmi_wwan_q/parameters/bridge_ipv4 + +[ 117.030116] net wwan0: link_state 0x0 -> 0x1 +[ 117.041259] br0: port 2(wwan0) entered blocking state +[ 117.046326] br0: port 2(wwan0) entered forwarding state +[ 117.336688] net wwan0: sip = 100.182.152.85, tip=100.182.152.86, ipv4=100.182.152.85 +[ 121.612281] random: crng init done +[ 128.143645] net wwan0: PC Mac Address: 5e:6b:82:fa:ab:c3 +[ 128.151936] net wwan0: rx_pkts=1, rx_len=312 +[ 128.203578] net wwan0: PC Mac Address: 5e:6b:82:fa:ab:c3 +[ 131.012891] net wwan0: sip = 100.182.152.85, tip=100.182.152.86, ipv4=100.182.152.85 +[ 131.341780] net wwan0: rx_pkts=1, rx_len=316 +[ 131.434642] net wwan0: rx_pkts=1, rx_len=1404 +[ 131.439416] net wwan0: rx_pkts=3, rx_len=4212 +[ 131.512782] net wwan0: rx_pkts=4, rx_len=5616 +[ 131.535345] net wwan0: rx_pkts=7, rx_len=9828 +[ 133.778699] net wwan0: rx_pkts=8, rx_len=11232 +[ 134.143941] net wwan0: rx_pkts=9, rx_len=12636 +[ 140.053957] net wwan0: rx_pkts=11, rx_len=15444 diff --git a/root/package/link4all/rm500q/log/how_to_use_bridge_and_QMAP.txt b/root/package/link4all/rm500q/log/how_to_use_bridge_and_QMAP.txt new file mode 100644 index 00000000..10acd169 --- /dev/null +++ b/root/package/link4all/rm500q/log/how_to_use_bridge_and_QMAP.txt @@ -0,0 +1,234 @@ +1. Enable QUECTEL_BRIDGE_MODE in qmi_wwan_q.c + +2. set qmap_mode to 4 + +3. if you want add wwan0.2 to br2, wwan0.3 to br3 + set bridge_mode to BIT(1)|BIT(2) + +4. Guide to use .... +# insmod qmi_wwan_q.ko qmap_mode=4 bridge_mode=6 +[243467.331669] qmi_wwan_q 3-1:1.4: cdc-wdm0: USB WDM device +[243467.337136] qmi_wwan_q 3-1:1.4: Quectel EC25&EC21&EG91&EG95&EG06&EP06&EM06&EG12&EP12&EM12&EG16&EG18&BG96&AG35 work on RawIP mode +[243467.349471] qmi_wwan_q 3-1:1.4: rx_urb_size = 32768 +[243467.364803] qmi_wwan_q 3-1:1.4 wwan0: register 'qmi_wwan_q' at usb-1c1b000.usb-1, WWAN/QMI device, 96:42:59:a9:f5:e4 +[243467.376025] net wwan0: qmap_register_device wwan0.1 +[243467.381658] net wwan0: qmap_register_device wwan0.2 +[243467.387281] net wwan0: qmap_register_device wwan0.3 +[243467.392851] net wwan0: qmap_register_device wwan0.4 +[243467.398106] usbcore: registered new interface driver qmi_wwan_q + +# cat /sys/class/net/wwan0.2/bridge_mode +1 + +# cat /sys/class/net/wwan0.3/bridge_mode +1 + +# brctl addbr br2 +# brctl addif br2 wwan0.2 +[243492.518563] br2: port 1(wwan0.2) entered blocking state +[243492.523888] br2: port 1(wwan0.2) entered disabled state +[243492.535948] device wwan0.2 entered promiscuous mode + +# brctl addbr br3 +# brctl addif br3 wwan0.3 +[243507.486717] br3: port 1(wwan0.3) entered blocking state +[243507.492248] br3: port 1(wwan0.3) entered disabled state +[243507.497982] device wwan0.3 entered promiscuous mode + +# brctl show +bridge name bridge id STP enabled interfaces +br2 8000.964259a9f5e4 no wwan0.2 +br3 8000.964259a9f5e4 no wwan0.3 + +# ./quectel-qmi-proxy & +# Find /sys/bus/usb/devices/3-1 idVendor=2c7c idProduct=0512 +Find /sys/bus/usb/devices/3-1:1.4/usbmisc/cdc-wdm0 +Will use cdc-wdm /dev/cdc-wdm0 +qmi_proxy_init enter +qmi_proxy_loop enter +link_prot 2 +ul_data_aggregation_protocol 5 +dl_data_aggregation_protocol 5 +dl_data_aggregation_max_datagrams 32 +dl_data_aggregation_max_size 16384 +ul_data_aggregation_max_datagrams 16 +ul_data_aggregation_max_size 3072 +qmi_proxy_init finished, rx_urb_size is 16384 +local server: quectel-qmi-proxy sockfd = 4 +qmi_start_server: qmi_proxy_server_fd = 4 + +# ./quectel-CM -n 2 & +# [01-04_02:13:53:053] Quectel_QConnectManager_Linux_V1.4.3 +[01-04_02:13:53:056] Find /sys/bus/usb/devices/3-1 idVendor=0x2c7c idProduct=0x512 +[01-04_02:13:53:056] Auto find qmichannel = /dev/cdc-wdm0 +[01-04_02:13:53:056] Auto find usbnet_adapter = wwan0 +[01-04_02:13:53:056] qmap_mode = 4, muxid = 0x82, qmap_netcard = wwan0.2 +[01-04_02:13:53:057] Modem works in QMI mode +[01-04_02:13:53:057] qmap_mode = 4, muxid = 0x82, qmap_netcard = wwan0.2 ++++ ClientFd=5 +[01-04_02:13:53:058] connect to quectel-qmi-proxy sockfd = 7 + +[01-04_02:13:53:058] cdc_wdm_fd = 7 ++++ ClientFd=5 QMIType=1 ClientId=18 +[01-04_02:13:53:130] Get clientWDS = 18 ++++ ClientFd=5 QMIType=2 ClientId=1 +[01-04_02:13:53:194] Get clientDMS = 1 ++++ ClientFd=5 QMIType=3 ClientId=2 +[01-04_02:13:53:258] Get clientNAS = 2 ++++ ClientFd=5 QMIType=11 ClientId=2 +[01-04_02:13:53:333] Get clientUIM = 2 +[01-04_02:13:53:386] requestBaseBandVersion EM12GBATE1127 +[01-04_02:13:53:642] requestGetSIMStatus SIMStatus: SIM_READY +[01-04_02:13:53:706] requestGetProfile[2] IMS///0 +[01-04_02:13:53:770] requestRegistrationState2 MCC: 460, MNC: 11, PS: Attached, DataCap: LTE +[01-04_02:13:53:841] requestQueryDataCall IPv4ConnectionStatus: DISCONNECTED +[01-04_02:13:54:058] requestSetupDataCall WdsConnectionIPv4Handle: 0x78a3aba0 +[243527.630628] net wwan0: link_state 0x0 -> 0x2 +[01-04_02:13:54:319] ifconfig wwan0 up +[01-04_02:13:54:325] ifconfig wwan0.2 up +[01-04_02:13:54:330] echo '0x645026c8' > /sys/class/net/wwan0.2/bridge_ipv4 + +# udhcpc -i br2 +udhcpc: started, v1.29.3 +[243532.653027] br2: port 1(wwan0.2) entered blocking state +[243532.658384] br2: port 1(wwan0.2) entered forwarding state +udhcpc: sending discover +[243532.784337] wwan0.2 PC Mac Address: 96:42:59:a9:f5:e4 +[243532.794813] net wwan0: rx_pkts=1, rx_len=312 +udhcpc: sending select for 100.80.38.200 +[243532.894325] wwan0.2 PC Mac Address: 96:42:59:a9:f5:e4 +udhcpc: lease of 100.80.38.200 obtained, lease time 7200 +deleting routers +adding dns 202.102.213.68 +adding dns 61.132.163.68 + + +# ./quectel-CM -n 3 & +# [01-04_02:14:03:645] Quectel_QConnectManager_Linux_V1.4.3 +[01-04_02:14:03:648] Find /sys/bus/usb/devices/3-1 idVendor=0x2c7c idProduct=0x512 +[01-04_02:14:03:648] Auto find qmichannel = /dev/cdc-wdm0 +[01-04_02:14:03:648] Auto find usbnet_adapter = wwan0 +[01-04_02:14:03:649] qmap_mode = 4, muxid = 0x83, qmap_netcard = wwan0.3 +[01-04_02:14:03:649] Modem works in QMI mode +[01-04_02:14:03:649] qmap_mode = 4, muxid = 0x83, qmap_netcard = wwan0.3 +[01-04_02:14:03:650] connect to quectel-qmi-proxy sockfd = 7 + ++++ ClientFd=6 +[01-04_02:14:03:650] cdc_wdm_fd = 7 ++++ ClientFd=6 QMIType=1 ClientId=19 +[01-04_02:14:03:722] Get clientWDS = 19 ++++ ClientFd=6 QMIType=2 ClientId=2 +[01-04_02:14:03:786] Get clientDMS = 2 ++++ ClientFd=6 QMIType=3 ClientId=3 +[01-04_02:14:03:850] Get clientNAS = 3 ++++ ClientFd=6 QMIType=11 ClientId=3 +[01-04_02:14:03:914] Get clientUIM = 3 +[01-04_02:14:03:978] requestBaseBandVersion EM12GBATE1127 +[01-04_02:14:04:235] requestGetSIMStatus SIMStatus: SIM_READY +[01-04_02:14:04:298] requestGetProfile[3] lte///0 +[01-04_02:14:04:362] requestRegistrationState2 MCC: 460, MNC: 11, PS: Attached, DataCap: LTE +[01-04_02:14:04:426] requestQueryDataCall IPv4ConnectionStatus: DISCONNECTED +[01-04_02:14:04:555] requestSetupDataCall WdsConnectionIPv4Handle: 0x78a5c850 +[243538.126755] net wwan0: link_state 0x2 -> 0x6 +[01-04_02:14:04:815] ifconfig wwan0 up +[01-04_02:14:04:824] ifconfig wwan0.3 up +[01-04_02:14:04:829] echo '0x64548ae0' > /sys/class/net/wwan0.3/bridge_ipv4 + +# udhcpc -i br3 +udhcpc: started, v1.29.3 +[243541.850178] br3: port 1(wwan0.3) entered blocking state +[243541.855509] br3: port 1(wwan0.3) entered forwarding state +udhcpc: sending discover +[243541.976693] wwan0.3 PC Mac Address: 96:42:59:a9:f5:e4 +udhcpc: sending select for 100.84.138.224 +[243542.056668] wwan0.3 PC Mac Address: 96:42:59:a9:f5:e4 +udhcpc: lease of 100.84.138.224 obtained, lease time 7200 +deleting routers +adding dns 202.102.213.68 +adding dns 61.132.163.68 + +# ./quectel-CM -n 1 & +# [01-04_02:14:12:742] Quectel_QConnectManager_Linux_V1.4.3 +[01-04_02:14:12:744] Find /sys/bus/usb/devices/3-1 idVendor=0x2c7c idProduct=0x512 +[01-04_02:14:12:745] Auto find qmichannel = /dev/cdc-wdm0 +[01-04_02:14:12:745] Auto find usbnet_adapter = wwan0 +[01-04_02:14:12:745] qmap_mode = 4, muxid = 0x81, qmap_netcard = wwan0.1 +[01-04_02:14:12:745] Modem works in QMI mode +[01-04_02:14:12:746] qmap_mode = 4, muxid = 0x81, qmap_netcard = wwan0.1 +[01-04_02:14:12:746] connect to quectel-qmi-proxy sockfd = 7 + ++++ ClientFd=7 +[01-04_02:14:12:746] cdc_wdm_fd = 7 ++++ ClientFd=7 QMIType=1 ClientId=20 +[01-04_02:14:12:842] Get clientWDS = 20 ++++ ClientFd=7 QMIType=2 ClientId=3 +[01-04_02:14:12:906] Get clientDMS = 3 ++++ ClientFd=7 QMIType=3 ClientId=4 +[01-04_02:14:12:970] Get clientNAS = 4 ++++ ClientFd=7 QMIType=11 ClientId=4 +[01-04_02:14:13:034] Get clientUIM = 4 +[01-04_02:14:13:098] requestBaseBandVersion EM12GBATE1127 +[01-04_02:14:13:354] requestGetSIMStatus SIMStatus: SIM_READY +[01-04_02:14:13:418] requestGetProfile[1] ctnet///0 +[01-04_02:14:13:483] requestRegistrationState2 MCC: 460, MNC: 11, PS: Attached, DataCap: LTE +[01-04_02:14:13:546] requestQueryDataCall IPv4ConnectionStatus: DISCONNECTED +[01-04_02:14:13:610] requestSetupDataCall WdsConnectionIPv4Handle: 0x78a92b30 +[243547.182801] net wwan0: link_state 0x6 -> 0x7 +[01-04_02:14:13:874] ifconfig wwan0 up +[01-04_02:14:13:880] ifconfig wwan0.1 up +[01-04_02:14:13:885] busybox udhcpc -f -n -q -t 5 -i wwan0.1 +udhcpc: started, v1.29.3 +udhcpc: sending discover +udhcpc: sending select for 10.175.212.85 +udhcpc: lease of 10.175.212.85 obtained, lease time 7200 +[01-04_02:14:14:175] deleting routers +[01-04_02:14:14:194] adding dns 202.102.213.68 +[01-04_02:14:14:195] adding dns 61.132.163.68 + +# ifconfig + +br2 Link encap:Ethernet HWaddr 96:42:59:A9:F5:E4 + inet addr:100.80.38.200 Bcast:100.80.38.207 Mask:255.255.255.240 + UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 + RX packets:2 errors:0 dropped:0 overruns:0 frame:0 + TX packets:2 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:1000 + RX bytes:612 (612.0 B) TX bytes:684 (684.0 B) + +br3 Link encap:Ethernet HWaddr 96:42:59:A9:F5:E4 + inet addr:100.84.138.224 Bcast:100.84.138.255 Mask:255.255.255.192 + UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 + RX packets:2 errors:0 dropped:0 overruns:0 frame:0 + TX packets:2 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:1000 + RX bytes:612 (612.0 B) TX bytes:684 (684.0 B) + + +wwan0 Link encap:Ethernet HWaddr 96:42:59:A9:F5:E4 + UP BROADCAST RUNNING NOARP MULTICAST MTU:1500 Metric:1 + RX packets:0 errors:0 dropped:0 overruns:0 frame:0 + TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:1000 + RX bytes:0 (0.0 B) TX bytes:0 (0.0 B) + +wwan0.1 Link encap:Ethernet HWaddr 96:42:59:A9:F5:E4 + inet addr:10.175.212.85 Bcast:10.175.212.87 Mask:255.255.255.252 + UP BROADCAST RUNNING NOARP MULTICAST MTU:1500 Metric:1 + RX packets:2 errors:0 dropped:0 overruns:0 frame:0 + TX packets:2 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:1000 + RX bytes:612 (612.0 B) TX bytes:664 (664.0 B) + +wwan0.2 Link encap:Ethernet HWaddr 96:42:59:A9:F5:E4 + UP BROADCAST RUNNING NOARP MULTICAST MTU:1500 Metric:1 + RX packets:2 errors:0 dropped:0 overruns:0 frame:0 + TX packets:2 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:1000 + RX bytes:612 (612.0 B) TX bytes:664 (664.0 B) + +wwan0.3 Link encap:Ethernet HWaddr 96:42:59:A9:F5:E4 + UP BROADCAST RUNNING NOARP MULTICAST MTU:1500 Metric:1 + RX packets:2 errors:0 dropped:0 overruns:0 frame:0 + TX packets:2 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:1000 + RX bytes:612 (612.0 B) TX bytes:664 (664.0 B) diff --git a/root/package/link4all/rm500q/src/Makefile b/root/package/link4all/rm500q/src/Makefile new file mode 100644 index 00000000..bf045db5 --- /dev/null +++ b/root/package/link4all/rm500q/src/Makefile @@ -0,0 +1 @@ +obj-m += rm500q.o \ No newline at end of file diff --git a/root/package/link4all/rm500q/src/rm500q.c b/root/package/link4all/rm500q/src/rm500q.c new file mode 100644 index 00000000..aa54e54b --- /dev/null +++ b/root/package/link4all/rm500q/src/rm500q.c @@ -0,0 +1,2434 @@ +/* + * Copyright (c) 2012 Bjørn Mork + * + * The probing code is heavily inspired by cdc_ether, which is: + * Copyright (C) 2003-2005 by David Brownell + * Copyright (C) 2006 by Ole Andre Vadla Ravnas (ActiveSync) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#if LINUX_VERSION_CODE > KERNEL_VERSION(3,16,0) //8b094cd03b4a3793220d8d8d86a173bfea8c285b +#include +#else +#define timespec64 timespec +#define ktime_get_ts64 ktime_get_ts +#define timespec64_sub timespec_sub +#endif +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef ETH_P_MAP +#define ETH_P_MAP 0xDA1A +#endif + +#if (ETH_P_MAP == 0x00F9) +#undef ETH_P_MAP +#define ETH_P_MAP 0xDA1A +#endif + +#ifndef ARPHRD_RAWIP +#define ARPHRD_RAWIP ARPHRD_NONE +#endif + +#ifdef CONFIG_PINCTRL_IPQ807x +#define CONFIG_QCA_NSS_DRV +#endif + +#if 1//def CONFIG_QCA_NSS_DRV +#define _RMNET_NSS_H_ +#define _RMENT_NSS_H_ +struct rmnet_nss_cb { + int (*nss_create)(struct net_device *dev); + int (*nss_free)(struct net_device *dev); + int (*nss_tx)(struct sk_buff *skb); +}; +static struct rmnet_nss_cb *rmnet_nss_callbacks __rcu __read_mostly; +#ifdef CONFIG_QCA_NSS_DRV +static uint __read_mostly qca_nss_enabled = 1; +module_param( qca_nss_enabled, uint, S_IRUGO); +#define rmnet_nss_dereference(nss_cb) do { \ + rcu_read_lock(); \ + nss_cb = rcu_dereference(rmnet_nss_callbacks); \ + rcu_read_unlock(); \ +} while(0) +#else +#define rmnet_nss_dereference(nss_cb) do { nss_cb = NULL; } while(0) +#endif +#endif + +/* This driver supports wwan (3G/LTE/?) devices using a vendor + * specific management protocol called Qualcomm MSM Interface (QMI) - + * in addition to the more common AT commands over serial interface + * management + * + * QMI is wrapped in CDC, using CDC encapsulated commands on the + * control ("master") interface of a two-interface CDC Union + * resembling standard CDC ECM. The devices do not use the control + * interface for any other CDC messages. Most likely because the + * management protocol is used in place of the standard CDC + * notifications NOTIFY_NETWORK_CONNECTION and NOTIFY_SPEED_CHANGE + * + * Alternatively, control and data functions can be combined in a + * single USB interface. + * + * Handling a protocol like QMI is out of the scope for any driver. + * It is exported as a character device using the cdc-wdm driver as + * a subdriver, enabling userspace applications ("modem managers") to + * handle it. + * + * These devices may alternatively/additionally be configured using AT + * commands on a serial interface + */ +#define VERSION_NUMBER "V1.2.0.21" +#define QUECTEL_WWAN_VERSION "Quectel_Linux&Android_QMI_WWAN_Driver_"VERSION_NUMBER +static const char driver_name[] = "qmi_wwan_q"; + +/* driver specific data */ +struct qmi_wwan_state { + struct usb_driver *subdriver; + atomic_t pmcount; + unsigned long unused; + struct usb_interface *control; + struct usb_interface *data; +}; + +/* default ethernet address used by the modem */ +static const u8 default_modem_addr[ETH_ALEN] = {0x02, 0x50, 0xf3}; + +#if 1 //Added by Quectel +/* + Quectel_WCDMA<E_Linux_USB_Driver_User_Guide_V1.9.pdf + 5.6. Test QMAP on GobiNet or QMI WWAN + 0 - no QMAP + 1 - QMAP (Aggregation protocol) + X - QMAP (Multiplexing and Aggregation protocol) +*/ +#define QUECTEL_WWAN_QMAP 4 //MAX is 7 + +#if defined(QUECTEL_WWAN_QMAP) +#define QUECTEL_QMAP_MUX_ID 0x81 + +static uint __read_mostly qmap_mode = 0; +module_param( qmap_mode, uint, S_IRUGO); +module_param_named( rx_qmap, qmap_mode, uint, S_IRUGO ); +#endif + +#if defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE) +#define QUECTEL_BRIDGE_MODE +#endif + +#ifdef QUECTEL_BRIDGE_MODE +static uint __read_mostly bridge_mode = 0/*|BIT(1)*/; +module_param( bridge_mode, uint, S_IRUGO ); +#endif + +#if defined(QUECTEL_WWAN_QMAP) +#define QUECTEL_UL_DATA_AGG 1 + +#if defined(QUECTEL_UL_DATA_AGG) +struct tx_agg_ctx { + /* QMIWDS_ADMIN_SET_DATA_FORMAT_RESP TLV_0x17 and TLV_0x18 */ + uint ul_data_aggregation_max_datagrams; //UplinkDataAggregationMaxDatagramsTlv + uint ul_data_aggregation_max_size; //UplinkDataAggregationMaxSizeTlv + uint dl_minimum_padding; //0x1A +}; +#endif + +typedef struct { + unsigned int size; + unsigned int rx_urb_size; + unsigned int ep_type; + unsigned int iface_id; + unsigned int qmap_mode; + unsigned int qmap_version; + unsigned int dl_minimum_padding; + char ifname[8][16]; + unsigned char mux_id[8]; +} RMNET_INFO; + +typedef struct sQmiWwanQmap +{ + struct usbnet *mpNetDev; + struct driver_info driver_info; + atomic_t refcount; + struct net_device *mpQmapNetDev[QUECTEL_WWAN_QMAP]; + uint link_state; + uint qmap_mode; + uint qmap_size; + uint qmap_version; + +#if defined(QUECTEL_UL_DATA_AGG) + struct tx_agg_ctx tx_ctx; + struct tasklet_struct txq; +#endif + +#ifdef QUECTEL_BRIDGE_MODE + uint bridge_mode; + uint bridge_ipv4; + unsigned char bridge_mac[ETH_ALEN]; +#endif + uint use_rmnet_usb; + RMNET_INFO rmnet_info; +} sQmiWwanQmap; + +#if LINUX_VERSION_CODE > KERNEL_VERSION(3,13,0) //8f84985fec10de64a6b4cdfea45f2b0ab8f07c78 +#define MHI_NETDEV_STATUS64 +#endif +struct qmap_priv { + struct usbnet *dev; + struct net_device *real_dev; + struct net_device *self_dev; + u8 offset_id; + u8 mux_id; + u8 qmap_version; // 5~v1, 9~v5 + u8 link_state; + +#if defined(MHI_NETDEV_STATUS64) + struct pcpu_sw_netstats __percpu *stats64; +#endif + + spinlock_t agg_lock; + struct sk_buff *agg_skb; + unsigned agg_count; + struct timespec64 agg_time; + struct hrtimer agg_hrtimer; + struct work_struct agg_wq; + +#ifdef QUECTEL_BRIDGE_MODE + uint bridge_mode; + uint bridge_ipv4; + unsigned char bridge_mac[ETH_ALEN]; +#endif +}; + +struct qmap_hdr { + u8 cd_rsvd_pad; + u8 mux_id; + u16 pkt_len; +} __packed; + +enum rmnet_map_v5_header_type { + RMNET_MAP_HEADER_TYPE_UNKNOWN, + RMNET_MAP_HEADER_TYPE_COALESCING = 0x1, + RMNET_MAP_HEADER_TYPE_CSUM_OFFLOAD = 0x2, + RMNET_MAP_HEADER_TYPE_ENUM_LENGTH +}; + +/* Main QMAP header */ +struct rmnet_map_header { +#if defined(__LITTLE_ENDIAN_BITFIELD) + u8 pad_len:6; + u8 next_hdr:1; + u8 cd_bit:1; +#elif defined (__BIG_ENDIAN_BITFIELD) + u8 cd_bit:1; + u8 next_hdr:1; + u8 pad_len:6; +#else +#error "Please fix " +#endif + u8 mux_id; + __be16 pkt_len; +} __aligned(1); + +/* QMAP v5 headers */ +struct rmnet_map_v5_csum_header { +#if defined(__LITTLE_ENDIAN_BITFIELD) + u8 next_hdr:1; + u8 header_type:7; + u8 hw_reserved:7; + u8 csum_valid_required:1; +#elif defined (__BIG_ENDIAN_BITFIELD) + u8 header_type:7; + u8 next_hdr:1; + u8 csum_valid_required:1; + u8 hw_reserved:7; +#else +#error "Please fix " +#endif + __be16 reserved; +} __aligned(1); + +#ifdef QUECTEL_BRIDGE_MODE +static int is_qmap_netdev(const struct net_device *netdev); +#endif +#endif + +static const struct driver_info rmnet_usb_info; + +#ifdef QUECTEL_BRIDGE_MODE +static int bridge_arp_reply(struct net_device *net, struct sk_buff *skb, uint bridge_ipv4) { + struct arphdr *parp; + u8 *arpptr, *sha; + u8 sip[4], tip[4], ipv4[4]; + struct sk_buff *reply = NULL; + + ipv4[0] = (bridge_ipv4 >> 24) & 0xFF; + ipv4[1] = (bridge_ipv4 >> 16) & 0xFF; + ipv4[2] = (bridge_ipv4 >> 8) & 0xFF; + ipv4[3] = (bridge_ipv4 >> 0) & 0xFF; + + 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 += net->addr_len; /* sha */ + memcpy(sip, arpptr, sizeof(sip)); + arpptr += sizeof(sip); + arpptr += net->addr_len; /* tha */ + memcpy(tip, arpptr, sizeof(tip)); + + pr_info("%s sip = %d.%d.%d.%d, tip=%d.%d.%d.%d, ipv4=%d.%d.%d.%d\n", netdev_name(net), + sip[0], sip[1], sip[2], sip[3], tip[0], tip[1], tip[2], tip[3], ipv4[0], ipv4[1], ipv4[2], ipv4[3]); + //wwan0 sip = 10.151.137.255, tip=10.151.138.0, ipv4=10.151.137.255 + if (tip[0] == ipv4[0] && tip[1] == ipv4[1] && (tip[2]&0xFC) == (ipv4[2]&0xFC) && tip[3] != ipv4[3]) + reply = arp_create(ARPOP_REPLY, ETH_P_ARP, *((__be32 *)sip), net, *((__be32 *)tip), sha, default_modem_addr, 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; +} + +static struct sk_buff *bridge_mode_tx_fixup(struct net_device *net, struct sk_buff *skb, uint bridge_ipv4, unsigned char *bridge_mac) { + struct ethhdr *ehdr; + const struct iphdr *iph; + + skb_reset_mac_header(skb); + ehdr = eth_hdr(skb); + + if (ehdr->h_proto == htons(ETH_P_ARP)) { + if (bridge_ipv4) + bridge_arp_reply(net, skb, bridge_ipv4); + 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) { + //if (udp_hdr(skb)->dest == htons(67)) //DHCP Request + { + memcpy(bridge_mac, ehdr->h_source, ETH_ALEN); + pr_info("%s PC Mac Address: %02x:%02x:%02x:%02x:%02x:%02x\n", netdev_name(net), + bridge_mac[0], bridge_mac[1], bridge_mac[2], bridge_mac[3], bridge_mac[4], bridge_mac[5]); + } + } + + if (memcmp(ehdr->h_source, bridge_mac, ETH_ALEN)) { + return NULL; + } + + return skb; +} + +static void bridge_mode_rx_fixup(sQmiWwanQmap *pQmapDev, struct net_device *net, struct sk_buff *skb) { + uint bridge_mode = 0; + unsigned char *bridge_mac; + + if (pQmapDev->qmap_mode > 1 || pQmapDev->use_rmnet_usb == 1) { + struct qmap_priv *priv = netdev_priv(net); + bridge_mode = priv->bridge_mode; + bridge_mac = priv->bridge_mac; + } + else { + bridge_mode = pQmapDev->bridge_mode; + bridge_mac = pQmapDev->bridge_mac; + } + + if (bridge_mode) + memcpy(eth_hdr(skb)->h_dest, bridge_mac, ETH_ALEN); + else + memcpy(eth_hdr(skb)->h_dest, net->dev_addr, ETH_ALEN); +} +#endif + +#if defined(QUECTEL_WWAN_QMAP) +static ssize_t qmap_mode_show(struct device *dev, struct device_attribute *attr, char *buf) { + struct net_device *netdev = to_net_dev(dev); + struct usbnet * usbnetdev = netdev_priv( netdev ); + struct qmi_wwan_state *info = (void *)&usbnetdev->data; + sQmiWwanQmap *pQmapDev = (sQmiWwanQmap *)info->unused; + + return snprintf(buf, PAGE_SIZE, "%d\n", pQmapDev->qmap_mode); +} + +static DEVICE_ATTR(qmap_mode, S_IRUGO, qmap_mode_show, NULL); + +static ssize_t qmap_size_show(struct device *dev, struct device_attribute *attr, char *buf) { + struct net_device *netdev = to_net_dev(dev); + struct usbnet * usbnetdev = netdev_priv( netdev ); + struct qmi_wwan_state *info = (void *)&usbnetdev->data; + sQmiWwanQmap *pQmapDev = (sQmiWwanQmap *)info->unused; + + return snprintf(buf, PAGE_SIZE, "%u\n", pQmapDev->qmap_size); +} + +static DEVICE_ATTR(qmap_size, S_IRUGO, qmap_size_show, NULL); + +static ssize_t link_state_show(struct device *dev, struct device_attribute *attr, char *buf) { + struct net_device *netdev = to_net_dev(dev); + struct usbnet * usbnetdev = netdev_priv( netdev ); + struct qmi_wwan_state *info = (void *)&usbnetdev->data; + sQmiWwanQmap *pQmapDev = (sQmiWwanQmap *)info->unused; + + return snprintf(buf, PAGE_SIZE, "0x%x\n", pQmapDev->link_state); +} + +static ssize_t link_state_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { + struct net_device *netdev = to_net_dev(dev); + struct usbnet * usbnetdev = netdev_priv( netdev ); + struct qmi_wwan_state *info = (void *)&usbnetdev->data; + sQmiWwanQmap *pQmapDev = (sQmiWwanQmap *)info->unused; + unsigned link_state = 0; + unsigned old_link = pQmapDev->link_state; + uint offset_id = 0; + + link_state = simple_strtoul(buf, NULL, 0); + + if (pQmapDev->qmap_mode == 1) { + pQmapDev->link_state = !!link_state; + } + else if (pQmapDev->qmap_mode > 1) { + offset_id = ((link_state&0x7F) - 1); + + if (offset_id >= pQmapDev->qmap_mode) { + dev_info(dev, "%s offset_id is %d. but qmap_mode is %d\n", __func__, offset_id, pQmapDev->qmap_mode); + return count; + } + + if (link_state&0x80) + pQmapDev->link_state &= ~(1 << offset_id); + else + pQmapDev->link_state |= (1 << offset_id); + } + + if (old_link != pQmapDev->link_state) { + struct net_device *qmap_net = pQmapDev->mpQmapNetDev[offset_id]; + + if (usbnetdev->net->flags & IFF_UP) { + if (pQmapDev->link_state) { + netif_carrier_on(usbnetdev->net); + } + } + + if (qmap_net && qmap_net != netdev) { + struct qmap_priv *priv = netdev_priv(qmap_net); + + priv->link_state = !!(pQmapDev->link_state & (1 << offset_id)); + + if (qmap_net->flags & IFF_UP) { + if (priv->link_state) { + netif_carrier_on(qmap_net); + if (netif_queue_stopped(qmap_net) && !netif_queue_stopped(usbnetdev->net)) + netif_wake_queue(qmap_net); + } + else { + netif_carrier_off(qmap_net); + } + } + } + + if (usbnetdev->net->flags & IFF_UP) { + if (!pQmapDev->link_state) { + netif_carrier_off(usbnetdev->net); + } + } + + dev_info(dev, "link_state 0x%x -> 0x%x\n", old_link, pQmapDev->link_state); + } + + return count; +} + +#ifdef QUECTEL_BRIDGE_MODE +static ssize_t bridge_mode_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { + struct net_device *netdev = to_net_dev(dev); + uint old_mode = 0; + uint bridge_mode = simple_strtoul(buf, NULL, 0); + + if (netdev->type != ARPHRD_ETHER) { + return count; + } + + if (is_qmap_netdev(netdev)) { + struct qmap_priv *priv = netdev_priv(netdev); + old_mode = priv->bridge_mode; + priv->bridge_mode = bridge_mode; + } + else { + struct usbnet * usbnetdev = netdev_priv( netdev ); + struct qmi_wwan_state *info = (void *)&usbnetdev->data; + sQmiWwanQmap *pQmapDev = (sQmiWwanQmap *)info->unused; + old_mode = pQmapDev->bridge_mode; + pQmapDev->bridge_mode = bridge_mode; + } + + if (old_mode != bridge_mode) { + dev_info(dev, "bridge_mode change to 0x%x\n", bridge_mode); + } + + return count; +} + +static ssize_t bridge_mode_show(struct device *dev, struct device_attribute *attr, char *buf) { + struct net_device *netdev = to_net_dev(dev); + uint bridge_mode = 0; + + if (is_qmap_netdev(netdev)) { + struct qmap_priv *priv = netdev_priv(netdev); + bridge_mode = priv->bridge_mode; + } + else { + struct usbnet * usbnetdev = netdev_priv( netdev ); + struct qmi_wwan_state *info = (void *)&usbnetdev->data; + sQmiWwanQmap *pQmapDev = (sQmiWwanQmap *)info->unused; + bridge_mode = pQmapDev->bridge_mode; + } + + return snprintf(buf, PAGE_SIZE, "%u\n", bridge_mode); +} + +static ssize_t bridge_ipv4_show(struct device *dev, struct device_attribute *attr, char *buf) { + struct net_device *netdev = to_net_dev(dev); + unsigned int bridge_ipv4 = 0; + unsigned char ipv4[4]; + + if (is_qmap_netdev(netdev)) { + struct qmap_priv *priv = netdev_priv(netdev); + bridge_ipv4 = priv->bridge_ipv4; + } + else { + struct usbnet * usbnetdev = netdev_priv( netdev ); + struct qmi_wwan_state *info = (void *)&usbnetdev->data; + sQmiWwanQmap *pQmapDev = (sQmiWwanQmap *)info->unused; + bridge_ipv4 = pQmapDev->bridge_ipv4; + } + + ipv4[0] = (bridge_ipv4 >> 24) & 0xFF; + ipv4[1] = (bridge_ipv4 >> 16) & 0xFF; + ipv4[2] = (bridge_ipv4 >> 8) & 0xFF; + ipv4[3] = (bridge_ipv4 >> 0) & 0xFF; + + return snprintf(buf, PAGE_SIZE, "%d.%d.%d.%d\n", ipv4[0], ipv4[1], ipv4[2], ipv4[3]); +} + +static ssize_t bridge_ipv4_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { + struct net_device *netdev = to_net_dev(dev); + + if (is_qmap_netdev(netdev)) { + struct qmap_priv *priv = netdev_priv(netdev); + priv->bridge_ipv4 = simple_strtoul(buf, NULL, 16); + } + else { + struct usbnet * usbnetdev = netdev_priv( netdev ); + struct qmi_wwan_state *info = (void *)&usbnetdev->data; + sQmiWwanQmap *pQmapDev = (sQmiWwanQmap *)info->unused; + pQmapDev->bridge_ipv4 = simple_strtoul(buf, NULL, 16); + } + + return count; +} +#endif + +static DEVICE_ATTR(link_state, S_IWUSR | S_IRUGO, link_state_show, link_state_store); +#ifdef QUECTEL_BRIDGE_MODE +static DEVICE_ATTR(bridge_mode, S_IWUSR | S_IRUGO, bridge_mode_show, bridge_mode_store); +static DEVICE_ATTR(bridge_ipv4, S_IWUSR | S_IRUGO, bridge_ipv4_show, bridge_ipv4_store); +#endif + +static struct attribute *qmi_wwan_sysfs_attrs[] = { + &dev_attr_link_state.attr, + &dev_attr_qmap_mode.attr, + &dev_attr_qmap_size.attr, +#ifdef QUECTEL_BRIDGE_MODE + &dev_attr_bridge_mode.attr, + &dev_attr_bridge_ipv4.attr, +#endif + NULL, +}; + +static struct attribute_group qmi_wwan_sysfs_attr_group = { + .attrs = qmi_wwan_sysfs_attrs, +}; + +#ifdef QUECTEL_BRIDGE_MODE +static struct attribute *qmi_qmap_sysfs_attrs[] = { + &dev_attr_bridge_mode.attr, + &dev_attr_bridge_ipv4.attr, + NULL, +}; + +static struct attribute_group qmi_qmap_sysfs_attr_group = { + .attrs = qmi_qmap_sysfs_attrs, +}; +#endif + +static int qmap_open(struct net_device *qmap_net) +{ + struct qmap_priv *priv = netdev_priv(qmap_net); + struct net_device *real_dev = priv->real_dev; + + //printk("%s %s real_dev %d %d %d %d+++\n", __func__, dev->name, + // netif_carrier_ok(real_dev), netif_queue_stopped(real_dev), netif_carrier_ok(dev), netif_queue_stopped(dev)); + + if (!(priv->real_dev->flags & IFF_UP)) + return -ENETDOWN; + + if (priv->link_state) { + netif_carrier_on(real_dev); + netif_carrier_on(qmap_net); + if (netif_queue_stopped(qmap_net) && !netif_queue_stopped(real_dev)) + netif_wake_queue(qmap_net); + } + //printk("%s %s real_dev %d %d %d %d---\n", __func__, dev->name, + // netif_carrier_ok(real_dev), netif_queue_stopped(real_dev), netif_carrier_ok(dev), netif_queue_stopped(dev)); + + return 0; +} + +static int qmap_stop(struct net_device *qmap_net) +{ + //printk("%s %s %d %d+++\n", __func__, dev->name, + // netif_carrier_ok(dev), netif_queue_stopped(dev)); + + netif_carrier_off(qmap_net); + return 0; +} + +static void qmap_wake_queue(sQmiWwanQmap *pQmapDev) +{ + uint i = 0; + + if (!pQmapDev || !pQmapDev->use_rmnet_usb) + return; + + for (i = 0; i < pQmapDev->qmap_mode; i++) { + struct net_device *qmap_net = pQmapDev->mpQmapNetDev[i]; + + if (qmap_net && netif_carrier_ok(qmap_net) && netif_queue_stopped(qmap_net)) { + netif_wake_queue(qmap_net); + } + } +} + +static struct sk_buff * add_qhdr(struct sk_buff *skb, u8 mux_id) { + struct qmap_hdr *qhdr; + int pad = 0; + + pad = skb->len%4; + if (pad) { + pad = 4 - pad; + if (skb_tailroom(skb) < pad) { + printk("skb_tailroom small!\n"); + pad = 0; + } + if (pad) + __skb_put(skb, pad); + } + + qhdr = (struct qmap_hdr *)skb_push(skb, sizeof(struct qmap_hdr)); + qhdr->cd_rsvd_pad = pad; + qhdr->mux_id = mux_id; + qhdr->pkt_len = cpu_to_be16(skb->len - sizeof(struct qmap_hdr)); + + return skb; +} + +static struct sk_buff * add_qhdr_v5(struct sk_buff *skb, u8 mux_id) { + struct rmnet_map_header *map_header; + struct rmnet_map_v5_csum_header *ul_header; + u32 padding, map_datalen; + + map_datalen = skb->len; + padding = map_datalen%4; + if (padding) { + padding = 4 - padding; + if (skb_tailroom(skb) < padding) { + printk("skb_tailroom small!\n"); + padding = 0; + } + if (padding) + __skb_put(skb, padding); + } + + map_header = (struct rmnet_map_header *)skb_push(skb, (sizeof(struct rmnet_map_header) + sizeof(struct rmnet_map_v5_csum_header))); + map_header->cd_bit = 0; + map_header->next_hdr = 1; + map_header->pad_len = padding; + map_header->mux_id = mux_id; + map_header->pkt_len = htons(map_datalen + padding); + + ul_header = (struct rmnet_map_v5_csum_header *)(map_header + 1); + memset(ul_header, 0, sizeof(*ul_header)); + ul_header->header_type = RMNET_MAP_HEADER_TYPE_CSUM_OFFLOAD; + if (skb->ip_summed == CHECKSUM_PARTIAL) { +#if 0 //TODO + skb->ip_summed = CHECKSUM_NONE; + /* Ask for checksum offloading */ + ul_header->csum_valid_required = 1; +#endif + } + + return skb; +} + +static void rmnet_vnd_update_rx_stats(struct net_device *net, + unsigned rx_packets, unsigned rx_bytes) { +#if defined(MHI_NETDEV_STATUS64) + struct qmap_priv *dev = netdev_priv(net); + struct pcpu_sw_netstats *stats64 = this_cpu_ptr(dev->stats64); + + u64_stats_update_begin(&stats64->syncp); + stats64->rx_packets += rx_packets; + stats64->rx_bytes += rx_bytes; + u64_stats_update_end(&stats64->syncp); +#else + net->stats.rx_packets += rx_packets; + net->stats.rx_bytes += rx_bytes; +#endif +} + +static void rmnet_vnd_update_tx_stats(struct net_device *net, + unsigned tx_packets, unsigned tx_bytes) { +#if defined(MHI_NETDEV_STATUS64) + struct qmap_priv *dev = netdev_priv(net); + struct pcpu_sw_netstats *stats64 = this_cpu_ptr(dev->stats64); + + u64_stats_update_begin(&stats64->syncp); + stats64->tx_packets += tx_packets; + stats64->tx_bytes += tx_bytes; + u64_stats_update_end(&stats64->syncp); +#else + net->stats.tx_packets += tx_packets; + net->stats.tx_bytes += tx_bytes; +#endif +} + +#if defined(MHI_NETDEV_STATUS64) +static struct rtnl_link_stats64 *_rmnet_vnd_get_stats64(struct net_device *net, struct rtnl_link_stats64 *stats) +{ + struct qmap_priv *dev = netdev_priv(net); + unsigned int start; + int cpu; + struct rmnet_nss_cb *nss_cb; + + netdev_stats_to_stats64(stats, &net->stats); + + rmnet_nss_dereference(nss_cb); + if (nss_cb) { // rmnet_nss.c:rmnet_nss_tx() will update rx stats + stats->rx_packets = 0; + stats->rx_bytes = 0; + } + + for_each_possible_cpu(cpu) { + struct pcpu_sw_netstats *stats64; + u64 rx_packets, rx_bytes; + u64 tx_packets, tx_bytes; + + stats64 = per_cpu_ptr(dev->stats64, cpu); + + do { + start = u64_stats_fetch_begin_irq(&stats64->syncp); + rx_packets = stats64->rx_packets; + rx_bytes = stats64->rx_bytes; + tx_packets = stats64->tx_packets; + tx_bytes = stats64->tx_bytes; + } while (u64_stats_fetch_retry_irq(&stats64->syncp, start)); + + stats->rx_packets += rx_packets; + stats->rx_bytes += rx_bytes; + stats->tx_packets += tx_packets; + stats->tx_bytes += tx_bytes; + } + + return stats; +} + +#if (LINUX_VERSION_CODE > KERNEL_VERSION( 4,10,0 )) //bc1f44709cf27fb2a5766cadafe7e2ad5e9cb221 +static void rmnet_vnd_get_stats64(struct net_device *net, struct rtnl_link_stats64 *stats) { + _rmnet_vnd_get_stats64(net, stats); +} +#else +static struct rtnl_link_stats64 *rmnet_vnd_get_stats64(struct net_device *net, struct rtnl_link_stats64 *stats) { + return _rmnet_vnd_get_stats64(net, stats); +} +#endif +#endif + +#if defined(QUECTEL_UL_DATA_AGG) +static void rmnet_usb_tx_wake_queue(unsigned long data) { + qmap_wake_queue((sQmiWwanQmap *)data); +} + +static void rmnet_usb_tx_skb_destructor(struct sk_buff *skb) { + struct net_device *net = skb->dev; + struct usbnet * dev = netdev_priv( net ); + struct qmi_wwan_state *info = (void *)&dev->data; + sQmiWwanQmap *pQmapDev = (sQmiWwanQmap *)info->unused; + + if (pQmapDev && pQmapDev->use_rmnet_usb) { + int i; + + for (i = 0; i < pQmapDev->qmap_mode; i++) { + struct net_device *qmap_net = pQmapDev->mpQmapNetDev[i]; + + if (qmap_net && netif_carrier_ok(qmap_net) && netif_queue_stopped(qmap_net)) { + tasklet_schedule(&pQmapDev->txq); + break; + } + } + } +} + +static int rmnet_usb_tx_agg_skip(struct sk_buff *skb, int offset) +{ + u8 *packet_start = skb->data + offset; + int ready2send = 0; + + if (skb->protocol == htons(ETH_P_IP)) { + struct iphdr *ip4h = (struct iphdr *)(packet_start); + + if (ip4h->protocol == IPPROTO_TCP) { + const struct tcphdr *th = (const struct tcphdr *)(packet_start + sizeof(struct iphdr)); + if (th->psh) { + ready2send = 1; + } + } + else if (ip4h->protocol == IPPROTO_ICMP) + ready2send = 1; + + } else if (skb->protocol == htons(ETH_P_IPV6)) { + struct ipv6hdr *ip6h = (struct ipv6hdr *)(packet_start); + + if (ip6h->nexthdr == NEXTHDR_TCP) { + const struct tcphdr *th = (const struct tcphdr *)(packet_start + sizeof(struct ipv6hdr)); + if (th->psh) { + ready2send = 1; + } + } else if (ip6h->nexthdr == NEXTHDR_ICMP) { + ready2send = 1; + } else if (ip6h->nexthdr == NEXTHDR_FRAGMENT) { + struct frag_hdr *frag; + + frag = (struct frag_hdr *)(packet_start + + sizeof(struct ipv6hdr)); + if (frag->nexthdr == IPPROTO_ICMPV6) + ready2send = 1; + } + } + + return ready2send; +} + +static void rmnet_usb_tx_agg_work(struct work_struct *work) +{ + struct qmap_priv *priv = + container_of(work, struct qmap_priv, agg_wq); + struct sk_buff *skb = NULL; + unsigned long flags; + + spin_lock_irqsave(&priv->agg_lock, flags); + if (likely(priv->agg_skb)) { + skb = priv->agg_skb; + priv->agg_skb = NULL; + priv->agg_count = 0; + skb->protocol = htons(ETH_P_MAP); + skb->dev = priv->real_dev; + ktime_get_ts64(&priv->agg_time); + } + spin_unlock_irqrestore(&priv->agg_lock, flags); + + if (skb) { + int err = dev_queue_xmit(skb); + if (err != NET_XMIT_SUCCESS) { + priv->self_dev->stats.tx_errors++; + } + } +} + +static enum hrtimer_restart rmnet_usb_tx_agg_timer_cb(struct hrtimer *timer) +{ + struct qmap_priv *priv = + container_of(timer, struct qmap_priv, agg_hrtimer); + + schedule_work(&priv->agg_wq); + return HRTIMER_NORESTART; +} + +static long agg_time_limit __read_mostly = 1000000L; //reduce this time, can get better TPUT performance, but will increase USB interrupts +module_param(agg_time_limit, long, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(agg_time_limit, "Maximum time packets sit in the agg buf"); + +static long agg_bypass_time __read_mostly = 10000000L; +module_param(agg_bypass_time, long, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(agg_bypass_time, "Skip agg when apart spaced more than this"); + +static int rmnet_usb_tx_agg(struct sk_buff *skb, struct qmap_priv *priv) { + struct qmi_wwan_state *info = (void *)&priv->dev->data; + sQmiWwanQmap *pQmapDev = (sQmiWwanQmap *)info->unused; + struct tx_agg_ctx *ctx = &pQmapDev->tx_ctx; + int ready2send = 0; + int xmit_more = 0; + struct timespec64 diff, now; + struct sk_buff *agg_skb = NULL; + unsigned long flags; + int err; + struct net_device *pNet = priv->self_dev; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(5,1,0) //6b16f9ee89b8d5709f24bc3ac89ae8b5452c0d7c +#if LINUX_VERSION_CODE > KERNEL_VERSION(3,16,0) + xmit_more = skb->xmit_more; +#endif +#else + xmit_more = netdev_xmit_more(); +#endif + + rmnet_vnd_update_tx_stats(pNet, 1, skb->len); + + if (ctx->ul_data_aggregation_max_datagrams == 1) { + skb->protocol = htons(ETH_P_MAP); + skb->dev = priv->real_dev; + if (!skb->destructor) + skb->destructor = rmnet_usb_tx_skb_destructor; + err = dev_queue_xmit(skb); + if (err != NET_XMIT_SUCCESS) + pNet->stats.tx_errors++; + return NET_XMIT_SUCCESS; + } + +new_packet: + spin_lock_irqsave(&priv->agg_lock, flags); + agg_skb = NULL; + ready2send = 0; + ktime_get_ts64(&now); + diff = timespec64_sub(now, priv->agg_time); + + if (priv->agg_skb) { + if ((priv->agg_skb->len + skb->len) < ctx->ul_data_aggregation_max_size) { + memcpy(skb_put(priv->agg_skb, skb->len), skb->data, skb->len); + priv->agg_count++; + + if (diff.tv_sec > 0 || diff.tv_nsec > agg_time_limit) { + ready2send = 1; + } + else if (priv->agg_count == ctx->ul_data_aggregation_max_datagrams) { + ready2send = 1; + } + else if (xmit_more == 0) { + struct rmnet_map_header *map_header = (struct rmnet_map_header *)skb->data; + size_t offset = sizeof(struct rmnet_map_header); + if (map_header->next_hdr) + offset += sizeof(struct rmnet_map_v5_csum_header); + + ready2send = rmnet_usb_tx_agg_skip(skb, offset); + } + + dev_kfree_skb_any(skb); + skb = NULL; + } + else { + ready2send = 1; + } + + if (ready2send) { + agg_skb = priv->agg_skb; + priv->agg_skb = NULL; + priv->agg_count = 0; + } + } + else if (skb) { + if (diff.tv_sec > 0 || diff.tv_nsec > agg_bypass_time) { + ready2send = 1; + } + else if (xmit_more == 0) { + struct rmnet_map_header *map_header = (struct rmnet_map_header *)skb->data; + size_t offset = sizeof(struct rmnet_map_header); + if (map_header->next_hdr) + offset += sizeof(struct rmnet_map_v5_csum_header); + + ready2send = rmnet_usb_tx_agg_skip(skb, offset); + } + + if (ready2send == 0) { + priv->agg_skb = alloc_skb(ctx->ul_data_aggregation_max_size, GFP_ATOMIC); + if (priv->agg_skb) { + skb_reset_network_header(priv->agg_skb); //protocol da1a is buggy, dev wwan0 + memcpy(skb_put(priv->agg_skb, skb->len), skb->data, skb->len); + priv->agg_count++; + dev_kfree_skb_any(skb); + skb = NULL; + } + else { + ready2send = 1; + } + } + + if (ready2send) { + agg_skb = skb; + skb = NULL; + } + } + + if (ready2send) { + priv->agg_time = now; + } + spin_unlock_irqrestore(&priv->agg_lock, flags); + + if (agg_skb) { + agg_skb->protocol = htons(ETH_P_MAP); + agg_skb->dev = priv->real_dev; + if (!agg_skb->destructor) + agg_skb->destructor = rmnet_usb_tx_skb_destructor; + err = dev_queue_xmit(agg_skb); + if (err != NET_XMIT_SUCCESS) { + pNet->stats.tx_errors++; + } + } + + if (skb) { + goto new_packet; + } + + if (priv->agg_skb) { + if (!hrtimer_is_queued(&priv->agg_hrtimer)) + hrtimer_start(&priv->agg_hrtimer, ns_to_ktime(NSEC_PER_MSEC * 2), HRTIMER_MODE_REL); + } + + return NET_XMIT_SUCCESS; +} +#endif + +static netdev_tx_t rmnet_vnd_start_xmit(struct sk_buff *skb, + struct net_device *pNet) +{ + int err; + struct qmap_priv *priv = netdev_priv(pNet); + + if (netif_queue_stopped(priv->real_dev)) { + netif_stop_queue(pNet); + return NETDEV_TX_BUSY; + } + + //printk("%s 1 skb=%p, len=%d, protocol=%x, hdr_len=%d\n", __func__, skb, skb->len, skb->protocol, skb->hdr_len); + if (pNet->type == ARPHRD_ETHER) { + skb_reset_mac_header(skb); + +#ifdef QUECTEL_BRIDGE_MODE + if (priv->bridge_mode && bridge_mode_tx_fixup(pNet, skb, priv->bridge_ipv4, priv->bridge_mac) == NULL) { + dev_kfree_skb_any (skb); + return NETDEV_TX_OK; + } +#endif + + if (skb_pull(skb, ETH_HLEN) == NULL) { + dev_kfree_skb_any (skb); + return NETDEV_TX_OK; + } + } + //printk("%s 2 skb=%p, len=%d, protocol=%x, hdr_len=%d\n", __func__, skb, skb->len, skb->protocol, skb->hdr_len); + + if (priv->qmap_version == 5) { + add_qhdr(skb, priv->mux_id); + } + else if (priv->qmap_version == 9) { + add_qhdr_v5(skb, priv->mux_id); + } + else { + dev_kfree_skb_any (skb); + return NETDEV_TX_OK; + } + //printk("%s skb=%p, len=%d, protocol=%x, hdr_len=%d\n", __func__, skb, skb->len, skb->protocol, skb->hdr_len); + + err = rmnet_usb_tx_agg(skb, priv); + + return err; +} + +static int rmnet_vnd_change_mtu(struct net_device *rmnet_dev, int new_mtu) +{ + if (new_mtu < 0 || new_mtu > 1500) + return -EINVAL; + + rmnet_dev->mtu = new_mtu; + return 0; +} + +/* drivers may override default ethtool_ops in their bind() routine */ +static const struct ethtool_ops rmnet_vnd_ethtool_ops = { + .get_link = ethtool_op_get_link, +}; + +static const struct net_device_ops rmnet_vnd_ops = { + .ndo_open = qmap_open, + .ndo_stop = qmap_stop, + .ndo_start_xmit = rmnet_vnd_start_xmit, + .ndo_change_mtu = rmnet_vnd_change_mtu, +#if defined(MHI_NETDEV_STATUS64) + .ndo_get_stats64 = rmnet_vnd_get_stats64, +#endif +}; + +static void rmnet_usb_ether_setup(struct net_device *rmnet_dev) +{ + ether_setup(rmnet_dev); + + rmnet_dev->flags |= IFF_NOARP; + rmnet_dev->flags &= ~(IFF_BROADCAST | IFF_MULTICAST); + + rmnet_dev->ethtool_ops = &rmnet_vnd_ethtool_ops; + rmnet_dev->netdev_ops = &rmnet_vnd_ops; +} + +static void rmnet_usb_rawip_setup(struct net_device *rmnet_dev) +{ + rmnet_dev->needed_headroom = 16; + + /* Raw IP mode */ + rmnet_dev->header_ops = NULL; /* No header */ + rmnet_dev->type = ARPHRD_RAWIP; + rmnet_dev->hard_header_len = 0; + rmnet_dev->flags |= IFF_NOARP; + rmnet_dev->flags &= ~(IFF_BROADCAST | IFF_MULTICAST); + + rmnet_dev->ethtool_ops = &rmnet_vnd_ethtool_ops; + rmnet_dev->netdev_ops = &rmnet_vnd_ops; +} + +static rx_handler_result_t qca_nss_rx_handler(struct sk_buff **pskb) +{ + struct sk_buff *skb = *pskb; + struct rmnet_nss_cb *nss_cb; + + if (!skb) + return RX_HANDLER_CONSUMED; + + //printk("%s skb=%p, len=%d, protocol=%x, hdr_len=%d\n", __func__, skb, skb->len, skb->protocol, skb->hdr_len); + + if (skb->pkt_type == PACKET_LOOPBACK) + return RX_HANDLER_PASS; + + /* Check this so that we dont loop around netif_receive_skb */ + if (skb->cb[0] == 1) { + skb->cb[0] = 0; + + return RX_HANDLER_PASS; + } + + rmnet_nss_dereference(nss_cb); + if (nss_cb) { + nss_cb->nss_tx(skb); + return RX_HANDLER_CONSUMED; + } + + return RX_HANDLER_PASS; +} + +static int qmap_register_device(sQmiWwanQmap * pDev, u8 offset_id) +{ + struct net_device *real_dev = pDev->mpNetDev->net; + struct net_device *qmap_net; + struct qmap_priv *priv; + int err; + struct rmnet_nss_cb *nss_cb; + char name[IFNAMSIZ]; + int need_rawip = 0; + + rmnet_nss_dereference(nss_cb); +#ifdef QUECTEL_BRIDGE_MODE + if (pDev->bridge_mode & BIT(offset_id)) + nss_cb = NULL; +#endif + + need_rawip = !!nss_cb; + sprintf(name, "%s_%d", real_dev->name, offset_id + 1); +#ifdef NET_NAME_UNKNOWN + qmap_net = alloc_netdev(sizeof(struct qmap_priv), name, + NET_NAME_UNKNOWN, + need_rawip ? rmnet_usb_rawip_setup : rmnet_usb_ether_setup); +#else + qmap_net = alloc_netdev(sizeof(struct qmap_priv), name, + need_rawip ? rmnet_usb_rawip_setup : rmnet_usb_ether_setup); +#endif + if (!qmap_net) + return -ENOBUFS; + + SET_NETDEV_DEV(qmap_net, &real_dev->dev); + priv = netdev_priv(qmap_net); + priv->offset_id = offset_id; + priv->real_dev = real_dev; + priv->self_dev = qmap_net; + priv->dev = pDev->mpNetDev; + priv->qmap_version = pDev->qmap_version; + priv->mux_id = QUECTEL_QMAP_MUX_ID + offset_id; + memcpy (qmap_net->dev_addr, real_dev->dev_addr, ETH_ALEN); + +#ifdef QUECTEL_BRIDGE_MODE + priv->bridge_mode = !!(pDev->bridge_mode & BIT(offset_id)); + qmap_net->sysfs_groups[0] = &qmi_qmap_sysfs_attr_group; +#endif + + priv->agg_skb = NULL; + priv->agg_count = 0; + hrtimer_init(&priv->agg_hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + priv->agg_hrtimer.function = rmnet_usb_tx_agg_timer_cb; + INIT_WORK(&priv->agg_wq, rmnet_usb_tx_agg_work); + ktime_get_ts64(&priv->agg_time); + spin_lock_init(&priv->agg_lock); + +#if defined(MHI_NETDEV_STATUS64) + priv->stats64 = netdev_alloc_pcpu_stats(struct pcpu_sw_netstats); + if (!priv->stats64) { + err = -ENOBUFS; + goto out_free_newdev; + } +#endif + + err = register_netdev(qmap_net); + if (err) + dev_info(&real_dev->dev, "%s(%s)=%d\n", __func__, qmap_net->name, err); + if (err < 0) + goto out_free_newdev; + netif_device_attach (qmap_net); + netif_carrier_off(qmap_net); + + if (nss_cb) { + int rc = nss_cb->nss_create(qmap_net); + if (rc) { + /* Log, but don't fail the device creation */ + netdev_err(qmap_net, "Device will not use NSS path: %d\n", rc); + } else { + netdev_info(qmap_net, "NSS context created\n"); + rtnl_lock(); + netdev_rx_handler_register(qmap_net, qca_nss_rx_handler, NULL); + rtnl_unlock(); + } + } + + strcpy(pDev->rmnet_info.ifname[offset_id], qmap_net->name); + pDev->rmnet_info.mux_id[offset_id] = priv->mux_id; + + pDev->mpQmapNetDev[offset_id] = qmap_net; + + dev_info(&real_dev->dev, "%s %s\n", __func__, qmap_net->name); + + return 0; + +out_free_newdev: + free_netdev(qmap_net); + return err; +} + +static void qmap_unregister_device(sQmiWwanQmap * pDev, u8 offset_id) { + struct net_device *qmap_net = pDev->mpQmapNetDev[offset_id]; + + if (qmap_net != NULL && qmap_net != pDev->mpNetDev->net) { + struct rmnet_nss_cb *nss_cb; + struct qmap_priv *priv = netdev_priv(qmap_net); + rx_handler_func_t *rx_handler; + unsigned long flags; + + pr_info("qmap_unregister_device(%s)\n", qmap_net->name); + pDev->mpQmapNetDev[offset_id] = NULL; + netif_carrier_off( qmap_net ); + netif_stop_queue( qmap_net ); + + hrtimer_cancel(&priv->agg_hrtimer); + cancel_work_sync(&priv->agg_wq); + spin_lock_irqsave(&priv->agg_lock, flags); + if (priv->agg_skb) { + kfree_skb(priv->agg_skb); + } + spin_unlock_irqrestore(&priv->agg_lock, flags); + + rmnet_nss_dereference(nss_cb); + rcu_read_lock(); + rx_handler = rcu_dereference(qmap_net->rx_handler); + rcu_read_unlock(); + if (nss_cb && rx_handler == qca_nss_rx_handler) { + rtnl_lock(); + netdev_rx_handler_unregister(qmap_net); + rtnl_unlock(); + nss_cb->nss_free(qmap_net); + } + +#if defined(MHI_NETDEV_STATUS64) + free_percpu(priv->stats64); +#endif + unregister_netdev (qmap_net); + free_netdev(qmap_net); + } +} + +typedef struct { + unsigned int size; + unsigned int rx_urb_size; + unsigned int ep_type; + unsigned int iface_id; + unsigned int MuxId; + unsigned int ul_data_aggregation_max_datagrams; //0x17 + unsigned int ul_data_aggregation_max_size ;//0x18 + unsigned int dl_minimum_padding; //0x1A +} QMAP_SETTING; + +int qma_setting_store(struct device *dev, QMAP_SETTING *qmap_settings, size_t size) { + struct net_device *netdev = to_net_dev(dev); + struct usbnet * usbnetdev = netdev_priv( netdev ); + struct qmi_wwan_state *info = (void *)&usbnetdev->data; + sQmiWwanQmap *pQmapDev = (sQmiWwanQmap *)info->unused; + + if (qmap_settings->size != size) { + dev_err(dev, "ERROR: qmap_settings.size donot match!\n"); + return -EOPNOTSUPP; + } + +#ifdef QUECTEL_UL_DATA_AGG + netif_tx_lock_bh(netdev); + if (pQmapDev->tx_ctx.ul_data_aggregation_max_datagrams == 1 && qmap_settings->ul_data_aggregation_max_datagrams > 1) { + pQmapDev->tx_ctx.ul_data_aggregation_max_datagrams = qmap_settings->ul_data_aggregation_max_datagrams; + pQmapDev->tx_ctx.ul_data_aggregation_max_size = qmap_settings->ul_data_aggregation_max_size; + pQmapDev->tx_ctx.dl_minimum_padding = qmap_settings->dl_minimum_padding; + dev_info(dev, "ul_data_aggregation_max_datagrams=%d, ul_data_aggregation_max_size=%d, dl_minimum_padding=%d\n", + pQmapDev->tx_ctx.ul_data_aggregation_max_datagrams, + pQmapDev->tx_ctx.ul_data_aggregation_max_size, + pQmapDev->tx_ctx.dl_minimum_padding); + } + netif_tx_unlock_bh(netdev); + return 0; +#endif + + return -EOPNOTSUPP; +} + +static int qmap_ndo_do_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) { + struct usbnet * usbnetdev = netdev_priv( dev ); + struct qmi_wwan_state *info = (void *)&usbnetdev->data; + sQmiWwanQmap *pQmapDev = (sQmiWwanQmap *)info->unused; + int rc = -EOPNOTSUPP; + uint link_state = 0; + QMAP_SETTING qmap_settings = {0}; + + switch (cmd) { + case 0x89F1: //SIOCDEVPRIVATE + rc = copy_from_user(&link_state, ifr->ifr_ifru.ifru_data, sizeof(link_state)); + if (!rc) { + char buf[32]; + snprintf(buf, sizeof(buf), "%u", link_state); + link_state_store(&dev->dev, NULL, buf, strlen(buf)); + } + break; + + case 0x89F2: //SIOCDEVPRIVATE + rc = copy_from_user(&qmap_settings, ifr->ifr_ifru.ifru_data, sizeof(qmap_settings)); + if (!rc) { + rc = qma_setting_store(&dev->dev, &qmap_settings, sizeof(qmap_settings)); + } + break; + + case 0x89F3: //SIOCDEVPRIVATE + if (pQmapDev->use_rmnet_usb) { + uint i; + + for (i = 0; i < pQmapDev->qmap_mode; i++) { + struct net_device *qmap_net = pQmapDev->mpQmapNetDev[i]; + + if (!qmap_net) + break; + + strcpy(pQmapDev->rmnet_info.ifname[i], qmap_net->name); + } + rc = copy_to_user(ifr->ifr_ifru.ifru_data, &pQmapDev->rmnet_info, sizeof(pQmapDev->rmnet_info)); + } + break; + + default: + break; + } + + return rc; +} + +#ifdef QUECTEL_BRIDGE_MODE +static int is_qmap_netdev(const struct net_device *netdev) { + return netdev->netdev_ops == &rmnet_vnd_ops; +} +#endif +#endif + +static struct sk_buff *qmi_wwan_tx_fixup(struct usbnet *dev, struct sk_buff *skb, gfp_t flags) { + //MDM9x07,MDM9628,MDM9x40,SDX20,SDX24 only work on RAW IP mode + if ((dev->driver_info->flags & FLAG_NOARP) == 0) + return skb; + + // Skip Ethernet header from message + if (dev->net->hard_header_len == 0) + return skb; + else + skb_reset_mac_header(skb); + +#ifdef QUECTEL_BRIDGE_MODE +{ + struct qmi_wwan_state *info = (void *)&dev->data; + sQmiWwanQmap *pQmapDev = (sQmiWwanQmap *)info->unused; + + if (pQmapDev->bridge_mode && bridge_mode_tx_fixup(dev->net, skb, pQmapDev->bridge_ipv4, pQmapDev->bridge_mac) == NULL) { + dev_kfree_skb_any (skb); + return NULL; + } +} +#endif + + if (skb_pull(skb, ETH_HLEN)) { + return skb; + } else { + dev_err(&dev->intf->dev, "Packet Dropped "); + } + + // Filter the packet out, release it + dev_kfree_skb_any(skb); + return NULL; +} +#endif + +/* Make up an ethernet header if the packet doesn't have one. + * + * A firmware bug common among several devices cause them to send raw + * IP packets under some circumstances. There is no way for the + * driver/host to know when this will happen. And even when the bug + * hits, some packets will still arrive with an intact header. + * + * The supported devices are only capably of sending IPv4, IPv6 and + * ARP packets on a point-to-point link. Any packet with an ethernet + * header will have either our address or a broadcast/multicast + * address as destination. ARP packets will always have a header. + * + * This means that this function will reliably add the appropriate + * header iff necessary, provided our hardware address does not start + * with 4 or 6. + * + * Another common firmware bug results in all packets being addressed + * to 00:a0:c6:00:00:00 despite the host address being different. + * This function will also fixup such packets. + */ +static int qmi_wwan_rx_fixup(struct usbnet *dev, struct sk_buff *skb) +{ + __be16 proto; + + /* 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) + return 0; + skb_push(skb, ETH_HLEN); + skb_reset_mac_header(skb); + eth_hdr(skb)->h_proto = proto; + memset(eth_hdr(skb)->h_source, 0, ETH_ALEN); +#if 1 //Added by Quectel + //some kernel will drop ethernet packet which's souce mac is all zero + memcpy(eth_hdr(skb)->h_source, default_modem_addr, ETH_ALEN); +#endif + +fix_dest: +#ifdef QUECTEL_BRIDGE_MODE +{ + struct qmi_wwan_state *info = (void *)&dev->data; + sQmiWwanQmap *pQmapDev = (sQmiWwanQmap *)info->unused; + bridge_mode_rx_fixup(pQmapDev, dev->net, skb); +} +#else + memcpy(eth_hdr(skb)->h_dest, dev->net->dev_addr, ETH_ALEN); +#endif + + return 1; +} + +#if defined(QUECTEL_WWAN_QMAP) +static struct sk_buff *qmap_qmi_wwan_tx_fixup(struct usbnet *dev, struct sk_buff *skb, gfp_t flags) { + struct qmi_wwan_state *info = (void *)&dev->data; + sQmiWwanQmap *pQmapDev = (sQmiWwanQmap *)info->unused; + + if (unlikely(pQmapDev == NULL)) { + goto drop_skb; + } else if (unlikely(pQmapDev->qmap_mode && !pQmapDev->link_state)) { + dev_dbg(&dev->net->dev, "link_state 0x%x, drop skb, len = %u\n", pQmapDev->link_state, skb->len); + goto drop_skb; + } else if (pQmapDev->qmap_mode == 0) { + skb = qmi_wwan_tx_fixup(dev, skb, flags); + } + else if (pQmapDev->qmap_mode > 1) { + WARN_ON(1); //never reach here. + } + else { + if (likely(skb)) { + skb = qmi_wwan_tx_fixup(dev, skb, flags); + + if (skb) { + add_qhdr(skb, QUECTEL_QMAP_MUX_ID); + } + else { + return NULL; + } + } + } + + return skb; +drop_skb: + dev_kfree_skb_any (skb); + return NULL; +} + +static void qmap_packet_decode(sQmiWwanQmap *pQmapDev, + struct sk_buff *skb_in, struct sk_buff_head *skb_chain) +{ + struct device *dev = &pQmapDev->mpNetDev->net->dev; + struct sk_buff *qmap_skb; + uint dl_minimum_padding = 0; + + if (pQmapDev->qmap_version == 9) + dl_minimum_padding = pQmapDev->tx_ctx.dl_minimum_padding; + + __skb_queue_head_init(skb_chain); + + while (skb_in->len > sizeof(struct qmap_hdr)) { + struct rmnet_map_header *map_header = (struct rmnet_map_header *)skb_in->data; + struct rmnet_map_v5_csum_header *ul_header = NULL; + size_t hdr_size = sizeof(struct rmnet_map_header); + struct net_device *qmap_net; + int pkt_len = ntohs(map_header->pkt_len); + int skb_len; + __be16 protocol; + int mux_id; + + if (map_header->next_hdr) { + ul_header = (struct rmnet_map_v5_csum_header *)(map_header + 1); + hdr_size += sizeof(struct rmnet_map_v5_csum_header); + } + + skb_len = pkt_len - (map_header->pad_len&0x3F); + skb_len -= dl_minimum_padding; + if (skb_len > 1500) { + dev_info(dev, "drop skb_len=%x larger than 1500\n", skb_len); + goto error_pkt; + } + + if (skb_in->len < (pkt_len + hdr_size)) { + dev_info(dev, "drop qmap unknow pkt, len=%d, pkt_len=%d\n", skb_in->len, pkt_len); + goto error_pkt; + } + + if (map_header->cd_bit) { + dev_info(dev, "skip qmap command packet\n"); + goto skip_pkt; + } + + switch (skb_in->data[hdr_size] & 0xf0) { + case 0x40: + protocol = htons(ETH_P_IP); + break; + case 0x60: + protocol = htons(ETH_P_IPV6); + break; + default: + dev_info(dev, "unknow skb->protocol %02x\n", skb_in->data[hdr_size]); + goto error_pkt; + } + + mux_id = map_header->mux_id - QUECTEL_QMAP_MUX_ID; + if (mux_id >= pQmapDev->qmap_mode) { + dev_info(dev, "drop qmap unknow mux_id %x\n", map_header->mux_id); + goto error_pkt; + } + + qmap_net = pQmapDev->mpQmapNetDev[mux_id]; + + if (qmap_net == NULL) { + dev_info(dev, "drop qmap unknow mux_id %x\n", map_header->mux_id); + goto skip_pkt; + } + + qmap_skb = netdev_alloc_skb(qmap_net, skb_len); + if (qmap_skb) { + skb_put(qmap_skb, skb_len); + memcpy(qmap_skb->data, skb_in->data + hdr_size, skb_len); + } + + if (qmap_skb == NULL) { + dev_info(dev, "fail to alloc skb, pkt_len = %d\n", skb_len); + goto error_pkt; + } + + skb_reset_transport_header(qmap_skb); + skb_reset_network_header(qmap_skb); + qmap_skb->pkt_type = PACKET_HOST; + skb_set_mac_header(qmap_skb, 0); + qmap_skb->protocol = protocol; + + if (ul_header && ul_header->header_type == RMNET_MAP_HEADER_TYPE_CSUM_OFFLOAD + && ul_header->csum_valid_required) { +#if 0 //TODO + qmap_skb->ip_summed = CHECKSUM_UNNECESSARY; +#endif + } + + if (qmap_skb->dev->type == ARPHRD_ETHER) { + skb_push(qmap_skb, ETH_HLEN); + skb_reset_mac_header(qmap_skb); + memcpy(eth_hdr(qmap_skb)->h_source, default_modem_addr, ETH_ALEN); + memcpy(eth_hdr(qmap_skb)->h_dest, qmap_net->dev_addr, ETH_ALEN); + eth_hdr(qmap_skb)->h_proto = protocol; +#ifdef QUECTEL_BRIDGE_MODE + bridge_mode_rx_fixup(pQmapDev, qmap_net, qmap_skb); +#endif + } + + __skb_queue_tail(skb_chain, qmap_skb); + +skip_pkt: + skb_pull(skb_in, pkt_len + hdr_size); + } + +error_pkt: + return; +} + +static int qmap_qmi_wwan_rx_fixup(struct usbnet *dev, struct sk_buff *skb_in) +{ + struct qmi_wwan_state *info = (void *)&dev->data; + sQmiWwanQmap *pQmapDev = (sQmiWwanQmap *)info->unused; + struct sk_buff *qmap_skb; + struct sk_buff_head skb_chain; + + if (pQmapDev->qmap_mode == 0) + return qmi_wwan_rx_fixup(dev, skb_in); + + qmap_packet_decode(pQmapDev, skb_in, &skb_chain); + + while ((qmap_skb = skb_dequeue (&skb_chain))) { + if (qmap_skb->dev != dev->net) { + WARN_ON(1); //never reach here. + } + else { + qmap_skb->protocol = 0; + usbnet_skb_return(dev, qmap_skb); + } + } + + return 0; +} +#endif + +/* very simplistic detection of IPv4 or IPv6 headers */ +static bool possibly_iphdr(const char *data) +{ + return (data[0] & 0xd0) == 0x40; +} + +/* disallow addresses which may be confused with IP headers */ +static int qmi_wwan_mac_addr(struct net_device *dev, void *p) +{ + int ret; + struct sockaddr *addr = p; + + ret = eth_prepare_mac_addr_change(dev, p); + if (ret < 0) + return ret; + if (possibly_iphdr(addr->sa_data)) + return -EADDRNOTAVAIL; + eth_commit_mac_addr_change(dev, p); + return 0; +} + +#if (LINUX_VERSION_CODE > KERNEL_VERSION( 4,10,0 )) //bc1f44709cf27fb2a5766cadafe7e2ad5e9cb221 +static void (*_usbnet_get_stats64)(struct net_device *net, struct rtnl_link_stats64 *stats); + +static void qmi_wwan_get_stats64(struct net_device *net, struct rtnl_link_stats64 *stats) { + if (_usbnet_get_stats64) ////c8b5d129ee293bcf972e7279ac996bb8a138505c + return _usbnet_get_stats64(net, stats); + + netdev_stats_to_stats64(stats, &net->stats); +} +#else +static struct rtnl_link_stats64 * (*_usbnet_get_stats64)(struct net_device *net, struct rtnl_link_stats64 *stats); + +static struct rtnl_link_stats64 * qmi_wwan_get_stats64(struct net_device *net, struct rtnl_link_stats64 *stats) { + if (_usbnet_get_stats64) + return _usbnet_get_stats64(net, stats); + + netdev_stats_to_stats64(stats, &net->stats); + return stats; +} +#endif + +static int qmi_wwan_open (struct net_device *net) { + struct usbnet * usbnetdev = netdev_priv( net ); + struct qmi_wwan_state *info = (void *)&usbnetdev->data; + sQmiWwanQmap *pQmapDev = (sQmiWwanQmap *)info->unused; + int retval; + + retval = usbnet_open(net); + + if (!retval) { + if (pQmapDev && pQmapDev->qmap_mode == 1) { + if (pQmapDev->link_state) + netif_carrier_on(net); + } + } + + return retval; +} + +static netdev_tx_t qmi_wwan_start_xmit (struct sk_buff *skb, + struct net_device *net) +{ + struct usbnet * usbnetdev = netdev_priv( net ); + struct qmi_wwan_state *info = (void *)&usbnetdev->data; + sQmiWwanQmap *pQmapDev = (sQmiWwanQmap *)info->unused; + int retval; + + retval = usbnet_start_xmit(skb, net); + + if (netif_queue_stopped(net) && pQmapDev && pQmapDev->use_rmnet_usb) { + int i; + + for (i = 0; i < pQmapDev->qmap_mode; i++) { + struct net_device *qmap_net = pQmapDev->mpQmapNetDev[i]; + if (qmap_net) { + netif_stop_queue(qmap_net); + } + } + } + + return retval; +} + +static const struct net_device_ops qmi_wwan_netdev_ops = { + .ndo_open = qmi_wwan_open, + .ndo_stop = usbnet_stop, + .ndo_start_xmit = qmi_wwan_start_xmit, + .ndo_tx_timeout = usbnet_tx_timeout, + .ndo_change_mtu = usbnet_change_mtu, + .ndo_get_stats64 = qmi_wwan_get_stats64, + .ndo_set_mac_address = qmi_wwan_mac_addr, + .ndo_validate_addr = eth_validate_addr, +#if defined(QUECTEL_WWAN_QMAP)// && defined(CONFIG_ANDROID) + .ndo_do_ioctl = qmap_ndo_do_ioctl, +#endif +}; + +static void ql_net_get_drvinfo(struct net_device *net, struct ethtool_drvinfo *info) +{ + /* Inherit standard device info */ + usbnet_get_drvinfo(net, info); + strlcpy(info->driver, driver_name, sizeof(info->driver)); + strlcpy(info->version, VERSION_NUMBER, sizeof(info->version)); +} + +static struct ethtool_ops ql_net_ethtool_ops; + +/* using a counter to merge subdriver requests with our own into a + * combined state + */ +static int qmi_wwan_manage_power(struct usbnet *dev, int on) +{ + struct qmi_wwan_state *info = (void *)&dev->data; + int rv; + + dev_dbg(&dev->intf->dev, "%s() pmcount=%d, on=%d\n", __func__, + atomic_read(&info->pmcount), on); + + if ((on && atomic_add_return(1, &info->pmcount) == 1) || + (!on && atomic_dec_and_test(&info->pmcount))) { + /* need autopm_get/put here to ensure the usbcore sees + * the new value + */ + rv = usb_autopm_get_interface(dev->intf); + dev->intf->needs_remote_wakeup = on; + if (!rv) + usb_autopm_put_interface(dev->intf); + } + return 0; +} + +static int qmi_wwan_cdc_wdm_manage_power(struct usb_interface *intf, int on) +{ + struct usbnet *dev = usb_get_intfdata(intf); + + /* can be called while disconnecting */ + if (!dev) + return 0; + return qmi_wwan_manage_power(dev, on); +} + +/* collect all three endpoints and register subdriver */ +static int qmi_wwan_register_subdriver(struct usbnet *dev) +{ + int rv; + struct usb_driver *subdriver = NULL; + struct qmi_wwan_state *info = (void *)&dev->data; + + /* collect bulk endpoints */ + rv = usbnet_get_endpoints(dev, info->data); + if (rv < 0) + goto err; + + /* update status endpoint if separate control interface */ + if (info->control != info->data) + dev->status = &info->control->cur_altsetting->endpoint[0]; + + /* require interrupt endpoint for subdriver */ + if (!dev->status) { + rv = -EINVAL; + goto err; + } + + /* for subdriver power management */ + atomic_set(&info->pmcount, 0); + + /* register subdriver */ + subdriver = usb_cdc_wdm_register(info->control, &dev->status->desc, + 4096, &qmi_wwan_cdc_wdm_manage_power); + if (IS_ERR(subdriver)) { + dev_err(&info->control->dev, "subdriver registration failed\n"); + rv = PTR_ERR(subdriver); + goto err; + } + + /* prevent usbnet from using status endpoint */ + dev->status = NULL; + + /* save subdriver struct for suspend/resume wrappers */ + info->subdriver = subdriver; + +err: + return rv; +} + +static int qmi_wwan_bind(struct usbnet *dev, struct usb_interface *intf) +{ + int status = -1; + struct usb_driver *driver = driver_of(intf); + struct qmi_wwan_state *info = (void *)&dev->data; + + BUILD_BUG_ON((sizeof(((struct usbnet *)0)->data) < + sizeof(struct qmi_wwan_state))); + + /* set up initial state */ + info->control = intf; + info->data = intf; + + status = qmi_wwan_register_subdriver(dev); + if (status < 0 && info->control != info->data) { + usb_set_intfdata(info->data, NULL); + usb_driver_release_interface(driver, info->data); + } + + /* Never use the same address on both ends of the link, even + * if the buggy firmware told us to. + */ + if (ether_addr_equal(dev->net->dev_addr, default_modem_addr)) + eth_hw_addr_random(dev->net); + + /* make MAC addr easily distinguishable from an IP header */ + if (possibly_iphdr(dev->net->dev_addr)) { + dev->net->dev_addr[0] |= 0x02; /* set local assignment bit */ + dev->net->dev_addr[0] &= 0xbf; /* clear "IP" bit */ + } + if (!_usbnet_get_stats64) + _usbnet_get_stats64 = dev->net->netdev_ops->ndo_get_stats64; + dev->net->netdev_ops = &qmi_wwan_netdev_ops; + + ql_net_ethtool_ops = *dev->net->ethtool_ops; + ql_net_ethtool_ops.get_drvinfo = ql_net_get_drvinfo; + dev->net->ethtool_ops = &ql_net_ethtool_ops; + +#if 1 //Added by Quectel + if (dev->driver_info->flags & FLAG_NOARP) { + int ret; + char buf[32] = "Module"; + + ret = usb_string(dev->udev, dev->udev->descriptor.iProduct, buf, sizeof(buf)); + if (ret > 0) { + buf[ret] = '\0'; + } + + dev_info(&intf->dev, "Quectel %s work on RawIP mode\n", buf); + dev->net->flags |= IFF_NOARP; + dev->net->flags &= ~(IFF_BROADCAST | IFF_MULTICAST); + + usb_control_msg( + interface_to_usbdev(intf), + usb_sndctrlpipe(interface_to_usbdev(intf), 0), + 0x22, //USB_CDC_REQ_SET_CONTROL_LINE_STATE + 0x21, //USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE + 1, //active CDC DTR + intf->cur_altsetting->desc.bInterfaceNumber, + NULL, 0, 100); + } + + //to advoid module report mtu 1460, but rx 1500 bytes IP packets, and cause the customer's system crash + //next setting can make usbnet.c:usbnet_change_mtu() do not modify rx_urb_size according to hard mtu + dev->rx_urb_size = ETH_DATA_LEN + ETH_HLEN + 6; + +#if defined(QUECTEL_WWAN_QMAP) + if (qmap_mode > QUECTEL_WWAN_QMAP) + qmap_mode = QUECTEL_WWAN_QMAP; + + if (!status) + { + sQmiWwanQmap *pQmapDev = (sQmiWwanQmap *)kzalloc(sizeof(sQmiWwanQmap), GFP_KERNEL); + + if (pQmapDev == NULL) + return -ENODEV; + +#ifdef QUECTEL_BRIDGE_MODE + pQmapDev->bridge_mode = bridge_mode; +#endif + pQmapDev->mpNetDev = dev; + pQmapDev->link_state = 1; + //on OpenWrt, if set rmnet_usb0.1 as WAN, '/sbin/netifd' will auto create VLAN for rmnet_usb0 + dev->net->features |= (NETIF_F_VLAN_CHALLENGED); + + if (dev->driver_info->flags & FLAG_NOARP) + { + int qmap_version = (dev->driver_info->data>>8)&0xFF; + int qmap_size = (dev->driver_info->data)&0xFF; + int idProduct = le16_to_cpu(dev->udev->descriptor.idProduct); + int lte_a = (idProduct == 0x0306 || idProduct == 0x0512 || idProduct == 0x0620 || idProduct == 0x0800 || idProduct == 0x90db|| idProduct == 0x90d5 || idProduct == 0x4d22 ); + + if (qmap_size > 4096 || dev->udev->speed >= USB_SPEED_SUPER) { //if meet this requirements, must be LTE-A or 5G + lte_a = 1; + } + + pQmapDev->qmap_mode = qmap_mode; + if (lte_a && pQmapDev->qmap_mode == 0) { + pQmapDev->qmap_mode = 1; //force use QMAP + if(qmap_mode == 0) + qmap_mode = 1; //old quectel-CM only check sys/module/wwan0/parameters/qmap_mode + } + + if (pQmapDev->qmap_mode) { + pQmapDev->qmap_version = qmap_version; + pQmapDev->qmap_size = qmap_size*1024; + dev->rx_urb_size = pQmapDev->qmap_size; + //for these modules, if send packet before qmi_start_network, or cause host PC crash, or cause modules crash + pQmapDev->link_state = !lte_a; + + if (pQmapDev->qmap_mode > 1) + pQmapDev->use_rmnet_usb = 1; + else if (idProduct == 0x0800 ){ + pQmapDev->use_rmnet_usb = 1; //benefit for ul data agg + pQmapDev->rmnet_info.iface_id = 4; + } + else if (idProduct == 0x4d22 ){ + pQmapDev->use_rmnet_usb = 1; //benefit for ul data agg + pQmapDev->rmnet_info.iface_id = 5; + } + else if ( idProduct == 0x90db ){ + pQmapDev->use_rmnet_usb = 1; //benefit for ul data agg + pQmapDev->rmnet_info.iface_id = 5; + } + pQmapDev->rmnet_info.size = sizeof(RMNET_INFO); + pQmapDev->rmnet_info.rx_urb_size = pQmapDev->qmap_size; + pQmapDev->rmnet_info.ep_type = 2; //DATA_EP_TYPE_HSUSB + pQmapDev->rmnet_info.qmap_mode = pQmapDev->qmap_mode; + pQmapDev->rmnet_info.qmap_version = pQmapDev->qmap_version; + pQmapDev->rmnet_info.dl_minimum_padding = 0; + +#if defined(QUECTEL_UL_DATA_AGG) + pQmapDev->tx_ctx.ul_data_aggregation_max_datagrams = 1; + pQmapDev->tx_ctx.ul_data_aggregation_max_size = 1500; +#endif + + if (pQmapDev->use_rmnet_usb) { + pQmapDev->driver_info = rmnet_usb_info; + pQmapDev->driver_info.data = dev->driver_info->data; + dev->driver_info = &pQmapDev->driver_info; + } + } + } + + info->unused = (unsigned long)pQmapDev; + dev->net->sysfs_groups[0] = &qmi_wwan_sysfs_attr_group; + + dev_info(&intf->dev, "rx_urb_size = %zd\n", dev->rx_urb_size); + } +#endif +#endif + + return status; +} + +static void qmi_wwan_unbind(struct usbnet *dev, struct usb_interface *intf) +{ + struct qmi_wwan_state *info = (void *)&dev->data; + struct usb_driver *driver = driver_of(intf); + struct usb_interface *other; + + if (dev->udev && dev->udev->state == USB_STATE_CONFIGURED) { + usb_control_msg( + interface_to_usbdev(intf), + usb_sndctrlpipe(interface_to_usbdev(intf), 0), + 0x22, //USB_CDC_REQ_SET_CONTROL_LINE_STATE + 0x21, //USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE + 0, //deactive CDC DTR + intf->cur_altsetting->desc.bInterfaceNumber, + NULL, 0, 100); + } + + if (info->subdriver && info->subdriver->disconnect) + info->subdriver->disconnect(info->control); + + /* allow user to unbind using either control or data */ + if (intf == info->control) + other = info->data; + else + other = info->control; + + /* only if not shared */ + if (other && intf != other) { + usb_set_intfdata(other, NULL); + usb_driver_release_interface(driver, other); + } + + info->subdriver = NULL; + info->data = NULL; + info->control = NULL; +} + +/* suspend/resume wrappers calling both usbnet and the cdc-wdm + * subdriver if present. + * + * NOTE: cdc-wdm also supports pre/post_reset, but we cannot provide + * wrappers for those without adding usbnet reset support first. + */ +static int qmi_wwan_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct usbnet *dev = usb_get_intfdata(intf); + struct qmi_wwan_state *info = (void *)&dev->data; + int ret; + + /* Both usbnet_suspend() and subdriver->suspend() MUST return 0 + * in system sleep context, otherwise, the resume callback has + * to recover device from previous suspend failure. + */ + ret = usbnet_suspend(intf, message); + if (ret < 0) + goto err; + + if (intf == info->control && info->subdriver && + info->subdriver->suspend) + ret = info->subdriver->suspend(intf, message); + if (ret < 0) + usbnet_resume(intf); +err: + return ret; +} + +static int qmi_wwan_resume(struct usb_interface *intf) +{ + struct usbnet *dev = usb_get_intfdata(intf); + struct qmi_wwan_state *info = (void *)&dev->data; + int ret = 0; + bool callsub = (intf == info->control && info->subdriver && + info->subdriver->resume); + + if (callsub) + ret = info->subdriver->resume(intf); + if (ret < 0) + goto err; + ret = usbnet_resume(intf); + if (ret < 0 && callsub) + info->subdriver->suspend(intf, PMSG_SUSPEND); + +#if defined(QUECTEL_WWAN_QMAP) + if (!netif_queue_stopped(dev->net)) { + qmap_wake_queue((sQmiWwanQmap *)info->unused); + } +#endif + +err: + return ret; +} + +static int qmi_wwan_reset_resume(struct usb_interface *intf) +{ + dev_info(&intf->dev, "device do not support reset_resume\n"); + intf->needs_binding = 1; + return -EOPNOTSUPP; +} + +static struct sk_buff *rmnet_usb_tx_fixup(struct usbnet *dev, struct sk_buff *skb, gfp_t flags) +{ + //printk("%s skb=%p, len=%d, protocol=%x, hdr_len=%d\n", __func__, skb, skb->len, skb->protocol, skb->hdr_len); + if (skb->protocol != htons(ETH_P_MAP)) { + dev_kfree_skb_any(skb); + return NULL; + } + + return skb; +} + +static int rmnet_usb_rx_fixup(struct usbnet *dev, struct sk_buff *skb) +{ + struct net_device *net = dev->net; + unsigned headroom = skb_headroom(skb); + +#if (LINUX_VERSION_CODE < KERNEL_VERSION( 3,3,1 )) //7bdd402706cf26bfef9050dfee3f229b7f33ee4f +//some customers port to v3.2 + if (net->type == ARPHRD_ETHER && headroom < ETH_HLEN) { + unsigned tailroom = skb_tailroom(skb); + + if ((tailroom + headroom) >= ETH_HLEN) { + unsigned moveroom = ETH_HLEN - headroom; + + memmove(skb->data + moveroom ,skb->data, skb->len); + skb->data += moveroom; + skb->tail += moveroom; + #ifdef WARN_ONCE + WARN_ONCE(1, "It is better reserve headroom in usbnet.c:rx_submit()!\n"); + #endif + } + } +#endif + + //printk("%s skb=%p, len=%d, protocol=%x, hdr_len=%d\n", __func__, skb, skb->len, skb->protocol, skb->hdr_len); + if (net->type == ARPHRD_ETHER && headroom >= ETH_HLEN) { + //usbnet.c rx_process() usbnet_skb_return() eth_type_trans() + skb_push(skb, ETH_HLEN); + skb_reset_mac_header(skb); + memcpy(eth_hdr(skb)->h_source, default_modem_addr, ETH_ALEN); + memcpy(eth_hdr(skb)->h_dest, net->dev_addr, ETH_ALEN); + eth_hdr(skb)->h_proto = htons(ETH_P_MAP); + + return 1; + } + + return 0; +} + +static rx_handler_result_t rmnet_usb_rx_handler(struct sk_buff **pskb) +{ + struct sk_buff *skb = *pskb; + struct usbnet *dev; + struct qmi_wwan_state *info; + sQmiWwanQmap *pQmapDev; + struct sk_buff *qmap_skb; + struct sk_buff_head skb_chain; + + if (!skb) + goto done; + + //printk("%s skb=%p, protocol=%x, len=%d\n", __func__, skb, skb->protocol, skb->len); + + if (skb->pkt_type == PACKET_LOOPBACK) + return RX_HANDLER_PASS; + + if (skb->protocol != htons(ETH_P_MAP)) { + WARN_ON(1); + return RX_HANDLER_PASS; + } + /* when open hyfi function, run cm will make system crash */ + //dev = rcu_dereference(skb->dev->rx_handler_data); + dev = netdev_priv(skb->dev); + + if (dev == NULL) { + WARN_ON(1); + return RX_HANDLER_PASS; + } + + info = (struct qmi_wwan_state *)&dev->data; + pQmapDev = (sQmiWwanQmap *)info->unused; + + qmap_packet_decode(pQmapDev, skb, &skb_chain); + while ((qmap_skb = __skb_dequeue (&skb_chain))) { + struct net_device *qmap_net = qmap_skb->dev; + + rmnet_vnd_update_rx_stats(qmap_net, 1, qmap_skb->len); + if (qmap_net->type == ARPHRD_ETHER) + __skb_pull(qmap_skb, ETH_HLEN); + netif_receive_skb(qmap_skb); + } + consume_skb(skb); + +done: + return RX_HANDLER_CONSUMED; +} + +static const struct driver_info qmi_wwan_info = { + .description = "WWAN/QMI device", + .flags = FLAG_WWAN, + .bind = qmi_wwan_bind, + .unbind = qmi_wwan_unbind, + .manage_power = qmi_wwan_manage_power, +}; + +#define qmi_wwan_raw_ip_info \ + .description = "WWAN/QMI device", \ + .flags = FLAG_WWAN | FLAG_RX_ASSEMBLE | FLAG_NOARP | FLAG_SEND_ZLP, \ + .bind = qmi_wwan_bind, \ + .unbind = qmi_wwan_unbind, \ + .manage_power = qmi_wwan_manage_power, \ + .tx_fixup = qmap_qmi_wwan_tx_fixup, \ + .rx_fixup = qmap_qmi_wwan_rx_fixup, \ + +static const struct driver_info rmnet_usb_info = { + .description = "RMNET/USB device", + .flags = FLAG_WWAN | FLAG_NOARP | FLAG_SEND_ZLP, + .bind = qmi_wwan_bind, + .unbind = qmi_wwan_unbind, + .manage_power = qmi_wwan_manage_power, + .tx_fixup = rmnet_usb_tx_fixup, + .rx_fixup = rmnet_usb_rx_fixup, +}; + +static const struct driver_info qmi_wwan_raw_ip_info_mdm9x07 = { + qmi_wwan_raw_ip_info + .data = (5<<8)|4, //QMAPV1 and 4KB +}; + +// mdm9x40/sdx12/sdx20/sdx24 share the same config +static const struct driver_info qmi_wwan_raw_ip_info_mdm9x40 = { + qmi_wwan_raw_ip_info + .data = (5<<8)|16, //QMAPV1 and 16KB +}; + +static const struct driver_info qmi_wwan_raw_ip_info_sdx55 = { + qmi_wwan_raw_ip_info + .data = (9<<8)|31, //QMAPV5 and 31KB +}; + +/* map QMI/wwan function by a fixed interface number */ +#define QMI_FIXED_INTF(vend, prod, num) \ + USB_DEVICE_INTERFACE_NUMBER(vend, prod, num), \ + .driver_info = (unsigned long)&qmi_wwan_info + +#define QMI_FIXED_RAWIP_INTF(vend, prod, num, chip) \ + USB_DEVICE_INTERFACE_NUMBER(vend, prod, num), \ + .driver_info = (unsigned long)&qmi_wwan_raw_ip_info_##chip + +static const struct usb_device_id products[] = { + { QMI_FIXED_INTF(0x05C6, 0x9003, 4) }, /* Quectel UC20 */ + { QMI_FIXED_INTF(0x05C6, 0x9215, 4) }, /* Quectel EC20 (MDM9215) */ + { QMI_FIXED_RAWIP_INTF(0x2C7C, 0x0125, 4, mdm9x07) }, /* Quectel EC20 (MDM9X07)/EC25/EG25 */ + { QMI_FIXED_RAWIP_INTF(0x2C7C, 0x030A, 6, mdm9x07) }, /* Quectel EG05 */ + { QMI_FIXED_RAWIP_INTF(0x2C7C, 0x0121, 4, mdm9x07) }, /* Quectel EC21 */ + { QMI_FIXED_RAWIP_INTF(0x2C7C, 0x0191, 4, mdm9x07) }, /* Quectel EG91 */ + { QMI_FIXED_RAWIP_INTF(0x2C7C, 0x0195, 4, mdm9x07) }, /* Quectel EG95 */ + { QMI_FIXED_RAWIP_INTF(0x2C7C, 0x0306, 4, mdm9x40) }, /* Quectel EG06/EP06/EM06 */ + { QMI_FIXED_RAWIP_INTF(0x2C7C, 0x0512, 4, mdm9x40) }, /* Quectel EG12/EP12/EM12/EG16/EG18 */ + { QMI_FIXED_RAWIP_INTF(0x2C7C, 0x0296, 4, mdm9x07) }, /* Quectel BG96 */ + { QMI_FIXED_RAWIP_INTF(0x2C7C, 0x0435, 4, mdm9x07) }, /* Quectel AG35 */ + { QMI_FIXED_RAWIP_INTF(0x2C7C, 0x0620, 4, mdm9x40) }, /* Quectel EG20 */ + { QMI_FIXED_RAWIP_INTF(0x2C7C, 0x0800, 4, sdx55) }, /* Quectel RG500 */ + { QMI_FIXED_RAWIP_INTF(0x05c6, 0x90d5, 3, sdx55) }, /* Foxconn T99W240T00 */ + { QMI_FIXED_RAWIP_INTF(0x05c6, 0x90db, 2, sdx55) }, /* SIM8200 */ + { QMI_FIXED_RAWIP_INTF(0x02dee, 0x4d22, 5, sdx55) }, /* Meige SRM815 */ + { } /* END */ +}; +MODULE_DEVICE_TABLE(usb, products); + +static int qmi_wwan_probe(struct usb_interface *intf, + const struct usb_device_id *prod) +{ + struct usb_device_id *id = (struct usb_device_id *)prod; + + /* Workaround to enable dynamic IDs. This disables usbnet + * blacklisting functionality. Which, if required, can be + * reimplemented here by using a magic "blacklist" value + * instead of 0 in the static device id table + */ + if (!id->driver_info) { + dev_dbg(&intf->dev, "setting defaults for dynamic device id\n"); + id->driver_info = (unsigned long)&qmi_wwan_info; + } + + if (intf->cur_altsetting->desc.bInterfaceClass != 0xff) { + dev_info(&intf->dev, "Quectel module not qmi_wwan mode! please check 'at+qcfg=\"usbnet\"'\n"); + return -ENODEV; + } + + return usbnet_probe(intf, id); +} + +#if defined(QUECTEL_WWAN_QMAP) +static int qmap_qmi_wwan_probe(struct usb_interface *intf, + const struct usb_device_id *prod) +{ + int status = qmi_wwan_probe(intf, prod); + + if (!status) { + struct usbnet *dev = usb_get_intfdata(intf); + struct qmi_wwan_state *info = (void *)&dev->data; + sQmiWwanQmap *pQmapDev = (sQmiWwanQmap *)info->unused; + unsigned i; + + if (!pQmapDev) + return status; + + tasklet_init(&pQmapDev->txq, rmnet_usb_tx_wake_queue, (unsigned long)pQmapDev); + + if (pQmapDev->qmap_mode == 1) { + pQmapDev->mpQmapNetDev[0] = dev->net; + if (pQmapDev->use_rmnet_usb) { + pQmapDev->mpQmapNetDev[0] = NULL; + qmap_register_device(pQmapDev, 0); + } + } + else if (pQmapDev->qmap_mode > 1) { + for (i = 0; i < pQmapDev->qmap_mode; i++) { + qmap_register_device(pQmapDev, i); + } + } + + if (pQmapDev->use_rmnet_usb) { + rtnl_lock(); + /* when open hyfi function, run cm will make system crash */ + //netdev_rx_handler_register(dev->net, rmnet_usb_rx_handler, dev); + netdev_rx_handler_register(dev->net, rmnet_usb_rx_handler, NULL); + rtnl_unlock(); + } + + if (pQmapDev->link_state == 0) { + netif_carrier_off(dev->net); + } + } + + return status; +} + +static void qmap_qmi_wwan_disconnect(struct usb_interface *intf) +{ + struct usbnet *dev = usb_get_intfdata(intf); + struct qmi_wwan_state *info; + sQmiWwanQmap *pQmapDev; + uint i; + + if (!dev) + return; + + info = (void *)&dev->data; + pQmapDev = (sQmiWwanQmap *)info->unused; + + if (!pQmapDev) { + return usbnet_disconnect(intf); + } + + pQmapDev->link_state = 0; + + if (pQmapDev->qmap_mode > 1) { + for (i = 0; i < pQmapDev->qmap_mode; i++) { + qmap_unregister_device(pQmapDev, i); + } + } + + if (pQmapDev->use_rmnet_usb) { + qmap_unregister_device(pQmapDev, 0); + rtnl_lock(); + netdev_rx_handler_unregister(dev->net); + rtnl_unlock(); + } + + tasklet_kill(&pQmapDev->txq); + + usbnet_disconnect(intf); + info->unused = 0; + kfree(pQmapDev); +} +#endif + +static struct usb_driver qmi_wwan_driver = { + .name = "qmi_wwan_q", + .id_table = products, + .probe = qmi_wwan_probe, +#if defined(QUECTEL_WWAN_QMAP) + .probe = qmap_qmi_wwan_probe, + .disconnect = qmap_qmi_wwan_disconnect, +#else + .probe = qmi_wwan_probe, + .disconnect = usbnet_disconnect, +#endif + .suspend = qmi_wwan_suspend, + .resume = qmi_wwan_resume, + .reset_resume = qmi_wwan_reset_resume, + .supports_autosuspend = 1, + .disable_hub_initiated_lpm = 1, +}; + +#ifdef CONFIG_QCA_NSS_DRV +/* + EXTRA_CFLAGS="-I$(STAGING_DIR)/usr/include/qca-nss-drv $(EXTRA_CFLAGS)" + qsdk/qca/src/data-kernel/drivers/rmnet-nss/rmnet_nss.c +*/ +#include "rmnet_nss.c" +#endif + +static int __init qmi_wwan_driver_init(void) +{ + RCU_INIT_POINTER(rmnet_nss_callbacks, NULL); +#ifdef CONFIG_QCA_NSS_DRV + if (qca_nss_enabled) + rmnet_nss_init(); +#endif + return usb_register(&qmi_wwan_driver); +} +module_init(qmi_wwan_driver_init); +static void __exit qmi_wwan_driver_exit(void) +{ +#ifdef CONFIG_QCA_NSS_DRV + if (qca_nss_enabled) + rmnet_nss_exit(); +#endif + usb_deregister(&qmi_wwan_driver); +} +module_exit(qmi_wwan_driver_exit); + +MODULE_AUTHOR("Bjørn Mork "); +MODULE_DESCRIPTION("Qualcomm MSM Interface (QMI) WWAN driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(QUECTEL_WWAN_VERSION); diff --git a/root/package/link4all/rm500q/src/rmnet_nss.c b/root/package/link4all/rm500q/src/rmnet_nss.c new file mode 100644 index 00000000..deda11cc --- /dev/null +++ b/root/package/link4all/rm500q/src/rmnet_nss.c @@ -0,0 +1,424 @@ +/* Copyright (c) 2019, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define RMNET_NSS_HASH_BITS 8 +#define hash_add_ptr(table, node, key) \ + hlist_add_head(node, &table[hash_ptr(key, HASH_BITS(table))]) + +static DEFINE_HASHTABLE(rmnet_nss_ctx_hashtable, RMNET_NSS_HASH_BITS); + +struct rmnet_nss_ctx { + struct hlist_node hnode; + struct net_device *rmnet_dev; + struct nss_rmnet_rx_handle *nss_ctx; +}; + +enum __rmnet_nss_stat { + RMNET_NSS_RX_ETH, + RMNET_NSS_RX_FAIL, + RMNET_NSS_RX_NON_ETH, + RMNET_NSS_RX_BUSY, + RMNET_NSS_TX_NO_CTX, + RMNET_NSS_TX_SUCCESS, + RMNET_NSS_TX_FAIL, + RMNET_NSS_TX_NONLINEAR, + RMNET_NSS_TX_BAD_IP, + RMNET_NSS_EXCEPTIONS, + RMNET_NSS_EX_BAD_HDR, + RMNET_NSS_EX_BAD_IP, + RMNET_NSS_EX_SUCCESS, + RMNET_NSS_TX_BAD_FRAGS, + RMNET_NSS_TX_LINEARIZE_FAILS, + RMNET_NSS_TX_NON_ZERO_HEADLEN_FRAGS, + RMNET_NSS_TX_BUSY_LOOP, + RMNET_NSS_NUM_STATS, +}; + +static unsigned long rmnet_nss_stats[RMNET_NSS_NUM_STATS]; + +#define RMNET_NSS_STAT(name, counter, desc) \ + module_param_named(name, rmnet_nss_stats[counter], ulong, 0444); \ + MODULE_PARM_DESC(name, desc) + +RMNET_NSS_STAT(rmnet_nss_rx_ethernet, RMNET_NSS_RX_ETH, + "Number of Ethernet headers successfully removed"); +RMNET_NSS_STAT(rmnet_nss_rx_fail, RMNET_NSS_RX_FAIL, + "Number of Ethernet headers that could not be removed"); +RMNET_NSS_STAT(rmnet_nss_rx_non_ethernet, RMNET_NSS_RX_NON_ETH, + "Number of non-Ethernet packets received"); +RMNET_NSS_STAT(rmnet_nss_rx_busy, RMNET_NSS_RX_BUSY, + "Number of packets dropped decause rmnet_data device was busy"); +RMNET_NSS_STAT(rmnet_nss_tx_slow, RMNET_NSS_TX_NO_CTX, + "Number of packets sent over non-NSS-accelerated rmnet device"); +RMNET_NSS_STAT(rmnet_nss_tx_fast, RMNET_NSS_TX_SUCCESS, + "Number of packets sent over NSS-accelerated rmnet device"); +RMNET_NSS_STAT(rmnet_nss_tx_fail, RMNET_NSS_TX_FAIL, + "Number of packets that NSS could not transmit"); +RMNET_NSS_STAT(rmnet_nss_tx_nonlinear, RMNET_NSS_TX_NONLINEAR, + "Number of non linear sent over NSS-accelerated rmnet device"); +RMNET_NSS_STAT(rmnet_nss_tx_invalid_ip, RMNET_NSS_TX_BAD_IP, + "Number of ingress packets with invalid IP headers"); +RMNET_NSS_STAT(rmnet_nss_tx_invalid_frags, RMNET_NSS_TX_BAD_FRAGS, + "Number of ingress packets with invalid frag format"); +RMNET_NSS_STAT(rmnet_nss_tx_linearize_fail, RMNET_NSS_TX_LINEARIZE_FAILS, + "Number of ingress packets where linearize in tx fails"); +RMNET_NSS_STAT(rmnet_nss_tx_exceptions, RMNET_NSS_EXCEPTIONS, + "Number of times our DL exception handler was invoked"); +RMNET_NSS_STAT(rmnet_nss_exception_non_ethernet, RMNET_NSS_EX_BAD_HDR, + "Number of non-Ethernet exception packets"); +RMNET_NSS_STAT(rmnet_nss_exception_invalid_ip, RMNET_NSS_EX_BAD_IP, + "Number of exception packets with invalid IP headers"); +RMNET_NSS_STAT(rmnet_nss_exception_success, RMNET_NSS_EX_SUCCESS, + "Number of exception packets handled successfully"); +RMNET_NSS_STAT(rmnet_nss_tx_non_zero_headlen_frags, RMNET_NSS_TX_NON_ZERO_HEADLEN_FRAGS, + "Number of packets with non zero headlen"); +RMNET_NSS_STAT(rmnet_nss_tx_busy_loop, RMNET_NSS_TX_BUSY_LOOP, + "Number of times tx packets busy looped"); + +static void rmnet_nss_inc_stat(enum __rmnet_nss_stat stat) +{ + if (stat >= 0 && stat < RMNET_NSS_NUM_STATS) + rmnet_nss_stats[stat]++; +} + +static struct rmnet_nss_ctx *rmnet_nss_find_ctx(struct net_device *dev) +{ + struct rmnet_nss_ctx *ctx; + struct hlist_head *bucket; + u32 hash; + + hash = hash_ptr(dev, HASH_BITS(rmnet_nss_ctx_hashtable)); + bucket = &rmnet_nss_ctx_hashtable[hash]; + hlist_for_each_entry(ctx, bucket, hnode) { + if (ctx->rmnet_dev == dev) + return ctx; + } + + return NULL; +} + +static void rmnet_nss_free_ctx(struct rmnet_nss_ctx *ctx) +{ + if (ctx) { + hash_del(&ctx->hnode); + nss_rmnet_rx_xmit_callback_unregister(ctx->nss_ctx); + nss_rmnet_rx_destroy_sync(ctx->nss_ctx); + kfree(ctx); + } +} + +/* Pull off an ethernet header, if possible */ +static int rmnet_nss_ethhdr_pull(struct sk_buff *skb) +{ + if (!skb->protocol || skb->protocol == htons(ETH_P_802_3)) { + void *ret = skb_pull(skb, sizeof(struct ethhdr)); + + rmnet_nss_inc_stat((ret) ? RMNET_NSS_RX_ETH : + RMNET_NSS_RX_FAIL); + return !ret; + } + + rmnet_nss_inc_stat(RMNET_NSS_RX_NON_ETH); + return -1; +} + +/* Copy headers to linear section for non linear packets */ +static int rmnet_nss_adjust_header(struct sk_buff *skb) +{ + struct iphdr *iph; + skb_frag_t *frag; + int bytes = 0; + u8 transport; + + if (skb_shinfo(skb)->nr_frags != 1) { + rmnet_nss_inc_stat(RMNET_NSS_TX_BAD_FRAGS); + return -EINVAL; + } + + if (skb_headlen(skb)) { + rmnet_nss_inc_stat(RMNET_NSS_TX_NON_ZERO_HEADLEN_FRAGS); + return 0; + } + + frag = &skb_shinfo(skb)->frags[0]; + + iph = (struct iphdr *)(skb_frag_address(frag)); + + if (iph->version == 4) { + bytes = iph->ihl*4; + transport = iph->protocol; + } else if (iph->version == 6) { + struct ipv6hdr *ip6h = (struct ipv6hdr *)iph; + + bytes = sizeof(struct ipv6hdr); + /* Dont have to account for extension headers yet */ + transport = ip6h->nexthdr; + } else { + rmnet_nss_inc_stat(RMNET_NSS_TX_BAD_IP); + return -EINVAL; + } + + if (transport == IPPROTO_TCP) { + struct tcphdr *th; + + th = (struct tcphdr *)((u8 *)iph + bytes); + bytes += th->doff * 4; + } else if (transport == IPPROTO_UDP) { + bytes += sizeof(struct udphdr); + } else { + /* cant do anything else here unfortunately so linearize */ + if (skb_linearize(skb)) { + rmnet_nss_inc_stat(RMNET_NSS_TX_LINEARIZE_FAILS); + return -EINVAL; + } else { + return 0; + } + } + + if (bytes > skb_frag_size(frag)) { + rmnet_nss_inc_stat(RMNET_NSS_TX_BAD_FRAGS); + return -EINVAL; + } + + skb_push(skb, bytes); + memcpy(skb->data, iph, bytes); + + /* subtract to account for skb_push */ + skb->len -= bytes; + + frag->page_offset += bytes; + skb_frag_size_sub(frag, bytes); + + /* subtract to account for skb_frag_size_sub */ + skb->data_len -= bytes; + + return 0; +} + +/* Main downlink handler + * Looks up NSS contex associated with the device. If the context is found, + * we add a dummy ethernet header with the approriate protocol field set, + * the pass the packet off to NSS for hardware acceleration. + */ +int rmnet_nss_tx(struct sk_buff *skb) +{ + struct ethhdr *eth; + struct rmnet_nss_ctx *ctx; + struct net_device *dev = skb->dev; + nss_tx_status_t rc; + unsigned int len; + u8 version; + + if (skb_is_nonlinear(skb)) { + if (rmnet_nss_adjust_header(skb)) + goto fail; + else + rmnet_nss_inc_stat(RMNET_NSS_TX_NONLINEAR); + } + + version = ((struct iphdr *)skb->data)->version; + + ctx = rmnet_nss_find_ctx(dev); + if (!ctx) { + rmnet_nss_inc_stat(RMNET_NSS_TX_NO_CTX); + return -EINVAL; + } + + eth = (struct ethhdr *)skb_push(skb, sizeof(*eth)); + memset(ð->h_dest, 0, ETH_ALEN * 2); + if (version == 4) { + eth->h_proto = htons(ETH_P_IP); + } else if (version == 6) { + eth->h_proto = htons(ETH_P_IPV6); + } else { + rmnet_nss_inc_stat(RMNET_NSS_TX_BAD_IP); + goto fail; + } + + skb->protocol = htons(ETH_P_802_3); + /* Get length including ethhdr */ + len = skb->len; + +transmit: + rc = nss_rmnet_rx_tx_buf(ctx->nss_ctx, skb); + if (rc == NSS_TX_SUCCESS) { + /* Increment rmnet_data device stats. + * Don't call rmnet_data_vnd_rx_fixup() to do this, as + * there's no guarantee the skb pointer is still valid. + */ + dev->stats.rx_packets++; + dev->stats.rx_bytes += len; + rmnet_nss_inc_stat(RMNET_NSS_TX_SUCCESS); + return 0; + } else if (rc == NSS_TX_FAILURE_QUEUE) { + rmnet_nss_inc_stat(RMNET_NSS_TX_BUSY_LOOP); + goto transmit; + } + +fail: + rmnet_nss_inc_stat(RMNET_NSS_TX_FAIL); + kfree_skb(skb); + return 1; +} + +/* Called by NSS in the DL exception case. + * Since the packet cannot be sent over the accelerated path, we need to + * handle it. Remove the ethernet header and pass it onward to the stack + * if possible. + */ +void rmnet_nss_receive(struct net_device *dev, struct sk_buff *skb, + struct napi_struct *napi) +{ + rmnet_nss_inc_stat(RMNET_NSS_EXCEPTIONS); + + if (!skb) + return; + + if (rmnet_nss_ethhdr_pull(skb)) { + rmnet_nss_inc_stat(RMNET_NSS_EX_BAD_HDR); + goto drop; + } + + /* reset header pointers */ + skb_reset_transport_header(skb); + skb_reset_network_header(skb); + skb_reset_mac_header(skb); + + /* reset packet type */ + skb->pkt_type = PACKET_HOST; + + skb->dev = dev; + + /* reset protocol type */ + switch (skb->data[0] & 0xF0) { + case 0x40: + skb->protocol = htons(ETH_P_IP); + break; + case 0x60: + skb->protocol = htons(ETH_P_IPV6); + break; + default: + rmnet_nss_inc_stat(RMNET_NSS_EX_BAD_IP); + goto drop; + } + + rmnet_nss_inc_stat(RMNET_NSS_EX_SUCCESS); + + /* Set this so that we dont loop around netif_receive_skb */ + + skb->cb[0] = 1; + + netif_receive_skb(skb); + return; + +drop: + kfree_skb(skb); +} + +/* Called by NSS in the UL acceleration case. + * We are guaranteed to have an ethernet packet here from the NSS hardware, + * We need to pull the header off and invoke our ndo_start_xmit function + * to handle transmitting the packet to the network stack. + */ +void rmnet_nss_xmit(struct net_device *dev, struct sk_buff *skb) +{ + netdev_tx_t ret; + + skb_pull(skb, sizeof(struct ethhdr)); + rmnet_nss_inc_stat(RMNET_NSS_RX_ETH); + + /* NSS takes care of shaping, so bypassing Qdiscs like this is OK */ + ret = dev->netdev_ops->ndo_start_xmit(skb, dev); + if (unlikely(ret == NETDEV_TX_BUSY)) { + dev_kfree_skb_any(skb); + rmnet_nss_inc_stat(RMNET_NSS_RX_BUSY); + } +} + +/* Create and register an NSS context for an rmnet_data device */ +int rmnet_nss_create_vnd(struct net_device *dev) +{ + struct rmnet_nss_ctx *ctx; + + ctx = kzalloc(sizeof(*ctx), GFP_ATOMIC); + if (!ctx) + return -ENOMEM; + + ctx->rmnet_dev = dev; + ctx->nss_ctx = nss_rmnet_rx_create_sync_nexthop(dev, NSS_N2H_INTERFACE, + NSS_C2C_TX_INTERFACE); + if (!ctx->nss_ctx) { + kfree(ctx); + return -1; + } + + nss_rmnet_rx_register(ctx->nss_ctx, rmnet_nss_receive, dev); + nss_rmnet_rx_xmit_callback_register(ctx->nss_ctx, rmnet_nss_xmit); + hash_add_ptr(rmnet_nss_ctx_hashtable, &ctx->hnode, dev); + return 0; +} + +/* Unregister and destroy the NSS context for an rmnet_data device */ +int rmnet_nss_free_vnd(struct net_device *dev) +{ + struct rmnet_nss_ctx *ctx; + + ctx = rmnet_nss_find_ctx(dev); + rmnet_nss_free_ctx(ctx); + + return 0; +} + +static const struct rmnet_nss_cb rmnet_nss = { + .nss_create = rmnet_nss_create_vnd, + .nss_free = rmnet_nss_free_vnd, + .nss_tx = rmnet_nss_tx, +}; + +int __init rmnet_nss_init(void) +{ + pr_err("%s(): initializing rmnet_nss\n", __func__); + RCU_INIT_POINTER(rmnet_nss_callbacks, &rmnet_nss); + return 0; +} + +void __exit rmnet_nss_exit(void) +{ + struct hlist_node *tmp; + struct rmnet_nss_ctx *ctx; + int bkt; + + pr_err("%s(): exiting rmnet_nss\n", __func__); + RCU_INIT_POINTER(rmnet_nss_callbacks, NULL); + + /* Tear down all NSS contexts */ + hash_for_each_safe(rmnet_nss_ctx_hashtable, bkt, tmp, ctx, hnode) + rmnet_nss_free_ctx(ctx); +} + +#if 0 +MODULE_LICENSE("GPL v2"); +module_init(rmnet_nss_init); +module_exit(rmnet_nss_exit); +#endif diff --git a/root/package/qca/nss-firmware/Makefile b/root/package/qca/nss-firmware/Makefile new file mode 100644 index 00000000..b56a3652 --- /dev/null +++ b/root/package/qca/nss-firmware/Makefile @@ -0,0 +1,73 @@ +# +# Copyright (C) 2021 OpenWrt.org +# +# This is free software, licensed under the GNU General Public License v2. +# See /LICENSE for more information. +# + +include $(TOPDIR)/rules.mk + +PKG_NAME:=nss-firmware +PKG_RELEASE:=1 + +PKG_SOURCE_PROTO:=git +PKG_SOURCE_DATE:=2021-09-14 +PKG_SOURCE_URL:=https://github.com/quic/qca-sdk-nss-fw.git +PKG_SOURCE_VERSION:=b06171cb0d14360c90a4b8f9f835dc1c4647ea07 +PKG_MIRROR_HASH:=e851d4f6af8aaccbc34a05bc007991a88287913f17c5d9f0172a00745aed6e48 + +PKG_LICENSE_FILES:=LICENSE.md + +PKG_MAINTAINER:=Robert Marko + +include $(INCLUDE_DIR)/package.mk + +VERSION_PATH=$(PKG_BUILD_DIR)/QCA_Networking_2021.SPF_11.4/CS + +define Package/nss-firmware-default + SECTION:=firmware + CATEGORY:=Firmware + URL:=$(PKG_SOURCE_URL) +endef + +define Package/nss-firmware-ipq6018 +$(Package/nss-firmware-default) + DEPENDS:=@TARGET_ipq60xx + TITLE:=NSS firmware for IPQ6018 devices + NSS_ARCHIVE:=$(VERSION_PATH)/IPQ6018.ATH.11.4/BIN-NSS.CP.11.4.0.5-5-R.tar.bz2 +endef + +define Package/nss-firmware-ipq8074 +$(Package/nss-firmware-default) + DEPENDS:=@TARGET_ipq807x + TITLE:=NSS firmware for IPQ8074 devices + NSS_ARCHIVE:=$(VERSION_PATH)/IPQ8074.ATH.11.4/BIN-NSS.HK.11.4.0.5-5-R.tar.bz2 +endef + +define Build/Compile + true +endef + +define Package/nss-firmware-ipq6018/install + mkdir -p $(PKG_BUILD_DIR)/IPQ6018 + $(TAR) -C $(PKG_BUILD_DIR)/IPQ6018 -xf $(NSS_ARCHIVE) --strip-components=1 + $(INSTALL_DIR) $(1)/lib/firmware/ + $(INSTALL_DATA) \ + $(PKG_BUILD_DIR)/IPQ6018/retail_router0.bin \ + $(1)/lib/firmware/qca-nss0.bin +endef + +define Package/nss-firmware-ipq8074/install + mkdir -p $(PKG_BUILD_DIR)/IPQ8074 + $(TAR) -C $(PKG_BUILD_DIR)/IPQ8074 -xf $(NSS_ARCHIVE) --strip-components=1 + $(INSTALL_DIR) $(1)/lib/firmware/ + $(INSTALL_DATA) \ + $(PKG_BUILD_DIR)/IPQ8074/retail_router0.bin \ + $(1)/lib/firmware/qca-nss0.bin + $(INSTALL_DATA) \ + $(PKG_BUILD_DIR)/IPQ8074/retail_router1.bin \ + $(1)/lib/firmware/qca-nss1.bin +endef + +$(eval $(call BuildPackage,nss-firmware-ipq6018)) +$(eval $(call BuildPackage,nss-firmware-ipq8074)) diff --git a/root/package/qca/qca-nss-cfi/Makefile b/root/package/qca/qca-nss-cfi/Makefile new file mode 100644 index 00000000..64f7bd7a --- /dev/null +++ b/root/package/qca/qca-nss-cfi/Makefile @@ -0,0 +1,71 @@ +include $(TOPDIR)/rules.mk + +PKG_NAME:=qca-nss-cfi +PKG_RELEASE:=1 + +PKG_SOURCE_PROTO:=git +PKG_SOURCE_DATE:=2021-03-22 +PKG_SOURCE_URL:=https://source.codeaurora.org/quic/qsdk/oss/lklm/nss-cfi +PKG_SOURCE_VERSION:=73f2e5f5bf93cc244036ce5641faf9f859692cdf +PKG_MIRROR_HASH:=8c8edc9b8d8c68fdd14640152245013a93c123957b6b338c06f266f6c9db8cbd + +include $(INCLUDE_DIR)/kernel.mk +include $(INCLUDE_DIR)/package.mk + +# v1.0 is for Akronite +# v2.0 is for Hawkeye/Cypress/Maple +ifneq (, $(findstring $(BOARD), ipq50xx ipq60xx ipq807x)) + CFI_OCF_DIR:=ocf/v2.0 + CFI_CRYPTOAPI_DIR:=cryptoapi/v2.0 +else + CFI_CRYPTOAPI_DIR:=cryptoapi/v1.1 + CFI_OCF_DIR:=ocf/v1.0 + CFI_IPSEC_DIR:=ipsec/v1.0 +endif + +define KernelPackage/qca-nss-cfi-cryptoapi + SECTION:=kernel + CATEGORY:=Kernel modules + SUBMENU:=Network Devices + DEPENDS:=@TARGET_ipq60xx +kmod-crypto-authenc +kmod-qca-nss-crypto + TITLE:=Kernel driver for NSS cfi + FILES:=$(PKG_BUILD_DIR)/$(CFI_CRYPTOAPI_DIR)/qca-nss-cfi-cryptoapi.ko + AUTOLOAD:=$(call AutoLoad,59,qca-nss-cfi-cryptoapi) +endef + +define Build/InstallDev + $(INSTALL_DIR) $(1)/usr/include/qca-nss-cfi + $(CP) $(PKG_BUILD_DIR)/$(CFI_CRYPTOAPI_DIR)/../exports/* $(1)/usr/include/qca-nss-cfi + $(CP) $(PKG_BUILD_DIR)/include/* $(1)/usr/include/qca-nss-cfi +endef + +define KernelPackage/qca-nss-cfi/Description +This package contains a NSS cfi driver for QCA chipset +endef + +EXTRA_CFLAGS+= \ + -DCONFIG_NSS_DEBUG_LEVEL=4 \ + -I$(LINUX_DIR)/crypto/ocf \ + -I$(STAGING_DIR)/usr/include/qca-nss-crypto \ + -I$(STAGING_DIR)/usr/include/crypto \ + -I$(STAGING_DIR)/usr/include/qca-nss-drv + +ifneq (, $(findstring $(BOARD), ipq50xx ipq60xx ipq807x)) +EXTRA_CFLAGS+= -I$(STAGING_DIR)/usr/include/qca-nss-clients +endif + +define Build/Compile + $(MAKE) -C "$(LINUX_DIR)" \ + CROSS_COMPILE="$(TARGET_CROSS)" \ + EXTRA_CFLAGS="$(EXTRA_CFLAGS)" \ + CFI_CRYPTOAPI_DIR=$(CFI_CRYPTOAPI_DIR) \ + CFI_IPSEC_DIR=$(CFI_IPSEC_DIR) \ + CFI_OCF_DIR=$(CFI_OCF_DIR) \ + $(KERNEL_MAKE_FLAGS) \ + M="$(PKG_BUILD_DIR)" \ + SoC="$(BOARD)_64" \ + "cryptoapi=y" \ + modules +endef + +$(eval $(call KernelPackage,qca-nss-cfi-cryptoapi)) diff --git a/root/package/qca/qca-nss-cfi/patches/100-remove-noise-logs.patch b/root/package/qca/qca-nss-cfi/patches/100-remove-noise-logs.patch new file mode 100644 index 00000000..085aeaf2 --- /dev/null +++ b/root/package/qca/qca-nss-cfi/patches/100-remove-noise-logs.patch @@ -0,0 +1,30 @@ +--- a/cryptoapi/v2.0/nss_cryptoapi_ahash.c ++++ b/cryptoapi/v2.0/nss_cryptoapi_ahash.c +@@ -449,9 +449,12 @@ int nss_cryptoapi_ahash_digest(struct ahash_request *req) + int nss_cryptoapi_ahash_export(struct ahash_request *req, void *out) + { + struct nss_cryptoapi_ctx *ctx __attribute__((unused)) = crypto_tfm_ctx(req->base.tfm); ++ struct nss_cryptoapi_req_ctx *rctx = ahash_request_ctx(req); ++ struct nss_cryptoapi_req_ctx *state = out; ++ ++ *state = *rctx; + +- nss_cfi_warn("%px: ahash .export is not supported", ctx); +- return -ENOSYS; ++ return 0; + }; + + /* +@@ -464,7 +467,10 @@ int nss_cryptoapi_ahash_export(struct ahash_request *req, void *out) + int nss_cryptoapi_ahash_import(struct ahash_request *req, const void *in) + { + struct nss_cryptoapi_ctx *ctx __attribute__((unused)) = crypto_tfm_ctx(req->base.tfm); ++ struct nss_cryptoapi_req_ctx *rctx = ahash_request_ctx(req); ++ const struct nss_cryptoapi_req_ctx *state = in; ++ ++ *rctx = *state; + +- nss_cfi_warn("%px: ahash .import is not supported", ctx); +- return -ENOSYS; ++ return 0; + } diff --git a/root/package/qca/qca-nss-clients/Makefile b/root/package/qca/qca-nss-clients/Makefile new file mode 100644 index 00000000..2d9b8264 --- /dev/null +++ b/root/package/qca/qca-nss-clients/Makefile @@ -0,0 +1,115 @@ +include $(TOPDIR)/rules.mk + +PKG_NAME:=qca-nss-clients +PKG_RELEASE:=1 + +PKG_SOURCE_PROTO:=git +PKG_SOURCE_DATE:=2021-12-01 +PKG_SOURCE_URL:=https://source.codeaurora.org/quic/qsdk/oss/lklm/nss-clients +PKG_SOURCE_VERSION:=24fdeb3357cf4135f43add427e2b318139b6a1b9 +PKG_MIRROR_HASH:=c46b9cdf4bbd394507a92dc39f9183735a61c7ccdfed38832c525ed6d2381eb6 + +include $(INCLUDE_DIR)/kernel.mk +include $(INCLUDE_DIR)/package.mk + +define KernelPackage/qca-nss-drv-pppoe + SECTION:=kernel + CATEGORY:=Kernel modules + SUBMENU:=Network Devices + TITLE:=Kernel driver for NSS PPPoE connection manager + DEPENDS:=@TARGET_ipq60xx +kmod-bonding +kmod-pppoe +kmod-qca-nss-drv + FILES:=$(PKG_BUILD_DIR)/pppoe/qca-nss-pppoe.ko + AUTOLOAD:=$(call AutoLoad,51,qca-nss-pppoe) +endef + +define KernelPackage/qca-nss-drv-pppoe/Description +Kernel modules for NSS connection manager - Support for PPPoE +endef + +define KernelPackage/qca-nss-drv-bridge-mgr + SECTION:=kernel + CATEGORY:=Kernel modules + SUBMENU:=Network Devices + TITLE:=Kernel driver for NSS bridge manager + DEPENDS:=@TARGET_ipq60xx +kmod-bonding +kmod-qca-nss-drv-vlan-mgr + FILES:=$(PKG_BUILD_DIR)/bridge/qca-nss-bridge-mgr.ko + AUTOLOAD:=$(call AutoLoad,51,qca-nss-bridge-mgr) +endef + +define KernelPackage/qca-nss-drv-bridge-mgr/Description +Kernel modules for NSS bridge manager +endef + +define KernelPackage/qca-nss-drv-vlan-mgr + SECTION:=kernel + CATEGORY:=Kernel modules + SUBMENU:=Network Devices + TITLE:=Kernel driver for NSS vlan manager + DEPENDS:=@TARGET_ipq60xx +kmod-bonding +kmod-qca-nss-drv + FILES:=$(PKG_BUILD_DIR)/vlan/qca-nss-vlan.ko + AUTOLOAD:=$(call AutoLoad,51,qca-nss-vlan) +endef + +define KernelPackage/qca-nss-drv-vlan-mgr/Description +Kernel modules for NSS vlan manager +endef + +define KernelPackage/qca-nss-drv-wifi-meshmgr + SECTION:=kernel + CATEGORY:=Kernel modules + SUBMENU:=Network Devices + DEPENDS:=@TARGET_ipq60xx +kmod-qca-nss-drv + TITLE:=NSS WiFi-Mesh Manager for QCA NSS driver + FILES:=$(PKG_BUILD_DIR)/wifi_meshmgr/qca-nss-wifi-meshmgr.ko + AUTOLOAD:=$(call AutoLoad,51,qca-nss-wifi-meshmgr) +endef + +define KernelPackage/qca-nss-drv-wifi-meshmgr/Description +Kernel module for NSS WiFi Mesh manager +endef + +EXTRA_CFLAGS+= \ + -I$(STAGING_DIR)/usr/include/qca-nss-cfi \ + -I$(STAGING_DIR)/usr/include/qca-nss-crypto \ + -I$(STAGING_DIR)/usr/include/qca-nss-drv \ + -I$(STAGING_DIR)/usr/include/qca-nss-gmac \ + -I$(STAGING_DIR)/usr/include/qca-ssdk \ + -I$(STAGING_DIR)/usr/include/qca-ssdk/fal \ + -I$(STAGING_DIR)/usr/include/nat46 + +ifneq ($(CONFIG_PACKAGE_kmod-qca-nss-drv-pppoe),) +MAKE_OPTS+=pppoe=y +endif + +ifneq ($(CONFIG_PACKAGE_kmod-qca-nss-drv-vlan-mgr),) +MAKE_OPTS+=vlan-mgr=y +endif + +ifneq ($(CONFIG_PACKAGE_kmod-qca-nss-drv-bridge-mgr),) +MAKE_OPTS+=bridge-mgr=y +endif + +ifneq ($(CONFIG_PACKAGE_kmod-qca-nss-drv-wifi-meshmgr),) +MAKE_OPTS+=wifi-meshmgr=y +endif + +define Build/InstallDev + $(INSTALL_DIR) $(1)/usr/include/qca-nss-clients + $(CP) $(PKG_BUILD_DIR)/netlink/include/* $(1)/usr/include/qca-nss-clients/ + $(CP) $(PKG_BUILD_DIR)/exports/* $(1)/usr/include/qca-nss-clients/ +endef + +define Build/Compile + $(MAKE) -C "$(LINUX_DIR)" $(strip $(MAKE_OPTS)) \ + CROSS_COMPILE="$(TARGET_CROSS)" \ + EXTRA_CFLAGS="$(EXTRA_CFLAGS)" \ + $(KERNEL_MAKE_FLAGS) \ + M="$(PKG_BUILD_DIR)" \ + SoC="$(BOARD)_64" \ + modules +endef + +$(eval $(call KernelPackage,qca-nss-drv-pppoe)) +$(eval $(call KernelPackage,qca-nss-drv-vlan-mgr)) +$(eval $(call KernelPackage,qca-nss-drv-bridge-mgr)) +$(eval $(call KernelPackage,qca-nss-drv-wifi-meshmgr)) diff --git a/root/package/qca/qca-nss-clients/files/qca-nss-ipsec b/root/package/qca/qca-nss-clients/files/qca-nss-ipsec new file mode 100644 index 00000000..bb202e8e --- /dev/null +++ b/root/package/qca/qca-nss-clients/files/qca-nss-ipsec @@ -0,0 +1,92 @@ +#!/bin/sh /etc/rc.common +# +# Copyright (c) 2018-2019 The Linux Foundation. All rights reserved. +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +NSS_IPSEC_LOG_FILE=/tmp/.nss_ipsec_log +NSS_IPSEC_LOG_STR_ECM="ECM_Loaded" + +ecm_load () { + if [ ! -d /sys/module/ecm ]; then + /etc/init.d/qca-nss-ecm start + if [ -d /sys/module/ecm ]; then + echo ${NSS_IPSEC_LOG_STR_ECM} >> ${NSS_IPSEC_LOG_FILE} + fi + fi +} + +ecm_unload () { + if [ -f /tmp/.nss_ipsec_log ]; then + str=`grep ${NSS_IPSEC_LOG_STR_ECM} ${NSS_IPSEC_LOG_FILE}` + if [[ $str == ${NSS_IPSEC_LOG_STR_ECM} ]]; then + /etc/init.d/qca-nss-ecm stop + `sed 's/${NSS_IPSEC_LOG_STR_ECM}/ /g' $NSS_IPSEC_LOG_FILE > $NSS_IPSEC_LOG_FILE` + fi + fi +} + +ecm_disable() { + + if [ ! -d /sys/module/ecm ]; then + return; + fi + + echo 1 > /sys/kernel/debug/ecm/front_end_ipv4_stop + echo 1 > /sys/kernel/debug/ecm/front_end_ipv6_stop + echo 1 > /sys/kernel/debug/ecm/ecm_db/defunct_all + sleep 2 +} + +ecm_enable() { + if [ ! -d /sys/module/ecm ]; then + return; + fi + + echo 0 > /sys/kernel/debug/ecm/ecm_db/defunct_all + echo 0 > /sys/kernel/debug/ecm/front_end_ipv4_stop + echo 0 > /sys/kernel/debug/ecm/front_end_ipv6_stop +} + +start() { + ecm_load + + local kernel_version=$(uname -r) + + insmod /lib/modules/${kernel_version}/qca-nss-ipsec-klips.ko + if [ "$?" -gt 0 ]; then + echo "Failed to load plugin. Please start ecm if not done already" + ecm_enable + return + fi + + /etc/init.d/ipsec start + sleep 2 + ipsec eroute + + ecm_enable +} + +stop() { + ecm_disable + + /etc/init.d/ipsec stop + rmmod qca-nss-ipsec-klips + + ecm_unload +} + +restart() { + stop + start +} diff --git a/root/package/qca/qca-nss-clients/files/qca-nss-mirred.init b/root/package/qca/qca-nss-clients/files/qca-nss-mirred.init new file mode 100644 index 00000000..1f931f09 --- /dev/null +++ b/root/package/qca/qca-nss-clients/files/qca-nss-mirred.init @@ -0,0 +1,28 @@ +#!/bin/sh /etc/rc.common + +########################################################################### +# Copyright (c) 2019, The Linux Foundation. All rights reserved. +# Permission to use, copy, modify, and/or distribute this software for +# any purpose with or without fee is hereby granted, provided that the +# above copyright notice and this permission notice appear in all copies. +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +########################################################################### + +restart() { + rmmod act_nssmirred.ko + insmod act_nssmirred.ko +} + +start() { + insmod act_nssmirred.ko +} + +stop() { + rmmod act_nssmirred.ko +} diff --git a/root/package/qca/qca-nss-clients/files/qca-nss-ovpn.init b/root/package/qca/qca-nss-clients/files/qca-nss-ovpn.init new file mode 100644 index 00000000..622e295e --- /dev/null +++ b/root/package/qca/qca-nss-clients/files/qca-nss-ovpn.init @@ -0,0 +1,69 @@ +#!/bin/sh /etc/rc.common + +########################################################################### +# Copyright (c) 2019, The Linux Foundation. All rights reserved. +# Permission to use, copy, modify, and/or distribute this software for +# any purpose with or without fee is hereby granted, provided that the +# above copyright notice and this permission notice appear in all copies. +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +########################################################################### + +ecm_disable() { + if [ ! -d /sys/module/ecm ]; then + return + fi + + echo 1 > /sys/kernel/debug/ecm/front_end_ipv4_stop + echo 1 > /sys/kernel/debug/ecm/front_end_ipv6_stop + echo 1 > /sys/kernel/debug/ecm/ecm_db/defunct_all + sleep 2 +} + +ecm_enable() { + if [ ! -d /sys/module/ecm ]; then + return + fi + + echo 0 > /sys/kernel/debug/ecm/ecm_db/defunct_all + echo 0 > /sys/kernel/debug/ecm/front_end_ipv4_stop + echo 0 > /sys/kernel/debug/ecm/front_end_ipv6_stop +} + +restart() { + ecm_disable + + /etc/init.d/openvpn stop + rmmod qca-nss-ovpn-link + rmmod qca-nss-ovpn-mgr + + insmod qca-nss-ovpn-mgr + insmod qca-nss-ovpn-link + + if [ "$?" -gt 0 ]; then + echo "Failed to load plugin. Please start ecm if not done already" + ecm_enable + return + fi + + ecm_enable +} + +start() { + restart +} + +stop() { + ecm_disable + + /etc/init.d/openvpn stop + rmmod qca-nss-ovpn-link + rmmod qca-nss-ovpn-mgr + + ecm_enable +} diff --git a/root/package/qca/qca-nss-crypto/Makefile b/root/package/qca/qca-nss-crypto/Makefile new file mode 100644 index 00000000..7b4baec2 --- /dev/null +++ b/root/package/qca/qca-nss-crypto/Makefile @@ -0,0 +1,62 @@ +include $(TOPDIR)/rules.mk + +PKG_NAME:=qca-nss-crypto +PKG_RELEASE:=1 + +PKG_SOURCE_PROTO:=git +PKG_SOURCE_DATE:=2021-03-22 +PKG_SOURCE_URL:=https://source.codeaurora.org/quic/qsdk/oss/lklm/nss-crypto +PKG_SOURCE_VERSION:=2271a3a66f7e8284d42a9e787ddec6f24a1d2e15 +PKG_MIRROR_HASH:=7e70ffa2cbddc3830376f25047575db8867f2027b2ae3a7276d87ead3e95eb80 + +include $(INCLUDE_DIR)/kernel.mk +include $(INCLUDE_DIR)/package.mk + +# v1.0 is for Akronite +# v2.0 is for Hawkeye/Cypress/Maple +ifneq (, $(findstring $(BOARD), ipq50xx ipq60xx ipq807x)) +NSS_CRYPTO_DIR:=v2.0 +else +NSS_CRYPTO_DIR:=v1.0 +endif + +define KernelPackage/qca-nss-crypto + SECTION:=kernel + CATEGORY:=Kernel modules + SUBMENU:=Network Devices + TITLE:=Kernel driver for NSS crypto driver + FILES:= \ + $(PKG_BUILD_DIR)/$(NSS_CRYPTO_DIR)/src/qca-nss-crypto.ko \ + $(PKG_BUILD_DIR)/$(NSS_CRYPTO_DIR)/tool/qca-nss-crypto-tool.ko + DEPENDS:=@TARGET_ipq60xx +kmod-qca-nss-drv + AUTOLOAD:=$(call AutoLoad,52,qca-nss-crypto) +endef + +define KernelPackage/qca-nss-crypto/Description +This package contains a NSS crypto driver for QCA chipset +endef + +define Build/InstallDev + $(INSTALL_DIR) $(1)/usr/include/qca-nss-crypto + $(CP) $(PKG_BUILD_DIR)/$(NSS_CRYPTO_DIR)/include/* $(1)/usr/include/qca-nss-crypto +endef + +EXTRA_CFLAGS+= \ + -DCONFIG_NSS_DEBUG_LEVEL=4 \ + -I$(STAGING_DIR)/usr/include/qca-nss-crypto \ + -I$(STAGING_DIR)/usr/include/qca-nss-drv \ + -I$(PKG_BUILD_DIR)/$(NSS_CRYPTO_DIR)/include \ + -I$(PKG_BUILD_DIR)/$(NSS_CRYPTO_DIR)/src + +define Build/Compile + $(MAKE) -C "$(LINUX_DIR)" \ + NSS_CRYPTO_DIR=$(NSS_CRYPTO_DIR) \ + CROSS_COMPILE="$(TARGET_CROSS)" \ + EXTRA_CFLAGS="$(EXTRA_CFLAGS)" \ + $(KERNEL_MAKE_FLAGS) \ + M="$(PKG_BUILD_DIR)" \ + SoC="$(BOARD)_64" \ + modules +endef + +$(eval $(call KernelPackage,qca-nss-crypto)) diff --git a/root/package/qca/qca-nss-dp/Makefile b/root/package/qca/qca-nss-dp/Makefile new file mode 100644 index 00000000..4f85a43d --- /dev/null +++ b/root/package/qca/qca-nss-dp/Makefile @@ -0,0 +1,54 @@ +include $(TOPDIR)/rules.mk + +PKG_NAME:=qca-nss-dp +PKG_RELEASE:=1 + +PKG_SOURCE_PROTO:=git +PKG_SOURCE_DATE:=2021-03-26 +PKG_SOURCE_URL:=https://source.codeaurora.org/quic/qsdk/oss/lklm/nss-dp +PKG_SOURCE_VERSION:=e0c89348d5ad99559ce2fbe15d37b3b5bc66aa03 +PKG_MIRROR_HASH:=f369f0c3b33b5f4ad6d0a6ad6ac5495f63c9ecaf94e4e7fa345169f3e44fcf45 + +include $(INCLUDE_DIR)/kernel.mk +include $(INCLUDE_DIR)/package.mk + +define KernelPackage/qca-nss-dp + SECTION:=kernel + CATEGORY:=Kernel modules + SUBMENU:=Network Devices + DEPENDS:=@TARGET_ipq60xx +kmod-qca-ssdk + TITLE:=Kernel driver for NSS data plane + FILES:=$(PKG_BUILD_DIR)/qca-nss-dp.ko + AUTOLOAD:=$(call AutoLoad,31,qca-nss-dp) +endef + +define KernelPackage/qca-nss-dp/Description +This package contains a NSS data plane driver for QCA chipset +endef + +define Build/InstallDev + mkdir -p $(1)/usr/include/qca-nss-dp + $(CP) $(PKG_BUILD_DIR)/exports/* $(1)/usr/include/qca-nss-dp/ +endef + +EXTRA_CFLAGS+= \ + -I$(STAGING_DIR)/usr/include/qca-ssdk + +NSS_DP_HAL_DIR:=$(PKG_BUILD_DIR)/hal + +define Build/Configure + $(LN) $(NSS_DP_HAL_DIR)/arch/$(BOARD)/nss_$(BOARD).h \ + $(PKG_BUILD_DIR)/exports/nss_dp_arch.h +endef + +define Build/Compile + $(MAKE) -C "$(LINUX_DIR)" \ + CROSS_COMPILE="$(TARGET_CROSS)" \ + EXTRA_CFLAGS="$(EXTRA_CFLAGS)" \ + $(KERNEL_MAKE_FLAGS) \ + M="$(PKG_BUILD_DIR)" \ + SoC="$(BOARD)_64" \ + modules +endef + +$(eval $(call KernelPackage,qca-nss-dp)) diff --git a/root/package/qca/qca-nss-drv/Makefile b/root/package/qca/qca-nss-drv/Makefile new file mode 100644 index 00000000..4fce341e --- /dev/null +++ b/root/package/qca/qca-nss-drv/Makefile @@ -0,0 +1,109 @@ +include $(TOPDIR)/rules.mk + +PKG_NAME:=qca-nss-drv +PKG_RELEASE:=1 + +PKG_SOURCE_PROTO:=git +PKG_SOURCE_DATE:=2021-11-12 +PKG_SOURCE_URL:=https://source.codeaurora.org/quic/qsdk/oss/lklm/nss-drv +PKG_SOURCE_VERSION:=5d47e4261bae9bc343ce50402011ee64729684f9 +PKG_MIRROR_HASH:=832a6f3af2d37f3e38231a3df41d5d7d9c7abc4975c0445f962146e8950a9433 + +include $(INCLUDE_DIR)/kernel.mk +include $(INCLUDE_DIR)/package.mk + +define KernelPackage/qca-nss-drv + SECTION:=kernel + CATEGORY:=Kernel modules + SUBMENU:=Network Devices + DEPENDS:=@TARGET_ipq60xx +kmod-qca-nss-dp + TITLE:=Kernel driver for NSS (core driver) + FILES:=$(PKG_BUILD_DIR)/qca-nss-drv.ko + AUTOLOAD:=$(call AutoLoad,32,qca-nss-drv) +endef + +define KernelPackage/qca-nss-drv/install + $(INSTALL_DIR) $(1)/lib/debug + $(INSTALL_DIR) $(1)/etc/init.d + $(INSTALL_DIR) $(1)/etc/sysctl.d + $(INSTALL_DIR) $(1)/etc/config + + $(INSTALL_BIN) ./files/qca-nss-drv.debug $(1)/lib/debug/qca-nss-drv + $(INSTALL_BIN) ./files/qca-nss-drv.init $(1)/etc/init.d/qca-nss-drv + $(INSTALL_BIN) ./files/qca-nss-drv.sysctl $(1)/etc/sysctl.d/qca-nss-drv.conf + $(INSTALL_BIN) ./files/qca-nss-drv.conf $(1)/etc/config/nss +endef + +define KernelPackage/qca-nss-drv/Description +This package contains a NSS driver for QCA chipset +endef + +define Build/InstallDev + mkdir -p $(1)/usr/include/qca-nss-drv + $(CP) $(PKG_BUILD_DIR)/exports/* $(1)/usr/include/qca-nss-drv/ +endef + +EXTRA_CFLAGS+= -Wno-unused-variable \ + -I$(STAGING_DIR)/usr/include/qca-nss-dp \ + -I$(STAGING_DIR)/usr/include/qca-ssdk + +ifeq ($(CONFIG_KERNEL_IPQ_MEM_PROFILE),256) +EXTRA_CFLAGS+= -DNSS_MEM_PROFILE_LOW +endif + +ifeq ($(CONFIG_KERNEL_IPQ_MEM_PROFILE),512) +EXTRA_CFLAGS+= -DNSS_MEM_PROFILE_MEDIUM +endif + +ifeq ($(CONFIG_KERNEL_SKB_FIXED_SIZE_2K),y) +EXTRA_CFLAGS+= -DNSS_SKB_FIXED_SIZE_2K +endif + +DRV_MAKE_OPTS:= +ifeq ($(CONFIG_KERNEL_IPQ_MEM_PROFILE),256) +DRV_MAKE_OPTS+= \ + NSS_DRV_C2C_ENABLE=n \ + NSS_DRV_CAPWAP_ENABLE=n \ + NSS_DRV_CLMAP_ENABLE=n \ + NSS_DRV_CRYPTO_ENABLE=n \ + NSS_DRV_DTLS_ENABLE=n \ + NSS_DRV_GRE_ENABLE=n \ + NSS_DRV_GRE_REDIR_ENABLE=n \ + NSS_DRV_GRE_TUNNEL_ENABLE=n \ + NSS_DRV_IGS_ENABLE=n \ + NSS_DRV_IPSEC_ENABLE=n \ + NSS_DRV_LAG_ENABLE=n \ + NSS_DRV_L2TP_ENABLE=n \ + NSS_DRV_MAPT_ENABLE=n \ + NSS_DRV_OAM_ENABLE=n \ + NSS_DRV_PPTP_ENABLE=n \ + NSS_DRV_PORTID_ENABLE=n \ + NSS_DRV_PVXLAN_ENABLE=n \ + NSS_DRV_QRFS_ENABLE=n \ + NSS_DRV_QVPN_ENABLE=n \ + NSS_DRV_RMNET_ENABLE=n \ + NSS_DRV_SHAPER_ENABLE=n \ + NSS_DRV_SJACK_ENABLE=n \ + NSS_DRV_TLS_ENABLE=n \ + NSS_DRV_TRUSTSEC_ENABLE=n \ + NSS_DRV_TSTAMP_ENABLE=n \ + NSS_DRV_TUN6RD_ENABLE=n \ + NSS_DRV_TUNIPIP6_ENABLE=n \ + NSS_DRV_VXLAN_ENABLE=n +endif + +define Build/Configure + $(LN) arch/nss_$(BOARD)_64.h $(PKG_BUILD_DIR)/exports/nss_arch.h +endef + +define Build/Compile + $(MAKE) -C "$(LINUX_DIR)" $(strip $(DRV_MAKE_OPTS)) \ + CROSS_COMPILE="$(TARGET_CROSS)" \ + EXTRA_CFLAGS="$(EXTRA_CFLAGS)" \ + $(KERNEL_MAKE_FLAGS) \ + M="$(PKG_BUILD_DIR)" \ + SoC="$(BOARD)_64" \ + modules +endef + +$(eval $(call KernelPackage,qca-nss-drv)) diff --git a/root/package/qca/qca-nss-drv/files/qca-nss-drv.conf b/root/package/qca/qca-nss-drv/files/qca-nss-drv.conf new file mode 100644 index 00000000..a8a1fbf4 --- /dev/null +++ b/root/package/qca/qca-nss-drv/files/qca-nss-drv.conf @@ -0,0 +1,6 @@ +config nss_firmware 'qca_nss_0' + +config nss_firmware 'qca_nss_1' + +config general + option enable_rps '1' diff --git a/root/package/qca/qca-nss-drv/files/qca-nss-drv.debug b/root/package/qca/qca-nss-drv/files/qca-nss-drv.debug new file mode 100644 index 00000000..5d435c3a --- /dev/null +++ b/root/package/qca/qca-nss-drv/files/qca-nss-drv.debug @@ -0,0 +1,26 @@ +#!/bin/sh /sbin/sysdebug +# +# Copyright (c) 2015-2016, The Linux Foundation. All rights reserved. +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +# + +log cat /sys/kernel/debug/qca-nss-drv/stats/pppoe +log cat /sys/kernel/debug/qca-nss-drv/stats/n2h +log cat /sys/kernel/debug/qca-nss-drv/stats/ipv6 +log cat /sys/kernel/debug/qca-nss-drv/stats/ipv4 +log cat /sys/kernel/debug/qca-nss-drv/stats/gmac +log cat /sys/kernel/debug/qca-nss-drv/stats/drv +log cat /sys/kernel/debug/qca-nss-drv/stats/wifi +log cat /sys/kernel/debug/qca-nss-drv/stats/wifi_if +log cat /sys/kernel/debug/qca-nss-drv/stats/eth_rx diff --git a/root/package/qca/qca-nss-drv/files/qca-nss-drv.hotplug b/root/package/qca/qca-nss-drv/files/qca-nss-drv.hotplug new file mode 100644 index 00000000..1e481383 --- /dev/null +++ b/root/package/qca/qca-nss-drv/files/qca-nss-drv.hotplug @@ -0,0 +1,70 @@ +#!/bin/sh +# +# Copyright (c) 2015-2016, The Linux Foundation. All rights reserved. +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +# + +KERNEL=`uname -r` +case "${KERNEL}" in + 3.4*) + select_or_load=load_nss_fw + ;; + *) + select_or_load=select_nss_fw + ;; +esac + +load_nss_fw () { + ls -l $1 | awk ' { print $9,$5 } '> /dev/console + echo 1 > /sys/class/firmware/$DEVICENAME/loading + cat $1 > /sys/class/firmware/$DEVICENAME/data + echo 0 > /sys/class/firmware/$DEVICENAME/loading +} + +select_nss_fw () { + rm -f /lib/firmware/$DEVICENAME + ln -s $1 /lib/firmware/$DEVICENAME + ls -l /lib/firmware/$DEVICENAME | awk ' { print $9,$5 } '> /dev/console +} + +[ "$ACTION" != "add" ] && exit + +# dev name for UCI, since it doesn't let you use . or - +SDEVNAME=$(echo ${DEVICENAME} | sed s/[.-]/_/g) + +SELECTED_FW=$(uci get nss.${SDEVNAME}.firmware 2>/dev/null) +[ -e "${SELECTED_FW}" ] && { + $select_or_load ${SELECTED_FW} + exit +} + +case $DEVICENAME in + qca-nss0* | qca-nss.0*) + if [ -e /lib/firmware/qca-nss0-enterprise.bin ] ; then + $select_or_load /lib/firmware/qca-nss0-enterprise.bin + else + $select_or_load /lib/firmware/qca-nss0-retail.bin + fi + exit + ;; + qca-nss1* | qca-nss.1*) + if [ -e /lib/firmware/qca-nss1-enterprise.bin ] ; then + $select_or_load /lib/firmware/qca-nss1-enterprise.bin + else + $select_or_load /lib/firmware/qca-nss1-retail.bin + fi + exit + ;; +esac + diff --git a/root/package/qca/qca-nss-drv/files/qca-nss-drv.init b/root/package/qca/qca-nss-drv/files/qca-nss-drv.init new file mode 100644 index 00000000..de12cb6d --- /dev/null +++ b/root/package/qca/qca-nss-drv/files/qca-nss-drv.init @@ -0,0 +1,50 @@ +#!/bin/sh /etc/rc.common +# +# Copyright (c) 2015-2017, The Linux Foundation. All rights reserved. +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +# + +START=70 + +enable_rps() { + irq_nss_rps=`grep nss_queue1 /proc/interrupts | cut -d ':' -f 1 | tr -d ' '` + for entry in $irq_nss_rps + do + echo 2 > /proc/irq/$entry/smp_affinity + done + + irq_nss_rps=`grep nss_queue2 /proc/interrupts | cut -d ':' -f 1 | tr -d ' '` + for entry in $irq_nss_rps + do + echo 4 > /proc/irq/$entry/smp_affinity + done + + irq_nss_rps=`grep nss_queue3 /proc/interrupts | cut -d ':' -f 1 | tr -d ' '` + for entry in $irq_nss_rps + do + echo 8 > /proc/irq/$entry/smp_affinity + done + + # Enable NSS RPS + sysctl -w dev.nss.rps.enable=1 >/dev/null 2>/dev/null + +} + + +start() { + local rps_enabled="$(uci_get nss @general[0] enable_rps)" + if [ "$rps_enabled" -eq 1 ]; then + enable_rps + fi +} diff --git a/root/package/qca/qca-nss-drv/files/qca-nss-drv.sysctl b/root/package/qca/qca-nss-drv/files/qca-nss-drv.sysctl new file mode 100644 index 00000000..0276bba1 --- /dev/null +++ b/root/package/qca/qca-nss-drv/files/qca-nss-drv.sysctl @@ -0,0 +1,3 @@ +# Default Number of connection configuration +dev.nss.ipv4cfg.ipv4_conn=4096 +dev.nss.ipv6cfg.ipv6_conn=4096 diff --git a/root/package/qca/qca-nss-ecm/Makefile b/root/package/qca/qca-nss-ecm/Makefile new file mode 100644 index 00000000..a32cc87c --- /dev/null +++ b/root/package/qca/qca-nss-ecm/Makefile @@ -0,0 +1,90 @@ +include $(TOPDIR)/rules.mk + +PKG_NAME:=qca-nss-ecm +PKG_RELEASE:=1 + +PKG_SOURCE_PROTO:=git +PKG_SOURCE_DATE:=2021-11-09 +PKG_SOURCE_URL:=https://source.codeaurora.org/quic/qsdk/oss/lklm/qca-nss-ecm +PKG_SOURCE_VERSION:=33122aebdd54803b5817065060289d6af5dfd3ba +PKG_MIRROR_HASH:=a857db70804e19a20efef2d1ad519fe26bce3a45decd6ae3bb4a26d53f464a8d + +include $(INCLUDE_DIR)/kernel.mk +include $(INCLUDE_DIR)/package.mk + +define KernelPackage/qca-nss-ecm + SECTION:=kernel + CATEGORY:=Kernel modules + SUBMENU:=Network Support + DEPENDS:=@TARGET_ipq60xx +iptables-mod-extra +iptables-mod-physdev \ + +kmod-ipt-conntrack +kmod-ipt-physdev +kmod-pppoe +kmod-qca-nss-drv + TITLE:=QCA NSS Enhanced Connection Manager (ECM) + FILES:=$(PKG_BUILD_DIR)/*.ko + KCONFIG:= \ + CONFIG_BRIDGE_NETFILTER=y \ + CONFIG_NF_CONNTRACK_EVENTS=y \ + CONFIG_NF_CONNTRACK_CHAIN_EVENTS=y \ + CONFIG_NF_CONNTRACK_DSCPREMARK_EXT=n +endef + +define KernelPackage/qca-nss-ecm/Description +This package contains the QCA NSS Enhanced Connection Manager +endef + +define KernelPackage/qca-nss-ecm/install + $(INSTALL_DIR) $(1)/etc/config + $(INSTALL_DATA) ./files/qca-nss-ecm.uci $(1)/etc/config/ecm + $(INSTALL_DIR) $(1)/etc/init.d + $(INSTALL_BIN) ./files/qca-nss-ecm.init $(1)/etc/init.d/qca-nss-ecm + $(INSTALL_DIR) $(1)/etc/firewall.d + $(INSTALL_DATA) ./files/qca-nss-ecm.firewall $(1)/etc/firewall.d/qca-nss-ecm + $(INSTALL_DIR) $(1)/etc/sysctl.d + $(INSTALL_BIN) ./files/qca-nss-ecm.sysctl $(1)/etc/sysctl.d/qca-nss-ecm.conf + $(INSTALL_DIR) $(1)/etc/uci-defaults + $(INSTALL_DATA) ./files/qca-nss-ecm.defaults $(1)/etc/uci-defaults/99-qca-nss-ecm + $(INSTALL_DIR) $(1)/usr/bin + $(INSTALL_BIN) ./files/ecm_dump.sh $(1)/usr/bin/ + $(INSTALL_DIR) $(1)/lib/netifd/offload + $(INSTALL_BIN) ./files/on-demand-down $(1)/lib/netifd/offload/on-demand-down +endef + +EXTRA_CFLAGS+=-I$(STAGING_DIR)/usr/include/qca-nss-drv + +ifneq (, $(findstring $(BOARD), ipq60xx ipq807x)) +ECM_MAKE_OPTS+= \ + ECM_BAND_STEERING_ENABLE=n \ + ECM_CLASSIFIER_DSCP_ENABLE=n \ + ECM_CLASSIFIER_HYFI_ENABLE=n \ + ECM_CLASSIFIER_MARK_ENABLE=n \ + ECM_CLASSIFIER_PCC_ENABLE=n \ + ECM_FRONT_END_NSS_ENABLE=y \ + ECM_INTERFACE_BOND_ENABLE=n \ + ECM_INTERFACE_GRE_TAP_ENABLE=n \ + ECM_INTERFACE_GRE_TUN_ENABLE=n \ + ECM_INTERFACE_IPSEC_ENABLE=n \ + ECM_INTERFACE_L2TPV2_ENABLE=n \ + ECM_INTERFACE_PPPOE_ENABLE=y \ + ECM_INTERFACE_PPTP_ENABLE=n \ + ECM_INTERFACE_RAWIP_ENABLE=n \ + ECM_INTERFACE_SIT_ENABLE=n \ + ECM_INTERFACE_TUNIPIP6_ENABLE=n \ + ECM_INTERFACE_VLAN_ENABLE=n \ + ECM_MULTICAST_ENABLE=n +endif + +define Build/InstallDev + mkdir -p $(1)/usr/include/qca-nss-ecm + $(CP) $(PKG_BUILD_DIR)/exports/* $(1)/usr/include/qca-nss-ecm +endef + +define Build/Compile + $(MAKE) -C "$(LINUX_DIR)" $(strip $(ECM_MAKE_OPTS)) \ + CROSS_COMPILE="$(TARGET_CROSS)" \ + EXTRA_CFLAGS="$(EXTRA_CFLAGS)" \ + $(KERNEL_MAKE_FLAGS) \ + M="$(PKG_BUILD_DIR)" \ + SoC="$(BOARD)_64" \ + modules +endef + +$(eval $(call KernelPackage,qca-nss-ecm)) diff --git a/root/package/qca/qca-nss-ecm/files/ecm_dump.sh b/root/package/qca/qca-nss-ecm/files/ecm_dump.sh new file mode 100755 index 00000000..dbf7de75 --- /dev/null +++ b/root/package/qca/qca-nss-ecm/files/ecm_dump.sh @@ -0,0 +1,95 @@ +#!/bin/sh +# +# Copyright (c) 2015-2016, The Linux Foundation. All rights reserved. +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +# + +ECM_MODULE=${1:-ecm_state} +MOUNT_ROOT=/dev/ecm + +# +# usage: ecm_dump.sh [module=ecm_db] +# +# with no parameters, ecm_dump.sh will attempt to mount the +# ecm_db state file and cat its contents. +# +# example with a parameter: ecm_dump.sh ecm_classifier_default +# +# this will cause ecm_dump to attempt to find and mount the state +# file for the ecm_classifier_default module, and if successful +# cat the contents. +# + +# this is one of the state files, which happens to be the +# last module started in ecm +ECM_STATE=/sys/kernel/debug/ecm/ecm_state/state_dev_major + +# tests to see if ECM is up and ready to receive commands. +# returns 0 if ECM is fully up and ready, else 1 +ecm_is_ready() { + if [ ! -e "${ECM_STATE}" ] + then + return 1 + fi + return 0 +} + +# +# module_state_mount(module_name) +# Mounts the state file of the module, if supported +# +module_state_mount() { + local module_name=$1 + local mount_dir=$2 + local state_file="/sys/kernel/debug/ecm/${module_name}/state_dev_major" + + if [ -e "${mount_dir}/${module_name}" ] + then + # already mounted + return 0 + fi + + #echo "Mount state file for $module_name ..." + if [ ! -e "$state_file" ] + then + #echo "... $module_name does not support state" + return 1 + fi + + local major="`cat $state_file`" + #echo "... Mounting state $state_file with major: $major" + mknod "${mount_dir}/${module_name}" c $major 0 +} + +# +# main +# +ecm_is_ready || { + #echo "ECM is not running" + exit 1 +} + +# all state files are mounted under MOUNT_ROOT, so make sure it exists +mkdir -p ${MOUNT_ROOT} + +# +# attempt to mount state files for the requested module and cat it +# if the mount succeeded +# +module_state_mount ${ECM_MODULE} ${MOUNT_ROOT} && { + cat ${MOUNT_ROOT}/${ECM_MODULE} + exit 0 +} + +exit 2 diff --git a/root/package/qca/qca-nss-ecm/files/on-demand-down b/root/package/qca/qca-nss-ecm/files/on-demand-down new file mode 100644 index 00000000..02d708e0 --- /dev/null +++ b/root/package/qca/qca-nss-ecm/files/on-demand-down @@ -0,0 +1,6 @@ +#!/bin/sh +# Copyright (c) 2016 The Linux Foundation. All rights reserved. + +[ -e "/sys/kernel/debug/ecm/ecm_db/defunct_all" ] && { + echo 1 > /sys/kernel/debug/ecm/ecm_db/defunct_all +} diff --git a/root/package/qca/qca-nss-ecm/files/qca-nss-ecm.defaults b/root/package/qca/qca-nss-ecm/files/qca-nss-ecm.defaults new file mode 100644 index 00000000..308e265c --- /dev/null +++ b/root/package/qca/qca-nss-ecm/files/qca-nss-ecm.defaults @@ -0,0 +1,28 @@ +#!/bin/sh +# +# Copyright (c) 2015-2016, The Linux Foundation. All rights reserved. +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +# + +uci -q batch << EOF + delete firewall.qcanssecm + set firewall.qcanssecm=include + set firewall.qcanssecm.type=script + set firewall.qcanssecm.path=/etc/firewall.d/qca-nss-ecm + set firewall.qcanssecm.family=any + set firewall.qcanssecm.reload=1 + commit firewall +EOF + +exit 0 diff --git a/root/package/qca/qca-nss-ecm/files/qca-nss-ecm.firewall b/root/package/qca/qca-nss-ecm/files/qca-nss-ecm.firewall new file mode 100644 index 00000000..24c64def --- /dev/null +++ b/root/package/qca/qca-nss-ecm/files/qca-nss-ecm.firewall @@ -0,0 +1,18 @@ +#!/bin/sh +# +# Copyright (c) 2015-2016, The Linux Foundation. All rights reserved. +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +# + +iptables -A FORWARD -m physdev --physdev-is-bridged -j ACCEPT diff --git a/root/package/qca/qca-nss-ecm/files/qca-nss-ecm.init b/root/package/qca/qca-nss-ecm/files/qca-nss-ecm.init new file mode 100644 index 00000000..48862dfb --- /dev/null +++ b/root/package/qca/qca-nss-ecm/files/qca-nss-ecm.init @@ -0,0 +1,139 @@ +#!/bin/sh /etc/rc.common +# +# Copyright (c) 2014, 2019-2020 The Linux Foundation. All rights reserved. +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# The shebang above has an extra space intentially to avoid having +# openwrt build scripts automatically enable this package starting +# at boot. + +START=19 + +get_front_end_mode() { + config_load "ecm" + config_get front_end global acceleration_engine "auto" + + case $front_end in + auto) + echo '0' + ;; + nss) + echo '1' + ;; + sfe) + echo '2' + ;; + *) + echo 'uci_option_acceleration_engine is invalid' + esac +} + +support_bridge() { + #NSS support bridge acceleration + [ -d /sys/kernel/debug/ecm/ecm_nss_ipv4 ] && return 0 + #SFE doesn't support bridge acceleration + [ -d /sys/kernel/debug/ecm/ecm_sfe_ipv4 ] && return 1 +} + +load_sfe() { + local kernel_version=$(uname -r) + + [ -e "/lib/modules/$kernel_version/shortcut-fe.ko" ] && { + [ -d /sys/module/shortcut_fe ] || insmod shortcut-fe + } + + [ -e "/lib/modules/$kernel_version/shortcut-fe-ipv6.ko" ] && { + [ -d /sys/module/shortcut_fe_ipv6 ] || insmod shortcut-fe-ipv6 + } + + [ -e "/lib/modules/$kernel_version/shortcut-fe-drv.ko" ] && { + [ -d /sys/module/shortcut_fe_drv ] || insmod shortcut-fe-drv + } +} + +load_ecm() { + [ -d /sys/module/ecm ] || { + [ ! -e /proc/device-tree/MP_256 ] && load_sfe + insmod ecm front_end_selection=$(get_front_end_mode) + } + + support_bridge && { + sysctl -w net.bridge.bridge-nf-call-ip6tables=1 + sysctl -w net.bridge.bridge-nf-call-iptables=1 + } +} + +unload_ecm() { + sysctl -w net.bridge.bridge-nf-call-ip6tables=0 + sysctl -w net.bridge.bridge-nf-call-iptables=0 + + if [ -d /sys/module/ecm ]; then + # + # Stop ECM frontends + # + echo 1 > /sys/kernel/debug/ecm/front_end_ipv4_stop + echo 1 > /sys/kernel/debug/ecm/front_end_ipv6_stop + + # + # Defunct the connections + # + echo 1 > /sys/kernel/debug/ecm/ecm_db/defunct_all + sleep 5; + + rmmod ecm + sleep 1 + fi +} + +start() { + # If SFE CM is loaded, return. + if [ -d /sys/module/shortcut_fe_cm ]; then + echo "shortcut_fe CM is loaded, unload it first" + echo "cmd: /etc/init.d/shortcut_fe stop" + return + fi + + load_ecm + + # If the acceleration engine is NSS, enable wifi redirect. + [ -d /sys/kernel/debug/ecm/ecm_nss_ipv4 ] && sysctl -w dev.nss.general.redirect=1 + + support_bridge && { + echo 'net.bridge.bridge-nf-call-ip6tables=1' >> /etc/sysctl.d/qca-nss-ecm.conf + echo 'net.bridge.bridge-nf-call-iptables=1' >> /etc/sysctl.d/qca-nss-ecm.conf + } + + if [ -d /sys/module/qca_ovsmgr ]; then + insmod ecm_ovs + fi +} + +stop() { + # If ECM is already not loaded, just return. + if [ ! -d /sys/module/ecm ]; then + return + fi + + # If the acceleration engine is NSS, disable wifi redirect. + [ -d /sys/kernel/debug/ecm/ecm_nss_ipv4 ] && sysctl -w dev.nss.general.redirect=0 + + sed '/net.bridge.bridge-nf-call-ip6tables=1/d' -i /etc/sysctl.d/qca-nss-ecm.conf + sed '/net.bridge.bridge-nf-call-iptables=1/d' -i /etc/sysctl.d/qca-nss-ecm.conf + + if [ -d /sys/module/ecm_ovs ]; then + rmmod ecm_ovs + fi + + unload_ecm +} diff --git a/root/package/qca/qca-nss-ecm/files/qca-nss-ecm.sysctl b/root/package/qca/qca-nss-ecm/files/qca-nss-ecm.sysctl new file mode 100644 index 00000000..1a3d76b1 --- /dev/null +++ b/root/package/qca/qca-nss-ecm/files/qca-nss-ecm.sysctl @@ -0,0 +1,2 @@ +# nf_conntrack_tcp_no_window_check is 0 by default, set it to 1 +net.netfilter.nf_conntrack_tcp_no_window_check=1 diff --git a/root/package/qca/qca-nss-ecm/files/qca-nss-ecm.uci b/root/package/qca/qca-nss-ecm/files/qca-nss-ecm.uci new file mode 100644 index 00000000..4f2de687 --- /dev/null +++ b/root/package/qca/qca-nss-ecm/files/qca-nss-ecm.uci @@ -0,0 +1,2 @@ +config ecm 'global' + option acceleration_engine 'auto' diff --git a/root/package/qca/qca-nss-ecm/patches/200-resolve-high-load.patch b/root/package/qca/qca-nss-ecm/patches/200-resolve-high-load.patch new file mode 100644 index 00000000..253b450a --- /dev/null +++ b/root/package/qca/qca-nss-ecm/patches/200-resolve-high-load.patch @@ -0,0 +1,61 @@ +From 6924b71ed809b37fffd74d6428a8ca83e5919746 Mon Sep 17 00:00:00 2001 +From: Dirk Buchwalder +Date: Sun, 27 Jun 2021 16:52:39 +0200 +Subject: [PATCH] qca-nss-ecm: resolve the cpu high load regarding ecm + +If using ECM, cpu load goes up (around 1.0) and stucks there. +This is due to using uninterruptible sleep function, +the patch changes this to interruptible sleep function. + +Signed-off-by: Dirk Buchwalder +--- + frontends/nss/ecm_nss_ipv4.c | 4 ++-- + frontends/nss/ecm_nss_ipv6.c | 4 ++-- + 2 files changed, 4 insertions(+), 4 deletions(-) + +diff --git a/frontends/nss/ecm_nss_ipv4.c b/frontends/nss/ecm_nss_ipv4.c +index e00553c..94b39cd 100644 +--- a/frontends/nss/ecm_nss_ipv4.c ++++ b/frontends/nss/ecm_nss_ipv4.c +@@ -2471,7 +2471,7 @@ static void ecm_nss_ipv4_stats_sync_req_work(struct work_struct *work) + } + spin_unlock_bh(&ecm_nss_ipv4_lock); + +- usleep_range(ECM_NSS_IPV4_STATS_SYNC_UDELAY - 100, ECM_NSS_IPV4_STATS_SYNC_UDELAY); ++ msleep_interruptible(ECM_NSS_IPV4_STATS_SYNC_UDELAY / 1000); + + /* + * If index is 0, we are starting a new round, but if we still have time remain +@@ -2485,7 +2485,7 @@ static void ecm_nss_ipv4_stats_sync_req_work(struct work_struct *work) + } + + if (time_after(ecm_nss_ipv4_next_req_time, current_jiffies)) { +- msleep(jiffies_to_msecs(ecm_nss_ipv4_next_req_time - current_jiffies)); ++ msleep_interruptible(jiffies_to_msecs(ecm_nss_ipv4_next_req_time - current_jiffies)); + } + ecm_nss_ipv4_roll_check_jiffies = jiffies; + ecm_nss_ipv4_next_req_time = ecm_nss_ipv4_roll_check_jiffies + ECM_NSS_IPV4_STATS_SYNC_PERIOD; +diff --git a/frontends/nss/ecm_nss_ipv6.c b/frontends/nss/ecm_nss_ipv6.c +index 82e739f..30af050 100644 +--- a/frontends/nss/ecm_nss_ipv6.c ++++ b/frontends/nss/ecm_nss_ipv6.c +@@ -2210,7 +2210,7 @@ static void ecm_nss_ipv6_stats_sync_req_work(struct work_struct *work) + } + spin_unlock_bh(&ecm_nss_ipv6_lock); + +- usleep_range(ECM_NSS_IPV6_STATS_SYNC_UDELAY - 100, ECM_NSS_IPV6_STATS_SYNC_UDELAY); ++ msleep_interruptible(ECM_NSS_IPV6_STATS_SYNC_UDELAY / 1000); + + /* + * If index is 0, we are starting a new round, but if we still have time remain +@@ -2224,7 +2224,7 @@ static void ecm_nss_ipv6_stats_sync_req_work(struct work_struct *work) + } + + if (time_after(ecm_nss_ipv6_next_req_time, current_jiffies)) { +- msleep(jiffies_to_msecs(ecm_nss_ipv6_next_req_time - current_jiffies)); ++ msleep_interruptible(jiffies_to_msecs(ecm_nss_ipv6_next_req_time - current_jiffies)); + } + ecm_nss_ipv6_roll_check_jiffies = jiffies; + ecm_nss_ipv6_next_req_time = ecm_nss_ipv6_roll_check_jiffies + ECM_NSS_IPV6_STATS_SYNC_PERIOD; +-- +2.31.1 diff --git a/root/package/qca/qca-nss-fw-eip/Makefile b/root/package/qca/qca-nss-fw-eip/Makefile new file mode 100644 index 00000000..104fe5c1 --- /dev/null +++ b/root/package/qca/qca-nss-fw-eip/Makefile @@ -0,0 +1,25 @@ +include $(TOPDIR)/rules.mk + +PKG_NAME:=nss-eip-firmware +PKG_VERSION=2.5.7 +PKG_RELEASE:=1 + +include $(INCLUDE_DIR)/package.mk + +define Package/nss-eip-firmware + SECTION:=firmware + CATEGORY:=Firmware + TITLE:=NSS EIP-197 firmware + DEPENDS:=@(TARGET_ipq60xx||TARGET_ipq807x) +endef + +define Build/Compile + true +endef + +define Package/nss-eip-firmware/install + $(INSTALL_DIR) $(1)/lib/firmware/ + $(CP) ./files/$(BOARD)/* $(1)/lib/firmware/ +endef + +$(eval $(call BuildPackage,nss-eip-firmware)) diff --git a/root/package/qca/qca-nss-fw-eip/files/ipq60xx/ifpp.bin b/root/package/qca/qca-nss-fw-eip/files/ipq60xx/ifpp.bin new file mode 100644 index 0000000000000000000000000000000000000000..033ad555266d6ddd9bd39a3af4fe9c6c5f631ce7 GIT binary patch literal 12272 zcmeHNe{dAnegE#>-rcTrpxx7*PLgrh`-O##;{#)SPz;L%SfXZpmW6|CoGu;PmEAU3 z;s#8ovEOkAx{L7m5|{)^;|g$X;>k?G;Pgj4ld~hYBq2pfnihL#dc}%OTiX@3GsH8F z>G%8g_H=iWFr;?>Xo1<;)7$sHKR@3e@9hGEu*6tIW$c=n<5$CH!DY&f#e9s##X^s| z6f!2r`iu{@tkVJd7MV#-akwPqId{=y~MBBKw^% zTXR$M2usEHxU4T0^SfXd+XVycY4##3=JCIBu?y0*bDpeQv#0LwO#Tn@>HMP=ng>}? zIc*K=kp5RLhm;U=MgCxrg)cO>3vi}gdjJFzAa~D37{WU%;PZRv?ke=a zpD7s=4AuoD7knNXO$%vsK!Z#d(V+3oXm_Hug@M^&wrg||-9Lder@IB$JqBRH+n-V& z0~d}d_xRBFz##J8;AkAI7n1mUoOEA@x?h-m!%IXLtc6Li=Hqo3FPsA)(eHQiLmT;+ z;f5T^hq7vg_tj^WM$;&a4};(`Ka`ny8{#Tm*b3iZ8S{ssU(X&He81_mb&WpNTpFWw z&nnCeAf6hmw;BeFXWN14^Pw=`^_OU07>=UMg8J9=8-2J8#y1^fe@kdkxIguki4UGx zqXTon8|C?L07Lmk7ID8Sf~i_XJvMwo7XaYfXp^aU{h*51Pr=1wfOwr`KP&UXc+me5 zdA&LXuRhLe9r7Z){R`TDWcW1a_hXbr+P|Ulo8txT{5tXAHa?teC12YZkb%Ksyp{lC zzlHauc)ta&Wnif|C(;R1{_~*U%Kj|VbZA5Bh%Q;r=_SNaEB=PWEiRJLlWsI~R#f+< z!5oH$86{}An&!#YF&%Ld>tLfd&ts_t(BNzSoQX`!I8mYDssu|dI?cWn^;ySaFNfN+ zaqo(NQ0Rx>%{QV>3-w~K_k7dHm$XyX0bTCX+AXoP<;Jurd?)=M>F_oq{YrY7c*C}R z(!T7M(!WYS>N=SDiR*mk(TsiHmp!>{U-stA2J!s1yIiX?`?CA8JH*D!oa_zRwb_l? z!R+U<`w){ylS>iL2Iew&$nRxdE0mKgDK`)Z9JgY+qko5StL9PEBL}mH3B>h6#=`S( zje&V=DO18sDtO_cc!<^`tr*g1&J8p-AGZ$b8uVzi<~SRU%PSxBx2~Mye?ooi-vpz7 zmY_c66U?V(iN^P5f$_2%j5~s0^af8@aeaYs*M3@uv;?2orfHa0{x|s(-)f$uZvt)A zit)AwbDP2P5s*}`A*pV7=&K>JiBfF9ZPzpm1(@Tp8N$0DIIS>7?pmevYZW}_|-qH+KGKuC-6K%(?v$xrH z{|c->M>dwt#Txm=u{o_Fzfs&x_^nw3BT(@z#u4|6tsBJQfnW%{%CvDubWTBFzY{wX zaf`Y=5WlRold#Y+mt)~=#r3gaK}Ia-h=mhXSg6i7Oj3EQ!9ZLDj~y#I+b&2F7RbI;IU)>FA!JB}APj{4 zl{7%Ah`(Nz;#kB}{@G413EibLBz zqc?%|eDVYvj>$$3_Efbts>P7(g;}3`J~__x*kt+isBA2r>I0N80}yXCaC_|@y0NZguO(YYD7KF>N;7SPqfnA5X(%?C#u=bbHS4#``aSL}X(^2)_zojSLmt`FF{Uheu@Y+$WPeDe9_a@|&W{5!Jf0SL!XE} zDZltUN=?3Ua*I>{={fF8=HuA|`B=;i^aeq~Z9Cp6SIKAZ_{9OmWgbwlj=UKLqWKi| zyNNLo3`sCoNz5#cMaQ33@;KHDPvd?~wyqDJMx0MohaJ8|n`p@+cLmwmp!%n{U*`3D zWJ^#e^#>u<-<}?5o2B3P_m!CVD0;0U54d?wt)ll)Yh=j`wvODz*SSXe-PN+xuf24< z_f+;K6+MwpHErC>x#hlf?ZfwleCo=-tLP};-lv`~)fmTaq#r(?v_CtPr}`4p_Xwpe zN2H_DN$Ka=_od|a_3lU9Uv)pZ{jc2bxK+=mJ@wbhk<7eXb(X)+O&ep&-)�{}%SggMK=Gpp}q8+qQ5F_yiFtF=fI&VzvPgYsU` z8P}WqM`+y61C$rmM7kwYdtLDF=e%-ut*Y=7a6aZUhhGq|7p9)L%xBIFn@{yx@`ntC zs0@dq&N@fVx2BQvy{yC6mj6=aB>D#SdYH2#(EXBsXM-N3nveM|!q0cOEQcZ<{G0MU z<#w{y%uP7jj8!J*I_HtNLZboe+8pr;0XD2d)dBZ7uvJQW{c2jwrFCLy8rRA zjq9JyhPKJE0D&Xbc&_H-#6mTF&kVIDcFOqgMErM>T+h0(E>9uTbN>UG3^sZrVg7dY z^XF*Kg4Y?uzEtLI*!Vxn&lpG!!av0W$`Ne#j`xMOXRt3 zeHiJ6MUEB34(g1*!#q!79wQxxsUDzzH|%hsY?zNxRGX1gpku z{p>ssrNyw%<1)pj#F1}Qbf?OGc>@Oaz%Vm!2XUJce1L|c*e%F<87ohx7E&G4&z!)W5*z0O!$930M zud9~biQj73>vIhGk?c?Oxy|chsDq^_KZX-#yT2F;H`_hJF!jiVvvQ-P1`v$U8oO;ZA2g zloqOALq8DTDQTfT2L^%T+3}S#x5igx{lVg9Co_icHAByI{Ljhg?>JxX97>P*6`FN9gW|x^ zBbckzqcw-PLm6x7+VYFuiCR8g-J^9PzPBNp5FL_VsOnK4xo8e&F%i~WcJ5a3U1YEP zOfDxsBh$4(-kaNt_h;ls<=@9~{8e*JH!pyDTM*>e43F9ju$=oOhVM-2d_to4rm$zl zVceru&okU=3*}`IYsxJ%+wEKctzA>CpK2}c&+#mHZGdP%=51DA0;;~HptUsjO{7^nUJO_MHU#y{c)kVrtw6_dL!VbF>dCV&=}Sw7{@!3Q zoUpodJ~!_&7g{Uvch!8f8DtG`;rALo$u-ttNjl(|Wc37QsXge^+m8Vxbhi6!NyGI3 z(1AMOI*UXd@iPp@(JKH^0YC}2R8#Lm;+|Y(+Y)z69pyxDC#dC2~-K7}wr_HV6xvq9hP zKx0$l4>f6Jo6uniI=!fY|aBdqkXfvl6QzV72b>_*S zXbp8^3Mhi}ya9jv>Yg{^8s|BFzoDJBzNKT|1n5gHFFhl9ePudB_&pAdB@yd%7mABr zyYh;e#CJkV>3gTt;^w9J?MO9tym+PuutNGK(h(%3neL5}@Gjlml8fIx<-1pMy+-45 z`L2}58}QzUV;xpTCm%T9${aJ)~=T8TBN(u5AT;Q^pM2h`M^syccnz91d|hW#ksbAi{+$YYT+Pa^-vv0plbXUyBj zHkJ-4iut3l2Z8>6vHUm9$GjI|!8?ALdU;)tuHInW&w{=DZ&p}e`UPSu<&OJzwp=f= z-F@S4qK?>u-jr#X>mUE#x*%PLJ@}rOsRK!C8{$MAkR-)RcSRD<+xSEI0Mskj6&qZ^ wz!eN!!N3&^T*1KqZ46-l*8pmvlVL-Hy&V5-00^zn&`#sn!_Ys57w-%H8{to-P5=M^ literal 0 HcmV?d00001 diff --git a/root/package/qca/qca-nss-fw-eip/files/ipq60xx/ipue.bin b/root/package/qca/qca-nss-fw-eip/files/ipq60xx/ipue.bin new file mode 100644 index 0000000000000000000000000000000000000000..153a7b4e8a3dd67accb063d33bff7e82d3b5f834 GIT binary patch literal 6116 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 literal 0 HcmV?d00001 diff --git a/root/package/qca/qca-nss-fw-eip/files/ipq60xx/opue.bin b/root/package/qca/qca-nss-fw-eip/files/ipq60xx/opue.bin new file mode 100644 index 0000000000000000000000000000000000000000..80fb1a211d58e4e08230a354e644b2c46817ef13 GIT binary patch literal 4068 zcmeHKU279j5IsrOG)dFuCWhG5er%c;TcKrJMUrYEh=LTs;%j|arHBQEtx!cooVG#> z1zo6`>Wjq}AEfL`1ff{m;s^TF{)ABT1M0_noSQ~$i}(w6VK{g0&YU}Q=e%5aLoFcW z*#^%UnhK*o2V)>tc-+02g-_RMDWlR-7DOjN;geI3#-Z~5$2a4Cr#O^wDpy1tjk+^C zPKV*1H=!Wp|RRLtugXwn}nwxfgQb&o&jhuJLzI&1ms#k|j%J-)wc4hLbBa&4P* zD29!REDZq0u^hH2Rb6OVlQmQqTBCiK(ShJ7%liijVwiJcb&NA+*>7Py!&n0JJFst*P@-+KP=W=`)a(n) zBm0*aAp7uo@pgkVd-FiRc&iF;;g$u9q}{_9-DSL1_ZK<;&%d4cuedPTX=p>fsu=VE zT0n8Azxw>(#tPSFf%Km8Gcn2ME9IwRg70L7>*Zckia!{gBVHS?Rmm}eP~%P>Ipbv_ zUCvj=MZA%UYQWWis{vO7t_EBUxEgRZz;CTq W2=f&2izJRvZS7%U@JDR5cf)U5t5W*_ literal 0 HcmV?d00001 diff --git a/root/package/qca/qca-nss-fw-eip/files/ipq807x/ifpp.bin b/root/package/qca/qca-nss-fw-eip/files/ipq807x/ifpp.bin new file mode 100644 index 0000000000000000000000000000000000000000..6e5cbcada3dfd0ff2d12198a73a3766d1982a8b1 GIT binary patch literal 12272 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 H=!5?k+&#gi literal 0 HcmV?d00001 diff --git a/root/package/qca/qca-nss-fw-eip/files/ipq807x/ipue.bin b/root/package/qca/qca-nss-fw-eip/files/ipq807x/ipue.bin new file mode 100644 index 0000000000000000000000000000000000000000..9a75f9789455ca5f39b137647192b877e3b34c6e GIT binary patch literal 6116 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 literal 0 HcmV?d00001 diff --git a/root/package/qca/qca-nss-fw-eip/files/ipq807x/ofpp.bin b/root/package/qca/qca-nss-fw-eip/files/ipq807x/ofpp.bin new file mode 100644 index 0000000000000000000000000000000000000000..6863d1068942d2eaeb9354a88ff886b53d1d6397 GIT binary patch literal 6128 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 + +/ { + #address-cells = <0x2>; + #size-cells = <0x2>; + model = "YLX Q60"; + compatible = "ylx,q60", "qcom,ipq6018"; + interrupt-parent = <&intc>; + qcom,msm-id = <0x1A5 0x0>; + + aliases { + led-boot = &led_sys; + led-failsafe = &led_sys; + led-running = &led_sys; + led-upgrade = &led_sys; + serial0 = &blsp1_uart3; + serial1 = &blsp1_uart2; + serial2 = &blsp1_uart5; + + ethernet0 = "/soc/dp1"; + ethernet1 = "/soc/dp2"; + ethernet2 = "/soc/dp3"; + ethernet3 = "/soc/dp4"; + ethernet4 = "/soc/dp5"; + }; + + chosen { + stdout-path = "serial0:115200n8"; + bootargs-append = " root=/dev/ubiblock0_1 swiotlb=1 coherent_pool=2M"; + }; + + soc { + mdio:mdio@90000 { + pinctrl-0 = <&mdio_pins>; + pinctrl-names = "default"; + phy-reset-gpio = <&tlmm 75 0>; + status = "ok"; + + phy0: ethernet-phy@0 { + reg = <24>; + }; + phy1: ethernet-phy@1 { + reg = <25>; + }; + phy2: ethernet-phy@2 { + reg = <26>; + }; + phy3: ethernet-phy@3 { + reg = <27>; + }; + phy4: ethernet-phy@4 { + reg = <28>; + }; + }; + + ess-switch@3a000000 { + switch_cpu_bmp = <0x1>; /* cpu port bitmap */ + switch_lan_bmp = <0x1e>; /* lan port bitmap */ + switch_wan_bmp = <0x20>; /* wan port bitmap */ + switch_inner_bmp = <0xc0>; /*inner port bitmap*/ + switch_mac_mode = <0x0>; /* mac mode for uniphy 0*/ + switch_mac_mode1 = <0xff>; /* mac mode for uniphy 1*/ + switch_mac_mode2 = <0xff>; /* mac mode for uniphy 2*/ + + qcom,port_phyinfo { + port@0 { + port_id = <1>; + phy_address = <24>; + }; + port@1 { + port_id = <2>; + phy_address = <25>; + }; + port@2 { + port_id = <3>; + phy_address = <26>; + }; + port@3 { + port_id = <4>; + phy_address = <27>; + }; + port@4 { + port_id = <5>; + phy_address = <28>; + }; + }; + }; + + dp1 { + device_type = "network"; + compatible = "qcom,nss-dp"; + qcom,id = <1>; + reg = <0x3a001000 0x200>; + qcom,mactype = <0>; + local-mac-address = [000000000000]; + qcom,link-poll = <1>; + qcom,phy-mdio-addr = <24>; + phy-mode = "sgmii"; + }; + + dp2 { + device_type = "network"; + compatible = "qcom,nss-dp"; + qcom,id = <2>; + reg = <0x3a001200 0x200>; + qcom,mactype = <0>; + local-mac-address = [000000000000]; + qcom,link-poll = <1>; + qcom,phy-mdio-addr = <25>; + phy-mode = "sgmii"; + }; + + dp3 { + device_type = "network"; + compatible = "qcom,nss-dp"; + qcom,id = <3>; + reg = <0x3a001400 0x200>; + qcom,mactype = <0>; + local-mac-address = [000000000000]; + qcom,link-poll = <1>; + qcom,phy-mdio-addr = <26>; + phy-mode = "sgmii"; + }; + + dp4 { + device_type = "network"; + compatible = "qcom,nss-dp"; + qcom,id = <4>; + reg = <0x3a001600 0x200>; + qcom,mactype = <0>; + local-mac-address = [000000000000]; + qcom,link-poll = <1>; + qcom,phy-mdio-addr = <27>; + phy-mode = "sgmii"; + }; + + dp5 { + device_type = "network"; + compatible = "qcom,nss-dp"; + qcom,id = <5>; + reg = <0x3a001800 0x200>; + qcom,mactype = <0>; + local-mac-address = [000000000000]; + qcom,link-poll = <1>; + qcom,phy-mdio-addr = <28>; + phy-mode = "sgmii"; + }; + + gpio_keys { + compatible = "gpio-keys"; + pinctrl-0 = <&button_pins>; + pinctrl-names = "default"; + + reset { + label = "reset"; + linux,code = ; + gpios = <&tlmm 0 GPIO_ACTIVE_LOW>; + linux,input-type = <1>; + debounce-interval = <60>; + }; + }; + + leds { + compatible = "gpio-leds"; + pinctrl-0 = <&leds_pins>; + pinctrl-names = "default"; + + wlan2g { + label = "wlan2g"; + gpios = <&tlmm 37 GPIO_ACTIVE_HIGH>; + }; + + wlan5g { + label = "wlan5g"; + gpios = <&tlmm 35 GPIO_ACTIVE_HIGH>; + }; + + led_sys: sys { + label = "sys"; + gpios = <&tlmm 66 GPIO_ACTIVE_HIGH>; + }; + + modem { + label = "modem"; + gpios = <&tlmm 67 GPIO_ACTIVE_HIGH>; + }; + }; + + watchdog { + compatible = "linux,wdt-gpio"; + gpios = <&tlmm 53 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&wdt_pins>; + pinctrl-names = "default"; + hw_algo = "toggle"; + hw_margin_ms = <1600>; + always-running; + }; + + sd-pwrseq { + /delete-property/ reset-gpios; + status = "disabled"; + }; + }; +}; + +&tlmm { + button_pins: button_pins { + reset_button { + pins = "gpio0"; + function = "gpio"; + drive-strength = <8>; + bias-pull-up; + }; + }; + + mdio_pins: mdio_pinmux { + mux_0 { + pins = "gpio64"; + function = "mdc"; + drive-strength = <8>; + bias-pull-up; + }; + mux_1 { + pins = "gpio65"; + function = "mdio"; + drive-strength = <8>; + bias-pull-up; + }; + mux_2 { + pins = "gpio75"; + function = "gpio"; + bias-pull-up; + }; + }; + + uart2_pins: uart2_pins { + mux { + pins = "gpio71", "gpio72"; + function = "blsp1_uart"; + drive-strength = <8>; + bias-disable; + }; + }; + + hsuart_pins: hsuart_pins { + mux { + pins = "gpio57", "gpio58"; + function = "blsp4_uart"; + drive-strength = <8>; + bias-disable; + }; + }; + + wdt_pins: wdt_pins { + mux { + pins = "gpio53"; + function = "gpio"; + drive-strength = <8>; + bias-disable; + output-high; + }; + }; + + leds_pins: leds_pins { + wlan2g { + pins = "gpio37"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + }; + + wlan5g { + pins = "gpio35"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + }; + + sys { + pins = "gpio66"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + }; + + modem { + pins = "gpio67"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + }; + }; +}; + +&blsp1_uart2 { + pinctrl-0 = <&uart2_pins>; + pinctrl-names = "default"; + dmas = <&blsp_dma 2>, + <&blsp_dma 3>; + dma-names = "tx", "rx"; + status = "ok"; +}; + +&blsp1_uart3 { + pinctrl-0 = <&serial_3_pins>; + pinctrl-names = "default"; + status = "ok"; +}; + +&blsp1_uart5 { + pinctrl-0 = <&hsuart_pins>; + pinctrl-names = "default"; + dmas = <&blsp_dma 8>, + <&blsp_dma 9>; + dma-names = "tx", "rx"; + status = "ok"; +}; + +&nss_crypto { + status = "ok"; +}; + +&qpic_bam { + status = "ok"; +}; + +&qpic_nand { + status = "ok"; + + nand@0 { + reg = <0>; + #address-cells = <1>; + #size-cells = <1>; + + nand-ecc-strength = <4>; + nand-ecc-step-size = <512>; + nand-bus-width = <8>; + }; +}; + + +&spi_0 { + cs-select = <0>; + status = "ok"; + + m25p80@0 { + compatible = "jedec,spi-nor", "n25q128a11"; + #address-cells = <1>; + #size-cells = <1>; + reg = <0>; + spi-max-frequency = <50000000>; + }; +}; + +&ssphy_0 { + status = "ok"; +}; + +&qusb_phy_1{ + status = "ok"; +}; + +&qusb_phy_0 { + status = "ok"; +}; + +&usb2 { + status = "ok"; +}; + +&usb3 { + status = "ok"; +}; diff --git a/root/target/linux/ipq60xx/files-5.4/arch/arm64/boot/dts/qcom/ipq6018-x5.dts b/root/target/linux/ipq60xx/files-5.4/arch/arm64/boot/dts/qcom/ipq6018-x5.dts new file mode 100644 index 00000000..1ee2b702 --- /dev/null +++ b/root/target/linux/ipq60xx/files-5.4/arch/arm64/boot/dts/qcom/ipq6018-x5.dts @@ -0,0 +1,614 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2020 The Linux Foundation. All rights reserved. + */ + +/dts-v1/; + +#include "ipq6018.dtsi" +#include + +/ { + #address-cells = <0x2>; + #size-cells = <0x2>; + model = "YLX X5"; + compatible = "ylx,x5", "qcom,ipq6018"; + interrupt-parent = <&intc>; + qcom,msm-id = <0x1A5 0x0>; + + aliases { + led-boot = &led_sys; + led-failsafe = &led_sys; + led-running = &led_sys; + led-upgrade = &led_sys; + serial0 = &blsp1_uart3; + serial1 = &blsp1_uart2; + serial2 = &blsp1_uart5; + + ethernet0 = "/soc/dp1"; + ethernet1 = "/soc/dp2"; + ethernet2 = "/soc/dp3"; + ethernet3 = "/soc/dp4"; + ethernet4 = "/soc/dp5"; + }; + + chosen { + stdout-path = "serial0:115200n8"; + bootargs-append = " root=/dev/ubiblock0_1 swiotlb=1 coherent_pool=2M"; + }; + + soc { + mdio:mdio@90000 { + pinctrl-0 = <&mdio_pins>; + pinctrl-names = "default"; + phy-reset-gpio = <&tlmm 75 0>; + status = "ok"; + + phy0: ethernet-phy@0 { + reg = <24>; + }; + phy1: ethernet-phy@1 { + reg = <25>; + }; + phy2: ethernet-phy@2 { + reg = <26>; + }; + phy3: ethernet-phy@3 { + reg = <27>; + }; + phy4: ethernet-phy@4 { + reg = <28>; + }; + }; + + ess-switch@3a000000 { + switch_cpu_bmp = <0x1>; /* cpu port bitmap */ + switch_lan_bmp = <0x1e>; /* lan port bitmap */ + switch_wan_bmp = <0x20>; /* wan port bitmap */ + switch_inner_bmp = <0xc0>; /*inner port bitmap*/ + switch_mac_mode = <0x0>; /* mac mode for uniphy 0*/ + switch_mac_mode1 = <0xff>; /* mac mode for uniphy 1*/ + switch_mac_mode2 = <0xff>; /* mac mode for uniphy 2*/ + + qcom,port_phyinfo { + port@0 { + port_id = <1>; + phy_address = <24>; + }; + port@1 { + port_id = <2>; + phy_address = <25>; + }; + port@2 { + port_id = <3>; + phy_address = <26>; + }; + port@3 { + port_id = <4>; + phy_address = <27>; + }; + port@4 { + port_id = <5>; + phy_address = <28>; + }; + }; + }; + + dp1 { + device_type = "network"; + compatible = "qcom,nss-dp"; + qcom,id = <1>; + reg = <0x3a001000 0x200>; + qcom,mactype = <0>; + local-mac-address = [000000000000]; + qcom,link-poll = <1>; + qcom,phy-mdio-addr = <24>; + phy-mode = "sgmii"; + }; + + dp2 { + device_type = "network"; + compatible = "qcom,nss-dp"; + qcom,id = <2>; + reg = <0x3a001200 0x200>; + qcom,mactype = <0>; + local-mac-address = [000000000000]; + qcom,link-poll = <1>; + qcom,phy-mdio-addr = <25>; + phy-mode = "sgmii"; + }; + + dp3 { + device_type = "network"; + compatible = "qcom,nss-dp"; + qcom,id = <3>; + reg = <0x3a001400 0x200>; + qcom,mactype = <0>; + local-mac-address = [000000000000]; + qcom,link-poll = <1>; + qcom,phy-mdio-addr = <26>; + phy-mode = "sgmii"; + }; + + dp4 { + device_type = "network"; + compatible = "qcom,nss-dp"; + qcom,id = <4>; + reg = <0x3a001600 0x200>; + qcom,mactype = <0>; + local-mac-address = [000000000000]; + qcom,link-poll = <1>; + qcom,phy-mdio-addr = <27>; + phy-mode = "sgmii"; + }; + + dp5 { + device_type = "network"; + compatible = "qcom,nss-dp"; + qcom,id = <5>; + reg = <0x3a001800 0x200>; + qcom,mactype = <0>; + local-mac-address = [000000000000]; + qcom,link-poll = <1>; + qcom,phy-mdio-addr = <28>; + phy-mode = "sgmii"; + }; + + gpio_keys { + compatible = "gpio-keys"; + pinctrl-0 = <&button_pins>; + pinctrl-names = "default"; + + reset { + label = "reset"; + linux,code = ; + gpios = <&tlmm 0 GPIO_ACTIVE_LOW>; + linux,input-type = <1>; + debounce-interval = <60>; + }; + }; + + gpio-export { + compatible = "gpio-export"; + fan { + label = "fan"; + gpio-export,name = "fan"; + gpio-export,output = <1>; + gpios = <&tlmm 29 GPIO_ACTIVE_HIGH>; + }; + lte1 { + label = "lte1"; + gpio-export,name = "lte1"; + gpio-export,output = <1>; + gpios = <&aw9523b 15 GPIO_ACTIVE_HIGH>; + }; + lte2 { + label = "lte2"; + gpio-export,name = "lte2"; + gpio-export,output = <1>; + gpios = <&aw9523b 14 GPIO_ACTIVE_HIGH>; + }; + lte3 { + label = "lte3"; + gpio-export,name = "lte3"; + gpio-export,output = <1>; + gpios = <&aw9523b 13 GPIO_ACTIVE_HIGH>; + }; + lte4 { + label = "lte4"; + gpio-export,name = "lte4"; + gpio-export,output = <1>; + gpios = <&aw9523b 12 GPIO_ACTIVE_HIGH>; + }; + lte1i { + label = "lte1i"; + gpio-export,name = "lte1i"; + gpio-export,output = <1>; + gpios = <&aw9523b 8 GPIO_ACTIVE_HIGH>; + }; + lte2i { + label = "lte2i"; + gpio-export,name = "lte2i"; + gpio-export,output = <1>; + gpios = <&aw9523b 9 GPIO_ACTIVE_HIGH>; + }; + lte3i { + label = "lte3i"; + gpio-export,name = "lte3i"; + gpio-export,output = <1>; + gpios = <&aw9523b 10 GPIO_ACTIVE_HIGH>; + }; + lte4i { + label = "lte4i"; + gpio-export,name = "lte4i"; + gpio-export,output = <1>; + gpios = <&aw9523b 11 GPIO_ACTIVE_HIGH>; + }; + }; + + leds { + compatible = "gpio-leds"; + pinctrl-0 = <&leds_pins>; + pinctrl-names = "default"; + + wlan2g { + label = "wlan2g"; + gpios = <&tlmm 37 GPIO_ACTIVE_HIGH>; + }; + + wlan5g { + label = "wlan5g"; + gpios = <&tlmm 35 GPIO_ACTIVE_HIGH>; + }; + + led_sys: sys { + label = "sys"; + gpios = <&tlmm 66 GPIO_ACTIVE_HIGH>; + }; + + modem { + label = "modem"; + gpios = <&tlmm 67 GPIO_ACTIVE_HIGH>; + }; + }; + + watchdog { + compatible = "linux,wdt-gpio"; + gpios = <&tlmm 53 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&wdt_pins>; + pinctrl-names = "default"; + hw_algo = "toggle"; + hw_margin_ms = <1600>; + always-running; + }; + + sd-pwrseq { + /delete-property/ reset-gpios; + status = "disabled"; + }; + }; +}; + +&tlmm { + button_pins: button_pins { + reset_button { + pins = "gpio0"; + function = "gpio"; + drive-strength = <8>; + bias-pull-up; + }; + }; + + i2c_1_pins: i2c_1_pins { + mux { + pins = "gpio42", "gpio43"; + function = "blsp2_i2c"; + drive-strength = <8>; + bias-pull-down; + }; + }; + + spi_1_pins: spi_1_pins { + mux { + pins = "gpio69", "gpio71", "gpio72"; + function = "blsp1_spi"; + drive-strength = <8>; + bias-pull-down; + }; + mux2 { + pins = "gpio68","gpio70"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + }; + }; + + axp2402_irq: axp2402_irq { + mux{ + pins = "gpio33"; + function = "gpio"; + drive-strength = <8>; + bias-disable; + }; + }; + + aw9523_irq: aw9523_irq { + mux { + pins = "gpio51"; + function = "gpio"; + drive-strength = <8>; + bias-disable; + }; + }; + + mdio_pins: mdio_pinmux { + mux_0 { + pins = "gpio64"; + function = "mdc"; + drive-strength = <8>; + bias-pull-up; + }; + mux_1 { + pins = "gpio65"; + function = "mdio"; + drive-strength = <8>; + bias-pull-up; + }; + mux_2 { + pins = "gpio75"; + function = "gpio"; + bias-pull-up; + }; + }; + + // uart2_pins: uart2_pins { + // mux { + // pins = "gpio71", "gpio72"; + // function = "blsp1_uart"; + // drive-strength = <8>; + // bias-disable; + // }; + // }; + + hsuart_pins: hsuart_pins { + mux { + pins = "gpio57", "gpio58"; + function = "blsp4_uart"; + drive-strength = <8>; + bias-disable; + }; + }; + + wdt_pins: wdt_pins { + mux { + pins = "gpio53"; + function = "gpio"; + drive-strength = <8>; + bias-disable; + output-high; + }; + }; + + leds_pins: leds_pins { + wlan2g { + pins = "gpio37"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + }; + + wlan5g { + pins = "gpio35"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + }; + + sys { + pins = "gpio66"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + }; + + modem { + pins = "gpio67"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + }; + }; +}; + +// &blsp1_uart2 { +// pinctrl-0 = <&uart2_pins>; +// pinctrl-names = "default"; +// dmas = <&blsp_dma 2>, +// <&blsp_dma 3>; +// dma-names = "tx", "rx"; +// status = "ok"; +// }; + +&blsp1_uart3 { + pinctrl-0 = <&serial_3_pins>; + pinctrl-names = "default"; + status = "ok"; +}; + +&blsp1_uart5 { + pinctrl-0 = <&hsuart_pins>; + pinctrl-names = "default"; + dmas = <&blsp_dma 8>, + <&blsp_dma 9>; + dma-names = "tx", "rx"; + status = "ok"; +}; + +&nss_crypto { + status = "ok"; +}; + +&qpic_bam { + status = "ok"; +}; + +&qpic_nand { + status = "ok"; + + nand@0 { + reg = <0>; + #address-cells = <1>; + #size-cells = <1>; + + nand-ecc-strength = <4>; + nand-ecc-step-size = <512>; + nand-bus-width = <8>; + }; +}; + + +&spi_0 { + cs-select = <0>; + status = "ok"; + + m25p80@0 { + compatible = "jedec,spi-nor", "n25q128a11"; + #address-cells = <1>; + #size-cells = <1>; + reg = <0>; + spi-max-frequency = <50000000>; + }; +}; + +&ssphy_0 { + status = "ok"; +}; + +&qusb_phy_1{ + status = "ok"; +}; + +&qusb_phy_0 { + status = "ok"; +}; + +&usb2 { + status = "ok"; +}; + +&usb3 { + status = "ok"; +}; + +&CPU0 { + operating-points = < + /* kHz uV (fixed) */ + 864000 1100000 + 1056000 1100000 + 1320000 1100000 + 1440000 1100000 + 1608000 1100000 + 1800000 1100000 + >; + clock-latency = <200000>; +}; + +&CPU1 { + operating-points = < + /* kHz uV (fixed) */ + 864000 1100000 + 1056000 1100000 + 1320000 1100000 + 1440000 1100000 + 1608000 1100000 + 1800000 1100000 + >; + clock-latency = <200000>; +}; + +&CPU2 { + operating-points = < + /* kHz uV (fixed) */ + 864000 1100000 + 1056000 1100000 + 1320000 1100000 + 1440000 1100000 + 1608000 1100000 + 1800000 1100000 + >; + clock-latency = <200000>; +}; + +&CPU3 { + operating-points = < + /* kHz uV (fixed) */ + 864000 1100000 + 1056000 1100000 + 1320000 1100000 + 1440000 1100000 + 1608000 1100000 + 1800000 1100000 + >; + clock-latency = <200000>; +}; + +&i2c_1 { + pinctrl-0 = <&i2c_1_pins>; + pinctrl-names = "default"; + clock-frequency = <400000>; + status = "ok"; + axp2402@46{ + compatible = "X-Powers, axp2402"; + reg = <0x46>; + status = "ok"; + axp2402-charger{ + compatible = "X-Powers, axp2402-charger"; + pinctrl-0 = <&axp2402_irq>; + pinctrl-names = "default"; + irqpin-gpios = <&tlmm 33 GPIO_ACTIVE_LOW>; + ichg_cc = <1536>; //æœ€å¤§å……ç”µç”µæµ + idpm = <3072>; //æœ€å¤§è¾“å…¥ç”µæµ + chg_target_voltage = <8440>; //电池电压 + vsys_min=<5400>; //è®¾ç½®æ”¾ç”µä¿æŠ¤ç”µåŽ‹ + battery_max_capacity = <15000>; //ç”µæ± æœ€å¤§å®¹é‡ + status = "ok"; + }; + }; + + aw9523b: gpio-expander@58 { + compatible = "awinic,aw9523b-gpio"; + reg = <0x58>; + pinctrl-names = "default"; + pinctrl-0 = <&aw9523_irq>; + interrupt-parent = <&tlmm>; + interrupts = <51 IRQ_TYPE_LEVEL_LOW>; + gpio-controller; + #gpio-cells = <2>; + interrupt-controller; + #interrupt-cells = <2>; + wakeup-source; + }; +}; + +&spi_1 { /* BLSP1 QUP1 */ + pinctrl-0 = <&spi_1_pins>; + pinctrl-names = "default"; + cs-select = <0>; + status = "ok"; + ili9341@0 { + #address-cells = <1>; + #size-cells = <1>; + compatible = "ilitek,ili9341"; + reg = <0>; + spi-max-frequency = <4000000>; + //txbuflen = <16>; + rotate = <270>; + bgr; + fps = <10>; + buswidth = <8>; + // dc-gpios = <&tlmm 70 GPIO_ACTIVE_LOW>; + // reset-gpios = <&tlmm 68 GPIO_ACTIVE_LOW>; + dc-gpios = <&tlmm 70 GPIO_ACTIVE_HIGH>; + reset-gpios = <&tlmm 68 GPIO_ACTIVE_HIGH>; + debug = <0>; + }; + // st7789v@0 { + // #address-cells = <1>; + // #size-cells = <1>; + // compatible = "sitronix,st7789v"; + // reg = <0>; + // spi-max-frequency = <2000000>; + // // txbuflen = <16>; + // rotate = <270>; + // bgr; + // fps = <5>; + // buswidth = <8>; + // dc-gpios = <&tlmm 70 GPIO_ACTIVE_LOW>; + // reset-gpios = <&tlmm 68 GPIO_ACTIVE_LOW>; + // debug = <0>; + // gamma = "d0,00,02,07,0b,1a,31,54,40,29,12,12,12,17;d0,00,02,07,05,25,2d,44,45,1c,18,16,1c,1d"; + // }; +}; + +&rpm_glink { + status = "disabled"; +}; \ No newline at end of file diff --git a/root/target/linux/ipq60xx/files-5.4/arch/arm64/boot/dts/qcom/ipq6018-x511.dts b/root/target/linux/ipq60xx/files-5.4/arch/arm64/boot/dts/qcom/ipq6018-x511.dts new file mode 100644 index 00000000..5081ca4e --- /dev/null +++ b/root/target/linux/ipq60xx/files-5.4/arch/arm64/boot/dts/qcom/ipq6018-x511.dts @@ -0,0 +1,469 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2020 The Linux Foundation. All rights reserved. + */ + +/dts-v1/; + +#include "ipq6018.dtsi" +#include + +/ { + #address-cells = <0x2>; + #size-cells = <0x2>; + model = "YLX X511"; + compatible = "ylx,x511", "qcom,ipq6018"; + interrupt-parent = <&intc>; + qcom,msm-id = <0x1A5 0x0>; + + aliases { + led-boot = &led_sys; + led-failsafe = &led_sys; + led-running = &led_sys; + led-upgrade = &led_sys; + serial0 = &blsp1_uart3; + serial1 = &blsp1_uart2; + serial2 = &blsp1_uart5; + + ethernet0 = "/soc/dp1"; + ethernet1 = "/soc/dp2"; + ethernet2 = "/soc/dp3"; + ethernet3 = "/soc/dp4"; + ethernet4 = "/soc/dp5"; + }; + + chosen { + stdout-path = "serial0:115200n8"; + bootargs-append = " root=/dev/ubiblock0_1 swiotlb=1 coherent_pool=2M"; + }; + + soc { + mdio:mdio@90000 { + pinctrl-0 = <&mdio_pins>; + pinctrl-names = "default"; + phy-reset-gpio = <&tlmm 75 0>; + status = "ok"; + + phy0: ethernet-phy@0 { + reg = <24>; + }; + phy1: ethernet-phy@1 { + reg = <25>; + }; + phy2: ethernet-phy@2 { + reg = <26>; + }; + phy3: ethernet-phy@3 { + reg = <27>; + }; + phy4: ethernet-phy@4 { + reg = <28>; + }; + }; + + ess-switch@3a000000 { + switch_cpu_bmp = <0x1>; /* cpu port bitmap */ + switch_lan_bmp = <0x1e>; /* lan port bitmap */ + switch_wan_bmp = <0x20>; /* wan port bitmap */ + switch_inner_bmp = <0xc0>; /*inner port bitmap*/ + switch_mac_mode = <0x0>; /* mac mode for uniphy 0*/ + switch_mac_mode1 = <0xff>; /* mac mode for uniphy 1*/ + switch_mac_mode2 = <0xff>; /* mac mode for uniphy 2*/ + + qcom,port_phyinfo { + port@0 { + port_id = <1>; + phy_address = <24>; + }; + port@1 { + port_id = <2>; + phy_address = <25>; + }; + port@2 { + port_id = <3>; + phy_address = <26>; + }; + port@3 { + port_id = <4>; + phy_address = <27>; + }; + port@4 { + port_id = <5>; + phy_address = <28>; + }; + }; + }; + + dp1 { + device_type = "network"; + compatible = "qcom,nss-dp"; + qcom,id = <1>; + reg = <0x3a001000 0x200>; + qcom,mactype = <0>; + local-mac-address = [000000000000]; + qcom,link-poll = <1>; + qcom,phy-mdio-addr = <24>; + phy-mode = "sgmii"; + }; + + dp2 { + device_type = "network"; + compatible = "qcom,nss-dp"; + qcom,id = <2>; + reg = <0x3a001200 0x200>; + qcom,mactype = <0>; + local-mac-address = [000000000000]; + qcom,link-poll = <1>; + qcom,phy-mdio-addr = <25>; + phy-mode = "sgmii"; + }; + + dp3 { + device_type = "network"; + compatible = "qcom,nss-dp"; + qcom,id = <3>; + reg = <0x3a001400 0x200>; + qcom,mactype = <0>; + local-mac-address = [000000000000]; + qcom,link-poll = <1>; + qcom,phy-mdio-addr = <26>; + phy-mode = "sgmii"; + }; + + dp4 { + device_type = "network"; + compatible = "qcom,nss-dp"; + qcom,id = <4>; + reg = <0x3a001600 0x200>; + qcom,mactype = <0>; + local-mac-address = [000000000000]; + qcom,link-poll = <1>; + qcom,phy-mdio-addr = <27>; + phy-mode = "sgmii"; + }; + + dp5 { + device_type = "network"; + compatible = "qcom,nss-dp"; + qcom,id = <5>; + reg = <0x3a001800 0x200>; + qcom,mactype = <0>; + local-mac-address = [000000000000]; + qcom,link-poll = <1>; + qcom,phy-mdio-addr = <28>; + phy-mode = "sgmii"; + }; + + gpio_keys { + compatible = "gpio-keys"; + pinctrl-0 = <&button_pins>; + pinctrl-names = "default"; + + reset { + label = "reset"; + linux,code = ; + gpios = <&tlmm 0 GPIO_ACTIVE_LOW>; + linux,input-type = <1>; + debounce-interval = <60>; + }; + }; + + i2c { + compatible = "i2c-gpio"; + pinctrl-0 = <&i2c_gpio_pins>; + #address-cells = <1>; + #size-cells = <1>; + + sda-gpios = <&tlmm 47 GPIO_ACTIVE_HIGH>; + scl-gpios = <&tlmm 46 GPIO_ACTIVE_HIGH>; + + pcf8563: pcf8563@51 { + compatible = "nxp,pcf8563"; + reg = <0x51>; + #clock-cells = <0>; + }; + }; + + leds { + compatible = "gpio-leds"; + pinctrl-0 = <&leds_pins>; + pinctrl-names = "default"; + + wlan2g { + label = "wlan2g"; + gpios = <&tlmm 37 GPIO_ACTIVE_HIGH>; + }; + + wlan5g { + label = "wlan5g"; + gpios = <&tlmm 35 GPIO_ACTIVE_HIGH>; + }; + + led_sys: sys { + label = "sys"; + gpios = <&tlmm 66 GPIO_ACTIVE_HIGH>; + }; + + modem { + label = "modem"; + gpios = <&tlmm 67 GPIO_ACTIVE_HIGH>; + }; + }; + + watchdog { + compatible = "linux,wdt-gpio"; + gpios = <&tlmm 53 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&wdt_pins>; + pinctrl-names = "default"; + hw_algo = "toggle"; + hw_margin_ms = <1600>; + always-running; + }; + + sd-pwrseq { + /delete-property/ reset-gpios; + status = "disabled"; + }; + }; +}; + +&tlmm { + i2c_gpio_pins:i2c_gpio_pins { + scl { + pins = "gpio46"; + function = "gpio"; + drive-strength = <8>; + bias-pull-up; + }; + sda { + pins = "gpio47"; + function = "gpio"; + drive-strength = <8>; + bias-pull-up; + }; + }; + + button_pins: button_pins { + reset_button { + pins = "gpio0"; + function = "gpio"; + drive-strength = <8>; + bias-pull-up; + }; + }; + + mdio_pins: mdio_pinmux { + mux_0 { + pins = "gpio64"; + function = "mdc"; + drive-strength = <8>; + bias-pull-up; + }; + mux_1 { + pins = "gpio65"; + function = "mdio"; + drive-strength = <8>; + bias-pull-up; + }; + mux_2 { + pins = "gpio75"; + function = "gpio"; + bias-pull-up; + }; + }; + + uart2_pins: uart2_pins { + mux { + pins = "gpio71", "gpio72"; + function = "blsp1_uart"; + drive-strength = <8>; + bias-disable; + }; + }; + + hsuart_pins: hsuart_pins { + mux { + pins = "gpio57", "gpio58"; + function = "blsp4_uart"; + drive-strength = <8>; + bias-disable; + }; + }; + + wdt_pins: wdt_pins { + mux { + pins = "gpio53"; + function = "gpio"; + drive-strength = <8>; + bias-disable; + output-high; + }; + }; + + leds_pins: leds_pins { + wlan2g { + pins = "gpio37"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + }; + + wlan5g { + pins = "gpio35"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + }; + + sys { + pins = "gpio66"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + }; + + modem { + pins = "gpio67"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + }; + }; +}; + +&blsp1_uart2 { + pinctrl-0 = <&uart2_pins>; + pinctrl-names = "default"; + dmas = <&blsp_dma 2>, + <&blsp_dma 3>; + dma-names = "tx", "rx"; + status = "ok"; +}; + +&blsp1_uart3 { + pinctrl-0 = <&serial_3_pins>; + pinctrl-names = "default"; + status = "ok"; +}; + +&blsp1_uart5 { + pinctrl-0 = <&hsuart_pins>; + pinctrl-names = "default"; + dmas = <&blsp_dma 8>, + <&blsp_dma 9>; + dma-names = "tx", "rx"; + status = "ok"; +}; + +&nss_crypto { + status = "ok"; +}; + +&qpic_bam { + status = "ok"; +}; + +&qpic_nand { + status = "ok"; + + nand@0 { + reg = <0>; + #address-cells = <1>; + #size-cells = <1>; + + nand-ecc-strength = <4>; + nand-ecc-step-size = <512>; + nand-bus-width = <8>; + }; +}; + + +&spi_0 { + cs-select = <0>; + status = "ok"; + + m25p80@0 { + compatible = "jedec,spi-nor", "n25q128a11"; + #address-cells = <1>; + #size-cells = <1>; + reg = <0>; + spi-max-frequency = <50000000>; + }; +}; + +&ssphy_0 { + status = "ok"; +}; + +&qusb_phy_1{ + status = "ok"; +}; + +&qusb_phy_0 { + status = "ok"; +}; + +&usb2 { + status = "ok"; +}; + +&usb3 { + status = "ok"; +}; + + +&CPU0 { + operating-points = < + /* kHz uV (fixed) */ + 864000 1100000 + 1056000 1100000 + 1320000 1100000 + 1440000 1100000 + 1608000 1100000 + 1800000 1100000 + >; + clock-latency = <200000>; +}; + +&CPU1 { + operating-points = < + /* kHz uV (fixed) */ + 864000 1100000 + 1056000 1100000 + 1320000 1100000 + 1440000 1100000 + 1608000 1100000 + 1800000 1100000 + >; + clock-latency = <200000>; +}; + +&CPU2 { + operating-points = < + /* kHz uV (fixed) */ + 864000 1100000 + 1056000 1100000 + 1320000 1100000 + 1440000 1100000 + 1608000 1100000 + 1800000 1100000 + >; + clock-latency = <200000>; +}; + +&CPU3 { + operating-points = < + /* kHz uV (fixed) */ + 864000 1100000 + 1056000 1100000 + 1320000 1100000 + 1440000 1100000 + 1608000 1100000 + 1800000 1100000 + >; + clock-latency = <200000>; +}; + +&rpm_glink { + status = "disabled"; +}; + diff --git a/root/target/linux/ipq60xx/files-5.4/arch/arm64/boot/dts/qcom/ipq6018-x8.dts b/root/target/linux/ipq60xx/files-5.4/arch/arm64/boot/dts/qcom/ipq6018-x8.dts new file mode 100644 index 00000000..ff00507e --- /dev/null +++ b/root/target/linux/ipq60xx/files-5.4/arch/arm64/boot/dts/qcom/ipq6018-x8.dts @@ -0,0 +1,631 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2020 The Linux Foundation. All rights reserved. +*/ + +/dts-v1/; + +#include "ipq6018.dtsi" +#include + +/ { + model = "openmptcprouter_5GX8"; + compatible = "ylx,x8", "qcom,ipq6018"; + interrupt-parent = <&intc>; + qcom,msm-id = <0x1A5 0x0>; + + aliases { + led-boot = &power; + led-failsafe = &power; + led-running = &power; + led-upgrade = &power; + serial0 = &blsp1_uart3; + /* + * Aliases as required by u-boot + * to patch MAC addresses + */ + ethernet0 = "/soc/dp1"; + ethernet1 = "/soc/dp2"; + ethernet2 = "/soc/dp3"; + ethernet3 = "/soc/dp4"; + ethernet4 = "/soc/dp5"; + }; + + chosen { + stdout-path = "serial0:115200n8"; + bootargs-append = " root=/dev/ubiblock0_1 swiotlb=1"; + }; +}; + +&blsp1_uart3 { + pinctrl-0 = <&serial_3_pins>; + pinctrl-names = "default"; + status = "ok"; +}; + +&i2c_1 { + pinctrl-0 = <&i2c_1_pins>; + pinctrl-names = "default"; + status = "ok"; +}; + +&spi_0 { + cs-select = <0>; + status = "ok"; + + m25p80@0 { + #address-cells = <1>; + #size-cells = <1>; + reg = <0>; + compatible = "n25q128a11"; + spi-max-frequency = <50000000>; + + SBL1@0 { + label = "0:SBL1"; + reg = <0x0 0xc0000>; + read-only; + }; + + MIBIB@c0000 { + label = "0:MIBIB"; + reg = <0xc0000 0x10000>; + }; + + BOOTCONFIG@d0000 { + label = "0:BOOTCONFIG"; + reg = <0xd0000 0x20000>; + read-only; + }; + + BOOTCONFIG1@f0000 { + label = "0:BOOTCONFIG1"; + reg = <0xf0000 0x20000>; + read-only; + }; + + QSEE@110000 { + label = "0:QSEE"; + reg = <0x110000 0x1a0000>; + read-only; + }; + + QSEE_1@2b0000 { + label = "0:QSEE_1"; + reg = <0x2b0000 0x1a0000>; + read-only; + }; + + DEVCFG@450000 { + label = "0:DEVCFG"; + reg = <0x450000 0x10000>; + read-only; + }; + + DEVCFG_1@460000 { + label = "0:DEVCFG_1"; + reg = <0x460000 0x10000>; + read-only; + }; + + RPM@470000 { + label = "0:RPM"; + reg = <0x4700000 0x40000>; + }; + + RPM_1@4b0000 { + label = "0:RPM_1"; + reg = <0x4b0000 0x40000>; + read-only; + }; + + CDT@4f0000 { + label = "0:CDT"; + reg = <0x4f0000 0x10000>; + }; + + CDT_1@500000 { + label = "0:CDT_1"; + reg = <0x500000 0x10000>; + read-only; + }; + + APPSBLENV@510000 { + label = "0:APPSBLENV"; + reg = <0x510000 0x10000>; + }; + + APPSBL@520000 { + label = "0:APPSBL"; + reg = <0x520000 0xa0000>; + }; + + APPSBL_1@5c0000 { + label = "0:APPSBL_1"; + reg = <0x5c0000 0xa0000>; + }; + + ART: ART@660000 { + label = "0:ART"; + reg = <0x660000 0x40000>; + }; + }; +}; + +&tlmm { + spi_0_pins: spi-0-pins { + pins = "gpio38", "gpio39", "gpio40", "gpio41"; + function = "blsp0_spi"; + drive-strength = <8>; + bias-pull-down; + }; + + i2c_1_pins: i2c_1_pins { + mux { + pins = "gpio42", "gpio43"; + function = "blsp2_i2c"; + drive-strength = <8>; + bias-pull-down; + }; + }; + + button_pins: button_pins { + wps_button { + pins = "gpio9"; + function = "gpio"; + drive-strength = <8>; + bias-pull-up; + }; + }; + + rst_usb_hub_pinmux { + mux { + pins = "gpio66"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + output-high; + }; + }; + + mdio_pins: mdio_pinmux { + mux_0 { + pins = "gpio64"; + function = "mdc"; + drive-strength = <8>; + bias-pull-up; + }; + mux_1 { + pins = "gpio65"; + function = "mdio"; + drive-strength = <8>; + bias-pull-up; + }; + mux_2 { + pins = "gpio75"; + function = "gpio"; + bias-pull-up; + }; + mux_3 { + pins = "gpio77"; + function = "gpio"; + bias-pull-up; + }; + }; + + pcie0_pins: pcie_pins { + pcie0_rst { + pins = "gpio60"; + function = "pcie0_rst"; + drive-strength = <8>; + bias-pull-down; + }; + pcie0_wake { + pins = "gpio36"; + function = "pcie0_wake"; + drive-strength = <8>; + bias-pull-down; + }; + }; + + leds_pins: leds_pins { + power { + pins = "gpio25"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + }; + + wlan5g { + pins = "gpio35"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + }; + + wlan2g { + pins = "gpio37"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + }; + + usb1 { + pins = "gpio24"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + }; + + usb0 { + pins = "gpio50"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + }; + + lte1 { + pins = "gpio34"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + }; + + lte2 { + pins = "gpio29"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + }; + + lte3 { + pins = "gpio30"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + }; + + lte4 { + pins = "gpio31"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + }; + + lte5 { + pins = "gpio32"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + }; + + lte6 { + pins = "gpio23"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + }; + + lte1pwr { + pins = "gpio0"; + function = "gpio"; + drive-strength = <8>; + bias-pull-up; + output-high; + }; + + lte2pwr { + pins = "gpio16"; + function = "gpio"; + drive-strength = <8>; + bias-pull-up; + output-high; + }; + + lte3pwr { + pins = "gpio18"; + function = "gpio"; + drive-strength = <8>; + bias-pull-up; + output-high; + }; + + lte4pwr { + pins = "gpio2"; + function = "gpio"; + drive-strength = <8>; + bias-pull-up; + output-high; + }; + + lte5pwr { + pins = "gpio21"; + function = "gpio"; + drive-strength = <8>; + bias-pull-up; + output-high; + }; + + lte6pwr { + pins = "gpio22"; + function = "gpio"; + drive-strength = <8>; + bias-pull-up; + output-high; + }; + }; +}; + +&soc { + mdio: mdio@90000 { + pinctrl-0 = <&mdio_pins>; + pinctrl-names = "default"; + phy-reset-gpio = <&tlmm 75 0 &tlmm 77 1>; + status = "ok"; + phy0: ethernet-phy@0 { + reg = <0>; + }; + phy1: ethernet-phy@1 { + reg = <1>; + }; + phy2: ethernet-phy@2 { + reg = <2>; + }; + phy3: ethernet-phy@3 { + reg = <3>; + }; + phy4: ethernet-phy@4 { + reg = <0x30>; + }; + }; + + dp1 { + device_type = "network"; + compatible = "qcom,nss-dp"; + qcom,id = <1>; + reg = <0x3a001000 0x200>; + qcom,mactype = <0>; + local-mac-address = [000000000000]; + qcom,link-poll = <1>; + qcom,phy-mdio-addr = <0>; + phy-mode = "sgmii"; + }; + + dp2 { + device_type = "network"; + compatible = "qcom,nss-dp"; + qcom,id = <2>; + reg = <0x3a001200 0x200>; + qcom,mactype = <0>; + local-mac-address = [000000000000]; + qcom,link-poll = <1>; + qcom,phy-mdio-addr = <1>; + phy-mode = "sgmii"; + }; + + dp3 { + device_type = "network"; + compatible = "qcom,nss-dp"; + qcom,id = <3>; + reg = <0x3a001400 0x200>; + qcom,mactype = <0>; + local-mac-address = [000000000000]; + qcom,link-poll = <1>; + qcom,phy-mdio-addr = <2>; + phy-mode = "sgmii"; + }; + + dp4 { + device_type = "network"; + compatible = "qcom,nss-dp"; + qcom,id = <4>; + reg = <0x3a001600 0x200>; + qcom,mactype = <0>; + local-mac-address = [000000000000]; + qcom,link-poll = <1>; + qcom,phy-mdio-addr = <3>; + phy-mode = "sgmii"; + }; + + dp5 { + device_type = "network"; + compatible = "qcom,nss-dp"; + qcom,id = <5>; + reg = <0x3a003000 0x3fff>; + qcom,mactype = <1>; + local-mac-address = [000000000000]; + qcom,link-poll = <1>; + qcom,phy-mdio-addr = <30>; + phy-mode = "sgmii"; + }; + + ess-switch@3a000000 { + switch_cpu_bmp = <0x1>; /* cpu port bitmap */ + switch_lan_bmp = <0x1e>; /* lan port bitmap */ + switch_wan_bmp = <0x20>; /* wan port bitmap */ + switch_inner_bmp = <0xc0>; /*inner port bitmap*/ + switch_mac_mode = <0x0>; /* mac mode for uniphy instance0*/ + switch_mac_mode1 = <0xe>; /* mac mode for uniphy instance1*/ + switch_mac_mode2 = <0xff>; /* mac mode for uniphy instance2*/ + qcom,port_phyinfo { + port@0 { + port_id = <1>; + phy_address = <0>; + }; + port@1 { + port_id = <2>; + phy_address = <1>; + }; + port@2 { + port_id = <3>; + phy_address = <2>; + }; + port@3 { + port_id = <4>; + phy_address = <3>; + }; + port@4 { + port_id = <5>; + phy_address = <30>; + phy_i2c_address = <30>; + phy-i2c-mode; /*i2c access phy */ + media-type = "sfp"; /* fiber mode */ + }; + }; + }; + + gpio_keys { + compatible = "gpio-keys"; + pinctrl-0 = <&button_pins>; + pinctrl-names = "default"; + + button@1 { + label = "wps"; + linux,code = ; + gpios = <&tlmm 9 GPIO_ACTIVE_LOW>; + linux,input-type = <1>; + debounce-interval = <60>; + }; + }; + + leds { + compatible = "gpio-leds"; + pinctrl-0 = <&leds_pins>; + pinctrl-names = "default"; + + led@24 { + label = "green:usb1"; + gpios = <&tlmm 24 GPIO_ACTIVE_HIGH>; + }; + + led@50 { + label = "green:usb0"; + gpios = <&tlmm 50 GPIO_ACTIVE_HIGH>; + }; + + power: led@25 { + label = "green:power"; + gpios = <&tlmm 25 GPIO_ACTIVE_HIGH>; + }; + + led@35 { + label = "green:wlan5g"; + gpios = <&tlmm 35 GPIO_ACTIVE_HIGH>; + }; + + led@37 { + label = "green:wlan2g"; + gpios = <&tlmm 37 GPIO_ACTIVE_HIGH>; + }; + + led@34 { + label = "green:lte1"; + gpios = <&tlmm 34 GPIO_ACTIVE_HIGH>; + }; + + led@29 { + label = "green:lte2"; + gpios = <&tlmm 29 GPIO_ACTIVE_HIGH>; + }; + + led@30 { + label = "green:lte3"; + gpios = <&tlmm 30 GPIO_ACTIVE_HIGH>; + }; + + led@31 { + label = "green:lte4"; + gpios = <&tlmm 31 GPIO_ACTIVE_HIGH>; + }; + + led@32 { + label = "green:lte5"; + gpios = <&tlmm 32 GPIO_ACTIVE_HIGH>; + }; + + led@23 { + label = "green:lte6"; + gpios = <&tlmm 23 GPIO_ACTIVE_HIGH>; + }; + + led@0 { + label = "green:lte1pwr"; + gpios = <&tlmm 0 GPIO_ACTIVE_HIGH>; + }; + + led@16 { + label = "green:lte2pwr"; + gpios = <&tlmm 16 GPIO_ACTIVE_HIGH>; + }; + + led@18 { + label = "green:lte3pwr"; + gpios = <&tlmm 18 GPIO_ACTIVE_HIGH>; + }; + + led@2 { + label = "green:lte4pwr"; + gpios = <&tlmm 2 GPIO_ACTIVE_HIGH>; + }; + + led@21 { + label = "green:lte5pwr"; + gpios = <&tlmm 21 GPIO_ACTIVE_HIGH>; + }; + + led@22 { + label = "green:lte6pwr"; + gpios = <&tlmm 22 GPIO_ACTIVE_HIGH>; + }; + }; +}; + + +&qpic_bam { + status = "ok"; +}; + +&qpic_nand { + status = "ok"; + + nand@0 { + reg = <0>; + #address-cells = <1>; + #size-cells = <1>; + + nand-ecc-strength = <4>; + nand-ecc-step-size = <512>; + nand-bus-width = <8>; + }; +}; + +&pcie_phy { + status = "ok"; +}; + +&pcie0 { + perst-gpio = <&tlmm 60 1>; + status = "ok"; +}; + +&qusb_phy_1 { + status = "ok"; +}; + +&usb2 { + status = "ok"; +}; + +&qusb_phy_0 { + status = "ok"; +}; + +&ssphy_0 { + status = "ok"; +}; + +&usb3 { + status = "ok"; +}; + +&nss_crypto { + status = "ok"; +}; \ No newline at end of file diff --git a/root/target/linux/ipq60xx/generic/target.mk b/root/target/linux/ipq60xx/generic/target.mk new file mode 100644 index 00000000..f5cb1fb1 --- /dev/null +++ b/root/target/linux/ipq60xx/generic/target.mk @@ -0,0 +1 @@ +BOARDNAME:=Generic diff --git a/root/target/linux/ipq60xx/image/Makefile b/root/target/linux/ipq60xx/image/Makefile new file mode 100644 index 00000000..3e9a6828 --- /dev/null +++ b/root/target/linux/ipq60xx/image/Makefile @@ -0,0 +1,83 @@ +include $(TOPDIR)/rules.mk +include $(INCLUDE_DIR)/image.mk + +define Device/Default + PROFILES := Default + KERNEL_DEPENDS = $$(wildcard $(DTS_DIR)/$$(DEVICE_DTS).dts) + KERNEL_INITRAMFS_PREFIX := $$(IMG_PREFIX)-$(1)-initramfs + KERNEL_PREFIX := $$(IMAGE_PREFIX) + KERNEL_LOADADDR := 0x41080000 + DEVICE_DTS_DIR := $(DTS_DIR)/qcom + DEVICE_DTS = $$(SOC)-$(lastword $(subst _, ,$(1))) + IMAGE/sysupgrade.bin = sysupgrade-tar | append-metadata + IMAGE/sysupgrade.bin/squashfs := +endef + +define Device/FitImage + KERNEL_SUFFIX := -fit-uImage.itb + KERNEL = kernel-bin | gzip | fit gzip $$(DEVICE_DTS_DIR)/$$(DEVICE_DTS).dtb + KERNEL_NAME := Image +endef + +define Device/FitImageLzma + KERNEL_SUFFIX := -fit-uImage.itb + KERNEL = kernel-bin | lzma | fit lzma $$(DEVICE_DTS_DIR)/$$(DEVICE_DTS).dtb + KERNEL_NAME := Image +endef + +define Device/UbiFit + KERNEL_IN_UBI := 1 + IMAGES := nand-factory.ubi nand-sysupgrade.bin + IMAGE/nand-factory.ubi := append-ubi + IMAGE/nand-sysupgrade.bin := sysupgrade-tar | append-metadata +endef + +define Device/ylx_q60 + $(call Device/FitImage) + $(call Device/UbiFit) + DEVICE_VENDOR := YLX + DEVICE_MODEL := Q60 + DEVICE_DTS_CONFIG := config@cp03-c1 + SOC := ipq6018 + BLOCKSIZE := 128k + PAGESIZE := 2048 +endef +TARGET_DEVICES += ylx_q60 + +define Device/ylx_x5 + $(call Device/FitImage) + $(call Device/UbiFit) + DEVICE_VENDOR := YLX + DEVICE_MODEL := X5 + DEVICE_DTS_CONFIG := config@cp01-c1 + SOC := ipq6018 + BLOCKSIZE := 128k + PAGESIZE := 2048 +endef +TARGET_DEVICES += ylx_x5 + +define Device/ylx_x8 + $(call Device/FitImage) + $(call Device/UbiFit) + DEVICE_VENDOR := YLX + DEVICE_MODEL := X8 + DEVICE_DTS_CONFIG := config@cp01-c3 + SOC := ipq6018 + BLOCKSIZE := 128k + PAGESIZE := 2048 +endef +TARGET_DEVICES += ylx_x8 + +define Device/ylx_x511 + $(call Device/FitImage) + $(call Device/UbiFit) + DEVICE_VENDOR := YLX + DEVICE_MODEL := X511 + DEVICE_DTS_CONFIG := config@cp03-c1 + SOC := ipq6018 + BLOCKSIZE := 128k + PAGESIZE := 2048 +endef +TARGET_DEVICES += ylx_x511 + +$(eval $(call BuildImage)) diff --git a/root/target/linux/ipq60xx/modules.mk b/root/target/linux/ipq60xx/modules.mk new file mode 100644 index 00000000..1eceb4ac --- /dev/null +++ b/root/target/linux/ipq60xx/modules.mk @@ -0,0 +1,19 @@ +define KernelPackage/usb-phy-msm + TITLE:=DWC3 USB QCOM PHY driver for IPQ60xx and IPQ807x + DEPENDS:=@TARGET_ipq60xx +kmod-usb-dwc3-qcom + KCONFIG:= \ + CONFIG_PHY_QCOM_QMP \ + CONFIG_PHY_QCOM_QUSB2 + FILES:= \ + $(LINUX_DIR)/drivers/phy/qualcomm/phy-qcom-qmp.ko \ + $(LINUX_DIR)/drivers/phy/qualcomm/phy-qcom-qusb2.ko + AUTOLOAD:=$(call AutoLoad,45,phy-qcom-qmp phy-qcom-qusb2,1) + $(call AddDepends/usb) +endef + +define KernelPackage/usb-phy-msm/description + This driver provides support for the USB PHY drivers + within the IPQ60xx and IPQ807x SoCs. +endef + +$(eval $(call KernelPackage,usb-phy-msm)) diff --git a/root/target/linux/ipq60xx/patches-5.4/001-mhi-use-irq_flags-if-controller-driver-configures.patch b/root/target/linux/ipq60xx/patches-5.4/001-mhi-use-irq_flags-if-controller-driver-configures.patch new file mode 100644 index 00000000..8b7b9ea7 --- /dev/null +++ b/root/target/linux/ipq60xx/patches-5.4/001-mhi-use-irq_flags-if-controller-driver-configures.patch @@ -0,0 +1,83 @@ +From 6ffcc18d9c0b4522c95aab71ff3ff5a56e699945 Mon Sep 17 00:00:00 2001 +From: Carl Huang +Date: Mon, 4 Jan 2021 18:11:28 +0800 +Subject: [PATCH] mhi: use irq_flags if controller driver configures it + +If controller driver has specified the irq_flags, mhi uses this specified +irq_flags. Otherwise, mhi uses default irq_flags. + +The purpose of this change is to support one MSI vector for QCA6390. +MHI will use one same MSI vector too in this scenario. + +In case of one MSI vector, IRQ_NO_BALANCING is needed when irq handler +is requested. The reason is if irq migration happens, the msi_data may +change too. However, the msi_data is already programmed to QCA6390 +hardware during initialization phase. This msi_data inconsistence will +result in crash in kernel. + +Another issue is in case of one MSI vector, IRQF_NO_SUSPEND will trigger +WARNINGS because QCA6390 wants to disable the IRQ during the suspend. + +To avoid above two issues, QCA6390 driver specifies the irq_flags in case +of one MSI vector when mhi_register_controller is called. + +Signed-off-by: Carl Huang +Reviewed-by: Manivannan Sadhasivam +Signed-off-by: Manivannan Sadhasivam +--- + drivers/bus/mhi/core/init.c | 9 +++++++-- + include/linux/mhi.h | 2 ++ + 2 files changed, 9 insertions(+), 2 deletions(-) + +diff --git a/drivers/bus/mhi/core/init.c b/drivers/bus/mhi/core/init.c +index f0697f433c2f1b..aa575d3fb3aeaa 100644 +--- a/drivers/bus/mhi/core/init.c ++++ b/drivers/bus/mhi/core/init.c +@@ -154,12 +154,17 @@ int mhi_init_irq_setup(struct mhi_controller *mhi_cntrl) + { + struct mhi_event *mhi_event = mhi_cntrl->mhi_event; + struct device *dev = &mhi_cntrl->mhi_dev->dev; ++ unsigned long irq_flags = IRQF_SHARED | IRQF_NO_SUSPEND; + int i, ret; + ++ /* if controller driver has set irq_flags, use it */ ++ if (mhi_cntrl->irq_flags) ++ irq_flags = mhi_cntrl->irq_flags; ++ + /* Setup BHI_INTVEC IRQ */ + ret = request_threaded_irq(mhi_cntrl->irq[0], mhi_intvec_handler, + mhi_intvec_threaded_handler, +- IRQF_SHARED | IRQF_NO_SUSPEND, ++ irq_flags, + "bhi", mhi_cntrl); + if (ret) + return ret; +@@ -177,7 +182,7 @@ int mhi_init_irq_setup(struct mhi_controller *mhi_cntrl) + + ret = request_irq(mhi_cntrl->irq[mhi_event->irq], + mhi_irq_handler, +- IRQF_SHARED | IRQF_NO_SUSPEND, ++ irq_flags, + "mhi", mhi_event); + if (ret) { + dev_err(dev, "Error requesting irq:%d for ev:%d\n", +diff --git a/include/linux/mhi.h b/include/linux/mhi.h +index 562862ff819c43..b7138127664f1f 100644 +--- a/include/linux/mhi.h ++++ b/include/linux/mhi.h +@@ -360,6 +360,7 @@ struct mhi_controller_config { + * @fbc_download: MHI host needs to do complete image transfer (optional) + * @pre_init: MHI host needs to do pre-initialization before power up + * @wake_set: Device wakeup set flag ++ * @irq_flags: irq flags passed to request_irq (optional) + * + * Fields marked as (required) need to be populated by the controller driver + * before calling mhi_register_controller(). For the fields marked as (optional) +@@ -456,6 +457,7 @@ struct mhi_controller { + bool pre_init; + bool wake_set; + void *priv_data; ++ unsigned long irq_flags; + int disable_rddm_prealloc; + u32 rddm_seg_len; + }; diff --git a/root/target/linux/ipq60xx/patches-5.4/002-bus-mhi-core-Add-support-for-forced-PM-resume.patch b/root/target/linux/ipq60xx/patches-5.4/002-bus-mhi-core-Add-support-for-forced-PM-resume.patch new file mode 100644 index 00000000..645b7989 --- /dev/null +++ b/root/target/linux/ipq60xx/patches-5.4/002-bus-mhi-core-Add-support-for-forced-PM-resume.patch @@ -0,0 +1,105 @@ +From cab2d3fd6866e089b5c50db09dece131f85bfebd Mon Sep 17 00:00:00 2001 +From: Loic Poulain +Date: Thu, 9 Dec 2021 18:46:33 +0530 +Subject: [PATCH] bus: mhi: core: Add support for forced PM resume + +For whatever reason, some devices like QCA6390, WCN6855 using ath11k +are not in M3 state during PM resume, but still functional. The +mhi_pm_resume should then not fail in those cases, and let the higher +level device specific stack continue resuming process. + +Add an API mhi_pm_resume_force(), to force resuming irrespective of the +current MHI state. This fixes a regression with non functional ath11k WiFi +after suspend/resume cycle on some machines. + +Bug report: https://bugzilla.kernel.org/show_bug.cgi?id=214179 + +Link: https://lore.kernel.org/regressions/871r5p0x2u.fsf@codeaurora.org/ +Fixes: 020d3b26c07a ("bus: mhi: Early MHI resume failure in non M3 state") +Cc: stable@vger.kernel.org #5.13 +Reported-by: Kalle Valo +Reported-by: Pengyu Ma +Tested-by: Kalle Valo +Acked-by: Kalle Valo +Signed-off-by: Loic Poulain +[mani: Switched to API, added bug report, reported-by tags and CCed stable] +Signed-off-by: Manivannan Sadhasivam +Link: https://lore.kernel.org/r/20211209131633.4168-1-manivannan.sadhasivam@linaro.org +Signed-off-by: Greg Kroah-Hartman +--- + drivers/bus/mhi/core/pm.c | 21 ++++++++++++++++++--- + drivers/net/wireless/ath/ath11k/mhi.c | 6 +++++- + include/linux/mhi.h | 13 +++++++++++++ + 3 files changed, 36 insertions(+), 4 deletions(-) + +diff --git a/drivers/bus/mhi/core/pm.c b/drivers/bus/mhi/core/pm.c +index fb99e3727155b6..547e6e769546a4 100644 +--- a/drivers/bus/mhi/core/pm.c ++++ b/drivers/bus/mhi/core/pm.c +@@ -885,7 +885,7 @@ int mhi_pm_suspend(struct mhi_controller *mhi_cntrl) + } + EXPORT_SYMBOL_GPL(mhi_pm_suspend); + +-int mhi_pm_resume(struct mhi_controller *mhi_cntrl) ++static int __mhi_pm_resume(struct mhi_controller *mhi_cntrl, bool force) + { + struct mhi_chan *itr, *tmp; + struct device *dev = &mhi_cntrl->mhi_dev->dev; +@@ -902,6 +902,13 @@ int mhi_pm_resume(struct mhi_controller *mhi_cntrl) + if (MHI_PM_IN_ERROR_STATE(mhi_cntrl->pm_state)) + return -EIO; + ++ if (mhi_get_mhi_state(mhi_cntrl) != MHI_STATE_M3) { ++ dev_warn(dev, "Resuming from non M3 state (%s)\n", ++ TO_MHI_STATE_STR(mhi_get_mhi_state(mhi_cntrl))); ++ if (!force) ++ return -EINVAL; ++ } ++ + /* Notify clients about exiting LPM */ + list_for_each_entry_safe(itr, tmp, &mhi_cntrl->lpm_chans, node) { + mutex_lock(&itr->mutex); +@@ -940,8 +947,19 @@ int mhi_pm_resume(struct mhi_controller *mhi_cntrl) + + return 0; + } ++ ++int mhi_pm_resume(struct mhi_controller *mhi_cntrl) ++{ ++ return __mhi_pm_resume(mhi_cntrl, false); ++} + EXPORT_SYMBOL_GPL(mhi_pm_resume); + ++int mhi_pm_resume_force(struct mhi_controller *mhi_cntrl) ++{ ++ return __mhi_pm_resume(mhi_cntrl, true); ++} ++EXPORT_SYMBOL_GPL(mhi_pm_resume_force); ++ + int __mhi_device_get_sync(struct mhi_controller *mhi_cntrl) + { + int ret; +diff --git a/include/linux/mhi.h b/include/linux/mhi.h +index 7239858790353a..a5cc4cdf9cc86f 100644 +--- a/include/linux/mhi.h ++++ b/include/linux/mhi.h +@@ -679,6 +679,19 @@ int mhi_pm_suspend(struct mhi_controller *mhi_cntrl); + */ + int mhi_pm_resume(struct mhi_controller *mhi_cntrl); + ++/** ++ * mhi_pm_resume_force - Force resume MHI from suspended state ++ * @mhi_cntrl: MHI controller ++ * ++ * Resume the device irrespective of its MHI state. As per the MHI spec, devices ++ * has to be in M3 state during resume. But some devices seem to be in a ++ * different MHI state other than M3 but they continue working fine if allowed. ++ * This API is intented to be used for such devices. ++ * ++ * Return: 0 if the resume succeeds, a negative error code otherwise ++ */ ++int mhi_pm_resume_force(struct mhi_controller *mhi_cntrl); ++ + /** + * mhi_download_rddm_image - Download ramdump image from device for + * debugging purpose. diff --git a/root/target/linux/ipq60xx/patches-5.4/003-axp2402support.patch b/root/target/linux/ipq60xx/patches-5.4/003-axp2402support.patch new file mode 100644 index 00000000..470b0c24 --- /dev/null +++ b/root/target/linux/ipq60xx/patches-5.4/003-axp2402support.patch @@ -0,0 +1,1657 @@ +Index: linux-5.4.164/drivers/mfd/Kconfig +=================================================================== +--- linux-5.4.164.orig/drivers/mfd/Kconfig ++++ linux-5.4.164/drivers/mfd/Kconfig +@@ -211,6 +211,15 @@ config MFD_AXP20X_RSB + components like regulators or the PEK (Power Enable Key) under the + corresponding menus. + ++config MFD_AXP2402 ++ bool "X-Power AXP2402" ++ select MFD_CORE ++ select REGMAP_I2C ++ select REGMAP_IRQ ++ depends on I2C=y ++ help ++ If you say Y here you get support for the X-Power AXP2402 power mananger IC. ++ + config MFD_CROS_EC_DEV + tristate "ChromeOS Embedded Controller multifunction device" + select MFD_CORE +Index: linux-5.4.164/drivers/mfd/Makefile +=================================================================== +--- linux-5.4.164.orig/drivers/mfd/Makefile ++++ linux-5.4.164/drivers/mfd/Makefile +@@ -145,6 +145,8 @@ obj-$(CONFIG_MFD_AXP20X) += axp20x.o + obj-$(CONFIG_MFD_AXP20X_I2C) += axp20x-i2c.o + obj-$(CONFIG_MFD_AXP20X_RSB) += axp20x-rsb.o + ++obj-$(CONFIG_MFD_AXP2402) += axp2402.o ++ + obj-$(CONFIG_MFD_LP3943) += lp3943.o + obj-$(CONFIG_MFD_LP8788) += lp8788.o lp8788-irq.o + +Index: linux-5.4.164/drivers/mfd/axp2402.c +=================================================================== +--- /dev/null ++++ linux-5.4.164/drivers/mfd/axp2402.c +@@ -0,0 +1,335 @@ ++/* ++ * Core driver for X-Powers AXP2402 BMU ++ * ++ * Copyright (c) 2018, X-Powers CORPORATION. All rights reserved. ++ ++ * This program is free software; you can redistribute it and/or modify it ++ * under the terms and conditions of the GNU General Public License, ++ * version 2, as published by the Free Software Foundation. ++ ++ * This program is distributed in the hope it will be useful, but WITHOUT ++ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ++ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for ++ * more details. ++ ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++static struct resource charger_resources[] = { ++ { ++ .start = AXP2402_IRQ_IC_TEMOV, ++ .end = AXP2402_IRQ_IC_TEMOV, ++ .flags = IORESOURCE_IRQ, ++ } ++}; ++ ++enum AXP2402_cells { ++ CHARGER = 0, ++}; ++ ++static struct mfd_cell AXP2402s[] = { ++ [CHARGER] = { ++ .name = "axp2402-charger", ++ .num_resources = ARRAY_SIZE(charger_resources), ++ .resources = &charger_resources[0], ++ .of_compatible = "X-Powers, axp2402-charger", ++ }, ++}; ++ ++struct regmap_irq AXP2402_irqs[] = { ++ /* INT1 IRQs*/ ++ [AXP2402_IRQ_IC_TEMOV] = { ++ .mask = AXP2402_INT1_MASK_IC_TEMOV, ++ }, ++ [AXP2402_IRQ_EVENT_TIMEOUT] = { ++ .mask = AXP2402_INT1_MASK_EVENT_TIMEOUT, ++ }, ++ [AXP2402_IRQ_OVER_DISCHG] = { ++ .mask = AXP2402_INT1_MASK_OVER_DISCHG, ++ }, ++ [AXP2402_IRQ_ACOK_HIGHGOLOW] = { ++ .mask = AXP2402_INT1_MASK_ACOK_HIGHGOLOW, ++ }, ++ [AXP2402_IRQ_ACOK_HIGHGOLOW] = { ++ .mask = AXP2402_INT1_MASK_ACOK_HIGHGOLOW, ++ }, ++ [AXP2402_IRQ_VAC_OCP] = { ++ .mask = AXP2402_INT1_MASK_VAC_OCP, ++ }, ++ [AXP2402_IRQ_VAC_OVP] = { ++ .mask = AXP2402_INT1_MASK_VAC_OVP, ++ }, ++ /* INT2 IRQs*/ ++ [AXP2402_IRQ_TEMP_WL2] = { ++ .reg_offset = 1, ++ .mask = AXP2402_INT2_MASK_TEMP_WL2, ++ }, ++ [AXP2402_IRQ_TEMP_WL1] = { ++ .reg_offset = 1, ++ .mask = AXP2402_INT2_MASK_TEMP_WL1, ++ }, ++ [AXP2402_IRQ_BAT_CHARGERDONE] = { ++ .reg_offset = 1, ++ .mask = AXP2402_INT2_MASK_BAT_CHARGERDONE, ++ }, ++ [AXP2402_IRQ_BAT_CHARGING] = { ++ .reg_offset = 1, ++ .mask = AXP2402_INT2_MASK_BAT_CHARGING, ++ }, ++ [AXP2402_IRQ_BAT_TIMEOUT] = { ++ .reg_offset = 1, ++ .mask = AXP2402_INT2_MASK_BAT_TIMEOUT, ++ }, ++ [AXP2402_IRQ_BAT_REMOVAL] = { ++ .reg_offset = 1, ++ .mask = AXP2402_INT2_MASK_BAT_REMOVAL, ++ }, ++ [AXP2402_IRQ_BAT_APPEND] = { ++ .reg_offset = 1, ++ .mask = AXP2402_INT2_MASK_BAT_APPEND, ++ }, ++ ++ /* INT3 IRQs*/ ++ [AXP2402_IRQ_BAT_QBWUT] = { ++ .reg_offset = 2, ++ .mask = AXP2402_INT3_MASK_BAT_QBWUT, ++ }, ++ [AXP2402_IRQ_BAT_BWUT] = { ++ .reg_offset = 2, ++ .mask = AXP2402_INT3_MASK_BAT_BWUT, ++ }, ++ ++ [AXP2402_IRQ_BAT_QBWOT] = { ++ .reg_offset = 2, ++ .mask = AXP2402_INT3_MASK_BAT_QBWOT, ++ }, ++ [AXP2402_IRQ_BAT_BWOT] = { ++ .reg_offset = 2, ++ .mask = AXP2402_INT3_MASK_BAT_BWOT, ++ }, ++ [AXP2402_IRQ_BAT_QBCUT] = { ++ .reg_offset = 2, ++ .mask = AXP2402_INT3_MASK_BAT_QBCUT, ++ }, ++ [AXP2402_IRQ_BAT_BCUT] = { ++ .reg_offset = 2, ++ .mask = AXP2402_INT3_MASK_BAT_BCUT, ++ }, ++ [AXP2402_IRQ_BAT_QBCOT] = { ++ .reg_offset = 2, ++ .mask = AXP2402_INT3_MASK_BAT_QBCOT, ++ }, ++ [AXP2402_IRQ_BAT_BC0T] = { ++ .reg_offset = 2, ++ .mask = AXP2402_INT3_MASK_BAT_BC0T, ++ }, ++ /* INT4 IRQs*/ ++ [AXP2402_IRQ_BAT_QBCR] = { ++ .reg_offset = 3, ++ .mask = AXP2402_INT4_MASK_BAT_QBCR, ++ }, ++ [AXP2402_IRQ_BAT_BCR] = { ++ .reg_offset = 3, ++ .mask = AXP2402_INT4_MASK_BAT_BCR, ++ }, ++ [AXP2402_IRQ_BAT_QBWR] = { ++ .reg_offset = 3, ++ .mask = AXP2402_INT4_MASK_BAT_QBWR, ++ }, ++ [AXP2402_IRQ_BAT_BWR] = { ++ .reg_offset = 3, ++ .mask = AXP2402_INT4_MASK_BAT_BWR, ++ }, ++ /* INT5 IRQs*/ ++ [AXP2402_IRQ_BAT_PERCENTAGE_CHANGE] = { ++ .reg_offset = 4, ++ .mask = AXP2402_INT4_MASK_BAT_PERCENTAGE_CHANGE, ++ }, ++ ++}; ++ ++static struct regmap_irq_chip AXP2402_irq_chip = { ++ .name = "axp2402", ++ .irqs = AXP2402_irqs, ++ .num_irqs = ARRAY_SIZE(AXP2402_irqs), ++ .num_regs = NUM_INT_REG, ++ .status_base = AXP2402_REG_IRQ_STS1, ++ .mask_base = AXP2402_REG_IRQ_MASK1, ++ .mask_invert = true, ++}; ++ ++static bool is_volatile_reg(struct device *dev, unsigned int reg) ++{ ++ /* Nearly all registers have status bits mixed in, except a few */ ++ switch (reg) { ++ case AXP2402_REG_BMU_CTRL: ++ case AXP2402_REG_SYS_CTRL: ++ case AXP2402_REG_INDPM_CTRL: ++ case AXP2402_REG_JIETA_COOLTEMP: ++ case AXP2402_REG_JIETA_WARMTEMP: ++ case AXP2402_REG_JIETA_SET: ++ case AXP2402_REG_CHG_CTRL0: ++ case AXP2402_REG_CHG_CTRL1: ++ case AXP2402_REG_CHG_CTRL2: ++ case AXP2402_REG_CHG_CTRL3: ++ case AXP2402_REG_CHG_CTRL4: ++ case AXP2402_REG_CHG_CTRL5: ++ case AXP2402_REG_CHG_CTRL6: ++ case AXP2402_REG_ADC_CTRL: ++ case AXP2402_REG_TS_CTRL: ++ case AXP2402_REG_IRQ_MASK1: ++ case AXP2402_REG_IRQ_MASK2: ++ case AXP2402_REG_IRQ_MASK3: ++ case AXP2402_REG_IRQ_MASK4: ++ case AXP2402_REG_IRQ_MASK5: ++ case AXP2402_REG_CAP_CAP0: ++ case AXP2402_REG_CAP_CAP1: ++ case AXP2402_REG_CAP_WARNING: ++ case AXP2402_REG_CAP_CTRL: ++ case AXP2402_REG_CAP_PERCENT: ++ case AXP2402_REG_CAP_RDC1: ++ case AXP2402_REG_CAP_RDC2: ++ case AXP2402_REG_VLTF_CHARGE: ++ case AXP2402_REG_VHTF_CHARGE: ++ case AXP2402_REG_VLTF_WORK: ++ case AXP2402_REG_VHTF_WORK: ++ return true; ++ } ++ return true; ++} ++ ++static const struct regmap_config AXP2402_regmap_config = { ++ .reg_bits = 8, ++ .val_bits = 8, ++ .max_register = AXP2402_MAX_REG, ++ .num_reg_defaults_raw = AXP2402_NUM_REGS, ++ .cache_type = REGCACHE_RBTREE, ++ .volatile_reg = is_volatile_reg, ++}; ++ ++#ifdef CONFIG_OF ++static const struct of_device_id AXP2402_of_match[] = { ++ { .compatible = "X-Powers, axp2402",}, ++ {}, ++}; ++MODULE_DEVICE_TABLE(of, AXP2402_of_match); ++#endif ++ ++static int AXP2402_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id) ++{ ++ struct AXP2402_platform_data *pdata = dev_get_platdata(&client->dev); ++ int irq_base = 0; ++ struct AXP2402 *AXP2402; ++ int ret; ++ ++ if (!pdata && !client->dev.of_node) ++ { ++ dev_err(&client->dev, ++ "AXP2402 requires platform data or of_node\n"); ++ return -EINVAL; ++ } ++ ++ if (pdata) ++ irq_base = pdata->irq_base; ++ ++ AXP2402 = devm_kzalloc(&client->dev, sizeof(*AXP2402), GFP_KERNEL); ++ ++ if (!AXP2402) ++ return -ENOMEM; ++ ++ AXP2402->dev = &client->dev; ++ i2c_set_clientdata(client, AXP2402); ++ AXP2402->rmap = devm_regmap_init_i2c(client, &AXP2402_regmap_config); ++ if (IS_ERR(AXP2402->rmap)) ++ { ++ ret = PTR_ERR(AXP2402->rmap); ++ dev_err(&client->dev, "regmap_init failed with err: %d\n", ret); ++ return ret; ++ } ++ ++ if (client->irq) ++ { ++ ret = regmap_add_irq_chip(AXP2402->rmap, client->irq, IRQF_ONESHOT | IRQF_TRIGGER_LOW, irq_base, &AXP2402_irq_chip, &AXP2402->irq_data); ++ if (ret) ++ { ++ dev_err(&client->dev, ++ "IRQ init failed with err: %d\n", ret); ++ return ret; ++ } ++ } ++ else ++ { ++ AXP2402s[CHARGER].num_resources = 0; ++ printk("axp2402 irq init \n"); ++ } ++ ++ ret = mfd_add_devices(AXP2402->dev, -1, AXP2402s, ARRAY_SIZE(AXP2402s), NULL, 0, regmap_irq_get_domain(AXP2402->irq_data)); ++ if (ret) ++ { ++ dev_err(&client->dev, "add mfd devices failed with err: %d\n",ret); ++ goto err_irq_exit; ++ } ++ return 0; ++ ++err_irq_exit: ++ if (client->irq) ++ regmap_del_irq_chip(client->irq, AXP2402->irq_data); ++ return ret; ++} ++ ++static int AXP2402_i2c_remove(struct i2c_client *client) ++{ ++ struct AXP2402 *AXP2402 = i2c_get_clientdata(client); ++ ++ mfd_remove_devices(AXP2402->dev); ++ if (client->irq) ++ regmap_del_irq_chip(client->irq, AXP2402->irq_data); ++ ++ return 0; ++} ++ ++static const struct i2c_device_id AXP2402_id_table[] = { ++ { "axp2402", 0 }, ++ {}, ++}; ++MODULE_DEVICE_TABLE(i2c, AXP2402_id_table); ++ ++static struct i2c_driver AXP2402_driver = { ++ .driver = { ++ .name = "axp2402", ++ .of_match_table = of_match_ptr(AXP2402_of_match), ++ }, ++ .probe = AXP2402_i2c_probe, ++ .remove = AXP2402_i2c_remove, ++ .id_table = AXP2402_id_table, ++}; ++ ++static int __init AXP2402_init(void) ++{ ++ return i2c_add_driver(&AXP2402_driver); ++} ++subsys_initcall(AXP2402_init); ++ ++static void __exit AXP2402_exit(void) ++{ ++ i2c_del_driver(&AXP2402_driver); ++} ++module_exit(AXP2402_exit); ++ ++MODULE_DESCRIPTION("AXP2402 Core Driver"); ++MODULE_AUTHOR("X-Powers"); ++MODULE_LICENSE("GPL v2"); +\ No newline at end of file +Index: linux-5.4.164/drivers/power/supply/Kconfig +=================================================================== +--- linux-5.4.164.orig/drivers/power/supply/Kconfig ++++ linux-5.4.164/drivers/power/supply/Kconfig +@@ -337,6 +337,14 @@ config AXP20X_POWER + This driver provides support for the power supply features of + AXP20x PMIC. + ++config AXP2402_POWER ++ tristate "X-Power AXP2402 charger support" ++ depends on MFD_AXP2402 ++ help ++ Say Y here to enable support for the battery charger in the ++ AXP2402 PMIC ++ ++ + config AXP288_CHARGER + tristate "X-Powers AXP288 Charger" + depends on MFD_AXP20X && EXTCON_AXP288 +Index: linux-5.4.164/drivers/power/supply/Makefile +=================================================================== +--- linux-5.4.164.orig/drivers/power/supply/Makefile ++++ linux-5.4.164/drivers/power/supply/Makefile +@@ -23,6 +23,7 @@ obj-$(CONFIG_CHARGER_ADP5061) += adp5061 + obj-$(CONFIG_BATTERY_ACT8945A) += act8945a_charger.o + obj-$(CONFIG_BATTERY_AXP20X) += axp20x_battery.o + obj-$(CONFIG_CHARGER_AXP20X) += axp20x_ac_power.o ++obj-$(CONFIG_AXP2402_POWER) += axp2402_charger.o + obj-$(CONFIG_BATTERY_CPCAP) += cpcap-battery.o + obj-$(CONFIG_BATTERY_DS2760) += ds2760_battery.o + obj-$(CONFIG_BATTERY_DS2780) += ds2780_battery.o +Index: linux-5.4.164/drivers/power/supply/axp2402_charger.c +=================================================================== +--- /dev/null ++++ linux-5.4.164/drivers/power/supply/axp2402_charger.c +@@ -0,0 +1,1009 @@ ++/* ++ * Battery charger driver for X-Powers AXP2402 BMU ++ * ++ * Copyright (c) 2018, X-Powers CORPORATION. All rights reserved. ++ ++ * This program is free software; you can redistribute it and/or modify it ++ * under the terms and conditions of the GNU General Public License, ++ * version 2, as published by the Free Software Foundation. ++ ++ * This program is distributed in the hope it will be useful, but WITHOUT ++ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ++ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for ++ * more details. ++ ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define POLL_INTERVAL (HZ * 2) /* Used when no irq */ ++ ++struct AXP2402_charger { ++ struct device *dev; ++ int ac_online; ++ int prev_ac_online; ++ int battery_online; ++ int prev_battery_online; ++ int irq; ++ int idpm; //è¾“å…¥é™æµå€¼ ++ int vsys_min; //最å°ç”µåŽ‹ ++ u16 ichg_cc; //充电电压 ++ int chg_target_voltage; //电池电压 ++ int battery_max_capacity; //ç”µæ± æœ€å¤§å®¹é‡ ++ struct task_struct *poll_task; ++ struct power_supply *ac; ++ struct power_supply *battery; ++ struct AXP2402_platform_data *pdata; ++ struct gpio_desc *gpiod; ++}; ++ ++static enum power_supply_property AXP2402_ac_props[] = ++{ ++ POWER_SUPPLY_PROP_ONLINE, ++ POWER_SUPPLY_PROP_CHARGE_TYPE, ++ POWER_SUPPLY_PROP_HEALTH, ++ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, ++ POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, ++ POWER_SUPPLY_PROP_SCOPE, ++}; ++ ++static enum power_supply_property AXP2402_battery_props[]={ ++ POWER_SUPPLY_PROP_ONLINE, ++ POWER_SUPPLY_PROP_STATUS, ++ POWER_SUPPLY_PROP_HEALTH, ++ POWER_SUPPLY_PROP_TECHNOLOGY, ++ POWER_SUPPLY_PROP_TEMP, ++ POWER_SUPPLY_PROP_CAPACITY, ++ POWER_SUPPLY_PROP_SCOPE, ++ POWER_SUPPLY_PROP_CAPACITY_LEVEL, ++ POWER_SUPPLY_PROP_CURRENT_NOW, ++ POWER_SUPPLY_PROP_VOLTAGE_NOW, ++}; ++static int AXP2402_get_battery_status(struct AXP2402_charger *charger , union power_supply_propval *val) ++{ ++ uint8_t battery_status; ++ int ret,status; ++ ++ ret = AXP2402_read(charger->dev->parent, AXP2402_REG_BMU_STATUS3, &battery_status); ++ ++ switch(battery_status&0x1c) ++ { ++ case 0x0: ++ status = POWER_SUPPLY_STATUS_NOT_CHARGING; ++ break; ++ case 0x04: ++ case 0x08: ++ case 0x0C: ++ status = POWER_SUPPLY_STATUS_CHARGING; ++ break; ++ case 0x10: ++ status = POWER_SUPPLY_STATUS_FULL; ++ break; ++ default: ++ ret = AXP2402_read(charger->dev->parent, AXP2402_REG_POWER_STATUS, &battery_status); ++ if(battery_status & AXP2402_BAT_IS_CHARGING) ++ { ++ status = POWER_SUPPLY_STATUS_DISCHARGING; ++ ++ }else{ ++ ++ status = POWER_SUPPLY_STATUS_UNKNOWN; ++ ++ } ++ ret = 0; ++ } ++ ++ val->intval=status; ++ return ret; ++} ++ ++static int AXP2402_get_battery_health(struct AXP2402_charger *charger, union power_supply_propval *val) ++{ ++ uint8_t battery_health; ++ int health,ret; ++ ret = AXP2402_read(charger->dev->parent, AXP2402_REG_BMU_STATUS1, &battery_health); ++ ++ switch(battery_health & 0x03) ++ { ++ case 0x0: ++ health = POWER_SUPPLY_HEALTH_GOOD; ++ break; ++ case 0x01: ++ health = POWER_SUPPLY_HEALTH_COLD; ++ break; ++ case 0x02: ++ health = POWER_SUPPLY_HEALTH_OVERHEAT; ++ break; ++ default: ++ health = POWER_SUPPLY_HEALTH_UNKNOWN; ++ } ++ ++ ret = AXP2402_read(charger->dev->parent, AXP2402_REG_POWER_STATUS,&battery_health); ++ ++ if((battery_health & AXP2402_BAT_IS_NOT_CONNECTED) == 0) ++ { ++ health = POWER_SUPPLY_HEALTH_UNKNOWN; ++ } ++ ++ val->intval = health; ++ ++ return 0; ++} ++static int AXP2402_get_battery_online(struct AXP2402_charger *charger, union power_supply_propval *val) ++{ ++ uint8_t battery_indication; ++ int ret; ++ ++ ret = AXP2402_read(charger->dev->parent, AXP2402_REG_POWER_STATUS, &battery_indication); ++ if((battery_indication & 0x10) != 0) ++ val->intval = 1; ++ else ++ val->intval = 0; ++ return 0; ++} ++ ++static int AXP2402_get_battery_current_now(struct AXP2402_charger *charger, union power_supply_propval *val) ++{ ++ ++ uint8_t bat_current; ++ int ret; ++ int current_temp; ++ uint8_t status; ++ ++ ret = AXP2402_read(charger->dev->parent, AXP2402_REG_POWER_STATUS, &status); ++ ++ if(status & AXP2402_BAT_IS_CHARGING){ ++ ++ val->intval = 0; ++ return 0; ++ } ++ ++ ret = AXP2402_read(charger->dev->parent, AXP2402_REG_ADC_DISIBATL, &bat_current); ++ ++ current_temp = (int)bat_current; ++ ret = AXP2402_read(charger->dev->parent, AXP2402_REG_ADC_DISIBATH, &bat_current); ++ ++ current_temp = (current_temp + bat_current * 16)*2; ++ ++ val->intval = current_temp; ++ ++ return 0; ++} ++ ++static int AXP2402_get_battery_capacity(struct AXP2402_charger *charger, union power_supply_propval *val) ++{ ++ /* ++ uint8_t cap_max; ++ int temp; ++ int ret; ++ ++ ret = AXP2402_read(charger->dev->parent, AXP2402_REG_CAP_CAP1, &cap_max); ++ temp = cap_max; ++ ret = AXP2402_read(charger->dev->parent, AXP2402_REG_CAP_CAP0, &cap_max); ++ temp = (temp + cap_max*256)*1456/1000; ++ val->intval=temp; ++ */ ++ ++ uint8_t oc_percentage; ++ int ret; ++ int battery_temp; ++ ++ ret = AXP2402_read(charger->dev->parent, AXP2402_REG_BAT_CAP,&oc_percentage); ++ if(oc_percentage & AXP2402_BAT_IS_VALID){ ++ battery_temp = oc_percentage & 0x07f; ++ val->intval = battery_temp; ++ }else{ ++ //dev_info(charger->dev,"oc_percentage = 0x%x",oc_percentage); ++ ret = AXP2402_read(charger->dev->parent, AXP2402_REG_POWER_STATUS,&oc_percentage); ++ if((oc_percentage & (1<<2))){ ++ if((oc_percentage & AXP2402_BAT_IS_NOT_CONNECTED) ==0){ ++ dev_info(charger->dev,"battery is not connected\n"); ++ } ++ }else{ ++ dev_info(charger->dev,"battery doesn't have detected!\n"); ++ } ++ } ++ ++ return 0 ; ++} ++ ++static int AXP2402_get_battery_capacity_alert_min(struct AXP2402_charger *charger, union power_supply_propval *val) ++{ ++ uint8_t cap_alert; ++ int ret; ++ ++ ret = AXP2402_read(charger->dev->parent, AXP2402_REG_CAP_WARNING, &cap_alert); ++ val->intval=cap_alert >> 4; ++ return 0 ; ++} ++ ++static int AXP2402_get_battery_voltage_now(struct AXP2402_charger *charger, union power_supply_propval *val) ++{ ++ uint8_t voltage; ++ int temp; ++ int ret; ++ ++ ret = AXP2402_read(charger->dev->parent, AXP2402_REG_ADC_VBATL, &voltage); ++ ++ temp = voltage; ++ ++ ret = AXP2402_read(charger->dev->parent, AXP2402_REG_ADC_VBATH, &voltage); ++ ++ temp = (temp + voltage*16)*12/10; ++ ++ val->intval= temp*2; ++ ++ return ret; ++ ++} ++ ++static int AXP2402_get_battery_temp(struct AXP2402_charger *charger, union power_supply_propval *val) ++{ ++ uint8_t battery_temp; ++ int temp; ++ int ret; ++ ++ ret = AXP2402_read(charger->dev->parent, AXP2402_REG_ADC_TSL, &battery_temp); ++ temp = battery_temp; ++ ret = AXP2402_read(charger->dev->parent, AXP2402_REG_ADC_TSH, &battery_temp); ++ temp = (temp + battery_temp*16)*8/10; ++ val->intval= temp; ++ return 0; ++} ++ ++static int AXP2402_get_battery_soc(struct AXP2402_charger *charger, union power_supply_propval *val) ++{ ++ uint8_t oc_percentage; ++ int ret; ++ int battery_temp; ++ ++ ret = AXP2402_read(charger->dev->parent, AXP2402_REG_BAT_CAP, &oc_percentage); ++ if(oc_percentage & AXP2402_BAT_IS_VALID){ ++ ++ battery_temp = oc_percentage&0x7f; ++ if (battery_temp == 100) ++ val->intval = POWER_SUPPLY_CAPACITY_LEVEL_FULL; ++ else if (battery_temp >= 80) ++ val->intval = POWER_SUPPLY_CAPACITY_LEVEL_HIGH; ++ else if (battery_temp >= 50) ++ val->intval = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; ++ else if (battery_temp <= 1) ++ val->intval = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; ++ else if (battery_temp < 15) ++ val->intval = POWER_SUPPLY_CAPACITY_LEVEL_LOW; ++ else ++ val->intval = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN; ++ }else{ ++ dev_info(charger->dev,"battery capacity not valid"); ++ return -1; ++ } ++ ++ return 0; ++} ++ ++static int AXP2402_battery_get_property(struct power_supply *psy,enum power_supply_property psp,union power_supply_propval *val) ++{ ++ //struct AXP2402_charger *cdata = container_of(psy,struct AXP2402_charger,battery); ++ int ret; ++ struct AXP2402_charger *cdata = power_supply_get_drvdata(psy); ++ ++ switch(psp){ ++ case POWER_SUPPLY_PROP_STATUS: ++ ret = AXP2402_get_battery_status(cdata,val); ++ break; ++ case POWER_SUPPLY_PROP_HEALTH: ++ ret = AXP2402_get_battery_health(cdata,val); ++ break; ++ case POWER_SUPPLY_PROP_ONLINE: ++ ret = AXP2402_get_battery_online(cdata,val); ++ break; ++ case POWER_SUPPLY_PROP_TECHNOLOGY: ++ val->intval = POWER_SUPPLY_TECHNOLOGY_LION; ++ ret=0; ++ break; ++ case POWER_SUPPLY_PROP_CAPACITY: ++ ret = AXP2402_get_battery_capacity(cdata,val); ++ break; ++ case POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN: ++ ret = AXP2402_get_battery_capacity_alert_min(cdata,val); ++ break; ++ case POWER_SUPPLY_PROP_TEMP: ++ ret = AXP2402_get_battery_temp(cdata,val); ++ break; ++ case POWER_SUPPLY_PROP_CAPACITY_LEVEL: ++ ret = AXP2402_get_battery_soc(cdata,val); ++ break; ++ case POWER_SUPPLY_PROP_SCOPE: ++ val->intval = POWER_SUPPLY_SCOPE_SYSTEM; ++ ret=0; ++ break; ++ case POWER_SUPPLY_PROP_CURRENT_NOW: ++ ret = AXP2402_get_battery_current_now(cdata,val); ++ break; ++ case POWER_SUPPLY_PROP_VOLTAGE_NOW: ++ ret = AXP2402_get_battery_voltage_now(cdata,val); ++ break; ++ default: ++ return -ENODATA; ++ } ++ ++ return ret ; ++ ++} ++ ++static int AXP2402_get_ac_online(struct AXP2402_charger *charger, union power_supply_propval *val) ++{ ++ int ret; ++ uint8_t charger_present; ++ ++ ret = AXP2402_read(charger->dev->parent, AXP2402_REG_POWER_STATUS, &charger_present); ++ charger_present &= 0x01; ++ if (charger_present) ++ val->intval = 1; ++ else ++ val->intval = 0; ++ ++ return 0; ++ ++} ++static int AXP2402_get_charger_type(struct AXP2402_charger *charger, union power_supply_propval *val) ++{ ++ uint8_t charger_status; ++ int ret,type; ++ ret = AXP2402_read(charger->dev->parent, AXP2402_REG_BMU_STATUS3, &charger_status); ++ ++ switch(charger_status&0x1c) ++ { ++ case 0x0: ++ type = POWER_SUPPLY_CHARGE_TYPE_NONE; ++ break; ++ case 0x04: ++ type = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; ++ break; ++ case 0x08: ++ case 0x0c: ++ type = POWER_SUPPLY_CHARGE_TYPE_FAST; ++ break; ++ default: ++ ret = AXP2402_read(charger->dev->parent, AXP2402_REG_POWER_STATUS, &charger_status); ++ if (!(charger_status & AXP2402_BAT_IS_CHARGING)) ++ { ++ type = POWER_SUPPLY_STATUS_DISCHARGING; ++ } ++ else ++ { ++ type = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; ++ } ++ ret = 0; ++ } ++ ++ val->intval=type; ++ return ret; ++} ++static int AXP2402_get_charger_health(struct AXP2402_charger *charger, union power_supply_propval *val) ++{ ++ uint8_t charger_health; ++ int health,ret; ++ ++ ret = AXP2402_read(charger->dev->parent, AXP2402_REG_POWER_STATUS, &charger_health); ++ if(charger_health&0x08) ++ health= POWER_SUPPLY_HEALTH_OVERVOLTAGE; ++ else ++ { ++ ret = AXP2402_read(charger->dev->parent, AXP2402_REG_BMU_STATUS1, &charger_health); ++ ++ switch(charger_health&0x0c) ++ { ++ case 0x0: /* Normal */ ++ health = POWER_SUPPLY_HEALTH_GOOD; ++ break; ++ case 0x08: /* Thermal Shutdown */ ++ health = POWER_SUPPLY_HEALTH_OVERHEAT; ++ break; ++ case 0x02: /* Charge Safety Timer Expiration */ ++ health = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE; ++ break; ++ default: ++ health = POWER_SUPPLY_HEALTH_UNKNOWN; ++ } ++ } ++ val->intval = health; ++ ++ return 0; ++ ++} ++ ++static int AXP2402_get_charger_current(struct AXP2402_charger *charger, union power_supply_propval *val) ++{ ++ uint8_t cur; ++ int ret, temp; ++ ++ ret = AXP2402_read(charger->dev->parent, AXP2402_REG_ADC_IBATL, &cur); ++ temp = (int)cur; ++ ret = AXP2402_read(charger->dev->parent, AXP2402_REG_ADC_IBATH, &cur); ++ temp = (temp + cur*16)*2; ++ val->intval= temp; ++ return 0; ++} ++ ++static int AXP2402_get_charger_voltage(struct AXP2402_charger *charger, union power_supply_propval *val) ++{ ++ uint8_t voltage; ++ int ret, temp; ++ ++ ret = AXP2402_read(charger->dev->parent, AXP2402_REG_ADC_VBATL, &voltage); ++ temp = voltage; ++ ret = AXP2402_read(charger->dev->parent, AXP2402_REG_ADC_VBATH, &voltage); ++ temp = (temp + voltage*16)*12/10; ++ val->intval= temp*2; ++ return 0; ++} ++ ++static int AXP2402_ac_get_property(struct power_supply *psy,enum power_supply_property psp,union power_supply_propval *val) ++{ ++ //struct AXP2402_charger *cdata = container_of(psy,struct AXP2402_charger,battery); ++ int ret; ++ struct AXP2402_charger *cdata = power_supply_get_drvdata(psy); ++ ++ switch(psp){ ++ case POWER_SUPPLY_PROP_CHARGE_TYPE: ++ ret = AXP2402_get_charger_type(cdata,val); ++ break; ++ case POWER_SUPPLY_PROP_HEALTH: ++ ret = AXP2402_get_charger_health(cdata,val); ++ break; ++ case POWER_SUPPLY_PROP_ONLINE: ++ ret = AXP2402_get_ac_online(cdata,val); ++ break; ++ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: ++ ret = AXP2402_get_charger_current(cdata,val); ++ break; ++ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: ++ ret = AXP2402_get_charger_voltage(cdata,val); ++ break; ++ case POWER_SUPPLY_PROP_SCOPE: ++ val->intval = POWER_SUPPLY_SCOPE_SYSTEM; ++ ret = 0; ++ break; ++ default: ++ return -ENODATA; ++ } ++ return ret=0 ; ++} ++ ++static int AXP2402_set_charger_current(struct AXP2402_charger *charger, const union power_supply_propval *val) ++{ ++ uint8_t temp; ++ int ret,cur; ++ ++ ret = AXP2402_read(charger->dev->parent, AXP2402_REG_CHG_CTRL2, &temp); ++ temp=temp&0x80; ++ cur=((val->intval)/64)|temp; ++ ret = AXP2402_write(charger->dev->parent,AXP2402_REG_CHG_CTRL2,cur); ++ dev_info(charger->dev,"set_charger_current\n"); ++ return ret; ++} ++ ++static int AXP2402_set_charger_voltage(struct AXP2402_charger *charger, const union power_supply_propval *val) ++{ ++ uint8_t temp; ++ int ret,voltage; ++ ++ ret = AXP2402_read(charger->dev->parent, AXP2402_REG_CHG_CTRL5, &temp); ++ temp= temp && 0x80; ++ voltage=(((val->intval)-8192)/8)||temp; ++ ret = AXP2402_write(charger->dev->parent,AXP2402_REG_CHG_CTRL5,voltage); ++ dev_info(charger->dev,"set_charger_voltage\n"); ++ return ret; ++} ++ ++ ++static int AXP2402_ac_set_property(struct power_supply *psy,enum power_supply_property psp, const union power_supply_propval *val) ++{ ++ int ret; ++ struct AXP2402_charger *cdata = power_supply_get_drvdata(psy); ++ ++ switch(psp){ ++ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: ++ ret = AXP2402_set_charger_current(cdata,val); ++ break; ++ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: ++ ret = AXP2402_set_charger_voltage(cdata,val); ++ break; ++ default: ++ return -ENODATA; ++ } ++ return ret; ++ ++} ++ ++static int AXP2402_charger_property_is_writeable(struct power_supply *psy,enum power_supply_property psp) ++{ ++ int ret; ++ ++ switch (psp) { ++ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: ++ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: ++ ret = 1; ++ break; ++ default: ++ ret = 0; ++ } ++ ++ return ret; ++} ++ ++static int AXP2402_config_charger(struct AXP2402_charger *charger) ++{ ++ uint8_t reg=0; ++ int i; ++ uint8_t val=0,cap1; ++ int cap; ++ int ocv_addr; ++ int ret; ++ int idpm = charger->idpm; ++ int vsys_min = charger->vsys_min; ++ int ichg_terminate = ICHG_TERMINATE; ++ int ichg_cc = charger->ichg_cc; ++ int chg_target_voltage = charger->chg_target_voltage; ++ int prechg_timeout = PRECHG_TIMEOUT; ++ int fastchg_timeout = FASTCHG_TIMEOUT; ++ int chg_led_type = CHG_LED_TYPE; ++ int cap_max = charger->battery_max_capacity; ++ uint8_t ocv_table[32]={0,0,1,2,4,6,8,10,12,15,18,23,29,35,41,47,52,56,63,70,76,79,82,85,87,89,91,93,100,100,100,100}; ++ ++ //config Irq_Mask ++ AXP2402_write(charger->dev->parent, AXP2402_REG_IRQ_MASK1, 0xFF); ++ AXP2402_write(charger->dev->parent, AXP2402_REG_IRQ_MASK2, 0xFF); ++ AXP2402_write(charger->dev->parent, AXP2402_REG_IRQ_MASK3, 0xFF); ++ AXP2402_write(charger->dev->parent, AXP2402_REG_IRQ_MASK4, 0xFF); ++ AXP2402_write(charger->dev->parent, AXP2402_REG_IRQ_MASK5, 0xFF); ++ ++ val = 0xC0; ++ ret = AXP2402_write(charger->dev->parent,AXP2402_ADC_MODE_CTRL,val); ++ val = 0x03; ++ ret = AXP2402_write(charger->dev->parent,AXP2402_REG_POWER_SAVE_MODE,val); ++ ++ reg =reg &80; //config INDPM 4096ma and bit7 set 1 ++ ret = AXP2402_write(charger->dev->parent,AXP2402_REG_INDPM_CTRL,(val|reg)); ++ ++ ++ ++ //config ichg_terminate ++ val=ichg_terminate/64-1; ++ val <<= 3; ++ ret = AXP2402_read(charger->dev->parent,AXP2402_REG_CHG_CTRL1,®); ++ reg = reg&0x87; ++ ret = AXP2402_write(charger->dev->parent,AXP2402_REG_CHG_CTRL1,(val|reg)); ++ ++ //config idpm ++ val=idpm/64; ++ ret = AXP2402_read(charger->dev->parent,AXP2402_REG_INDPM_CTRL,®); ++ reg =reg &0x80; ++ ret = AXP2402_write(charger->dev->parent,AXP2402_REG_INDPM_CTRL,(val|reg)); ++ ++ //config vsys_min ++ val=vsys_min/256; ++ ret = AXP2402_read(charger->dev->parent,AXP2402_REG_SYS_CTRL,®); ++ reg = reg&0Xc0; ++ ret = AXP2402_write(charger->dev->parent,AXP2402_REG_SYS_CTRL,(val|reg)); ++ ++ //config ichg_cc ++ val=ichg_cc/64; ++ ret = AXP2402_read(charger->dev->parent,AXP2402_REG_CHG_CTRL2,®); ++ reg= reg&80; ++ ret = AXP2402_write(charger->dev->parent,AXP2402_REG_CHG_CTRL2,(val|reg)); ++ ++ //config chg_target_voltage ++ val=(chg_target_voltage-8192)/8; ++ ret = AXP2402_read(charger->dev->parent,AXP2402_REG_CHG_CTRL5,®); ++ reg=reg&0x80; ++ ret = AXP2402_write(charger->dev->parent,AXP2402_REG_CHG_CTRL5,val); ++ ++ //config fastchg_timeout ++ if(fastchg_timeout==6) ++ val=0; ++ else if (fastchg_timeout==8) ++ val=1; ++ else if (fastchg_timeout==10) ++ val=2; ++ else if (fastchg_timeout==12) ++ val=3; ++ val<<=6; ++ ret = AXP2402_read(charger->dev->parent,AXP2402_REG_CHG_CTRL3,®); ++ reg= reg&0x3f; ++ ret = AXP2402_write(charger->dev->parent,AXP2402_REG_CHG_CTRL3,(val|reg)); ++ ++ //config prechg_timeout ++ if(prechg_timeout==40) ++ val=0; ++ else if (prechg_timeout==50) ++ val=1; ++ else if (prechg_timeout==60) ++ val=2; ++ else if (prechg_timeout==70) ++ val=3; ++ ret = AXP2402_read(charger->dev->parent,AXP2402_REG_CHG_CTRL1,®); ++ reg=reg&0xfc; ++ ret = AXP2402_write(charger->dev->parent,AXP2402_REG_CHG_CTRL1,(val|reg)); ++ ++ //config chg_led_type ++ if(chg_led_type==0) ++ val=0; ++ else if (chg_led_type==1) ++ val=1; ++ ret = AXP2402_read(charger->dev->parent,AXP2402_REG_CHG_LED,®); ++ reg=reg&0xfe; ++ ret = AXP2402_write(charger->dev->parent,AXP2402_REG_CHG_LED,(val|reg)); ++ ++ //config cap_max ++ cap=cap_max*1000/1456; ++ cap1=cap>>8&0x007f; ++ cap1 |= AXP2402_BAT_MAX_CAP_IS_VALID; //max cap is valid ++ ++ ret = AXP2402_write(charger->dev->parent, AXP2402_REG_CAP_CAP0,cap1); ++ cap1=cap&0x00FF; ++ ret = AXP2402_write(charger->dev->parent, AXP2402_REG_CAP_CAP1,cap1); ++ ++ //config ocv_table ++ ocv_addr = OCV_TABLE_BASE; ++ for(i = 0; i < 32; i++) ++ { ++ ret = AXP2402_write(charger->dev->parent, ocv_addr, ocv_table[i]); ++ printk("OCV table %d: %d\n", i, ocv_table[i]); ++ if (ret < 0) ++ { ++ dev_err(charger->dev, "%s(): error writing in register 0x%x\n", __func__, ocv_addr); ++ return ret; ++ } ++ ocv_addr++; ++ } ++ ++ ret = AXP2402_read(charger->dev->parent, AXP2402_REG_CAP_CAP0,®); ++ ++ dev_info(charger->dev,"CAP0 = 0x%x\n",reg); ++ ++ ret = AXP2402_read(charger->dev->parent, AXP2402_REG_CAP_CAP1,®); ++ ++ dev_info(charger->dev,"CAP1 = 0x%x\n",reg); ++ ++ ret = AXP2402_read(charger->dev->parent, AXP2402_ADC_MODE_CTRL,®); ++ ++ dev_info(charger->dev,"ADC_MODE = 0x%x\n",reg); ++ ++ ret = AXP2402_read(charger->dev->parent, AXP2402_REG_POWER_SAVE_MODE, ®); ++ ++ dev_info(charger->dev,"POWER_SAVE_MODE = 0x%x\n",reg); ++ ++ return 0; ++} ++ ++static irqreturn_t AXP2402_charger_isr(int irq, void *dev_id) ++{ ++ struct AXP2402_charger *charger = dev_id; ++ int ret; ++ uint8_t interrupt_pending = 0; ++ uint8_t interrupt_pos; ++ ++ ret = AXP2402_read(charger->dev->parent, AXP2402_REG_IRQ_STS1, &interrupt_pending); ++ interrupt_pos = 1<< AXP2402_INT1_MASK_ACOK_HIGHGOLOW; ++ ret = AXP2402_write(charger->dev->parent, AXP2402_REG_IRQ_STS1, interrupt_pending & interrupt_pos); ++ if ((interrupt_pending & interrupt_pos) !=0) ++ { ++ /* place corresponding handle code*/ ++ dev_info(charger->dev,"ACOK_HIGHGOLOW interrupt happens!\n"); ++ } ++ interrupt_pos = 1<< AXP2402_INT1_MASK_ACOK_LOWGOHIGH; ++ ret = AXP2402_write(charger->dev->parent, AXP2402_REG_IRQ_STS1, interrupt_pending & interrupt_pos); ++ ++ if ((interrupt_pending & interrupt_pos) !=0) ++ { ++ /* place corresponding handle code*/ ++ dev_info(charger->dev,"ACOK_LOWGOHIGH interrupt happens!\n"); ++ } ++ ++ ret = AXP2402_read(charger->dev->parent, AXP2402_REG_IRQ_STS2, &interrupt_pending); ++ interrupt_pos = 1<< AXP2402_INT2_MASK_BAT_CHARGING; ++ ret = AXP2402_write(charger->dev->parent, AXP2402_REG_IRQ_STS2, interrupt_pending & interrupt_pos); ++ if ((interrupt_pending & interrupt_pos) !=0) ++ { ++ /* place corresponding handle code*/ ++ dev_info(charger->dev,"BAT_CHARGING interrupt happens!\n"); ++ } ++ interrupt_pos = 1<< AXP2402_INT2_MASK_BAT_APPEND; ++ ret = AXP2402_write(charger->dev->parent, AXP2402_REG_IRQ_STS2, interrupt_pending & interrupt_pos); ++ ++ if ((interrupt_pending & interrupt_pos) !=0) ++ { ++ /* place corresponding handle code*/ ++ dev_info(charger->dev,"BAT_APPEND interrupt happens!\n"); ++ } ++ ++ ret = AXP2402_read(charger->dev->parent, AXP2402_REG_IRQ_STS4, &interrupt_pending); ++ interrupt_pos = 1<< AXP2402_INT4_MASK_BAT_PERCENTAGE_CHANGE; ++ ret = AXP2402_write(charger->dev->parent, AXP2402_REG_IRQ_STS4, interrupt_pending & interrupt_pos); ++ if ((interrupt_pending & interrupt_pos) !=0) ++ { ++ /* place corresponding handle code*/ ++ dev_info(charger->dev,"BAT_PERCENTAGE_CHANGE interrupt happens!\n"); ++ } ++ ++ AXP2402_write(charger->dev->parent, AXP2402_REG_IRQ_STS1, 0xFF); ++ AXP2402_write(charger->dev->parent, AXP2402_REG_IRQ_STS2, 0xFF); ++ AXP2402_write(charger->dev->parent, AXP2402_REG_IRQ_STS3, 0xFF); ++ AXP2402_write(charger->dev->parent, AXP2402_REG_IRQ_STS4, 0xFF); ++ AXP2402_write(charger->dev->parent, AXP2402_REG_IRQ_STS5, 0xFF); ++ AXP2402_write(charger->dev->parent, AXP2402_REG_IRQ_STS6, 0xFF); ++ ++ return IRQ_HANDLED; ++ ++} ++ ++static struct AXP2402_platform_data *AXP2402_parse_dt_charger_data(struct platform_device *pdev) ++{ ++ struct AXP2402_platform_data *pdata; ++ ++ pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); ++ if (!pdata) ++ { ++ dev_err(&pdev->dev, "Memory alloc for AXP2402_pdata failed\n"); ++ return NULL; ++ } ++ pdata->irq_base = -1; ++ ++ return pdata; ++ ++} ++ ++static int AXP2402_charger_poll_task(void *data) ++{ ++ set_freezable(); ++ ++ while (!kthread_should_stop()) ++ { ++ schedule_timeout_interruptible(POLL_INTERVAL); ++ try_to_freeze(); ++ AXP2402_charger_isr(-1, data); ++ } ++ return 0; ++} ++ ++static const struct power_supply_desc AXP2402_charger_desc = { ++ .name = "axp2402-ac", ++ .type = POWER_SUPPLY_TYPE_MAINS, ++ .get_property = AXP2402_ac_get_property, ++ .set_property = AXP2402_ac_set_property, ++ .properties = AXP2402_ac_props, ++ .num_properties = ARRAY_SIZE(AXP2402_ac_props), ++ .property_is_writeable = AXP2402_charger_property_is_writeable, ++}; ++ ++static const struct power_supply_desc AXP2402_battery_desc = { ++ .name = "axp2402-battery", ++ .type = POWER_SUPPLY_TYPE_BATTERY, ++ .get_property = AXP2402_battery_get_property, ++ .properties = AXP2402_battery_props, ++ .num_properties =ARRAY_SIZE(AXP2402_battery_props), ++}; ++ ++ ++static int AXP2402_charger_probe(struct platform_device *pdev) ++{ ++ struct AXP2402_charger *cdata; ++ struct AXP2402_platform_data *pdata; ++ struct power_supply_config charger_cfg = {},battery_cfg ={}; ++ struct device_node *node; ++ uint8_t status1 = 0; ++ int val; ++ int ret; ++ int irq; ++ ++ pdata = dev_get_platdata(pdev->dev.parent); ++ if (IS_ENABLED(CONFIG_OF) && !pdata && pdev->dev.of_node) ++ pdata = AXP2402_parse_dt_charger_data(pdev); ++ if (!pdata) ++ { ++ dev_err(&pdev->dev, "%s():no platform data available\n", ++ __func__); ++ return -ENODEV; ++ } ++ ++ node = of_find_node_by_name(NULL, "axp2402-charger"); ++ ++ if(!node){ ++ dev_err(&pdev->dev, "%s():device node not found\n",__func__); ++ return -ENODEV; ++ } ++ ++ ++ cdata = devm_kzalloc(&pdev->dev, sizeof(*cdata), GFP_KERNEL); ++ if (!cdata) ++ { ++ dev_err(&pdev->dev, "failed to allocate memory status\n"); ++ return -ENOMEM; ++ } ++ ++ platform_set_drvdata(pdev, cdata); ++ ++ cdata->dev = &pdev->dev; ++ cdata->pdata = pdata; ++ ++ charger_cfg.supplied_to = pdata->supplied_to; ++ charger_cfg.num_supplicants = pdata->num_supplicants; ++ charger_cfg.of_node = pdev->dev.of_node; ++ charger_cfg.drv_data = cdata; ++ ++ cdata->ac = power_supply_register(&pdev->dev, &AXP2402_charger_desc,&charger_cfg); ++ if (IS_ERR(cdata->ac)) ++ { ++ dev_err(&pdev->dev, "failed: power supply register\n"); ++ return PTR_ERR(cdata->ac); ++ } ++ ++ battery_cfg.drv_data = cdata; ++ cdata->battery = power_supply_register(&pdev->dev,&AXP2402_battery_desc,&battery_cfg); ++ if (IS_ERR(cdata->battery)) ++ { ++ dev_err(&pdev->dev,"failed: battery supply register\n"); ++ return PTR_ERR(cdata->battery); ++ } ++ ++ cdata->gpiod = devm_gpiod_get(&pdev->dev,"irqpin",GPIOD_IN); ++ if(IS_ERR(cdata->gpiod)) ++ { ++ printk(KERN_ALERT "request pinctrl handle for device %s failed\n", dev_name(&pdev->dev)); ++ } ++ else printk(KERN_ALERT "request pinctrl handle for device %s ok\n", dev_name(&pdev->dev)); ++ ++ cdata->irq = gpiod_to_irq(cdata->gpiod); ++ printk("charger irq = %d\n",irq); ++ ++ if (IS_ERR_VALUE(cdata->irq)) ++ { ++ printk(KERN_ALERT "map gpio %d to virq failed, err =%d\n",desc_to_gpio(cdata->gpiod), cdata->irq); ++ } ++ else ++ { ++ dev_info(&pdev->dev,"map gpio %d to virq %d\n",desc_to_gpio(cdata->gpiod), cdata->irq); ++ } ++ ++ ret = of_property_read_u32(node,"idpm",&val); ++ if(ret == 0) ++ { ++ dev_info(&pdev->dev,"idpm = %d\n",val); ++ cdata->idpm = val; ++ } ++ ++ ret = of_property_read_u32(node, "ichg_cc",&val); ++ if(ret == 0) ++ { ++ dev_info(&pdev->dev,"ichg_cc = %d\n",val); ++ cdata->ichg_cc = val; ++ } ++ ++ ret = of_property_read_u32(node,"chg_target_voltage", &val); ++ if(ret == 0) ++ { ++ dev_info(&pdev->dev,"chg_target_voltage = %d\n",val); ++ cdata->chg_target_voltage = val; ++ } ++ ++ ret = of_property_read_u32(node, "battery_max_capacity", &val); ++ if(ret == 0) ++ { ++ dev_info(&pdev->dev,"battery_max_capacity = %d\n",val); ++ cdata->battery_max_capacity = val; ++ } ++ ++ ret = of_property_read_u32(node, "vsys_min",&val); ++ if(ret == 0) ++ { ++ dev_info(&pdev->dev,"vsys_min = %d\n", val); ++ cdata->vsys_min = val; ++ } ++ ++ ret = AXP2402_config_charger(cdata); ++ if (ret < 0) ++ { ++ dev_err(&pdev->dev, "charger config failed, err %d\n", ret); ++ goto fail_unregister_supply; ++ } ++ //Check for IC type_no. presence ++ ret = AXP2402_read(cdata->dev->parent, AXP2402_REG_IC_TypeNo, ++ &status1); ++ if (ret < 0) ++ { ++ dev_err(cdata->dev, "%s(): Error in reading reg 0x%x", __func__,AXP2402_REG_IC_TypeNo); ++ goto fail_unregister_supply; ++ } ++ ++ cdata ->ac_online=1; ++ power_supply_changed(cdata->ac); ++ if (cdata->irq != -ENXIO) ++ { ++ ret = devm_request_threaded_irq(&pdev->dev, cdata->irq, NULL,AXP2402_charger_isr, IRQF_ONESHOT | IRQF_TRIGGER_LOW, "axp2402-charger", cdata); ++ if (ret) { ++ dev_err(cdata->dev, ++ "Unable to register irq %d err %d\n", cdata->irq, ++ ret); ++ goto fail_unregister_supply; ++ } ++ } ++ else ++ { ++ cdata->poll_task = kthread_run(AXP2402_charger_poll_task,cdata, "axp2402-charger"); ++ if (IS_ERR(cdata->poll_task)) ++ { ++ ret = PTR_ERR(cdata->poll_task); ++ dev_err(cdata->dev, ++ "Unable to run kthread err %d\n", ret); ++ goto fail_unregister_supply; ++ } ++ } ++ ++ return 0; ++ ++fail_unregister_supply: ++ power_supply_unregister(cdata->ac); ++ power_supply_unregister(cdata->battery); ++ return ret; ++} ++ ++static int AXP2402_charger_remove(struct platform_device *pdev) ++{ ++ struct AXP2402_charger *cdata = platform_get_drvdata(pdev); ++ ++ if (cdata->irq == -ENXIO) ++ kthread_stop(cdata->poll_task); ++ ++ power_supply_unregister(cdata->ac); ++ power_supply_unregister(cdata->battery); ++ ++ return 0; ++} ++ ++static const struct of_device_id of_AXP2402_charger_match[] = { ++ { .compatible = "X-Powers, axp2402-charger", }, ++ { /* end */ } ++}; ++MODULE_DEVICE_TABLE(of, of_AXP2402_charger_match); ++ ++static struct platform_driver AXP2402_charger_driver = { ++ .driver = { ++ .name = "axp2402-charger", ++ .of_match_table = of_AXP2402_charger_match, ++ }, ++ .probe = AXP2402_charger_probe, ++ .remove = AXP2402_charger_remove, ++}; ++ ++static int __init AXP2402_charger_init(void) ++{ ++ return platform_driver_register(&AXP2402_charger_driver); ++} ++ ++subsys_initcall(AXP2402_charger_init); ++ ++static void __exit AXP2402_charger_exit(void) ++{ ++ platform_driver_unregister(&AXP2402_charger_driver); ++} ++module_exit(AXP2402_charger_exit); ++MODULE_LICENSE("GPL v2"); ++MODULE_AUTHOR("X-Powers"); ++MODULE_DESCRIPTION("AXP2402 Battery Charger Driver"); +\ No newline at end of file +Index: linux-5.4.164/include/linux/mfd/axp2402.h +=================================================================== +--- /dev/null ++++ linux-5.4.164/include/linux/mfd/axp2402.h +@@ -0,0 +1,217 @@ ++/* ++ * Core driver interface for X-Powers AXP2402 BMU ++ * ++ * Copyright (C) 2012 NVIDIA Corporation ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, but WITHOUT ++ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ++ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for ++ * more details. ++ * ++ * You should have received a copy of the GNU General Public License along ++ * with this program; if not, write to the Free Software Foundation, Inc., ++ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ++ * ++ */ ++ ++#ifndef __LINUX_MFD_AXP2402_H ++#define __LINUX_MFD_AXP2402_H ++ ++#include ++#include ++ ++/* Register addresses */ ++#define AXP2402_REG_POWER_STATUS (0x00) ++#define AXP2402_REG_BMU_STATUS1 (0x01) ++#define AXP2402_REG_BMU_STATUS2 (0x02) ++#define AXP2402_REG_IC_TypeNo (0x03) ++#define AXP2402_REG_BMU_STATUS3 (0x04) ++#define AXP2402_REG_BMU_CTRL (0x10) ++#define AXP2402_REG_SYS_CTRL (0x11) ++#define AXP2402_REG_INDPM_CTRL (0x13) ++#define AXP2402_REG_JIETA_COOLTEMP (0x19) ++#define AXP2402_REG_JIETA_WARMTEMP (0x1A) ++#define AXP2402_REG_JIETA_SET (0x1B) ++#define AXP2402_REG_CHG_CTRL0 (0x1F) ++#define AXP2402_REG_CHG_CTRL1 (0x20) ++#define AXP2402_REG_CHG_CTRL2 (0x21) ++#define AXP2402_REG_CHG_CTRL3 (0x22) ++#define AXP2402_REG_CHG_CTRL4 (0x23) ++#define AXP2402_REG_CHG_CTRL5 (0x24) ++#define AXP2402_REG_CHG_CTRL6 (0x25) ++#define AXP2402_REG_ADC_CTRL (0x26) ++#define AXP2402_REG_TS_CTRL (0x28) ++#define AXP2402_REG_CHG_LED (0x90) ++#define AXP2402_BAT_IS_CHARGING (1<<5) ++#define AXP2402_BAT_IS_NOT_CONNECTED (1<<4) ++//irq control ++#define AXP2402_REG_IRQ_MASK1 (0x40) ++#define AXP2402_REG_IRQ_MASK2 (0x41) ++#define AXP2402_REG_IRQ_MASK3 (0x42) ++#define AXP2402_REG_IRQ_MASK4 (0x43) ++#define AXP2402_REG_IRQ_MASK5 (0x44) ++#define AXP2402_REG_IRQ_STS1 (0x48) ++#define AXP2402_REG_IRQ_STS2 (0x49) ++#define AXP2402_REG_IRQ_STS3 (0x4A) ++#define AXP2402_REG_IRQ_STS4 (0x4B) ++#define AXP2402_REG_IRQ_STS5 (0x4C) ++#define AXP2402_REG_IRQ_STS6 (0x4D) ++ ++//Fule gauge ++#define AXP2402_REG_CAP_CAP0 (0xE0) ++#define AXP2402_REG_CAP_CAP1 (0xE1) ++#define AXP2402_REG_OCV_PERCENTAGE (0xE4) ++#define AXP2402_REG_CAP_WARNING (0xE6) ++#define AXP2402_REG_CAP_CTRL (0xE8) ++#define AXP2402_REG_CAP_PERCENT (0xE9) ++#define AXP2402_REG_BAT_CAP (0xB9) ++#define AXP2402_REG_CAP_RDC1 (0xBA) ++#define AXP2402_REG_CAP_RDC2 (0xBB) ++ ++//TS protect ++#define AXP2402_REG_VLTF_CHARGE (0x30) ++#define AXP2402_REG_VHTF_CHARGE (0x31) ++#define AXP2402_REG_VLTF_WORK (0x32) ++#define AXP2402_REG_VHTF_WORK (0x33) ++#define AXP2402_REG_POWER_SAVE_MODE (0x8f) ++#define AXP2402_ADC_MODE_CTRL (0x8a) ++#define AXP2402_BAT_MAX_CAP_IS_VALID (1<<7) ++#define AXP2402_BAT_MAX_CAP_NOT_VALID ~(1<<7) ++#define AXP2402_BAT_IS_VALID (0x80) ++ ++//ADC ++#define AXP2402_REG_ADC_INTTEMP (0xAA) ++#define AXP2402_REG_ADC_TSH (0xAC) ++#define AXP2402_REG_ADC_TSL (0xAD) ++#define AXP2402_REG_ADC_VBATH (0x78) ++#define AXP2402_REG_ADC_VBATL (0x79) ++#define AXP2402_REG_ADC_IBATH (0x7A) ++#define AXP2402_REG_ADC_IBATL (0x7B) ++#define AXP2402_REG_ADC_DISIBATH (0x7C) ++#define AXP2402_REG_ADC_DISIBATL (0x7D) ++ ++#define AXP2402_REG_ADDR_EXTENSION (0xFF) ++#define AXP2402_MAX_REG AXP2402_REG_ADDR_EXTENSION ++#define AXP2402_NUM_REGS (AXP2402_MAX_REG + 1) ++ ++ ++//ADC Control Bits ++#define AXP2402_ADC_BATVOL_ENABLE BIT(7) ++#define AXP2402_ADC_BATCUR_ENABLE BIT(6) ++#define AXP2402_ADC_ACINCUR_ENABLE BIT(4) ++#define AXP2402_ADC_DIETMP_ENABLE BIT(3) ++#define AXP2402_ADC_TSVOL_ENABLE BIT(0) ++ ++ ++//AXP2402 config ++#define IDPM 1024 //������������ֵ ++#define VSYS_MIN 6400 //����Vsysmin��ѹֵ ++#define ICHG_TERMINATE 128 //���ó���ֹ���� ++#define ICHG_CC 1024 //������ ++#define CHG_TARGET_VOLTAGE 8440 //���õ��Ŀ���ѹ ++#define PRECHG_TIMEOUT 50 //����Ԥ��糬ʱʱ�� ++#define FASTCHG_TIMEOUT 4 //���ú�����糬ʱʱ�� ++#define CHG_LED_TYPE 0 //���ó��ָʾ��(type A Ϊ0; type B Ϊ1) ++#define BATTERY_MAX_CAPACITY 7000 ++ ++#define OCV_TABLE_BASE 0xC0 ++ ++/* AXP2402 IRQs */ ++enum { ++ AXP2402_IRQ_IC_TEMOV, ++ AXP2402_IRQ_EVENT_TIMEOUT, ++ AXP2402_IRQ_OVER_DISCHG, ++ AXP2402_IRQ_ACOK_HIGHGOLOW, ++ AXP2402_IRQ_ACOK_LOWGOHIGH, ++ AXP2402_IRQ_VAC_OCP, ++ AXP2402_IRQ_VAC_OVP, ++ AXP2402_IRQ_TEMP_WL2, ++ AXP2402_IRQ_TEMP_WL1, ++ AXP2402_IRQ_BAT_CHARGERDONE, ++ AXP2402_IRQ_BAT_CHARGING, ++ AXP2402_IRQ_BAT_TIMEOUT, ++ AXP2402_IRQ_BAT_REMOVAL, ++ AXP2402_IRQ_BAT_APPEND, ++ AXP2402_IRQ_BAT_QBWUT, ++ AXP2402_IRQ_BAT_BWUT, ++ AXP2402_IRQ_BAT_QBWOT, ++ AXP2402_IRQ_BAT_BWOT, ++ AXP2402_IRQ_BAT_QBCUT, ++ AXP2402_IRQ_BAT_BCUT, ++ AXP2402_IRQ_BAT_QBCOT, ++ AXP2402_IRQ_BAT_BC0T, ++ AXP2402_IRQ_BAT_QBCR, ++ AXP2402_IRQ_BAT_BCR, ++ AXP2402_IRQ_BAT_QBWR, ++ AXP2402_IRQ_BAT_BWR, ++ AXP2402_IRQ_BAT_PERCENTAGE_CHANGE, ++}; ++ ++#define NUM_INT_REG 5 ++ ++#define AXP2402_INT1_MASK_IC_TEMOV 0 ++#define AXP2402_INT1_MASK_EVENT_TIMEOUT 1 ++#define AXP2402_INT1_MASK_OVER_DISCHG 3 ++#define AXP2402_INT1_MASK_ACOK_HIGHGOLOW 4 ++#define AXP2402_INT1_MASK_ACOK_LOWGOHIGH 5 ++#define AXP2402_INT1_MASK_VAC_OCP 6 ++#define AXP2402_INT1_MASK_VAC_OVP 7 ++#define AXP2402_INT2_MASK_TEMP_WL2 0 ++#define AXP2402_INT2_MASK_TEMP_WL1 1 ++#define AXP2402_INT2_MASK_BAT_CHARGERDONE 2 ++#define AXP2402_INT2_MASK_BAT_CHARGING 3 ++#define AXP2402_INT2_MASK_BAT_TIMEOUT 5 ++#define AXP2402_INT2_MASK_BAT_REMOVAL 6 ++#define AXP2402_INT2_MASK_BAT_APPEND 7 ++#define AXP2402_INT3_MASK_BAT_QBWUT 0 ++#define AXP2402_INT3_MASK_BAT_BWUT 1 ++#define AXP2402_INT3_MASK_BAT_QBWOT 2 ++#define AXP2402_INT3_MASK_BAT_BWOT 3 ++#define AXP2402_INT3_MASK_BAT_QBCUT 4 ++#define AXP2402_INT3_MASK_BAT_BCUT 5 ++#define AXP2402_INT3_MASK_BAT_QBCOT 6 ++#define AXP2402_INT3_MASK_BAT_BC0T 7 ++#define AXP2402_INT4_MASK_BAT_QBCR 4 ++#define AXP2402_INT4_MASK_BAT_BCR 5 ++#define AXP2402_INT4_MASK_BAT_QBWR 6 ++#define AXP2402_INT4_MASK_BAT_BWR 7 ++#define AXP2402_INT4_MASK_BAT_PERCENTAGE_CHANGE 0 ++ ++struct AXP2402 { ++ struct device *dev; ++ struct regmap *rmap; ++ struct regmap_irq_chip_data *irq_data; ++}; ++ ++struct AXP2402_platform_data { ++ int irq_base; ++ char **supplied_to; ++ size_t num_supplicants; ++}; ++ ++static inline int AXP2402_write(struct device *dev, int reg, uint8_t val) ++{ ++ struct AXP2402 *axp = dev_get_drvdata(dev); ++ ++ return regmap_write(axp->rmap, reg, val); ++} ++ ++static inline int AXP2402_read(struct device *dev, int reg, uint8_t *val) ++{ ++ struct AXP2402 *axp = dev_get_drvdata(dev); ++ unsigned int temp_val; ++ int ret; ++ ++ ret = regmap_read(axp->rmap, reg, &temp_val); ++ ++ if (!ret) ++ *val = temp_val; ++ return ret; ++} ++ ++#endif /*__LINUX_MFD_AXP2402_H*/ +\ No newline at end of file +Index: linux-5.4.164/include/linux/err.h +=================================================================== +--- linux-5.4.164.orig/include/linux/err.h ++++ linux-5.4.164/include/linux/err.h +@@ -19,7 +19,8 @@ + + #ifndef __ASSEMBLY__ + +-#define IS_ERR_VALUE(x) unlikely((unsigned long)(void *)(x) >= (unsigned long)-MAX_ERRNO) ++// #define IS_ERR_VALUE(x) unlikely((unsigned long)(void *)(x) >= (unsigned long)-MAX_ERRNO) ++#define IS_ERR_VALUE(x) ((unsigned long)(long)x >= (unsigned long)-MAX_ERRNO) + + static inline void * __must_check ERR_PTR(long error) + { diff --git a/root/target/linux/ipq60xx/patches-5.4/004-flashid.patch b/root/target/linux/ipq60xx/patches-5.4/004-flashid.patch new file mode 100644 index 00000000..a2219740 --- /dev/null +++ b/root/target/linux/ipq60xx/patches-5.4/004-flashid.patch @@ -0,0 +1,37 @@ +Index: linux-5.4.164/drivers/mtd/spi-nor/spi-nor.c +=================================================================== +--- linux-5.4.164.orig/drivers/mtd/spi-nor/spi-nor.c ++++ linux-5.4.164/drivers/mtd/spi-nor/spi-nor.c +@@ -5117,6 +5117,21 @@ int spi_nor_scan(struct spi_nor *nor, co + } + EXPORT_SYMBOL_GPL(spi_nor_scan); + ++ ++//david add flash id to sysfs ++static ssize_t m25p80_fid(struct device *dev, struct device_attribute *attr, char *buf) ++{ ++ struct spi_device *spi = to_spi_device(dev); ++ u8 code[5] = {0}, fid[8]; ++ ++ code[0] = 0x4b; ++ if (spi_write_then_read(spi, &code, 5, fid, 8) < 0) ++ return sprintf(buf, "\n"); ++ return sprintf(buf, "%08X%08X\n", ((u32 *)fid)[0], ((u32 *)fid)[1]); ++} ++static DEVICE_ATTR(fid, S_IRUGO, m25p80_fid, NULL); ++//add end ++ + static int spi_nor_probe(struct spi_mem *spimem) + { + struct spi_device *spi = spimem->spi; +@@ -5177,7 +5192,9 @@ static int spi_nor_probe(struct spi_mem + if (!nor->bouncebuf) + return -ENOMEM; + } +- ++//david add flash id to sysfs ++ device_create_file(&spi->dev, &dev_attr_fid); ++//add end + return mtd_device_register(&nor->mtd, data ? data->parts : NULL, + data ? data->nr_parts : 0); + } diff --git a/root/target/linux/ipq60xx/patches-5.4/005-cursor_blink.patch b/root/target/linux/ipq60xx/patches-5.4/005-cursor_blink.patch new file mode 100644 index 00000000..55cb11f9 --- /dev/null +++ b/root/target/linux/ipq60xx/patches-5.4/005-cursor_blink.patch @@ -0,0 +1,73 @@ +Index: linux-5.4.164/drivers/video/fbdev/core/Makefile +=================================================================== +--- linux-5.4.164.orig/drivers/video/fbdev/core/Makefile ++++ linux-5.4.164/drivers/video/fbdev/core/Makefile +@@ -7,7 +7,7 @@ fb-y := fbm + fb-$(CONFIG_FB_DEFERRED_IO) += fb_defio.o + + ifeq ($(CONFIG_FRAMEBUFFER_CONSOLE),y) +-fb-y += fbcon.o bitblit.o softcursor.o ++fb-y += fbcon.o bitblit.o + ifeq ($(CONFIG_FB_TILEBLITTING),y) + fb-y += tileblit.o + endif +Index: linux-5.4.164/drivers/video/fbdev/core/bitblit.c +=================================================================== +--- linux-5.4.164.orig/drivers/video/fbdev/core/bitblit.c ++++ linux-5.4.164/drivers/video/fbdev/core/bitblit.c +@@ -373,8 +373,8 @@ static void bit_cursor(struct vc_data *v + if (info->fbops->fb_cursor) + err = info->fbops->fb_cursor(info, &cursor); + +- if (err) +- soft_cursor(info, &cursor); ++ // if (err) ++ // soft_cursor(info, &cursor); + + ops->cursor_reset = 0; + } +Index: linux-5.4.164/drivers/video/fbdev/core/fbcon_ccw.c +=================================================================== +--- linux-5.4.164.orig/drivers/video/fbdev/core/fbcon_ccw.c ++++ linux-5.4.164/drivers/video/fbdev/core/fbcon_ccw.c +@@ -377,8 +377,8 @@ static void ccw_cursor(struct vc_data *v + if (info->fbops->fb_cursor) + err = info->fbops->fb_cursor(info, &cursor); + +- if (err) +- soft_cursor(info, &cursor); ++ // if (err) ++ // soft_cursor(info, &cursor); + + ops->cursor_reset = 0; + } +Index: linux-5.4.164/drivers/video/fbdev/core/fbcon_cw.c +=================================================================== +--- linux-5.4.164.orig/drivers/video/fbdev/core/fbcon_cw.c ++++ linux-5.4.164/drivers/video/fbdev/core/fbcon_cw.c +@@ -360,8 +360,8 @@ static void cw_cursor(struct vc_data *vc + if (info->fbops->fb_cursor) + err = info->fbops->fb_cursor(info, &cursor); + +- if (err) +- soft_cursor(info, &cursor); ++ // if (err) ++ // soft_cursor(info, &cursor); + + ops->cursor_reset = 0; + } +Index: linux-5.4.164/drivers/video/fbdev/core/fbcon_ud.c +=================================================================== +--- linux-5.4.164.orig/drivers/video/fbdev/core/fbcon_ud.c ++++ linux-5.4.164/drivers/video/fbdev/core/fbcon_ud.c +@@ -400,8 +400,8 @@ static void ud_cursor(struct vc_data *vc + if (info->fbops->fb_cursor) + err = info->fbops->fb_cursor(info, &cursor); + +- if (err) +- soft_cursor(info, &cursor); ++ // if (err) ++ // soft_cursor(info, &cursor); + + ops->cursor_reset = 0; + } diff --git a/root/target/linux/ipq60xx/patches-5.4/006-modems.patch b/root/target/linux/ipq60xx/patches-5.4/006-modems.patch new file mode 100644 index 00000000..cea676a4 --- /dev/null +++ b/root/target/linux/ipq60xx/patches-5.4/006-modems.patch @@ -0,0 +1,31 @@ +Index: linux-5.4.164/drivers/usb/serial/option.c +=================================================================== +--- linux-5.4.164.orig/drivers/usb/serial/option.c ++++ linux-5.4.164/drivers/usb/serial/option.c +@@ -585,6 +585,18 @@ static void option_instat_callback(struc + + + static const struct usb_device_id option_ids[] = { ++//david add modems: ++ { USB_DEVICE(0x2C7C, 0x0900) }, /* Quectel RG500U/RM500U */ ++ { USB_DEVICE(0x05C6, 0x90DB), .driver_info = RSVD(2) | RSVD(3) | RSVD(4) | RSVD(5)}, /* Yisean sim8200 */ ++ { USB_DEVICE(0x2dee, 0x4d22), .driver_info = RSVD(4) | RSVD(5) }, /* Meige srm815 */ ++ { USB_DEVICE(0x05c6, 0xf601), .driver_info = RSVD(4) | RSVD(5) }, /* Meige slm750 */ ++ { USB_DEVICE(0x05C6, 0x90D5), .driver_info = RSVD(2) | RSVD(3) }, /* Foxcomm T99W240 */ ++ { USB_DEVICE(0x2cb7 , 0x0a05), .driver_info = RSVD(0) | RSVD(1) | RSVD(6) }, /* Fibcom fm650 */ ++ { USB_DEVICE(0x2cb7, 0x0104), .driver_info = RSVD(4) }, /* Fibcom fm150 */ ++ { USB_DEVICE(0x2949 , 0x8800), .driver_info = RSVD(0) | RSVD(1) }, /* YouFang N510M */ ++ { USB_DEVICE(0x2949 , 0x8801), .driver_info = RSVD(0) | RSVD(1) }, /* YouFang N510M */ ++ { USB_DEVICE(0x2949 , 0x8802), .driver_info = RSVD(0) | RSVD(1) }, /* YouFang N510M */ ++//add end + { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_COLT) }, + { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_RICOLA) }, + { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_RICOLA_LIGHT) }, +@@ -1113,6 +1125,7 @@ static const struct usb_device_id option + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EC25, 0xff, 0xff, 0xff), + .driver_info = NUMEP2 }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EC25, 0xff, 0, 0) }, ++ { USB_DEVICE(0x2c7c, 0x030a),.driver_info = RSVD(0) | RSVD(1) | RSVD(6) | RSVD(7) | RSVD(8) },/* EM05-GL */ + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EG91, 0xff, 0xff, 0xff), + .driver_info = NUMEP2 }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EG91, 0xff, 0, 0) }, diff --git a/root/target/linux/ipq60xx/patches-5.4/691-mptcp.patch b/root/target/linux/ipq60xx/patches-5.4/691-mptcp.patch new file mode 100644 index 00000000..a1251c99 --- /dev/null +++ b/root/target/linux/ipq60xx/patches-5.4/691-mptcp.patch @@ -0,0 +1,24123 @@ +diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt +index 165abcb656c5..5d06ce2df29c 100644 +--- a/Documentation/admin-guide/kernel-parameters.txt ++++ b/Documentation/admin-guide/kernel-parameters.txt +@@ -2748,6 +2748,10 @@ + allocations which rules out almost all kernel + allocations. Use with caution! + ++ mptcp_htable_entries= ++ [KNL,NET] Set number of hash buckets for MPTCP token ++ hashtables. ++ + MTD_Partition= [MTD] + Format: ,,, + +diff --git a/Documentation/networking/ip-sysctl.txt b/Documentation/networking/ip-sysctl.txt +index 8af3771a3ebf..e8fecb8f6370 100644 +--- a/Documentation/networking/ip-sysctl.txt ++++ b/Documentation/networking/ip-sysctl.txt +@@ -818,6 +818,18 @@ tcp_rx_skb_cache - BOOLEAN + + Default: 0 (disabled) + ++MPTCP variables: ++ ++mptcp_enabled - INTEGER ++ Enable or disable Multipath TCP for new connections. ++ Possible values are: ++ ++ 0: Multipath TCP is disabled on all TCP-sockets that are newly created. ++ 1: Multipath TCP is enabled by default on all new TCP-sockets. Note that ++ existing sockets in LISTEN-state will still use regular TCP. ++ 2: Enables Multipath TCP only upon the request of the application ++ throught the socket-option MPTCP_ENABLED. ++ + UDP variables: + + udp_l3mdev_accept - BOOLEAN +diff --git a/drivers/infiniband/hw/cxgb4/cm.c b/drivers/infiniband/hw/cxgb4/cm.c +index 535ee41ee421..9f82f93e6e77 100644 +--- a/drivers/infiniband/hw/cxgb4/cm.c ++++ b/drivers/infiniband/hw/cxgb4/cm.c +@@ -3950,7 +3950,7 @@ static void build_cpl_pass_accept_req(struct sk_buff *skb, int stid , u8 tos) + */ + memset(&tmp_opt, 0, sizeof(tmp_opt)); + tcp_clear_options(&tmp_opt); +- tcp_parse_options(&init_net, skb, &tmp_opt, 0, NULL); ++ tcp_parse_options(&init_net, skb, &tmp_opt, NULL, 0, NULL, NULL); + + req = __skb_push(skb, sizeof(*req)); + memset(req, 0, sizeof(*req)); +diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h +index b04b5bd43f54..57e35d51db8c 100644 +--- a/include/linux/skbuff.h ++++ b/include/linux/skbuff.h +@@ -717,7 +717,7 @@ struct sk_buff { + * want to keep them across layers you have to do a skb_clone() + * first. This is owned by whoever has the skb queued ATM. + */ +- char cb[48] __aligned(8); ++ char cb[80] __aligned(8); + + union { + struct { +diff --git a/include/linux/tcp.h b/include/linux/tcp.h +index 358deb4ff830..aebfedba9838 100644 +--- a/include/linux/tcp.h ++++ b/include/linux/tcp.h +@@ -54,7 +54,7 @@ static inline unsigned int tcp_optlen(const struct sk_buff *skb) + /* TCP Fast Open */ + #define TCP_FASTOPEN_COOKIE_MIN 4 /* Min Fast Open Cookie size in bytes */ + #define TCP_FASTOPEN_COOKIE_MAX 16 /* Max Fast Open Cookie size in bytes */ +-#define TCP_FASTOPEN_COOKIE_SIZE 8 /* the size employed by this impl. */ ++#define TCP_FASTOPEN_COOKIE_SIZE 4 /* the size employed by this impl. */ + + /* TCP Fast Open Cookie as stored in memory */ + struct tcp_fastopen_cookie { +@@ -74,6 +74,56 @@ struct tcp_sack_block { + u32 end_seq; + }; + ++struct tcp_out_options { ++ u16 options; /* bit field of OPTION_* */ ++ u16 mss; /* 0 to disable */ ++ u8 ws; /* window scale, 0 to disable */ ++ u8 num_sack_blocks; /* number of SACK blocks to include */ ++ u8 hash_size; /* bytes in hash_location */ ++ __u8 *hash_location; /* temporary pointer, overloaded */ ++ __u32 tsval, tsecr; /* need to include OPTION_TS */ ++ struct tcp_fastopen_cookie *fastopen_cookie; /* Fast open cookie */ ++#ifdef CONFIG_MPTCP ++ u16 mptcp_options; /* bit field of MPTCP related OPTION_* */ ++ u8 dss_csum:1, /* dss-checksum required? */ ++ add_addr_v4:1, ++ add_addr_v6:1, ++ mptcp_ver:4; ++ ++ union { ++ struct { ++ __u64 sender_key; /* sender's key for mptcp */ ++ __u64 receiver_key; /* receiver's key for mptcp */ ++ } mp_capable; ++ ++ struct { ++ __u64 sender_truncated_mac; ++ __u32 sender_nonce; ++ /* random number of the sender */ ++ __u32 token; /* token for mptcp */ ++ u8 low_prio:1; ++ } mp_join_syns; ++ }; ++ ++ struct { ++ __u64 trunc_mac; ++ struct in_addr addr; ++ u16 port; ++ u8 addr_id; ++ } add_addr4; ++ ++ struct { ++ __u64 trunc_mac; ++ struct in6_addr addr; ++ u16 port; ++ u8 addr_id; ++ } add_addr6; ++ ++ u16 remove_addrs; /* list of address id */ ++ u8 addr_id; /* address id (mp_join or add_address) */ ++#endif /* CONFIG_MPTCP */ ++}; ++ + /*These are used to set the sack_ok field in struct tcp_options_received */ + #define TCP_SACK_SEEN (1 << 0) /*1 = peer is SACK capable, */ + #define TCP_DSACK_SEEN (1 << 2) /*1 = DSACK was received from peer*/ +@@ -97,6 +147,9 @@ struct tcp_options_received { + u16 mss_clamp; /* Maximal mss, negotiated at connection setup */ + }; + ++struct mptcp_cb; ++struct mptcp_tcp_sock; ++ + static inline void tcp_clear_options(struct tcp_options_received *rx_opt) + { + rx_opt->tstamp_ok = rx_opt->sack_ok = 0; +@@ -135,6 +188,8 @@ static inline struct tcp_request_sock *tcp_rsk(const struct request_sock *req) + return (struct tcp_request_sock *)req; + } + ++struct tcp_md5sig_key; ++ + struct tcp_sock { + /* inet_connection_sock has to be the first member of tcp_sock */ + struct inet_connection_sock inet_conn; +@@ -295,6 +350,7 @@ struct tcp_sock { + u32 rate_interval_us; /* saved rate sample: time elapsed */ + + u32 rcv_wnd; /* Current receiver window */ ++ u32 rcv_right_edge; /* Highest announced right edge */ + u32 write_seq; /* Tail(+1) of data held in tcp send buffer */ + u32 notsent_lowat; /* TCP_NOTSENT_LOWAT */ + u32 pushed_seq; /* Last pushed seq, required to talk to windows */ +@@ -397,6 +453,44 @@ struct tcp_sock { + */ + struct request_sock __rcu *fastopen_rsk; + u32 *saved_syn; ++ ++ /* MPTCP/TCP-specific callbacks */ ++ const struct tcp_sock_ops *ops; ++ ++ struct mptcp_cb *mpcb; ++ struct sock *meta_sk; ++ /* We keep these flags even if CONFIG_MPTCP is not checked, because ++ * it allows checking MPTCP capability just by checking the mpc flag, ++ * rather than adding ifdefs everywhere. ++ */ ++ u32 mpc:1, /* Other end is multipath capable */ ++ inside_tk_table:1, /* Is the tcp_sock inside the token-table? */ ++ send_mp_fclose:1, ++ request_mptcp:1, /* Did we send out an MP_CAPABLE? ++ * (this speeds up mptcp_doit() in tcp_recvmsg) ++ */ ++ pf:1, /* Potentially Failed state: when this flag is set, we ++ * stop using the subflow ++ */ ++ mp_killed:1, /* Killed with a tcp_done in mptcp? */ ++ is_master_sk:1, ++ close_it:1, /* Must close socket in mptcp_data_ready? */ ++ closing:1, ++ mptcp_ver:4, ++ mptcp_sched_setsockopt:1, ++ mptcp_pm_setsockopt:1, ++ record_master_info:1, ++ tcp_disconnect:1; ++ struct mptcp_tcp_sock *mptcp; ++#ifdef CONFIG_MPTCP ++#define MPTCP_SCHED_NAME_MAX 16 ++#define MPTCP_PM_NAME_MAX 16 ++ struct hlist_nulls_node tk_table; ++ u32 mptcp_loc_token; ++ u64 mptcp_loc_key; ++ char mptcp_sched_name[MPTCP_SCHED_NAME_MAX]; ++ char mptcp_pm_name[MPTCP_PM_NAME_MAX]; ++#endif /* CONFIG_MPTCP */ + }; + + enum tsq_enum { +@@ -408,6 +502,8 @@ enum tsq_enum { + TCP_MTU_REDUCED_DEFERRED, /* tcp_v{4|6}_err() could not call + * tcp_v{4|6}_mtu_reduced() + */ ++ MPTCP_PATH_MANAGER_DEFERRED, /* MPTCP deferred creation of new subflows */ ++ MPTCP_SUB_DEFERRED, /* A subflow got deferred - process them */ + }; + + enum tsq_flags { +@@ -417,6 +513,8 @@ enum tsq_flags { + TCPF_WRITE_TIMER_DEFERRED = (1UL << TCP_WRITE_TIMER_DEFERRED), + TCPF_DELACK_TIMER_DEFERRED = (1UL << TCP_DELACK_TIMER_DEFERRED), + TCPF_MTU_REDUCED_DEFERRED = (1UL << TCP_MTU_REDUCED_DEFERRED), ++ TCPF_PATH_MANAGER_DEFERRED = (1UL << MPTCP_PATH_MANAGER_DEFERRED), ++ TCPF_SUB_DEFERRED = (1UL << MPTCP_SUB_DEFERRED), + }; + + static inline struct tcp_sock *tcp_sk(const struct sock *sk) +@@ -440,6 +538,7 @@ struct tcp_timewait_sock { + #ifdef CONFIG_TCP_MD5SIG + struct tcp_md5sig_key *tw_md5_key; + #endif ++ struct mptcp_tw *mptcp_tw; + }; + + static inline struct tcp_timewait_sock *tcp_twsk(const struct sock *sk) +diff --git a/include/net/inet_common.h b/include/net/inet_common.h +index ae2ba897675c..aa91a56bd7af 100644 +--- a/include/net/inet_common.h ++++ b/include/net/inet_common.h +@@ -2,6 +2,7 @@ + #ifndef _INET_COMMON_H + #define _INET_COMMON_H + ++#include + #include + + extern const struct proto_ops inet_stream_ops; +@@ -16,6 +17,8 @@ + struct sockaddr; + struct socket; + ++int inet_create(struct net *net, struct socket *sock, int protocol, int kern); ++int inet6_create(struct net *net, struct socket *sock, int protocol, int kern); + int inet_release(struct socket *sock); + int inet_stream_connect(struct socket *sock, struct sockaddr *uaddr, + int addr_len, int flags); +diff --git a/include/net/inet_connection_sock.h b/include/net/inet_connection_sock.h +index 13792c0ef46e..e99cc510610f 100644 +--- a/include/net/inet_connection_sock.h ++++ b/include/net/inet_connection_sock.h +@@ -25,6 +25,7 @@ + + struct inet_bind_bucket; + struct tcp_congestion_ops; ++struct tcp_options_received; + + /* + * Pointers to address related TCP functions +diff --git a/include/net/inet_sock.h b/include/net/inet_sock.h +index 34c4436fd18f..828f79528b32 100644 +--- a/include/net/inet_sock.h ++++ b/include/net/inet_sock.h +@@ -79,7 +79,7 @@ struct inet_request_sock { + #define ireq_state req.__req_common.skc_state + #define ireq_family req.__req_common.skc_family + +- u16 snd_wscale : 4, ++ u32 snd_wscale : 4, + rcv_wscale : 4, + tstamp_ok : 1, + sack_ok : 1, +@@ -87,6 +87,8 @@ struct inet_request_sock { + ecn_ok : 1, + acked : 1, + no_srccheck: 1, ++ mptcp_rqsk : 1, ++ saw_mpc : 1, + smc_ok : 1; + u32 ir_mark; + union { +diff --git a/include/net/mptcp.h b/include/net/mptcp.h +new file mode 100644 +index 000000000000..196b8939cbab +--- /dev/null ++++ b/include/net/mptcp.h +@@ -0,0 +1,1577 @@ ++/* ++ * MPTCP implementation ++ * ++ * Initial Design & Implementation: ++ * Sébastien Barré ++ * ++ * Current Maintainer & Author: ++ * Christoph Paasch ++ * ++ * Additional authors: ++ * Jaakko Korkeaniemi ++ * Gregory Detal ++ * Fabien Duchêne ++ * Andreas Seelinger ++ * Lavkesh Lahngir ++ * Andreas Ripke ++ * Vlad Dogaru ++ * Octavian Purdila ++ * John Ronan ++ * Catalin Nicutar ++ * Brandon Heller ++ * ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License ++ * as published by the Free Software Foundation; either version ++ * 2 of the License, or (at your option) any later version. ++ */ ++ ++#ifndef _MPTCP_H ++#define _MPTCP_H ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++ ++#if defined(__LITTLE_ENDIAN_BITFIELD) ++ #define ntohll(x) be64_to_cpu(x) ++ #define htonll(x) cpu_to_be64(x) ++#elif defined(__BIG_ENDIAN_BITFIELD) ++ #define ntohll(x) (x) ++ #define htonll(x) (x) ++#endif ++ ++struct mptcp_loc4 { ++ u8 loc4_id; ++ u8 low_prio:1; ++ int if_idx; ++ struct in_addr addr; ++}; ++ ++struct mptcp_rem4 { ++ u8 rem4_id; ++ __be16 port; ++ struct in_addr addr; ++}; ++ ++struct mptcp_loc6 { ++ u8 loc6_id; ++ u8 low_prio:1; ++ int if_idx; ++ struct in6_addr addr; ++}; ++ ++struct mptcp_rem6 { ++ u8 rem6_id; ++ __be16 port; ++ struct in6_addr addr; ++}; ++ ++struct mptcp_request_sock { ++ struct tcp_request_sock req; ++ struct hlist_nulls_node hash_entry; ++ ++ union { ++ struct { ++ /* Only on initial subflows */ ++ u64 mptcp_loc_key; ++ u64 mptcp_rem_key; ++ u32 mptcp_loc_token; ++ }; ++ ++ struct { ++ /* Only on additional subflows */ ++ u32 mptcp_rem_nonce; ++ u32 mptcp_loc_nonce; ++ u64 mptcp_hash_tmac; ++ }; ++ }; ++ ++ u8 loc_id; ++ u8 rem_id; /* Address-id in the MP_JOIN */ ++ u16 dss_csum:1, ++ rem_key_set:1, ++ is_sub:1, /* Is this a new subflow? */ ++ low_prio:1, /* Interface set to low-prio? */ ++ rcv_low_prio:1, ++ mptcp_ver:4; ++}; ++ ++struct mptcp_options_received { ++ u16 saw_mpc:1, ++ dss_csum:1, ++ drop_me:1, ++ ++ is_mp_join:1, ++ join_ack:1, ++ ++ saw_low_prio:2, /* 0x1 - low-prio set for this subflow ++ * 0x2 - low-prio set for another subflow ++ */ ++ low_prio:1, ++ ++ saw_add_addr:2, /* Saw at least one add_addr option: ++ * 0x1: IPv4 - 0x2: IPv6 ++ */ ++ more_add_addr:1, /* Saw one more add-addr. */ ++ ++ saw_rem_addr:1, /* Saw at least one rem_addr option */ ++ more_rem_addr:1, /* Saw one more rem-addr. */ ++ ++ mp_fail:1, ++ mp_fclose:1; ++ u8 rem_id; /* Address-id in the MP_JOIN */ ++ u8 prio_addr_id; /* Address-id in the MP_PRIO */ ++ ++ const unsigned char *add_addr_ptr; /* Pointer to add-address option */ ++ const unsigned char *rem_addr_ptr; /* Pointer to rem-address option */ ++ ++ u32 data_ack; ++ u32 data_seq; ++ u16 data_len; ++ ++ u8 mptcp_ver; /* MPTCP version */ ++ ++ /* Key inside the option (from mp_capable or fast_close) */ ++ u64 mptcp_sender_key; ++ u64 mptcp_receiver_key; ++ ++ u32 mptcp_rem_token; /* Remote token */ ++ ++ u32 mptcp_recv_nonce; ++ u64 mptcp_recv_tmac; ++ u8 mptcp_recv_mac[20]; ++}; ++ ++struct mptcp_tcp_sock { ++ struct hlist_node node; ++ struct hlist_node cb_list; ++ struct mptcp_options_received rx_opt; ++ ++ /* Those three fields record the current mapping */ ++ u64 map_data_seq; ++ u32 map_subseq; ++ u16 map_data_len; ++ u16 slave_sk:1, ++ fully_established:1, ++ second_packet:1, ++ attached:1, ++ send_mp_fail:1, ++ include_mpc:1, ++ mapping_present:1, ++ map_data_fin:1, ++ low_prio:1, /* use this socket as backup */ ++ rcv_low_prio:1, /* Peer sent low-prio option to us */ ++ send_mp_prio:1, /* Trigger to send mp_prio on this socket */ ++ pre_established:1; /* State between sending 3rd ACK and ++ * receiving the fourth ack of new subflows. ++ */ ++ ++ /* isn: needed to translate abs to relative subflow seqnums */ ++ u32 snt_isn; ++ u32 rcv_isn; ++ u8 path_index; ++ u8 loc_id; ++ u8 rem_id; ++ u8 sk_err; ++ ++#define MPTCP_SCHED_SIZE 16 ++ u8 mptcp_sched[MPTCP_SCHED_SIZE] __aligned(8); ++ ++ int init_rcv_wnd; ++ u32 infinite_cutoff_seq; ++ struct delayed_work work; ++ u32 mptcp_loc_nonce; ++ struct tcp_sock *tp; ++ u32 last_end_data_seq; ++ ++ /* MP_JOIN subflow: timer for retransmitting the 3rd ack */ ++ struct timer_list mptcp_ack_timer; ++ ++ /* HMAC of the third ack */ ++ char sender_mac[SHA256_DIGEST_SIZE]; ++}; ++ ++struct mptcp_tw { ++ struct list_head list; ++ u64 loc_key; ++ u64 rcv_nxt; ++ struct mptcp_cb __rcu *mpcb; ++ u8 meta_tw:1, ++ in_list:1; ++}; ++ ++#define MPTCP_PM_NAME_MAX 16 ++struct mptcp_pm_ops { ++ struct list_head list; ++ ++ /* Signal the creation of a new MPTCP-session. */ ++ void (*new_session)(const struct sock *meta_sk); ++ void (*release_sock)(struct sock *meta_sk); ++ void (*fully_established)(struct sock *meta_sk); ++ void (*close_session)(struct sock *meta_sk); ++ void (*new_remote_address)(struct sock *meta_sk); ++ int (*get_local_id)(const struct sock *meta_sk, sa_family_t family, ++ union inet_addr *addr, bool *low_prio); ++ void (*addr_signal)(struct sock *sk, unsigned *size, ++ struct tcp_out_options *opts, struct sk_buff *skb); ++ void (*add_raddr)(struct mptcp_cb *mpcb, const union inet_addr *addr, ++ sa_family_t family, __be16 port, u8 id); ++ void (*rem_raddr)(struct mptcp_cb *mpcb, u8 rem_id); ++ void (*init_subsocket_v4)(struct sock *sk, struct in_addr addr); ++ void (*init_subsocket_v6)(struct sock *sk, struct in6_addr addr); ++ void (*established_subflow)(struct sock *sk); ++ void (*delete_subflow)(struct sock *sk); ++ void (*prio_changed)(struct sock *sk, int low_prio); ++ ++ char name[MPTCP_PM_NAME_MAX]; ++ struct module *owner; ++}; ++ ++struct mptcp_sched_ops { ++ struct list_head list; ++ ++ struct sock * (*get_subflow)(struct sock *meta_sk, ++ struct sk_buff *skb, ++ bool zero_wnd_test); ++ struct sk_buff * (*next_segment)(struct sock *meta_sk, ++ int *reinject, ++ struct sock **subsk, ++ unsigned int *limit); ++ void (*init)(struct sock *sk); ++ void (*release)(struct sock *sk); ++ ++ char name[MPTCP_SCHED_NAME_MAX]; ++ struct module *owner; ++}; ++ ++struct mptcp_cb { ++ /* list of sockets in this multipath connection */ ++ struct hlist_head conn_list; ++ /* list of sockets that need a call to release_cb */ ++ struct hlist_head callback_list; ++ ++ /* Lock used for protecting the different rcu-lists of mptcp_cb */ ++ spinlock_t mpcb_list_lock; ++ ++ /* High-order bits of 64-bit sequence numbers */ ++ u32 snd_high_order[2]; ++ u32 rcv_high_order[2]; ++ ++ u16 send_infinite_mapping:1, ++ send_mptcpv1_mpcapable:1, ++ rem_key_set:1, ++ in_time_wait:1, ++ list_rcvd:1, /* XXX TO REMOVE */ ++ addr_signal:1, /* Path-manager wants us to call addr_signal */ ++ dss_csum:1, ++ server_side:1, ++ infinite_mapping_rcv:1, ++ infinite_mapping_snd:1, ++ dfin_combined:1, /* Was the DFIN combined with subflow-fin? */ ++ passive_close:1, ++ snd_hiseq_index:1, /* Index in snd_high_order of snd_nxt */ ++ rcv_hiseq_index:1, /* Index in rcv_high_order of rcv_nxt */ ++ tcp_ca_explicit_set:1; /* was meta CC set by app? */ ++ ++#define MPTCP_SCHED_DATA_SIZE 8 ++ u8 mptcp_sched[MPTCP_SCHED_DATA_SIZE] __aligned(8); ++ const struct mptcp_sched_ops *sched_ops; ++ ++ struct sk_buff_head reinject_queue; ++ /* First cache-line boundary is here minus 8 bytes. But from the ++ * reinject-queue only the next and prev pointers are regularly ++ * accessed. Thus, the whole data-path is on a single cache-line. ++ */ ++ ++ u64 csum_cutoff_seq; ++ u64 infinite_rcv_seq; ++ ++ /***** Start of fields, used for connection closure */ ++ unsigned char mptw_state; ++ u8 dfin_path_index; ++ ++ struct list_head tw_list; ++ ++ /***** Start of fields, used for subflow establishment and closure */ ++ refcount_t mpcb_refcnt; ++ ++ /* Mutex needed, because otherwise mptcp_close will complain that the ++ * socket is owned by the user. ++ * E.g., mptcp_sub_close_wq is taking the meta-lock. ++ */ ++ struct mutex mpcb_mutex; ++ ++ /***** Start of fields, used for subflow establishment */ ++ struct sock *meta_sk; ++ ++ /* Master socket, also part of the conn_list, this ++ * socket is the one that the application sees. ++ */ ++ struct sock *master_sk; ++ ++ __u64 mptcp_loc_key; ++ __u64 mptcp_rem_key; ++ __u32 mptcp_loc_token; ++ __u32 mptcp_rem_token; ++ ++#define MPTCP_PM_SIZE 608 ++ u8 mptcp_pm[MPTCP_PM_SIZE] __aligned(8); ++ const struct mptcp_pm_ops *pm_ops; ++ ++ unsigned long path_index_bits; ++ ++ __u8 mptcp_ver; ++ ++ /* Original snd/rcvbuf of the initial subflow. ++ * Used for the new subflows on the server-side to allow correct ++ * autotuning ++ */ ++ int orig_sk_rcvbuf; ++ int orig_sk_sndbuf; ++ u32 orig_window_clamp; ++ ++ struct tcp_info *master_info; ++}; ++ ++#define MPTCP_VERSION_0 0 ++#define MPTCP_VERSION_1 1 ++ ++#define MPTCP_SUB_CAPABLE 0 ++#define MPTCP_SUB_LEN_CAPABLE_SYN 12 ++#define MPTCP_SUB_LEN_CAPABLE_SYN_ALIGN 12 ++#define MPTCP_SUB_LEN_CAPABLE_ACK 20 ++#define MPTCP_SUB_LEN_CAPABLE_ACK_ALIGN 20 ++ ++#define MPTCPV1_SUB_LEN_CAPABLE_SYN 4 ++#define MPTCPV1_SUB_LEN_CAPABLE_SYN_ALIGN 4 ++#define MPTCPV1_SUB_LEN_CAPABLE_SYNACK 12 ++#define MPTCPV1_SUB_LEN_CAPABLE_SYNACK_ALIGN 12 ++#define MPTCPV1_SUB_LEN_CAPABLE_ACK 20 ++#define MPTCPV1_SUB_LEN_CAPABLE_ACK_ALIGN 20 ++#define MPTCPV1_SUB_LEN_CAPABLE_DATA 22 ++#define MPTCPV1_SUB_LEN_CAPABLE_DATA_CSUM 24 ++#define MPTCPV1_SUB_LEN_CAPABLE_DATA_ALIGN 24 ++ ++#define MPTCP_SUB_JOIN 1 ++#define MPTCP_SUB_LEN_JOIN_SYN 12 ++#define MPTCP_SUB_LEN_JOIN_SYN_ALIGN 12 ++#define MPTCP_SUB_LEN_JOIN_SYNACK 16 ++#define MPTCP_SUB_LEN_JOIN_SYNACK_ALIGN 16 ++#define MPTCP_SUB_LEN_JOIN_ACK 24 ++#define MPTCP_SUB_LEN_JOIN_ACK_ALIGN 24 ++ ++#define MPTCP_SUB_DSS 2 ++#define MPTCP_SUB_LEN_DSS 4 ++#define MPTCP_SUB_LEN_DSS_ALIGN 4 ++ ++/* Lengths for seq and ack are the ones without the generic MPTCP-option header, ++ * as they are part of the DSS-option. ++ * To get the total length, just add the different options together. ++ */ ++#define MPTCP_SUB_LEN_SEQ 10 ++#define MPTCP_SUB_LEN_SEQ_CSUM 12 ++#define MPTCP_SUB_LEN_SEQ_ALIGN 12 ++ ++#define MPTCP_SUB_LEN_SEQ_64 14 ++#define MPTCP_SUB_LEN_SEQ_CSUM_64 16 ++#define MPTCP_SUB_LEN_SEQ_64_ALIGN 16 ++ ++#define MPTCP_SUB_LEN_ACK 4 ++#define MPTCP_SUB_LEN_ACK_ALIGN 4 ++ ++#define MPTCP_SUB_LEN_ACK_64 8 ++#define MPTCP_SUB_LEN_ACK_64_ALIGN 8 ++ ++/* This is the "default" option-length we will send out most often. ++ * MPTCP DSS-header ++ * 32-bit data sequence number ++ * 32-bit data ack ++ * ++ * It is necessary to calculate the effective MSS we will be using when ++ * sending data. ++ */ ++#define MPTCP_SUB_LEN_DSM_ALIGN (MPTCP_SUB_LEN_DSS_ALIGN + \ ++ MPTCP_SUB_LEN_SEQ_ALIGN + \ ++ MPTCP_SUB_LEN_ACK_ALIGN) ++ ++#define MPTCP_SUB_ADD_ADDR 3 ++#define MPTCP_SUB_LEN_ADD_ADDR4 8 ++#define MPTCP_SUB_LEN_ADD_ADDR4_VER1 16 ++#define MPTCP_SUB_LEN_ADD_ADDR6 20 ++#define MPTCP_SUB_LEN_ADD_ADDR6_VER1 28 ++#define MPTCP_SUB_LEN_ADD_ADDR4_ALIGN 8 ++#define MPTCP_SUB_LEN_ADD_ADDR4_ALIGN_VER1 16 ++#define MPTCP_SUB_LEN_ADD_ADDR6_ALIGN 20 ++#define MPTCP_SUB_LEN_ADD_ADDR6_ALIGN_VER1 28 ++ ++#define MPTCP_SUB_REMOVE_ADDR 4 ++#define MPTCP_SUB_LEN_REMOVE_ADDR 4 ++ ++#define MPTCP_SUB_PRIO 5 ++#define MPTCP_SUB_LEN_PRIO 3 ++#define MPTCP_SUB_LEN_PRIO_ADDR 4 ++#define MPTCP_SUB_LEN_PRIO_ALIGN 4 ++ ++#define MPTCP_SUB_FAIL 6 ++#define MPTCP_SUB_LEN_FAIL 12 ++#define MPTCP_SUB_LEN_FAIL_ALIGN 12 ++ ++#define MPTCP_SUB_FCLOSE 7 ++#define MPTCP_SUB_LEN_FCLOSE 12 ++#define MPTCP_SUB_LEN_FCLOSE_ALIGN 12 ++ ++ ++#define OPTION_MPTCP (1 << 5) ++ ++/* Max number of fastclose retransmissions */ ++#define MPTCP_FASTCLOSE_RETRIES 3 ++ ++#ifdef CONFIG_MPTCP ++ ++/* Used for checking if the mptcp initialization has been successful */ ++extern bool mptcp_init_failed; ++ ++/* MPTCP options */ ++#define OPTION_TYPE_SYN (1 << 0) ++#define OPTION_TYPE_SYNACK (1 << 1) ++#define OPTION_TYPE_ACK (1 << 2) ++#define OPTION_MP_CAPABLE (1 << 3) ++#define OPTION_DATA_ACK (1 << 4) ++#define OPTION_ADD_ADDR (1 << 5) ++#define OPTION_MP_JOIN (1 << 6) ++#define OPTION_MP_FAIL (1 << 7) ++#define OPTION_MP_FCLOSE (1 << 8) ++#define OPTION_REMOVE_ADDR (1 << 9) ++#define OPTION_MP_PRIO (1 << 10) ++ ++/* MPTCP flags: both TX and RX */ ++#define MPTCPHDR_SEQ 0x01 /* DSS.M option is present */ ++#define MPTCPHDR_FIN 0x02 /* DSS.F option is present */ ++#define MPTCPHDR_SEQ64_INDEX 0x04 /* index of seq in mpcb->snd_high_order */ ++#define MPTCPHDR_MPC_DATA 0x08 ++/* MPTCP flags: RX only */ ++#define MPTCPHDR_ACK 0x10 ++#define MPTCPHDR_SEQ64_SET 0x20 /* Did we received a 64-bit seq number? */ ++#define MPTCPHDR_SEQ64_OFO 0x40 /* Is it not in our circular array? */ ++#define MPTCPHDR_DSS_CSUM 0x80 ++/* MPTCP flags: TX only */ ++#define MPTCPHDR_INF 0x10 ++#define MPTCP_REINJECT 0x20 /* Did we reinject this segment? */ ++ ++struct mptcp_option { ++ __u8 kind; ++ __u8 len; ++#if defined(__LITTLE_ENDIAN_BITFIELD) ++ __u8 ver:4, ++ sub:4; ++#elif defined(__BIG_ENDIAN_BITFIELD) ++ __u8 sub:4, ++ ver:4; ++#else ++#error "Adjust your defines" ++#endif ++}; ++ ++struct mp_capable { ++ __u8 kind; ++ __u8 len; ++#if defined(__LITTLE_ENDIAN_BITFIELD) ++ __u8 ver:4, ++ sub:4; ++ __u8 h:1, ++ rsv:5, ++ b:1, ++ a:1; ++#elif defined(__BIG_ENDIAN_BITFIELD) ++ __u8 sub:4, ++ ver:4; ++ __u8 a:1, ++ b:1, ++ rsv:5, ++ h:1; ++#else ++#error "Adjust your defines" ++#endif ++ __u64 sender_key; ++ __u64 receiver_key; ++} __attribute__((__packed__)); ++ ++struct mp_join { ++ __u8 kind; ++ __u8 len; ++#if defined(__LITTLE_ENDIAN_BITFIELD) ++ __u8 b:1, ++ rsv:3, ++ sub:4; ++#elif defined(__BIG_ENDIAN_BITFIELD) ++ __u8 sub:4, ++ rsv:3, ++ b:1; ++#else ++#error "Adjust your defines" ++#endif ++ __u8 addr_id; ++ union { ++ struct { ++ u32 token; ++ u32 nonce; ++ } syn; ++ struct { ++ __u64 mac; ++ u32 nonce; ++ } synack; ++ struct { ++ __u8 mac[20]; ++ } ack; ++ } u; ++} __attribute__((__packed__)); ++ ++struct mp_dss { ++ __u8 kind; ++ __u8 len; ++#if defined(__LITTLE_ENDIAN_BITFIELD) ++ __u16 rsv1:4, ++ sub:4, ++ A:1, ++ a:1, ++ M:1, ++ m:1, ++ F:1, ++ rsv2:3; ++#elif defined(__BIG_ENDIAN_BITFIELD) ++ __u16 sub:4, ++ rsv1:4, ++ rsv2:3, ++ F:1, ++ m:1, ++ M:1, ++ a:1, ++ A:1; ++#else ++#error "Adjust your defines" ++#endif ++}; ++ ++struct mp_add_addr { ++ __u8 kind; ++ __u8 len; ++#if defined(__LITTLE_ENDIAN_BITFIELD) ++ union { ++ struct { ++ __u8 ipver:4, ++ sub:4; ++ } v0; ++ struct { ++ __u8 echo:1, ++ rsv:3, ++ sub:4; ++ } v1; ++ } u_bit; ++#elif defined(__BIG_ENDIAN_BITFIELD) ++ union { ++ struct { ++ __u8 sub:4, ++ ipver:4; ++ } v0; ++ struct { ++ __u8 sub:4, ++ rsv:3, ++ echo:1; ++ } v1; ++ } u_bit; ++#else ++#error "Adjust your defines" ++#endif ++ __u8 addr_id; ++ union { ++ struct { ++ struct in_addr addr; ++ __be16 port; ++ __u8 mac[8]; ++ } v4; ++ struct { ++ struct in6_addr addr; ++ __be16 port; ++ __u8 mac[8]; ++ } v6; ++ } u; ++} __attribute__((__packed__)); ++ ++struct mp_remove_addr { ++ __u8 kind; ++ __u8 len; ++#if defined(__LITTLE_ENDIAN_BITFIELD) ++ __u8 rsv:4, ++ sub:4; ++#elif defined(__BIG_ENDIAN_BITFIELD) ++ __u8 sub:4, ++ rsv:4; ++#else ++#error "Adjust your defines" ++#endif ++ /* list of addr_id */ ++ __u8 addrs_id; ++}; ++ ++struct mp_fail { ++ __u8 kind; ++ __u8 len; ++#if defined(__LITTLE_ENDIAN_BITFIELD) ++ __u16 rsv1:4, ++ sub:4, ++ rsv2:8; ++#elif defined(__BIG_ENDIAN_BITFIELD) ++ __u16 sub:4, ++ rsv1:4, ++ rsv2:8; ++#else ++#error "Adjust your defines" ++#endif ++ __be64 data_seq; ++} __attribute__((__packed__)); ++ ++struct mp_fclose { ++ __u8 kind; ++ __u8 len; ++#if defined(__LITTLE_ENDIAN_BITFIELD) ++ __u16 rsv1:4, ++ sub:4, ++ rsv2:8; ++#elif defined(__BIG_ENDIAN_BITFIELD) ++ __u16 sub:4, ++ rsv1:4, ++ rsv2:8; ++#else ++#error "Adjust your defines" ++#endif ++ __u64 key; ++} __attribute__((__packed__)); ++ ++struct mp_prio { ++ __u8 kind; ++ __u8 len; ++#if defined(__LITTLE_ENDIAN_BITFIELD) ++ __u8 b:1, ++ rsv:3, ++ sub:4; ++#elif defined(__BIG_ENDIAN_BITFIELD) ++ __u8 sub:4, ++ rsv:3, ++ b:1; ++#else ++#error "Adjust your defines" ++#endif ++ __u8 addr_id; ++} __attribute__((__packed__)); ++ ++struct mptcp_hashtable { ++ struct hlist_nulls_head *hashtable; ++ unsigned int mask; ++}; ++ ++static inline int mptcp_sub_len_dss(const struct mp_dss *m, const int csum) ++{ ++ return 4 + m->A * (4 + m->a * 4) + m->M * (10 + m->m * 4 + csum * 2); ++} ++ ++#define MPTCP_ENABLE 0x01 ++#define MPTCP_SOCKOPT 0x02 ++#define MPTCP_CLIENT_DISABLE 0x04 ++#define MPTCP_SERVER_DISABLE 0x08 ++ ++extern int sysctl_mptcp_enabled; ++extern int sysctl_mptcp_version; ++extern int sysctl_mptcp_checksum; ++extern int sysctl_mptcp_debug; ++extern int sysctl_mptcp_syn_retries; ++ ++extern struct workqueue_struct *mptcp_wq; ++ ++#define mptcp_debug(fmt, args...) \ ++ do { \ ++ if (unlikely(sysctl_mptcp_debug)) \ ++ pr_err(fmt, ##args); \ ++ } while (0) ++ ++static inline struct sock *mptcp_to_sock(const struct mptcp_tcp_sock *mptcp) ++{ ++ return (struct sock *)mptcp->tp; ++} ++ ++#define mptcp_for_each_sub(__mpcb, __mptcp) \ ++ hlist_for_each_entry_rcu(__mptcp, &((__mpcb)->conn_list), node) ++ ++/* Must be called with the appropriate lock held */ ++#define mptcp_for_each_sub_safe(__mpcb, __mptcp, __tmp) \ ++ hlist_for_each_entry_safe(__mptcp, __tmp, &((__mpcb)->conn_list), node) ++ ++/* Iterates over all bit set to 1 in a bitset */ ++#define mptcp_for_each_bit_set(b, i) \ ++ for (i = ffs(b) - 1; i >= 0; i = ffs(b >> (i + 1) << (i + 1)) - 1) ++ ++#define mptcp_for_each_bit_unset(b, i) \ ++ mptcp_for_each_bit_set(~b, i) ++ ++#define MPTCP_INC_STATS(net, field) SNMP_INC_STATS((net)->mptcp.mptcp_statistics, field) ++#define MPTCP_DEC_STATS(net, field) SNMP_DEC_STATS((net)->mptcp.mptcp_statistics, field) ++ ++enum ++{ ++ MPTCP_MIB_NUM = 0, ++ MPTCP_MIB_MPCAPABLEPASSIVE, /* Received SYN with MP_CAPABLE */ ++ MPTCP_MIB_MPCAPABLEACTIVE, /* Sent SYN with MP_CAPABLE */ ++ MPTCP_MIB_MPCAPABLEACTIVEACK, /* Received SYN/ACK with MP_CAPABLE */ ++ MPTCP_MIB_MPCAPABLEPASSIVEACK, /* Received third ACK with MP_CAPABLE */ ++ MPTCP_MIB_MPCAPABLEPASSIVEFALLBACK,/* Server-side fallback during 3-way handshake */ ++ MPTCP_MIB_MPCAPABLEACTIVEFALLBACK, /* Client-side fallback during 3-way handshake */ ++ MPTCP_MIB_MPCAPABLERETRANSFALLBACK,/* Client-side stopped sending MP_CAPABLE after too many SYN-retransmissions */ ++ MPTCP_MIB_CSUMENABLED, /* Created MPTCP-connection with DSS-checksum enabled */ ++ MPTCP_MIB_RETRANSSEGS, /* Segments retransmitted at the MPTCP-level */ ++ MPTCP_MIB_MPFAILRX, /* Received an MP_FAIL */ ++ MPTCP_MIB_CSUMFAIL, /* Received segment with invalid checksum */ ++ MPTCP_MIB_FASTCLOSERX, /* Recevied a FAST_CLOSE */ ++ MPTCP_MIB_FASTCLOSETX, /* Sent a FAST_CLOSE */ ++ MPTCP_MIB_FBACKSUB, /* Fallback upon ack without data-ack on new subflow */ ++ MPTCP_MIB_FBACKINIT, /* Fallback upon ack without data-ack on initial subflow */ ++ MPTCP_MIB_FBDATASUB, /* Fallback upon data without DSS at the beginning on new subflow */ ++ MPTCP_MIB_FBDATAINIT, /* Fallback upon data without DSS at the beginning on initial subflow */ ++ MPTCP_MIB_REMADDRSUB, /* Remove subflow due to REMOVE_ADDR */ ++ MPTCP_MIB_JOINNOTOKEN, /* Received MP_JOIN but the token was not found */ ++ MPTCP_MIB_JOINFALLBACK, /* Received MP_JOIN on session that has fallen back to reg. TCP */ ++ MPTCP_MIB_JOINSYNTX, /* Sent a SYN + MP_JOIN */ ++ MPTCP_MIB_JOINSYNRX, /* Received a SYN + MP_JOIN */ ++ MPTCP_MIB_JOINSYNACKRX, /* Received a SYN/ACK + MP_JOIN */ ++ MPTCP_MIB_JOINSYNACKMAC, /* HMAC was wrong on SYN/ACK + MP_JOIN */ ++ MPTCP_MIB_JOINACKRX, /* Received an ACK + MP_JOIN */ ++ MPTCP_MIB_JOINACKMAC, /* HMAC was wrong on ACK + MP_JOIN */ ++ MPTCP_MIB_JOINACKFAIL, /* Third ACK on new subflow did not contain an MP_JOIN */ ++ MPTCP_MIB_JOINACKRTO, /* Retransmission timer for third ACK + MP_JOIN timed out */ ++ MPTCP_MIB_JOINACKRXMIT, /* Retransmitted an ACK + MP_JOIN */ ++ MPTCP_MIB_NODSSWINDOW, /* Received too many packets without a DSS-option */ ++ MPTCP_MIB_DSSNOMATCH, /* Received a new mapping that did not match the previous one */ ++ MPTCP_MIB_INFINITEMAPRX, /* Received an infinite mapping */ ++ MPTCP_MIB_DSSTCPMISMATCH, /* DSS-mapping did not map with TCP's sequence numbers */ ++ MPTCP_MIB_DSSTRIMHEAD, /* Trimmed segment at the head (coalescing middlebox) */ ++ MPTCP_MIB_DSSSPLITTAIL, /* Trimmed segment at the tail (coalescing middlebox) */ ++ MPTCP_MIB_PURGEOLD, /* Removed old skb from the rcv-queue due to missing DSS-mapping */ ++ MPTCP_MIB_ADDADDRRX, /* Received an ADD_ADDR */ ++ MPTCP_MIB_ADDADDRTX, /* Sent an ADD_ADDR */ ++ MPTCP_MIB_REMADDRRX, /* Received a REMOVE_ADDR */ ++ MPTCP_MIB_REMADDRTX, /* Sent a REMOVE_ADDR */ ++ MPTCP_MIB_JOINALTERNATEPORT, /* Established a subflow on a different destination port-number */ ++ MPTCP_MIB_CURRESTAB, /* Current established MPTCP connections */ ++ __MPTCP_MIB_MAX ++}; ++ ++#define MPTCP_MIB_MAX __MPTCP_MIB_MAX ++struct mptcp_mib { ++ unsigned long mibs[MPTCP_MIB_MAX]; ++}; ++ ++extern struct lock_class_key meta_key; ++extern char *meta_key_name; ++extern struct lock_class_key meta_slock_key; ++extern char *meta_slock_key_name; ++ ++extern siphash_key_t mptcp_secret; ++ ++/* This is needed to ensure that two subsequent key/nonce-generation result in ++ * different keys/nonces if the IPs and ports are the same. ++ */ ++extern u32 mptcp_seed; ++ ++extern struct mptcp_hashtable mptcp_tk_htable; ++ ++/* Request-sockets can be hashed in the tk_htb for collision-detection or in ++ * the regular htb for join-connections. We need to define different NULLS ++ * values so that we can correctly detect a request-socket that has been ++ * recycled. See also c25eb3bfb9729. ++ */ ++#define MPTCP_REQSK_NULLS_BASE (1U << 29) ++ ++ ++void mptcp_data_ready(struct sock *sk); ++void mptcp_write_space(struct sock *sk); ++ ++void mptcp_add_meta_ofo_queue(const struct sock *meta_sk, struct sk_buff *skb, ++ struct sock *sk); ++void mptcp_cleanup_rbuf(struct sock *meta_sk, int copied); ++int mptcp_add_sock(struct sock *meta_sk, struct sock *sk, u8 loc_id, u8 rem_id, ++ gfp_t flags); ++void mptcp_del_sock(struct sock *sk); ++void mptcp_update_metasocket(const struct sock *meta_sk); ++void mptcp_reinject_data(struct sock *orig_sk, int clone_it); ++void mptcp_update_sndbuf(const struct tcp_sock *tp); ++void mptcp_send_fin(struct sock *meta_sk); ++void mptcp_send_active_reset(struct sock *meta_sk, gfp_t priority); ++bool mptcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle, ++ int push_one, gfp_t gfp); ++void tcp_parse_mptcp_options(const struct sk_buff *skb, ++ struct mptcp_options_received *mopt); ++void mptcp_parse_options(const uint8_t *ptr, int opsize, ++ struct mptcp_options_received *mopt, ++ const struct sk_buff *skb, ++ struct tcp_sock *tp); ++void mptcp_syn_options(const struct sock *sk, struct tcp_out_options *opts, ++ unsigned *remaining); ++void mptcp_synack_options(struct request_sock *req, ++ struct tcp_out_options *opts, ++ unsigned *remaining); ++void mptcp_established_options(struct sock *sk, struct sk_buff *skb, ++ struct tcp_out_options *opts, unsigned *size); ++void mptcp_options_write(__be32 *ptr, struct tcp_sock *tp, ++ const struct tcp_out_options *opts, ++ struct sk_buff *skb); ++void mptcp_close(struct sock *meta_sk, long timeout); ++bool mptcp_doit(struct sock *sk); ++int mptcp_create_master_sk(struct sock *meta_sk, __u64 remote_key, ++ int rem_key_set, __u8 mptcp_ver, u32 window); ++int mptcp_check_req_fastopen(struct sock *child, struct request_sock *req); ++int mptcp_check_req_master(struct sock *sk, struct sock *child, ++ struct request_sock *req, const struct sk_buff *skb, ++ const struct mptcp_options_received *mopt, ++ int drop, u32 tsoff); ++struct sock *mptcp_check_req_child(struct sock *meta_sk, ++ struct sock *child, ++ struct request_sock *req, ++ struct sk_buff *skb, ++ const struct mptcp_options_received *mopt); ++u32 __mptcp_select_window(struct sock *sk); ++void mptcp_select_initial_window(const struct sock *sk, int __space, __u32 mss, ++ __u32 *rcv_wnd, __u32 *window_clamp, ++ int wscale_ok, __u8 *rcv_wscale, ++ __u32 init_rcv_wnd); ++unsigned int mptcp_current_mss(struct sock *meta_sk); ++void mptcp_hmac(u8 ver, const u8 *key_1, const u8 *key_2, u8 *hash_out, ++ int arg_num, ...); ++void mptcp_clean_rtx_infinite(const struct sk_buff *skb, struct sock *sk); ++void mptcp_fin(struct sock *meta_sk); ++void mptcp_meta_retransmit_timer(struct sock *meta_sk); ++void mptcp_sub_retransmit_timer(struct sock *sk); ++int mptcp_write_wakeup(struct sock *meta_sk, int mib); ++void mptcp_sub_close_wq(struct work_struct *work); ++void mptcp_sub_close(struct sock *sk, unsigned long delay); ++struct sock *mptcp_select_ack_sock(const struct sock *meta_sk); ++void mptcp_prepare_for_backlog(struct sock *sk, struct sk_buff *skb); ++void mptcp_initialize_recv_vars(struct tcp_sock *meta_tp, struct mptcp_cb *mpcb, ++ __u64 remote_key); ++int mptcp_backlog_rcv(struct sock *meta_sk, struct sk_buff *skb); ++void mptcp_ack_handler(struct timer_list *t); ++bool mptcp_check_rtt(const struct tcp_sock *tp, int time); ++int mptcp_check_snd_buf(const struct tcp_sock *tp); ++bool mptcp_handle_options(struct sock *sk, const struct tcphdr *th, ++ const struct sk_buff *skb); ++void __init mptcp_init(void); ++void mptcp_destroy_sock(struct sock *sk); ++int mptcp_rcv_synsent_state_process(struct sock *sk, struct sock **skptr, ++ const struct sk_buff *skb, ++ const struct mptcp_options_received *mopt); ++unsigned int mptcp_xmit_size_goal(const struct sock *meta_sk, u32 mss_now, ++ int large_allowed); ++int mptcp_init_tw_sock(struct sock *sk, struct tcp_timewait_sock *tw); ++void mptcp_twsk_destructor(struct tcp_timewait_sock *tw); ++void mptcp_time_wait(struct sock *sk, int state, int timeo); ++void mptcp_disconnect(struct sock *meta_sk); ++bool mptcp_should_expand_sndbuf(const struct sock *sk); ++int mptcp_retransmit_skb(struct sock *meta_sk, struct sk_buff *skb); ++void mptcp_tsq_flags(struct sock *sk); ++void mptcp_tsq_sub_deferred(struct sock *meta_sk); ++struct mp_join *mptcp_find_join(const struct sk_buff *skb); ++void mptcp_hash_remove_bh(struct tcp_sock *meta_tp); ++struct sock *mptcp_hash_find(const struct net *net, const u32 token); ++int mptcp_lookup_join(struct sk_buff *skb, struct inet_timewait_sock *tw); ++int mptcp_do_join_short(struct sk_buff *skb, ++ const struct mptcp_options_received *mopt, ++ struct net *net); ++void mptcp_reqsk_destructor(struct request_sock *req); ++void mptcp_connect_init(struct sock *sk); ++void mptcp_sub_force_close(struct sock *sk); ++int mptcp_sub_len_remove_addr_align(u16 bitfield); ++void mptcp_join_reqsk_init(const struct mptcp_cb *mpcb, ++ const struct request_sock *req, ++ struct sk_buff *skb); ++void mptcp_reqsk_init(struct request_sock *req, const struct sock *sk, ++ const struct sk_buff *skb, bool want_cookie); ++int mptcp_conn_request(struct sock *sk, struct sk_buff *skb); ++void mptcp_enable_sock(struct sock *sk); ++void mptcp_disable_sock(struct sock *sk); ++void mptcp_disable_static_key(void); ++void mptcp_cookies_reqsk_init(struct request_sock *req, ++ struct mptcp_options_received *mopt, ++ struct sk_buff *skb); ++void mptcp_mpcb_put(struct mptcp_cb *mpcb); ++int mptcp_finish_handshake(struct sock *child, struct sk_buff *skb); ++int mptcp_get_info(const struct sock *meta_sk, char __user *optval, int optlen); ++void mptcp_clear_sk(struct sock *sk, int size); ++ ++/* MPTCP-path-manager registration/initialization functions */ ++int mptcp_register_path_manager(struct mptcp_pm_ops *pm); ++void mptcp_unregister_path_manager(struct mptcp_pm_ops *pm); ++void mptcp_init_path_manager(struct mptcp_cb *mpcb); ++void mptcp_cleanup_path_manager(struct mptcp_cb *mpcb); ++void mptcp_fallback_default(struct mptcp_cb *mpcb); ++void mptcp_get_default_path_manager(char *name); ++int mptcp_set_scheduler(struct sock *sk, const char *name); ++int mptcp_set_path_manager(struct sock *sk, const char *name); ++int mptcp_set_default_path_manager(const char *name); ++extern struct mptcp_pm_ops mptcp_pm_default; ++ ++/* MPTCP-scheduler registration/initialization functions */ ++int mptcp_register_scheduler(struct mptcp_sched_ops *sched); ++void mptcp_unregister_scheduler(struct mptcp_sched_ops *sched); ++void mptcp_init_scheduler(struct mptcp_cb *mpcb); ++void mptcp_cleanup_scheduler(struct mptcp_cb *mpcb); ++void mptcp_get_default_scheduler(char *name); ++int mptcp_set_default_scheduler(const char *name); ++bool mptcp_is_available(struct sock *sk, const struct sk_buff *skb, ++ bool zero_wnd_test); ++bool mptcp_is_def_unavailable(struct sock *sk); ++bool subflow_is_active(const struct tcp_sock *tp); ++bool subflow_is_backup(const struct tcp_sock *tp); ++struct sock *get_available_subflow(struct sock *meta_sk, struct sk_buff *skb, ++ bool zero_wnd_test); ++struct sk_buff *mptcp_next_segment(struct sock *meta_sk, ++ int *reinject, ++ struct sock **subsk, ++ unsigned int *limit); ++extern struct mptcp_sched_ops mptcp_sched_default; ++ ++/* Initializes function-pointers and MPTCP-flags */ ++static inline void mptcp_init_tcp_sock(struct sock *sk) ++{ ++ if (!mptcp_init_failed && sysctl_mptcp_enabled == MPTCP_ENABLE) ++ mptcp_enable_sock(sk); ++} ++ ++static inline void mptcp_init_listen(struct sock *sk) ++{ ++ if (!mptcp_init_failed && ++ sk->sk_type == SOCK_STREAM && sk->sk_protocol == IPPROTO_TCP && ++#ifdef CONFIG_TCP_MD5SIG ++ !rcu_access_pointer(tcp_sk(sk)->md5sig_info) && ++#endif ++ sysctl_mptcp_enabled & MPTCP_ENABLE && ++ !(sysctl_mptcp_enabled & MPTCP_SERVER_DISABLE)) ++ mptcp_enable_sock(sk); ++} ++ ++static inline void mptcp_init_connect(struct sock *sk) ++{ ++ if (!mptcp_init_failed && ++ sk->sk_type == SOCK_STREAM && sk->sk_protocol == IPPROTO_TCP && ++#ifdef CONFIG_TCP_MD5SIG ++ !rcu_access_pointer(tcp_sk(sk)->md5sig_info) && ++#endif ++ sysctl_mptcp_enabled & MPTCP_ENABLE && ++ !(sysctl_mptcp_enabled & MPTCP_CLIENT_DISABLE)) ++ mptcp_enable_sock(sk); ++} ++ ++static inline int mptcp_pi_to_flag(int pi) ++{ ++ return 1 << (pi - 1); ++} ++ ++static inline ++struct mptcp_request_sock *mptcp_rsk(const struct request_sock *req) ++{ ++ return (struct mptcp_request_sock *)req; ++} ++ ++static inline ++struct request_sock *rev_mptcp_rsk(const struct mptcp_request_sock *req) ++{ ++ return (struct request_sock *)req; ++} ++ ++static inline bool mptcp_can_sendpage(struct sock *sk) ++{ ++ struct mptcp_tcp_sock *mptcp; ++ ++ if (tcp_sk(sk)->mpcb->dss_csum) ++ return false; ++ ++ mptcp_for_each_sub(tcp_sk(sk)->mpcb, mptcp) { ++ struct sock *sk_it = mptcp_to_sock(mptcp); ++ ++ if (!(sk_it->sk_route_caps & NETIF_F_SG)) ++ return false; ++ } ++ ++ return true; ++} ++ ++static inline void mptcp_push_pending_frames(struct sock *meta_sk) ++{ ++ /* We check packets out and send-head here. TCP only checks the ++ * send-head. But, MPTCP also checks packets_out, as this is an ++ * indication that we might want to do opportunistic reinjection. ++ */ ++ if (tcp_sk(meta_sk)->packets_out || tcp_send_head(meta_sk)) { ++ struct tcp_sock *tp = tcp_sk(meta_sk); ++ ++ /* We don't care about the MSS, because it will be set in ++ * mptcp_write_xmit. ++ */ ++ __tcp_push_pending_frames(meta_sk, 0, tp->nonagle); ++ } ++} ++ ++static inline void mptcp_send_reset(struct sock *sk) ++{ ++ if (tcp_need_reset(sk->sk_state)) ++ tcp_sk(sk)->ops->send_active_reset(sk, GFP_ATOMIC); ++ mptcp_sub_force_close(sk); ++} ++ ++static inline void mptcp_sub_force_close_all(struct mptcp_cb *mpcb, ++ struct sock *except) ++{ ++ struct mptcp_tcp_sock *mptcp; ++ struct hlist_node *tmp; ++ ++ mptcp_for_each_sub_safe(mpcb, mptcp, tmp) { ++ struct sock *sk_it = mptcp_to_sock(mptcp); ++ ++ if (sk_it != except) ++ mptcp_send_reset(sk_it); ++ } ++} ++ ++static inline bool mptcp_is_data_mpcapable(const struct sk_buff *skb) ++{ ++ return TCP_SKB_CB(skb)->mptcp_flags & MPTCPHDR_MPC_DATA; ++} ++ ++static inline bool mptcp_is_data_seq(const struct sk_buff *skb) ++{ ++ return TCP_SKB_CB(skb)->mptcp_flags & MPTCPHDR_SEQ; ++} ++ ++static inline bool mptcp_is_data_fin(const struct sk_buff *skb) ++{ ++ return TCP_SKB_CB(skb)->mptcp_flags & MPTCPHDR_FIN; ++} ++ ++/* Is it a data-fin while in infinite mapping mode? ++ * In infinite mode, a subflow-fin is in fact a data-fin. ++ */ ++static inline bool mptcp_is_data_fin2(const struct sk_buff *skb, ++ const struct tcp_sock *tp) ++{ ++ return mptcp_is_data_fin(skb) || ++ (tp->mpcb->infinite_mapping_rcv && ++ (TCP_SKB_CB(skb)->tcp_flags & TCPHDR_FIN)); ++} ++ ++static inline u8 mptcp_get_64_bit(u64 data_seq, struct mptcp_cb *mpcb) ++{ ++ u64 data_seq_high = (u32)(data_seq >> 32); ++ ++ if (mpcb->rcv_high_order[0] == data_seq_high) ++ return 0; ++ else if (mpcb->rcv_high_order[1] == data_seq_high) ++ return MPTCPHDR_SEQ64_INDEX; ++ else ++ return MPTCPHDR_SEQ64_OFO; ++} ++ ++/* Sets the data_seq and returns pointer to the in-skb field of the data_seq. ++ * If the packet has a 64-bit dseq, the pointer points to the last 32 bits. ++ */ ++static inline __u32 *mptcp_skb_set_data_seq(const struct sk_buff *skb, ++ u32 *data_seq, ++ struct mptcp_cb *mpcb) ++{ ++ __u32 *ptr = (__u32 *)(skb_transport_header(skb) + TCP_SKB_CB(skb)->dss_off); ++ ++ if (TCP_SKB_CB(skb)->mptcp_flags & MPTCPHDR_SEQ64_SET) { ++ u64 data_seq64 = get_unaligned_be64(ptr); ++ ++ if (mpcb) ++ TCP_SKB_CB(skb)->mptcp_flags |= mptcp_get_64_bit(data_seq64, mpcb); ++ ++ *data_seq = (u32)data_seq64; ++ ptr++; ++ } else { ++ *data_seq = get_unaligned_be32(ptr); ++ } ++ ++ return ptr; ++} ++ ++static inline struct sock *mptcp_meta_sk(const struct sock *sk) ++{ ++ return tcp_sk(sk)->meta_sk; ++} ++ ++static inline struct tcp_sock *mptcp_meta_tp(const struct tcp_sock *tp) ++{ ++ return tcp_sk(tp->meta_sk); ++} ++ ++static inline int is_meta_tp(const struct tcp_sock *tp) ++{ ++ return tp->mpcb && mptcp_meta_tp(tp) == tp; ++} ++ ++static inline int is_meta_sk(const struct sock *sk) ++{ ++ return sk->sk_state != TCP_NEW_SYN_RECV && ++ sk->sk_type == SOCK_STREAM && sk->sk_protocol == IPPROTO_TCP && ++ mptcp(tcp_sk(sk)) && mptcp_meta_sk(sk) == sk; ++} ++ ++static inline int is_master_tp(const struct tcp_sock *tp) ++{ ++ return !mptcp(tp) || (!tp->mptcp->slave_sk && !is_meta_tp(tp)); ++} ++ ++static inline void mptcp_init_mp_opt(struct mptcp_options_received *mopt) ++{ ++ mopt->saw_mpc = 0; ++ mopt->dss_csum = 0; ++ mopt->drop_me = 0; ++ ++ mopt->is_mp_join = 0; ++ mopt->join_ack = 0; ++ ++ mopt->saw_low_prio = 0; ++ mopt->low_prio = 0; ++ ++ mopt->saw_add_addr = 0; ++ mopt->more_add_addr = 0; ++ ++ mopt->saw_rem_addr = 0; ++ mopt->more_rem_addr = 0; ++ ++ mopt->mp_fail = 0; ++ mopt->mp_fclose = 0; ++} ++ ++static inline void mptcp_reset_mopt(struct tcp_sock *tp) ++{ ++ struct mptcp_options_received *mopt = &tp->mptcp->rx_opt; ++ ++ mopt->saw_low_prio = 0; ++ mopt->saw_add_addr = 0; ++ mopt->more_add_addr = 0; ++ mopt->saw_rem_addr = 0; ++ mopt->more_rem_addr = 0; ++ mopt->join_ack = 0; ++ mopt->mp_fail = 0; ++ mopt->mp_fclose = 0; ++} ++ ++static inline __be32 mptcp_get_highorder_sndbits(const struct sk_buff *skb, ++ const struct mptcp_cb *mpcb) ++{ ++ return htonl(mpcb->snd_high_order[(TCP_SKB_CB(skb)->mptcp_flags & ++ MPTCPHDR_SEQ64_INDEX) ? 1 : 0]); ++} ++ ++static inline u64 mptcp_get_data_seq_64(const struct mptcp_cb *mpcb, int index, ++ u32 data_seq_32) ++{ ++ return ((u64)mpcb->rcv_high_order[index] << 32) | data_seq_32; ++} ++ ++static inline u64 mptcp_get_rcv_nxt_64(const struct tcp_sock *meta_tp) ++{ ++ struct mptcp_cb *mpcb = meta_tp->mpcb; ++ return mptcp_get_data_seq_64(mpcb, mpcb->rcv_hiseq_index, ++ meta_tp->rcv_nxt); ++} ++ ++static inline void mptcp_check_sndseq_wrap(struct tcp_sock *meta_tp, int inc) ++{ ++ if (unlikely(meta_tp->snd_nxt > meta_tp->snd_nxt + inc)) { ++ struct mptcp_cb *mpcb = meta_tp->mpcb; ++ mpcb->snd_hiseq_index = mpcb->snd_hiseq_index ? 0 : 1; ++ mpcb->snd_high_order[mpcb->snd_hiseq_index] += 2; ++ } ++} ++ ++static inline void mptcp_check_rcvseq_wrap(struct tcp_sock *meta_tp, ++ u32 old_rcv_nxt) ++{ ++ if (unlikely(old_rcv_nxt > meta_tp->rcv_nxt)) { ++ struct mptcp_cb *mpcb = meta_tp->mpcb; ++ mpcb->rcv_high_order[mpcb->rcv_hiseq_index] += 2; ++ mpcb->rcv_hiseq_index = mpcb->rcv_hiseq_index ? 0 : 1; ++ } ++} ++ ++static inline int mptcp_sk_can_send(const struct sock *sk) ++{ ++ return tcp_passive_fastopen(sk) || ++ ((1 << sk->sk_state) & (TCPF_ESTABLISHED | TCPF_CLOSE_WAIT) && ++ !tcp_sk(sk)->mptcp->pre_established); ++} ++ ++static inline int mptcp_sk_can_recv(const struct sock *sk) ++{ ++ return (1 << sk->sk_state) & (TCPF_ESTABLISHED | TCPF_FIN_WAIT1 | TCPF_FIN_WAIT2); ++} ++ ++static inline int mptcp_sk_can_send_ack(const struct sock *sk) ++{ ++ return !((1 << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV | ++ TCPF_CLOSE | TCPF_LISTEN)) && ++ !tcp_sk(sk)->mptcp->pre_established; ++} ++ ++static inline bool mptcp_can_sg(const struct sock *meta_sk) ++{ ++ struct mptcp_tcp_sock *mptcp; ++ ++ if (tcp_sk(meta_sk)->mpcb->dss_csum) ++ return false; ++ ++ mptcp_for_each_sub(tcp_sk(meta_sk)->mpcb, mptcp) { ++ struct sock *sk = mptcp_to_sock(mptcp); ++ ++ if (!mptcp_sk_can_send(sk)) ++ continue; ++ if (!(sk->sk_route_caps & NETIF_F_SG)) ++ return false; ++ } ++ return true; ++} ++ ++static inline void mptcp_set_rto(struct sock *sk) ++{ ++ struct inet_connection_sock *micsk = inet_csk(mptcp_meta_sk(sk)); ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct mptcp_tcp_sock *mptcp; ++ __u32 max_rto = 0; ++ ++ /* We are in recovery-phase on the MPTCP-level. Do not update the ++ * RTO, because this would kill exponential backoff. ++ */ ++ if (micsk->icsk_retransmits) ++ return; ++ ++ mptcp_for_each_sub(tp->mpcb, mptcp) { ++ struct sock *sk_it = mptcp_to_sock(mptcp); ++ ++ if ((mptcp_sk_can_send(sk_it) || sk_it->sk_state == TCP_SYN_RECV) && ++ inet_csk(sk_it)->icsk_retransmits == 0 && ++ inet_csk(sk_it)->icsk_backoff == 0 && ++ inet_csk(sk_it)->icsk_rto > max_rto) ++ max_rto = inet_csk(sk_it)->icsk_rto; ++ } ++ if (max_rto) { ++ micsk->icsk_rto = max_rto << 1; ++ ++ /* A successfull rto-measurement - reset backoff counter */ ++ micsk->icsk_backoff = 0; ++ } ++} ++ ++static inline void mptcp_sub_close_passive(struct sock *sk) ++{ ++ struct sock *meta_sk = mptcp_meta_sk(sk); ++ struct tcp_sock *tp = tcp_sk(sk), *meta_tp = tcp_sk(meta_sk); ++ ++ /* Only close, if the app did a send-shutdown (passive close), and we ++ * received the data-ack of the data-fin. ++ */ ++ if (tp->mpcb->passive_close && meta_tp->snd_una == meta_tp->write_seq) ++ mptcp_sub_close(sk, 0); ++} ++ ++static inline void mptcp_fallback_close(struct mptcp_cb *mpcb, ++ struct sock *except) ++{ ++ mptcp_sub_force_close_all(mpcb, except); ++ ++ if (mpcb->pm_ops->close_session) ++ mpcb->pm_ops->close_session(mptcp_meta_sk(except)); ++} ++ ++static inline bool mptcp_fallback_infinite(struct sock *sk, int flag) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct mptcp_cb *mpcb = tp->mpcb; ++ ++ /* If data has been acknowleged on the meta-level, fully_established ++ * will have been set before and thus we will not fall back to infinite ++ * mapping. ++ */ ++ if (likely(tp->mptcp->fully_established)) ++ return false; ++ ++ if (!(flag & MPTCP_FLAG_DATA_ACKED)) ++ return false; ++ ++ /* Don't fallback twice ;) */ ++ if (mpcb->infinite_mapping_snd) ++ return false; ++ ++ pr_debug("%s %#x will fallback - pi %d, src %pI4:%u dst %pI4:%u rcv_nxt %u from %pS\n", ++ __func__, mpcb->mptcp_loc_token, tp->mptcp->path_index, ++ &inet_sk(sk)->inet_saddr, ntohs(inet_sk(sk)->inet_sport), ++ &inet_sk(sk)->inet_daddr, ntohs(inet_sk(sk)->inet_dport), ++ tp->rcv_nxt, __builtin_return_address(0)); ++ if (!is_master_tp(tp)) { ++ MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_FBACKSUB); ++ return true; ++ } ++ ++ mpcb->infinite_mapping_snd = 1; ++ mpcb->infinite_mapping_rcv = 1; ++ mpcb->infinite_rcv_seq = mptcp_get_rcv_nxt_64(mptcp_meta_tp(tp)); ++ tp->mptcp->fully_established = 1; ++ ++ mptcp_fallback_close(mpcb, sk); ++ ++ MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_FBACKINIT); ++ ++ return false; ++} ++ ++static inline bool mptcp_v6_is_v4_mapped(const struct sock *sk) ++{ ++ return sk->sk_family == AF_INET6 && ++ ipv6_addr_type(&inet6_sk(sk)->saddr) == IPV6_ADDR_MAPPED; ++} ++ ++/* We are in or are becoming to be in infinite mapping mode */ ++static inline bool mptcp_in_infinite_mapping_weak(const struct mptcp_cb *mpcb) ++{ ++ return mpcb->infinite_mapping_rcv || ++ mpcb->infinite_mapping_snd || ++ mpcb->send_infinite_mapping; ++} ++ ++static inline bool mptcp_can_new_subflow(const struct sock *meta_sk) ++{ ++ /* Has been removed from the tk-table. Thus, no new subflows. ++ * ++ * Check for close-state is necessary, because we may have been closed ++ * without passing by mptcp_close(). ++ * ++ * When falling back, no new subflows are allowed either. ++ */ ++ return meta_sk->sk_state != TCP_CLOSE && ++ tcp_sk(meta_sk)->inside_tk_table && ++ !tcp_sk(meta_sk)->mpcb->infinite_mapping_rcv && ++ !tcp_sk(meta_sk)->mpcb->send_infinite_mapping; ++} ++ ++static inline int mptcp_subflow_count(const struct mptcp_cb *mpcb) ++{ ++ struct mptcp_tcp_sock *mptcp; ++ int i = 0; ++ ++ mptcp_for_each_sub(mpcb, mptcp) ++ i++; ++ ++ return i; ++} ++ ++/* TCP and MPTCP mpc flag-depending functions */ ++u16 mptcp_select_window(struct sock *sk); ++void mptcp_tcp_set_rto(struct sock *sk); ++ ++#else /* CONFIG_MPTCP */ ++#define mptcp_debug(fmt, args...) \ ++ do { \ ++ } while (0) ++ ++static inline struct sock *mptcp_to_sock(const struct mptcp_tcp_sock *mptcp) ++{ ++ return NULL; ++} ++ ++#define mptcp_for_each_sub(__mpcb, __mptcp) \ ++ if (0) ++ ++#define MPTCP_INC_STATS(net, field) \ ++ do { \ ++ } while(0) ++ ++#define MPTCP_DEC_STATS(net, field) \ ++ do { \ ++ } while(0) ++ ++static inline bool mptcp_is_data_fin(const struct sk_buff *skb) ++{ ++ return false; ++} ++static inline bool mptcp_is_data_seq(const struct sk_buff *skb) ++{ ++ return false; ++} ++static inline struct sock *mptcp_meta_sk(const struct sock *sk) ++{ ++ return NULL; ++} ++static inline struct tcp_sock *mptcp_meta_tp(const struct tcp_sock *tp) ++{ ++ return NULL; ++} ++static inline int is_meta_sk(const struct sock *sk) ++{ ++ return 0; ++} ++static inline int is_master_tp(const struct tcp_sock *tp) ++{ ++ return 0; ++} ++static inline void mptcp_del_sock(const struct sock *sk) {} ++static inline void mptcp_update_metasocket(const struct sock *meta_sk) {} ++static inline void mptcp_reinject_data(struct sock *orig_sk, int clone_it) {} ++static inline void mptcp_update_sndbuf(const struct tcp_sock *tp) {} ++static inline void mptcp_clean_rtx_infinite(const struct sk_buff *skb, ++ const struct sock *sk) {} ++static inline void mptcp_sub_close(struct sock *sk, unsigned long delay) {} ++static inline void mptcp_set_rto(const struct sock *sk) {} ++static inline void mptcp_send_fin(const struct sock *meta_sk) {} ++static inline void mptcp_parse_options(const uint8_t *ptr, const int opsize, ++ struct mptcp_options_received *mopt, ++ const struct sk_buff *skb, ++ const struct tcp_sock *tp) {} ++static inline void mptcp_syn_options(const struct sock *sk, ++ struct tcp_out_options *opts, ++ unsigned *remaining) {} ++static inline void mptcp_synack_options(struct request_sock *req, ++ struct tcp_out_options *opts, ++ unsigned *remaining) {} ++ ++static inline void mptcp_established_options(struct sock *sk, ++ struct sk_buff *skb, ++ struct tcp_out_options *opts, ++ unsigned *size) {} ++static inline void mptcp_options_write(__be32 *ptr, struct tcp_sock *tp, ++ const struct tcp_out_options *opts, ++ struct sk_buff *skb) {} ++static inline void mptcp_close(struct sock *meta_sk, long timeout) {} ++static inline bool mptcp_doit(struct sock *sk) ++{ ++ return false; ++} ++static inline int mptcp_check_req_fastopen(struct sock *child, ++ struct request_sock *req) ++{ ++ return 1; ++} ++static inline int mptcp_check_req_master(const struct sock *sk, ++ const struct sock *child, ++ const struct request_sock *req, ++ const struct sk_buff *skb, ++ const struct mptcp_options_received *mopt, ++ int drop, ++ u32 tsoff) ++{ ++ return 1; ++} ++static inline struct sock *mptcp_check_req_child(const struct sock *meta_sk, ++ const struct sock *child, ++ const struct request_sock *req, ++ struct sk_buff *skb, ++ const struct mptcp_options_received *mopt) ++{ ++ return NULL; ++} ++static inline unsigned int mptcp_current_mss(struct sock *meta_sk) ++{ ++ return 0; ++} ++static inline void mptcp_sub_close_passive(struct sock *sk) {} ++static inline bool mptcp_fallback_infinite(const struct sock *sk, int flag) ++{ ++ return false; ++} ++static inline void mptcp_init_mp_opt(const struct mptcp_options_received *mopt) {} ++static inline void mptcp_prepare_for_backlog(struct sock *sk, struct sk_buff *skb) {} ++static inline bool mptcp_check_rtt(const struct tcp_sock *tp, int time) ++{ ++ return false; ++} ++static inline int mptcp_check_snd_buf(const struct tcp_sock *tp) ++{ ++ return 0; ++} ++static inline void mptcp_push_pending_frames(struct sock *meta_sk) {} ++static inline void mptcp_send_reset(const struct sock *sk) {} ++static inline void mptcp_sub_force_close_all(struct mptcp_cb *mpcb, ++ struct sock *except) {} ++static inline bool mptcp_handle_options(struct sock *sk, ++ const struct tcphdr *th, ++ struct sk_buff *skb) ++{ ++ return false; ++} ++static inline void mptcp_reset_mopt(struct tcp_sock *tp) {} ++static inline void __init mptcp_init(void) {} ++static inline bool mptcp_can_sg(const struct sock *meta_sk) ++{ ++ return false; ++} ++static inline unsigned int mptcp_xmit_size_goal(const struct sock *meta_sk, ++ u32 mss_now, int large_allowed) ++{ ++ return 0; ++} ++static inline void mptcp_destroy_sock(struct sock *sk) {} ++static inline int mptcp_rcv_synsent_state_process(struct sock *sk, ++ struct sock **skptr, ++ struct sk_buff *skb, ++ const struct mptcp_options_received *mopt) ++{ ++ return 0; ++} ++static inline bool mptcp_can_sendpage(struct sock *sk) ++{ ++ return false; ++} ++static inline int mptcp_init_tw_sock(struct sock *sk, ++ struct tcp_timewait_sock *tw) ++{ ++ return 0; ++} ++static inline void mptcp_twsk_destructor(struct tcp_timewait_sock *tw) {} ++static inline void mptcp_disconnect(struct sock *meta_sk) {} ++static inline void mptcp_tsq_flags(struct sock *sk) {} ++static inline void mptcp_tsq_sub_deferred(struct sock *meta_sk) {} ++static inline void mptcp_hash_remove_bh(struct tcp_sock *meta_tp) {} ++static inline void mptcp_remove_shortcuts(const struct mptcp_cb *mpcb, ++ const struct sk_buff *skb) {} ++static inline void mptcp_init_tcp_sock(struct sock *sk) {} ++static inline void mptcp_init_listen(struct sock *sk) {} ++static inline void mptcp_init_connect(struct sock *sk) {} ++static inline void mptcp_disable_static_key(void) {} ++static inline void mptcp_cookies_reqsk_init(struct request_sock *req, ++ struct mptcp_options_received *mopt, ++ struct sk_buff *skb) {} ++static inline void mptcp_mpcb_put(struct mptcp_cb *mpcb) {} ++static inline void mptcp_fin(struct sock *meta_sk) {} ++static inline bool mptcp_in_infinite_mapping_weak(const struct mptcp_cb *mpcb) ++{ ++ return false; ++} ++static inline bool mptcp_can_new_subflow(const struct sock *meta_sk) ++{ ++ return false; ++} ++ ++#endif /* CONFIG_MPTCP */ ++ ++#endif /* _MPTCP_H */ +diff --git a/include/net/mptcp_v4.h b/include/net/mptcp_v4.h +new file mode 100644 +index 000000000000..c58d42b11f6a +--- /dev/null ++++ b/include/net/mptcp_v4.h +@@ -0,0 +1,76 @@ ++/* ++ * MPTCP implementation ++ * ++ * Initial Design & Implementation: ++ * Sébastien Barré ++ * ++ * Current Maintainer & Author: ++ * Christoph Paasch ++ * ++ * Additional authors: ++ * Jaakko Korkeaniemi ++ * Gregory Detal ++ * Fabien Duchêne ++ * Andreas Seelinger ++ * Lavkesh Lahngir ++ * Andreas Ripke ++ * Vlad Dogaru ++ * Octavian Purdila ++ * John Ronan ++ * Catalin Nicutar ++ * Brandon Heller ++ * ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License ++ * as published by the Free Software Foundation; either version ++ * 2 of the License, or (at your option) any later version. ++ */ ++ ++#ifndef MPTCP_V4_H_ ++#define MPTCP_V4_H_ ++ ++ ++#include ++#include ++#include ++#include ++#include ++ ++extern struct request_sock_ops mptcp_request_sock_ops; ++extern const struct inet_connection_sock_af_ops mptcp_v4_specific; ++extern struct tcp_request_sock_ops mptcp_request_sock_ipv4_ops; ++extern struct tcp_request_sock_ops mptcp_join_request_sock_ipv4_ops; ++ ++#ifdef CONFIG_MPTCP ++ ++int mptcp_v4_do_rcv(struct sock *meta_sk, struct sk_buff *skb); ++struct sock *mptcp_v4_search_req(const __be16 rport, const __be32 raddr, ++ const __be32 laddr, const struct net *net); ++int __mptcp_init4_subsockets(struct sock *meta_sk, const struct mptcp_loc4 *loc, ++ __be16 sport, struct mptcp_rem4 *rem, ++ struct sock **subsk); ++int mptcp_pm_v4_init(void); ++void mptcp_pm_v4_undo(void); ++u32 mptcp_v4_get_nonce(__be32 saddr, __be32 daddr, __be16 sport, __be16 dport); ++u64 mptcp_v4_get_key(__be32 saddr, __be32 daddr, __be16 sport, __be16 dport, ++ u32 seed); ++ ++static inline int mptcp_init4_subsockets(struct sock *meta_sk, ++ const struct mptcp_loc4 *loc, ++ struct mptcp_rem4 *rem) ++{ ++ return __mptcp_init4_subsockets(meta_sk, loc, 0, rem, NULL); ++} ++ ++#else ++ ++static inline int mptcp_v4_do_rcv(const struct sock *meta_sk, ++ const struct sk_buff *skb) ++{ ++ return 0; ++} ++ ++#endif /* CONFIG_MPTCP */ ++ ++#endif /* MPTCP_V4_H_ */ +diff --git a/include/net/mptcp_v6.h b/include/net/mptcp_v6.h +new file mode 100644 +index 000000000000..93e8c87c2eb1 +--- /dev/null ++++ b/include/net/mptcp_v6.h +@@ -0,0 +1,77 @@ ++/* ++ * MPTCP implementation ++ * ++ * Initial Design & Implementation: ++ * Sébastien Barré ++ * ++ * Current Maintainer & Author: ++ * Jaakko Korkeaniemi ++ * ++ * Additional authors: ++ * Jaakko Korkeaniemi ++ * Gregory Detal ++ * Fabien Duchêne ++ * Andreas Seelinger ++ * Lavkesh Lahngir ++ * Andreas Ripke ++ * Vlad Dogaru ++ * Octavian Purdila ++ * John Ronan ++ * Catalin Nicutar ++ * Brandon Heller ++ * ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License ++ * as published by the Free Software Foundation; either version ++ * 2 of the License, or (at your option) any later version. ++ */ ++ ++#ifndef _MPTCP_V6_H ++#define _MPTCP_V6_H ++ ++#include ++#include ++ ++#include ++ ++ ++#ifdef CONFIG_MPTCP ++extern const struct inet_connection_sock_af_ops mptcp_v6_mapped; ++extern const struct inet_connection_sock_af_ops mptcp_v6_specific; ++extern struct request_sock_ops mptcp6_request_sock_ops; ++extern struct tcp_request_sock_ops mptcp_request_sock_ipv6_ops; ++extern struct tcp_request_sock_ops mptcp_join_request_sock_ipv6_ops; ++ ++int mptcp_v6_do_rcv(struct sock *meta_sk, struct sk_buff *skb); ++struct sock *mptcp_v6_search_req(const __be16 rport, const struct in6_addr *raddr, ++ const struct in6_addr *laddr, const struct net *net); ++int __mptcp_init6_subsockets(struct sock *meta_sk, const struct mptcp_loc6 *loc, ++ __be16 sport, struct mptcp_rem6 *rem, ++ struct sock **subsk); ++int mptcp_pm_v6_init(void); ++void mptcp_pm_v6_undo(void); ++__u32 mptcp_v6_get_nonce(const __be32 *saddr, const __be32 *daddr, ++ __be16 sport, __be16 dport); ++u64 mptcp_v6_get_key(const __be32 *saddr, const __be32 *daddr, ++ __be16 sport, __be16 dport, u32 seed); ++ ++static inline int mptcp_init6_subsockets(struct sock *meta_sk, ++ const struct mptcp_loc6 *loc, ++ struct mptcp_rem6 *rem) ++{ ++ return __mptcp_init6_subsockets(meta_sk, loc, 0, rem, NULL); ++} ++ ++#else /* CONFIG_MPTCP */ ++ ++#define mptcp_v6_mapped ipv6_mapped ++ ++static inline int mptcp_v6_do_rcv(struct sock *meta_sk, struct sk_buff *skb) ++{ ++ return 0; ++} ++ ++#endif /* CONFIG_MPTCP */ ++ ++#endif /* _MPTCP_V6_H */ +diff --git a/include/net/net_namespace.h b/include/net/net_namespace.h +index 167e390ac9d4..7233acfcdb4d 100644 +--- a/include/net/net_namespace.h ++++ b/include/net/net_namespace.h +@@ -19,6 +19,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -123,6 +124,9 @@ struct net { + #if IS_ENABLED(CONFIG_IPV6) + struct netns_ipv6 ipv6; + #endif ++#if IS_ENABLED(CONFIG_MPTCP) ++ struct netns_mptcp mptcp; ++#endif + #if IS_ENABLED(CONFIG_IEEE802154_6LOWPAN) + struct netns_ieee802154_lowpan ieee802154_lowpan; + #endif +diff --git a/include/net/netns/mptcp.h b/include/net/netns/mptcp.h +new file mode 100644 +index 000000000000..6680f3bbcfc8 +--- /dev/null ++++ b/include/net/netns/mptcp.h +@@ -0,0 +1,52 @@ ++/* ++ * MPTCP implementation - MPTCP namespace ++ * ++ * Initial Design & Implementation: ++ * Sébastien Barré ++ * ++ * Current Maintainer: ++ * Christoph Paasch ++ * ++ * Additional authors: ++ * Jaakko Korkeaniemi ++ * Gregory Detal ++ * Fabien Duchêne ++ * Andreas Seelinger ++ * Lavkesh Lahngir ++ * Andreas Ripke ++ * Vlad Dogaru ++ * Octavian Purdila ++ * John Ronan ++ * Catalin Nicutar ++ * Brandon Heller ++ * ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License ++ * as published by the Free Software Foundation; either version ++ * 2 of the License, or (at your option) any later version. ++ */ ++ ++#ifndef __NETNS_MPTCP_H__ ++#define __NETNS_MPTCP_H__ ++ ++#include ++ ++enum { ++ MPTCP_PM_FULLMESH = 0, ++ MPTCP_PM_MAX ++}; ++ ++struct mptcp_mib; ++ ++struct netns_mptcp { ++ DEFINE_SNMP_STAT(struct mptcp_mib, mptcp_statistics); ++ ++#ifdef CONFIG_PROC_FS ++ struct proc_dir_entry *proc_net_mptcp; ++#endif ++ ++ void *path_managers[MPTCP_PM_MAX]; ++}; ++ ++#endif /* __NETNS_MPTCP_H__ */ +diff --git a/include/net/snmp.h b/include/net/snmp.h +index cb8ced4380a6..0aa0d10af2ce 100644 +--- a/include/net/snmp.h ++++ b/include/net/snmp.h +@@ -86,7 +86,6 @@ struct icmpv6msg_mib_device { + atomic_long_t mibs[ICMP6MSG_MIB_MAX]; + }; + +- + /* TCP */ + #define TCP_MIB_MAX __TCP_MIB_MAX + struct tcp_mib { +diff --git a/include/net/sock.h b/include/net/sock.h +index 079b5f6f13d8..8ae33ecd9d0a 100644 +--- a/include/net/sock.h ++++ b/include/net/sock.h +@@ -821,6 +821,7 @@ enum sock_flags { + SOCK_TXTIME, + SOCK_XDP, /* XDP is attached */ + SOCK_TSTAMP_NEW, /* Indicates 64 bit timestamps always */ ++ SOCK_MPTCP, /* MPTCP set on this socket */ + }; + + #define SK_FLAGS_TIMESTAMP ((1UL << SOCK_TIMESTAMP) | (1UL << SOCK_TIMESTAMPING_RX_SOFTWARE)) +@@ -1133,6 +1134,7 @@ struct proto { + void (*unhash)(struct sock *sk); + void (*rehash)(struct sock *sk); + int (*get_port)(struct sock *sk, unsigned short snum); ++ void (*clear_sk)(struct sock *sk, int size); + + /* Keeping track of sockets in use */ + #ifdef CONFIG_PROC_FS +diff --git a/include/net/tcp.h b/include/net/tcp.h +index b914959cd2c6..b290be3e510c 100644 +--- a/include/net/tcp.h ++++ b/include/net/tcp.h +@@ -182,6 +182,7 @@ + #define TCPOPT_SACK 5 /* SACK Block */ + #define TCPOPT_TIMESTAMP 8 /* Better RTT estimations/PAWS */ + #define TCPOPT_MD5SIG 19 /* MD5 Signature (RFC2385) */ ++#define TCPOPT_MPTCP 30 + #define TCPOPT_FASTOPEN 34 /* Fast open (RFC7413) */ + #define TCPOPT_EXP 254 /* Experimental */ + /* Magic number to be after the option value for sharing TCP +@@ -238,6 +239,31 @@ + */ + #define TFO_SERVER_WO_SOCKOPT1 0x400 + ++/* Flags from tcp_input.c for tcp_ack */ ++#define FLAG_DATA 0x01 /* Incoming frame contained data. */ ++#define FLAG_WIN_UPDATE 0x02 /* Incoming ACK was a window update. */ ++#define FLAG_DATA_ACKED 0x04 /* This ACK acknowledged new data. */ ++#define FLAG_RETRANS_DATA_ACKED 0x08 /* "" "" some of which was retransmitted. */ ++#define FLAG_SYN_ACKED 0x10 /* This ACK acknowledged SYN. */ ++#define FLAG_DATA_SACKED 0x20 /* New SACK. */ ++#define FLAG_ECE 0x40 /* ECE in this ACK */ ++#define FLAG_LOST_RETRANS 0x80 /* This ACK marks some retransmission lost */ ++#define FLAG_SLOWPATH 0x100 /* Do not skip RFC checks for window update.*/ ++#define FLAG_ORIG_SACK_ACKED 0x200 /* Never retransmitted data are (s)acked */ ++#define FLAG_SND_UNA_ADVANCED 0x400 /* Snd_una was changed (!= FLAG_DATA_ACKED) */ ++#define FLAG_DSACKING_ACK 0x800 /* SACK blocks contained D-SACK info */ ++#define FLAG_SET_XMIT_TIMER 0x1000 /* Set TLP or RTO timer */ ++#define FLAG_SACK_RENEGING 0x2000 /* snd_una advanced to a sacked seq */ ++#define FLAG_UPDATE_TS_RECENT 0x4000 /* tcp_replace_ts_recent() */ ++#define FLAG_NO_CHALLENGE_ACK 0x8000 /* do not call tcp_send_challenge_ack() */ ++#define FLAG_ACK_MAYBE_DELAYED 0x10000 /* Likely a delayed ACK */ ++ ++#define MPTCP_FLAG_DATA_ACKED 0x20000 ++ ++#define FLAG_ACKED (FLAG_DATA_ACKED|FLAG_SYN_ACKED) ++#define FLAG_NOT_DUP (FLAG_DATA|FLAG_WIN_UPDATE|FLAG_ACKED) ++#define FLAG_CA_ALERT (FLAG_DATA_SACKED|FLAG_ECE|FLAG_DSACKING_ACK) ++#define FLAG_FORWARD_PROGRESS (FLAG_ACKED|FLAG_DATA_SACKED) + + /* sysctl variables for tcp */ + extern int sysctl_tcp_max_orphans; +@@ -310,6 +336,98 @@ static inline bool tcp_too_many_orphans(struct sock *sk, int shift) + #define TCP_DEC_STATS(net, field) SNMP_DEC_STATS((net)->mib.tcp_statistics, field) + #define TCP_ADD_STATS(net, field, val) SNMP_ADD_STATS((net)->mib.tcp_statistics, field, val) + ++/**** START - Exports needed for MPTCP ****/ ++extern const struct tcp_request_sock_ops tcp_request_sock_ipv4_ops; ++extern const struct tcp_request_sock_ops tcp_request_sock_ipv6_ops; ++ ++struct mptcp_options_received; ++ ++void tcp_cleanup_rbuf(struct sock *sk, int copied); ++int tcp_close_state(struct sock *sk); ++void tcp_minshall_update(struct tcp_sock *tp, unsigned int mss_now, ++ const struct sk_buff *skb); ++int tcp_xmit_probe_skb(struct sock *sk, int urgent, int mib); ++void tcp_event_new_data_sent(struct sock *sk, struct sk_buff *skb); ++int tcp_transmit_skb(struct sock *sk, struct sk_buff *skb, int clone_it, ++ gfp_t gfp_mask); ++u32 tcp_tso_segs(struct sock *sk, unsigned int mss_now); ++unsigned int tcp_mss_split_point(const struct sock *sk, ++ const struct sk_buff *skb, ++ unsigned int mss_now, ++ unsigned int max_segs, ++ int nonagle); ++bool tcp_nagle_test(const struct tcp_sock *tp, const struct sk_buff *skb, ++ unsigned int cur_mss, int nonagle); ++bool tcp_snd_wnd_test(const struct tcp_sock *tp, const struct sk_buff *skb, ++ unsigned int cur_mss); ++unsigned int tcp_cwnd_test(const struct tcp_sock *tp, const struct sk_buff *skb); ++int tcp_init_tso_segs(struct sk_buff *skb, unsigned int mss_now); ++int __pskb_trim_head(struct sk_buff *skb, int len); ++void tcp_queue_skb(struct sock *sk, struct sk_buff *skb); ++void tcp_init_nondata_skb(struct sk_buff *skb, u32 seq, u8 flags); ++void tcp_reset(struct sock *sk); ++bool tcp_may_update_window(const struct tcp_sock *tp, const u32 ack, ++ const u32 ack_seq, const u32 nwin); ++bool tcp_urg_mode(const struct tcp_sock *tp); ++void tcp_ack_probe(struct sock *sk); ++void tcp_rearm_rto(struct sock *sk); ++int tcp_write_timeout(struct sock *sk); ++bool retransmits_timed_out(struct sock *sk, ++ unsigned int boundary, ++ unsigned int timeout); ++void tcp_write_err(struct sock *sk); ++void tcp_adjust_pcount(struct sock *sk, const struct sk_buff *skb, int decr); ++void tcp_update_skb_after_send(struct sock *sk, struct sk_buff *skb, ++ u64 prior_wstamp); ++void tcp_set_skb_tso_segs(struct sk_buff *skb, unsigned int mss_now); ++ ++void tcp_v4_reqsk_send_ack(const struct sock *sk, struct sk_buff *skb, ++ struct request_sock *req); ++void tcp_v4_send_reset(const struct sock *sk, struct sk_buff *skb); ++struct sock *tcp_v4_cookie_check(struct sock *sk, struct sk_buff *skb); ++void tcp_v4_reqsk_destructor(struct request_sock *req); ++ ++void tcp_v6_reqsk_send_ack(const struct sock *sk, struct sk_buff *skb, ++ struct request_sock *req); ++void tcp_v6_send_reset(const struct sock *sk, struct sk_buff *skb); ++struct sock *tcp_v6_cookie_check(struct sock *sk, struct sk_buff *skb); ++int tcp_v6_do_rcv(struct sock *sk, struct sk_buff *skb); ++int tcp_v6_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len); ++void tcp_v6_destroy_sock(struct sock *sk); ++void inet6_sk_rx_dst_set(struct sock *sk, const struct sk_buff *skb); ++void tcp_v6_hash(struct sock *sk); ++struct sock *tcp_v6_hnd_req(struct sock *sk,struct sk_buff *skb); ++struct sock *tcp_v6_syn_recv_sock(const struct sock *sk, struct sk_buff *skb, ++ struct request_sock *req, ++ struct dst_entry *dst, ++ struct request_sock *req_unhash, ++ bool *own_req); ++void tcp_v6_reqsk_destructor(struct request_sock *req); ++ ++unsigned int tcp_xmit_size_goal(struct sock *sk, u32 mss_now, ++ int large_allowed); ++u32 tcp_tso_acked(struct sock *sk, struct sk_buff *skb); ++void tcp_ack_tstamp(struct sock *sk, struct sk_buff *skb, u32 prior_snd_una); ++ ++void skb_clone_fraglist(struct sk_buff *skb); ++ ++void inet_twsk_free(struct inet_timewait_sock *tw); ++int tcp_v6_conn_request(struct sock *sk, struct sk_buff *skb); ++/* These states need RST on ABORT according to RFC793 */ ++static inline bool tcp_need_reset(int state) ++{ ++ return (1 << state) & ++ (TCPF_ESTABLISHED | TCPF_CLOSE_WAIT | TCPF_FIN_WAIT1 | ++ TCPF_FIN_WAIT2 | TCPF_SYN_RECV); ++} ++ ++int __must_check tcp_queue_rcv(struct sock *sk, struct sk_buff *skb, ++ bool *fragstolen); ++void tcp_ofo_queue(struct sock *sk); ++void tcp_data_queue_ofo(struct sock *sk, struct sk_buff *skb); ++int linear_payload_sz(bool first_skb); ++/**** END - Exports needed for MPTCP ****/ ++ + void tcp_tasklet_init(void); + + int tcp_v4_err(struct sk_buff *skb, u32); +@@ -411,7 +529,9 @@ int tcp_mmap(struct file *file, struct socket *sock, + #endif + void tcp_parse_options(const struct net *net, const struct sk_buff *skb, + struct tcp_options_received *opt_rx, +- int estab, struct tcp_fastopen_cookie *foc); ++ struct mptcp_options_received *mopt_rx, ++ int estab, struct tcp_fastopen_cookie *foc, ++ struct tcp_sock *tp); + const u8 *tcp_parse_md5sig_option(const struct tcphdr *th); + + /* +@@ -430,6 +550,7 @@ u16 tcp_get_syncookie_mss(struct request_sock_ops *rsk_ops, + + void tcp_v4_send_check(struct sock *sk, struct sk_buff *skb); + void tcp_v4_mtu_reduced(struct sock *sk); ++void tcp_v6_mtu_reduced(struct sock *sk); + void tcp_req_err(struct sock *sk, u32 seq, bool abort); + int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb); + struct sock *tcp_create_openreq_child(const struct sock *sk, +@@ -453,6 +574,7 @@ struct sk_buff *tcp_make_synack(const struct sock *sk, struct dst_entry *dst, + struct request_sock *req, + struct tcp_fastopen_cookie *foc, + enum tcp_synack_type synack_type); ++void tcp_reset_vars(struct sock *sk); + int tcp_disconnect(struct sock *sk, int flags); + + void tcp_finish_connect(struct sock *sk, struct sk_buff *skb); +@@ -462,6 +584,7 @@ struct sk_buff *tcp_make_synack(const struct sock *sk, struct dst_entry *dst, + /* From syncookies.c */ + struct sock *tcp_get_cookie_sock(struct sock *sk, struct sk_buff *skb, + struct request_sock *req, ++ const struct mptcp_options_received *mopt, + struct dst_entry *dst, u32 tsoff); + int __cookie_v4_check(const struct iphdr *iph, const struct tcphdr *th, + u32 cookie); +@@ -547,7 +670,8 @@ static inline u32 tcp_cookie_time(void) + + u32 __cookie_v4_init_sequence(const struct iphdr *iph, const struct tcphdr *th, + u16 *mssp); +-__u32 cookie_v4_init_sequence(const struct sk_buff *skb, __u16 *mss); ++__u32 cookie_v4_init_sequence(struct request_sock *req, const struct sock *sk, ++ const struct sk_buff *skb, __u16 *mss); + u64 cookie_init_timestamp(struct request_sock *req); + bool cookie_timestamp_decode(const struct net *net, + struct tcp_options_received *opt); +@@ -561,7 +685,8 @@ int __cookie_v6_check(const struct ipv6hdr *iph, const struct tcphdr *th, + + u32 __cookie_v6_init_sequence(const struct ipv6hdr *iph, + const struct tcphdr *th, u16 *mssp); +-__u32 cookie_v6_init_sequence(const struct sk_buff *skb, __u16 *mss); ++__u32 cookie_v6_init_sequence(struct request_sock *req, const struct sock *sk, ++ const struct sk_buff *skb, __u16 *mss); + #endif + /* tcp_output.c */ + +@@ -597,10 +722,16 @@ int tcp_fragment(struct sock *sk, enum tcp_queue tcp_queue, + void tcp_skb_collapse_tstamp(struct sk_buff *skb, + const struct sk_buff *next_skb); + ++u16 tcp_select_window(struct sock *sk); ++bool tcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle, ++ int push_one, gfp_t gfp); ++ + /* tcp_input.c */ + void tcp_rearm_rto(struct sock *sk); + void tcp_synack_rtt_meas(struct sock *sk, struct request_sock *req); + void tcp_reset(struct sock *sk); ++void tcp_set_rto(struct sock *sk); ++bool tcp_should_expand_sndbuf(const struct sock *sk); + void tcp_skb_mark_lost_uncond_verify(struct tcp_sock *tp, struct sk_buff *skb); + void tcp_fin(struct sock *sk); + +@@ -645,7 +776,7 @@ static inline int tcp_bound_to_half_wnd(struct tcp_sock *tp, int pktsize) + } + + /* tcp.c */ +-void tcp_get_info(struct sock *, struct tcp_info *); ++void tcp_get_info(struct sock *, struct tcp_info *, bool no_lock); + + /* Read 'sendfile()'-style from a TCP socket */ + int tcp_read_sock(struct sock *sk, read_descriptor_t *desc, +@@ -723,7 +854,7 @@ static inline u32 tcp_min_rtt(const struct tcp_sock *tp) + * Rcv_nxt can be after the window if our peer push more data + * than the offered window. + */ +-static inline u32 tcp_receive_window(const struct tcp_sock *tp) ++static inline u32 tcp_receive_window_now(const struct tcp_sock *tp) + { + s32 win = tp->rcv_wup + tp->rcv_wnd - tp->rcv_nxt; + +@@ -732,6 +863,32 @@ static inline u32 tcp_receive_window(const struct tcp_sock *tp) + return (u32) win; + } + ++/* right edge only moves forward, even if window shrinks due ++ * to mptcp meta ++ */ ++static inline void tcp_update_rcv_right_edge(struct tcp_sock *tp) ++{ ++ if (after(tp->rcv_wup + tp->rcv_wnd, tp->rcv_right_edge)) ++ tp->rcv_right_edge = tp->rcv_wup + tp->rcv_wnd; ++} ++ ++/* Compute receive window which will never shrink. The way MPTCP handles ++ * the receive window can cause the effective right edge to shrink, ++ * causing valid segments to become out of window. ++ * This function should be used when checking if a segment is valid for ++ * the max right edge announced. ++ */ ++static inline u32 tcp_receive_window_no_shrink(const struct tcp_sock *tp) ++{ ++ s32 win = tp->rcv_right_edge - tp->rcv_nxt; ++ ++ win = max_t(s32, win, tp->rcv_wup + tp->rcv_wnd - tp->rcv_nxt); ++ ++ if (unlikely(win < 0)) ++ win = 0; ++ return (u32) win; ++} ++ + /* Choose a new window, without checks for shrinking, and without + * scaling applied to the result. The caller does these things + * if necessary. This is a "raw" window selection. +@@ -829,6 +986,12 @@ struct tcp_skb_cb { + u16 tcp_gso_size; + }; + }; ++ ++#ifdef CONFIG_MPTCP ++ __u8 mptcp_flags; /* flags for the MPTCP layer */ ++ __u8 dss_off; /* Number of 4-byte words until ++ * seq-number */ ++#endif + __u8 tcp_flags; /* TCP header flags. (tcp[13]) */ + + __u8 sacked; /* State flags for SACK. */ +@@ -847,6 +1010,14 @@ struct tcp_skb_cb { + has_rxtstamp:1, /* SKB has a RX timestamp */ + unused:5; + __u32 ack_seq; /* Sequence number ACK'd */ ++ ++#ifdef CONFIG_MPTCP ++ union { /* For MPTCP outgoing frames */ ++ __u32 path_mask; /* paths that tried to send this skb */ ++ __u32 dss[6]; /* DSS options */ ++ }; ++#endif ++ + union { + struct { + /* There is space for up to 24 bytes */ +@@ -1088,6 +1259,8 @@ struct tcp_congestion_ops { + int tcp_set_allowed_congestion_control(char *allowed); + int tcp_set_congestion_control(struct sock *sk, const char *name, bool load, + bool reinit, bool cap_net_admin); ++int __tcp_set_congestion_control(struct sock *sk, const char *name, bool load, ++ bool reinit, bool cap_net_admin); + u32 tcp_slow_start(struct tcp_sock *tp, u32 acked); + void tcp_cong_avoid_ai(struct tcp_sock *tp, u32 w, u32 acked); + +@@ -1389,6 +1562,19 @@ static inline int tcp_win_from_space(const struct sock *sk, int space) + space - (space>>tcp_adv_win_scale); + } + ++#ifdef CONFIG_MPTCP ++extern struct static_key mptcp_static_key; ++static inline bool mptcp(const struct tcp_sock *tp) ++{ ++ return static_key_false(&mptcp_static_key) && tp->mpc; ++} ++#else ++static inline bool mptcp(const struct tcp_sock *tp) ++{ ++ return 0; ++} ++#endif ++ + /* Note: caller must be prepared to deal with negative returns */ + static inline int tcp_space(const struct sock *sk) + { +@@ -1981,6 +2167,30 @@ struct tcp_sock_af_ops { + #endif + }; + ++/* TCP/MPTCP-specific functions */ ++struct tcp_sock_ops { ++ u32 (*__select_window)(struct sock *sk); ++ u16 (*select_window)(struct sock *sk); ++ void (*select_initial_window)(const struct sock *sk, int __space, ++ __u32 mss, __u32 *rcv_wnd, ++ __u32 *window_clamp, int wscale_ok, ++ __u8 *rcv_wscale, __u32 init_rcv_wnd); ++ void (*init_buffer_space)(struct sock *sk); ++ void (*set_rto)(struct sock *sk); ++ bool (*should_expand_sndbuf)(const struct sock *sk); ++ void (*send_fin)(struct sock *sk); ++ bool (*write_xmit)(struct sock *sk, unsigned int mss_now, int nonagle, ++ int push_one, gfp_t gfp); ++ void (*send_active_reset)(struct sock *sk, gfp_t priority); ++ int (*write_wakeup)(struct sock *sk, int mib); ++ void (*retransmit_timer)(struct sock *sk); ++ void (*time_wait)(struct sock *sk, int state, int timeo); ++ void (*cleanup_rbuf)(struct sock *sk, int copied); ++ int (*set_cong_ctrl)(struct sock *sk, const char *name, bool load, ++ bool reinit, bool cap_net_admin); ++}; ++extern const struct tcp_sock_ops tcp_specific; ++ + struct tcp_request_sock_ops { + u16 mss_clamp; + #ifdef CONFIG_TCP_MD5SIG +@@ -1991,12 +2201,13 @@ struct tcp_request_sock_ops { + const struct sock *sk, + const struct sk_buff *skb); + #endif +- void (*init_req)(struct request_sock *req, +- const struct sock *sk_listener, +- struct sk_buff *skb); ++ int (*init_req)(struct request_sock *req, ++ const struct sock *sk_listener, ++ struct sk_buff *skb, ++ bool want_cookie); + #ifdef CONFIG_SYN_COOKIES +- __u32 (*cookie_init_seq)(const struct sk_buff *skb, +- __u16 *mss); ++ __u32 (*cookie_init_seq)(struct request_sock *req, const struct sock *sk, ++ const struct sk_buff *skb, __u16 *mss); + #endif + struct dst_entry *(*route_req)(const struct sock *sk, struct flowi *fl, + const struct request_sock *req); +@@ -2010,15 +2221,17 @@ struct tcp_request_sock_ops { + + #ifdef CONFIG_SYN_COOKIES + static inline __u32 cookie_init_sequence(const struct tcp_request_sock_ops *ops, ++ struct request_sock *req, + const struct sock *sk, struct sk_buff *skb, + __u16 *mss) + { + tcp_synq_overflow(sk); + __NET_INC_STATS(sock_net(sk), LINUX_MIB_SYNCOOKIESSENT); +- return ops->cookie_init_seq(skb, mss); ++ return ops->cookie_init_seq(req, sk, skb, mss); + } + #else + static inline __u32 cookie_init_sequence(const struct tcp_request_sock_ops *ops, ++ struct request_sock *req, + const struct sock *sk, struct sk_buff *skb, + __u16 *mss) + { +diff --git a/include/net/tcp_states.h b/include/net/tcp_states.h +index cc00118acca1..11084091e798 100644 +--- a/include/net/tcp_states.h ++++ b/include/net/tcp_states.h +@@ -22,6 +22,7 @@ enum { + TCP_LISTEN, + TCP_CLOSING, /* Now a valid state */ + TCP_NEW_SYN_RECV, ++ TCP_RST_WAIT, + + TCP_MAX_STATES /* Leave at the end! */ + }; +@@ -43,6 +44,7 @@ enum { + TCPF_LISTEN = (1 << TCP_LISTEN), + TCPF_CLOSING = (1 << TCP_CLOSING), + TCPF_NEW_SYN_RECV = (1 << TCP_NEW_SYN_RECV), ++ TCPF_RST_WAIT = (1 << TCP_RST_WAIT), + }; + + #endif /* _LINUX_TCP_STATES_H */ +diff --git a/include/net/transp_v6.h b/include/net/transp_v6.h +index a8f6020f1196..5e70b086fdfb 100644 +--- a/include/net/transp_v6.h ++++ b/include/net/transp_v6.h +@@ -58,6 +58,8 @@ void __ip6_dgram_sock_seq_show(struct seq_file *seq, struct sock *sp, + + /* address family specific functions */ + extern const struct inet_connection_sock_af_ops ipv4_specific; ++extern const struct inet_connection_sock_af_ops ipv6_mapped; ++extern const struct inet_connection_sock_af_ops ipv6_specific; + + void inet6_destroy_sock(struct sock *sk); + +diff --git a/include/trace/events/tcp.h b/include/trace/events/tcp.h +index cf97f6339acb..cf48dc87a734 100644 +--- a/include/trace/events/tcp.h ++++ b/include/trace/events/tcp.h +@@ -10,6 +10,7 @@ + #include + #include + #include ++#include + #include + + #define TP_STORE_V4MAPPED(__entry, saddr, daddr) \ +@@ -181,6 +182,13 @@ + TP_ARGS(sk) + ); + ++DEFINE_EVENT(tcp_event_sk_skb, mptcp_retransmit, ++ ++ TP_PROTO(const struct sock *sk, const struct sk_buff *skb), ++ ++ TP_ARGS(sk, skb) ++); ++ + TRACE_EVENT(tcp_retransmit_synack, + + TP_PROTO(const struct sock *sk, const struct request_sock *req), +@@ -248,6 +256,7 @@ + __field(__u32, srtt) + __field(__u32, rcv_wnd) + __field(__u64, sock_cookie) ++ __field(__u8, mptcp) + ), + + TP_fast_assign( +@@ -274,13 +283,15 @@ + __entry->ssthresh = tcp_current_ssthresh(sk); + __entry->srtt = tp->srtt_us >> 3; + __entry->sock_cookie = sock_gen_cookie(sk); ++ __entry->mptcp = mptcp(tp) ? tp->mptcp->path_index : 0; + ), + +- TP_printk("src=%pISpc dest=%pISpc mark=%#x data_len=%d snd_nxt=%#x snd_una=%#x snd_cwnd=%u ssthresh=%u snd_wnd=%u srtt=%u rcv_wnd=%u sock_cookie=%llx", ++ TP_printk("src=%pISpc dest=%pISpc mark=%#x data_len=%d snd_nxt=%#x snd_una=%#x snd_cwnd=%u ssthresh=%u snd_wnd=%u srtt=%u rcv_wnd=%u sock_cookie=%llx mptcp=%d", + __entry->saddr, __entry->daddr, __entry->mark, + __entry->data_len, __entry->snd_nxt, __entry->snd_una, + __entry->snd_cwnd, __entry->ssthresh, __entry->snd_wnd, +- __entry->srtt, __entry->rcv_wnd, __entry->sock_cookie) ++ __entry->srtt, __entry->rcv_wnd, __entry->sock_cookie, ++ __entry->mptcp) + ); + + #endif /* _TRACE_TCP_H */ +diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h +index 63038eb23560..7150eb62db86 100644 +--- a/include/uapi/linux/bpf.h ++++ b/include/uapi/linux/bpf.h +@@ -3438,6 +3438,7 @@ enum { + BPF_TCP_LISTEN, + BPF_TCP_CLOSING, /* Now a valid state */ + BPF_TCP_NEW_SYN_RECV, ++ BPF_TCP_RST_WAIT, + + BPF_TCP_MAX_STATES /* Leave at the end! */ + }; +diff --git a/include/uapi/linux/if.h b/include/uapi/linux/if.h +index 7fea0fd7d6f5..7255e08393db 100644 +--- a/include/uapi/linux/if.h ++++ b/include/uapi/linux/if.h +@@ -132,6 +132,9 @@ enum net_device_flags { + #define IFF_ECHO IFF_ECHO + #endif /* __UAPI_DEF_IF_NET_DEVICE_FLAGS_LOWER_UP_DORMANT_ECHO */ + ++#define IFF_NOMULTIPATH 0x80000 /* Disable for MPTCP */ ++#define IFF_MPBACKUP 0x100000 /* Use as backup path for MPTCP */ ++ + #define IFF_VOLATILE (IFF_LOOPBACK|IFF_POINTOPOINT|IFF_BROADCAST|IFF_ECHO|\ + IFF_MASTER|IFF_SLAVE|IFF_RUNNING|IFF_LOWER_UP|IFF_DORMANT) + +diff --git a/include/uapi/linux/in.h b/include/uapi/linux/in.h +index 60e1241d4b77..ff6185b1d79f 100644 +--- a/include/uapi/linux/in.h ++++ b/include/uapi/linux/in.h +@@ -76,6 +76,8 @@ enum { + #define IPPROTO_MPLS IPPROTO_MPLS + IPPROTO_RAW = 255, /* Raw IP packets */ + #define IPPROTO_RAW IPPROTO_RAW ++ IPPROTO_MPTCP = 262, /* Multipath TCP connection */ ++#define IPPROTO_MPTCP IPPROTO_MPTCP + IPPROTO_MAX + }; + #endif +diff --git a/include/uapi/linux/mptcp.h b/include/uapi/linux/mptcp.h +new file mode 100644 +index 000000000000..02078c80c846 +--- /dev/null ++++ b/include/uapi/linux/mptcp.h +@@ -0,0 +1,151 @@ ++/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ ++/* ++ * Netlink API for Multipath TCP ++ * ++ * Author: Gregory Detal ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License ++ * as published by the Free Software Foundation; either version ++ * 2 of the License, or (at your option) any later version. ++ */ ++ ++#ifndef _LINUX_MPTCP_H ++#define _LINUX_MPTCP_H ++ ++#define MPTCP_GENL_NAME "mptcp" ++#define MPTCP_GENL_EV_GRP_NAME "mptcp_events" ++#define MPTCP_GENL_CMD_GRP_NAME "mptcp_commands" ++#define MPTCP_GENL_VER 0x1 ++ ++/* ++ * ATTR types defined for MPTCP ++ */ ++enum { ++ MPTCP_ATTR_UNSPEC = 0, ++ ++ MPTCP_ATTR_TOKEN, /* u32 */ ++ MPTCP_ATTR_FAMILY, /* u16 */ ++ MPTCP_ATTR_LOC_ID, /* u8 */ ++ MPTCP_ATTR_REM_ID, /* u8 */ ++ MPTCP_ATTR_SADDR4, /* u32 */ ++ MPTCP_ATTR_SADDR6, /* struct in6_addr */ ++ MPTCP_ATTR_DADDR4, /* u32 */ ++ MPTCP_ATTR_DADDR6, /* struct in6_addr */ ++ MPTCP_ATTR_SPORT, /* u16 */ ++ MPTCP_ATTR_DPORT, /* u16 */ ++ MPTCP_ATTR_BACKUP, /* u8 */ ++ MPTCP_ATTR_ERROR, /* u8 */ ++ MPTCP_ATTR_FLAGS, /* u16 */ ++ MPTCP_ATTR_TIMEOUT, /* u32 */ ++ MPTCP_ATTR_IF_IDX, /* s32 */ ++ ++ __MPTCP_ATTR_AFTER_LAST ++}; ++ ++#define MPTCP_ATTR_MAX (__MPTCP_ATTR_AFTER_LAST - 1) ++ ++/* ++ * Events generated by MPTCP: ++ * - MPTCP_EVENT_CREATED: token, family, saddr4 | saddr6, daddr4 | daddr6, ++ * sport, dport ++ * A new connection has been created. It is the good time to allocate ++ * memory and send ADD_ADDR if needed. Depending on the traffic-patterns ++ * it can take a long time until the MPTCP_EVENT_ESTABLISHED is sent. ++ * ++ * - MPTCP_EVENT_ESTABLISHED: token, family, saddr4 | saddr6, daddr4 | daddr6, ++ * sport, dport ++ * A connection is established (can start new subflows). ++ * ++ * - MPTCP_EVENT_CLOSED: token ++ * A connection has stopped. ++ * ++ * - MPTCP_EVENT_ANNOUNCED: token, rem_id, family, daddr4 | daddr6 [, dport] ++ * A new address has been announced by the peer. ++ * ++ * - MPTCP_EVENT_REMOVED: token, rem_id ++ * An address has been lost by the peer. ++ * ++ * - MPTCP_EVENT_SUB_ESTABLISHED: token, family, loc_id, rem_id, ++ * saddr4 | saddr6, daddr4 | daddr6, sport, ++ * dport, backup, if_idx [, error] ++ * A new subflow has been established. 'error' should not be set. ++ * ++ * - MPTCP_EVENT_SUB_CLOSED: token, family, loc_id, rem_id, saddr4 | saddr6, ++ * daddr4 | daddr6, sport, dport, backup, if_idx ++ * [, error] ++ * A subflow has been closed. An error (copy of sk_err) could be set if an ++ * error has been detected for this subflow. ++ * ++ * - MPTCP_EVENT_SUB_PRIORITY: token, family, loc_id, rem_id, saddr4 | saddr6, ++ * daddr4 | daddr6, sport, dport, backup, if_idx ++ * [, error] ++ * The priority of a subflow has changed. 'error' should not be set. ++ * ++ * Commands for MPTCP: ++ * - MPTCP_CMD_ANNOUNCE: token, loc_id, family, saddr4 | saddr6 [, sport] ++ * Announce a new address to the peer. ++ * ++ * - MPTCP_CMD_REMOVE: token, loc_id ++ * Announce that an address has been lost to the peer. ++ * ++ * - MPTCP_CMD_SUB_CREATE: token, family, loc_id, rem_id, daddr4 | daddr6, ++ * dport [, saddr4 | saddr6, sport, backup, if_idx] ++ * Create a new subflow. ++ * ++ * - MPTCP_CMD_SUB_DESTROY: token, family, saddr4 | saddr6, daddr4 | daddr6, ++ * sport, dport ++ * Close a subflow. ++ * ++ * - MPTCP_CMD_SUB_PRIORITY: token, family, saddr4 | saddr6, daddr4 | daddr6, ++ * sport, dport, backup ++ * Change the priority of a subflow. ++ * ++ * - MPTCP_CMD_SET_FILTER: flags ++ * Set the filter on events. Set MPTCPF_* flags to only receive specific ++ * events. Default is to receive all events. ++ * ++ * - MPTCP_CMD_EXIST: token ++ * Check if this token is linked to an existing socket. ++ */ ++enum { ++ MPTCP_CMD_UNSPEC = 0, ++ ++ MPTCP_EVENT_CREATED, ++ MPTCP_EVENT_ESTABLISHED, ++ MPTCP_EVENT_CLOSED, ++ ++ MPTCP_CMD_ANNOUNCE, ++ MPTCP_CMD_REMOVE, ++ MPTCP_EVENT_ANNOUNCED, ++ MPTCP_EVENT_REMOVED, ++ ++ MPTCP_CMD_SUB_CREATE, ++ MPTCP_CMD_SUB_DESTROY, ++ MPTCP_EVENT_SUB_ESTABLISHED, ++ MPTCP_EVENT_SUB_CLOSED, ++ ++ MPTCP_CMD_SUB_PRIORITY, ++ MPTCP_EVENT_SUB_PRIORITY, ++ ++ MPTCP_CMD_SET_FILTER, ++ ++ MPTCP_CMD_EXIST, ++ ++ __MPTCP_CMD_AFTER_LAST ++}; ++ ++#define MPTCP_CMD_MAX (__MPTCP_CMD_AFTER_LAST - 1) ++ ++enum { ++ MPTCPF_EVENT_CREATED = (1 << 1), ++ MPTCPF_EVENT_ESTABLISHED = (1 << 2), ++ MPTCPF_EVENT_CLOSED = (1 << 3), ++ MPTCPF_EVENT_ANNOUNCED = (1 << 4), ++ MPTCPF_EVENT_REMOVED = (1 << 5), ++ MPTCPF_EVENT_SUB_ESTABLISHED = (1 << 6), ++ MPTCPF_EVENT_SUB_CLOSED = (1 << 7), ++ MPTCPF_EVENT_SUB_PRIORITY = (1 << 8), ++}; ++ ++#endif /* _LINUX_MPTCP_H */ +diff --git a/include/uapi/linux/tcp.h b/include/uapi/linux/tcp.h +index 81e697978e8b..09ef515261d2 100644 +--- a/include/uapi/linux/tcp.h ++++ b/include/uapi/linux/tcp.h +@@ -18,9 +18,15 @@ + #ifndef _UAPI_LINUX_TCP_H + #define _UAPI_LINUX_TCP_H + +-#include ++#ifndef __KERNEL__ ++#include ++#endif ++ + #include ++#include ++#include + #include ++#include + + struct tcphdr { + __be16 source; +@@ -134,6 +140,13 @@ enum { + #define TCP_REPAIR_OFF 0 + #define TCP_REPAIR_OFF_NO_WP -1 /* Turn off without window probes */ + ++#define MPTCP_ENABLED 42 ++#define MPTCP_SCHEDULER 43 ++#define MPTCP_PATH_MANAGER 44 ++#define MPTCP_INFO 45 ++ ++#define MPTCP_INFO_FLAG_SAVE_MASTER 0x01 ++ + struct tcp_repair_opt { + __u32 opt_code; + __u32 opt_val; +@@ -305,6 +318,53 @@ enum { + TCP_NLA_SRTT, /* smoothed RTT in usecs */ + }; + ++struct mptcp_meta_info { ++ __u8 mptcpi_state; ++ __u8 mptcpi_retransmits; ++ __u8 mptcpi_probes; ++ __u8 mptcpi_backoff; ++ ++ __u32 mptcpi_rto; ++ __u32 mptcpi_unacked; ++ ++ /* Times. */ ++ __u32 mptcpi_last_data_sent; ++ __u32 mptcpi_last_data_recv; ++ __u32 mptcpi_last_ack_recv; ++ ++ __u32 mptcpi_total_retrans; ++ ++ __u64 mptcpi_bytes_acked; /* RFC4898 tcpEStatsAppHCThruOctetsAcked */ ++ __u64 mptcpi_bytes_received; /* RFC4898 tcpEStatsAppHCThruOctetsReceived */ ++}; ++ ++struct mptcp_sub_info { ++ union { ++ struct sockaddr src; ++ struct sockaddr_in src_v4; ++ struct sockaddr_in6 src_v6; ++ }; ++ ++ union { ++ struct sockaddr dst; ++ struct sockaddr_in dst_v4; ++ struct sockaddr_in6 dst_v6; ++ }; ++}; ++ ++struct mptcp_info { ++ __u32 tcp_info_len; /* Length of each struct tcp_info in subflows pointer */ ++ __u32 sub_len; /* Total length of memory pointed to by subflows pointer */ ++ __u32 meta_len; /* Length of memory pointed to by meta_info */ ++ __u32 sub_info_len; /* Length of each struct mptcp_sub_info in subflow_info pointer */ ++ __u32 total_sub_info_len; /* Total length of memory pointed to by subflow_info */ ++ ++ struct mptcp_meta_info *meta_info; ++ struct tcp_info *initial; ++ struct tcp_info *subflows; /* Pointer to array of tcp_info structs */ ++ struct mptcp_sub_info *subflow_info; ++}; ++ + /* for TCP_MD5SIG socket option */ + #define TCP_MD5SIG_MAXKEYLEN 80 + +diff --git a/net/Kconfig b/net/Kconfig +index 0b2fecc83452..66f9158a3040 100644 +--- a/net/Kconfig ++++ b/net/Kconfig +@@ -94,6 +94,7 @@ if INET + source "net/ipv4/Kconfig" + source "net/ipv6/Kconfig" + source "net/netlabel/Kconfig" ++source "net/mptcp/Kconfig" + + endif # if INET + +diff --git a/net/Makefile b/net/Makefile +index 449fc0b221f8..08683343642e 100644 +--- a/net/Makefile ++++ b/net/Makefile +@@ -20,6 +20,7 @@ obj-$(CONFIG_TLS) += tls/ + obj-$(CONFIG_XFRM) += xfrm/ + obj-$(CONFIG_UNIX_SCM) += unix/ + obj-$(CONFIG_NET) += ipv6/ ++obj-$(CONFIG_MPTCP) += mptcp/ + obj-$(CONFIG_BPFILTER) += bpfilter/ + obj-$(CONFIG_PACKET) += packet/ + obj-$(CONFIG_NET_KEY) += key/ +diff --git a/net/core/dev.c b/net/core/dev.c +index a03036456221..aebb337662c3 100644 +--- a/net/core/dev.c ++++ b/net/core/dev.c +@@ -7892,7 +7892,7 @@ int __dev_change_flags(struct net_device *dev, unsigned int flags, + + dev->flags = (flags & (IFF_DEBUG | IFF_NOTRAILERS | IFF_NOARP | + IFF_DYNAMIC | IFF_MULTICAST | IFF_PORTSEL | +- IFF_AUTOMEDIA)) | ++ IFF_AUTOMEDIA | IFF_NOMULTIPATH | IFF_MPBACKUP)) | + (dev->flags & (IFF_UP | IFF_VOLATILE | IFF_PROMISC | + IFF_ALLMULTI)); + +diff --git a/net/core/filter.c b/net/core/filter.c +index 5ebc973ed4c5..516fc8689088 100644 +--- a/net/core/filter.c ++++ b/net/core/filter.c +@@ -73,6 +73,7 @@ + #include + #include + #include ++#include + + /** + * sk_filter_trim_cap - run a packet through a socket filter +@@ -4280,6 +4281,19 @@ static unsigned long bpf_xdp_copy(void *dst_buff, const void *src_buff, + if (sk->sk_mark != val) { + sk->sk_mark = val; + sk_dst_reset(sk); ++ ++ if (is_meta_sk(sk)) { ++ struct mptcp_tcp_sock *mptcp; ++ ++ mptcp_for_each_sub(tcp_sk(sk)->mpcb, mptcp) { ++ struct sock *sk_it = mptcp_to_sock(mptcp); ++ ++ if (val != sk_it->sk_mark) { ++ sk_it->sk_mark = val; ++ sk_dst_reset(sk_it); ++ } ++ } ++ } + } + break; + default: +@@ -4302,6 +4316,14 @@ static unsigned long bpf_xdp_copy(void *dst_buff, const void *src_buff, + if (val == -1) + val = 0; + inet->tos = val; ++ ++ /* Update TOS on mptcp subflow */ ++ if (is_meta_sk(sk)) { ++ struct mptcp_tcp_sock *mptcp; ++ ++ mptcp_for_each_sub(tcp_sk(sk)->mpcb, mptcp) ++ inet_sk(mptcp_to_sock(mptcp))->tos = val; ++ } + } + break; + default: +@@ -4324,6 +4346,17 @@ static unsigned long bpf_xdp_copy(void *dst_buff, const void *src_buff, + if (val == -1) + val = 0; + np->tclass = val; ++ ++ if (is_meta_sk(sk)) { ++ struct mptcp_tcp_sock *mptcp; ++ ++ mptcp_for_each_sub(tcp_sk(sk)->mpcb, mptcp) { ++ struct sock *sk_it = mptcp_to_sock(mptcp); ++ ++ if (sk_it->sk_family == AF_INET6) ++ inet6_sk(sk_it)->tclass = val; ++ } ++ } + } + break; + default: +diff --git a/net/core/net-traces.c b/net/core/net-traces.c +index 283ddb2dbc7d..8f526a0d1912 100644 +--- a/net/core/net-traces.c ++++ b/net/core/net-traces.c +@@ -60,3 +60,5 @@ + EXPORT_TRACEPOINT_SYMBOL_GPL(napi_poll); + + EXPORT_TRACEPOINT_SYMBOL_GPL(tcp_send_reset); ++ ++EXPORT_TRACEPOINT_SYMBOL_GPL(mptcp_retransmit); +diff --git a/net/core/skbuff.c b/net/core/skbuff.c +index ac083685214e..62bf97b4d5de 100644 +--- a/net/core/skbuff.c ++++ b/net/core/skbuff.c +@@ -582,7 +582,7 @@ static inline void skb_drop_fraglist(struct sk_buff *skb) + skb_drop_list(&skb_shinfo(skb)->frag_list); + } + +-static void skb_clone_fraglist(struct sk_buff *skb) ++void skb_clone_fraglist(struct sk_buff *skb) + { + struct sk_buff *list; + +diff --git a/net/core/sock.c b/net/core/sock.c +index 57b7a10703c3..8d716113e273 100644 +--- a/net/core/sock.c ++++ b/net/core/sock.c +@@ -135,6 +135,11 @@ + + #include + ++#ifdef CONFIG_MPTCP ++#include ++#include ++#endif ++ + #include + #include + +@@ -1063,6 +1068,19 @@ int sock_setsockopt(struct socket *sock, int level, int optname, + } else if (val != sk->sk_mark) { + sk->sk_mark = val; + sk_dst_reset(sk); ++ ++ if (is_meta_sk(sk)) { ++ struct mptcp_tcp_sock *mptcp; ++ ++ mptcp_for_each_sub(tcp_sk(sk)->mpcb, mptcp) { ++ struct sock *sk_it = mptcp_to_sock(mptcp); ++ ++ if (val != sk_it->sk_mark) { ++ sk_it->sk_mark = val; ++ sk_dst_reset(sk_it); ++ } ++ } ++ } + } + break; + +@@ -1563,6 +1581,23 @@ int sock_getsockopt(struct socket *sock, int level, int optname, + */ + static inline void sock_lock_init(struct sock *sk) + { ++#ifdef CONFIG_MPTCP ++ /* Reclassify the lock-class for subflows */ ++ if (sk->sk_type == SOCK_STREAM && sk->sk_protocol == IPPROTO_TCP) ++ if (mptcp(tcp_sk(sk)) || tcp_sk(sk)->is_master_sk) { ++ sock_lock_init_class_and_name(sk, meta_slock_key_name, ++ &meta_slock_key, ++ meta_key_name, ++ &meta_key); ++ ++ /* We don't yet have the mptcp-point. ++ * Thus we still need inet_sock_destruct ++ */ ++ sk->sk_destruct = inet_sock_destruct; ++ return; ++ } ++#endif ++ + if (sk->sk_kern_sock) + sock_lock_init_class_and_name( + sk, +@@ -1611,8 +1646,12 @@ static struct sock *sk_prot_alloc(struct proto *prot, gfp_t priority, + sk = kmem_cache_alloc(slab, priority & ~__GFP_ZERO); + if (!sk) + return sk; +- if (want_init_on_alloc(priority)) +- sk_prot_clear_nulls(sk, prot->obj_size); ++ if (want_init_on_alloc(priority)) { ++ if (prot->clear_sk) ++ prot->clear_sk(sk, prot->obj_size); ++ else ++ sk_prot_clear_nulls(sk, prot->obj_size); ++ } + } else + sk = kmalloc(prot->obj_size, priority); + +@@ -1846,6 +1885,7 @@ struct sock *sk_clone_lock(const struct sock *sk, const gfp_t priority) + atomic_set(&newsk->sk_zckey, 0); + + sock_reset_flag(newsk, SOCK_DONE); ++ sock_reset_flag(newsk, SOCK_MPTCP); + + /* sk->sk_memcg will be populated at accept() time */ + newsk->sk_memcg = NULL; +diff --git a/net/ipv4/Kconfig b/net/ipv4/Kconfig +index a926de2e42b5..6d73dc6e2586 100644 +--- a/net/ipv4/Kconfig ++++ b/net/ipv4/Kconfig +@@ -655,6 +655,51 @@ config TCP_CONG_BBR + bufferbloat, policers, or AQM schemes that do not provide a delay + signal. It requires the fq ("Fair Queue") pacing packet scheduler. + ++config TCP_CONG_LIA ++ tristate "MPTCP Linked Increase" ++ depends on MPTCP ++ default n ++ ---help--- ++ MultiPath TCP Linked Increase Congestion Control ++ To enable it, just put 'lia' in tcp_congestion_control ++ ++config TCP_CONG_OLIA ++ tristate "MPTCP Opportunistic Linked Increase" ++ depends on MPTCP ++ default n ++ ---help--- ++ MultiPath TCP Opportunistic Linked Increase Congestion Control ++ To enable it, just put 'olia' in tcp_congestion_control ++ ++config TCP_CONG_WVEGAS ++ tristate "MPTCP WVEGAS CONGESTION CONTROL" ++ depends on MPTCP ++ default n ++ ---help--- ++ wVegas congestion control for MPTCP ++ To enable it, just put 'wvegas' in tcp_congestion_control ++ ++config TCP_CONG_BALIA ++ tristate "MPTCP BALIA CONGESTION CONTROL" ++ depends on MPTCP ++ default n ++ ---help--- ++ Multipath TCP Balanced Linked Adaptation Congestion Control ++ To enable it, just put 'balia' in tcp_congestion_control ++ ++config TCP_CONG_MCTCPDESYNC ++ tristate "DESYNCHRONIZED MCTCP CONGESTION CONTROL (EXPERIMENTAL)" ++ depends on MPTCP ++ default n ++ ---help--- ++ Desynchronized MultiChannel TCP Congestion Control. This is experimental ++ code that only supports single path and must have set mptcp_ndiffports ++ larger than one. ++ To enable it, just put 'mctcpdesync' in tcp_congestion_control ++ For further details see: ++ http://ieeexplore.ieee.org/abstract/document/6911722/ ++ https://doi.org/10.1016/j.comcom.2015.07.010 ++ + choice + prompt "Default TCP congestion control" + default DEFAULT_CUBIC +@@ -692,6 +737,21 @@ choice + config DEFAULT_BBR + bool "BBR" if TCP_CONG_BBR=y + ++ config DEFAULT_LIA ++ bool "Lia" if TCP_CONG_LIA=y ++ ++ config DEFAULT_OLIA ++ bool "Olia" if TCP_CONG_OLIA=y ++ ++ config DEFAULT_WVEGAS ++ bool "Wvegas" if TCP_CONG_WVEGAS=y ++ ++ config DEFAULT_BALIA ++ bool "Balia" if TCP_CONG_BALIA=y ++ ++ config DEFAULT_MCTCPDESYNC ++ bool "Mctcpdesync (EXPERIMENTAL)" if TCP_CONG_MCTCPDESYNC=y ++ + config DEFAULT_RENO + bool "Reno" + endchoice +@@ -712,6 +772,10 @@ config DEFAULT_TCP_CONG + default "vegas" if DEFAULT_VEGAS + default "westwood" if DEFAULT_WESTWOOD + default "veno" if DEFAULT_VENO ++ default "lia" if DEFAULT_LIA ++ default "olia" if DEFAULT_OLIA ++ default "wvegas" if DEFAULT_WVEGAS ++ default "balia" if DEFAULT_BALIA + default "reno" if DEFAULT_RENO + default "dctcp" if DEFAULT_DCTCP + default "cdg" if DEFAULT_CDG +diff --git a/net/ipv4/af_inet.c b/net/ipv4/af_inet.c +index c800220c404d..b8f10024780a 100644 +--- a/net/ipv4/af_inet.c ++++ b/net/ipv4/af_inet.c +@@ -100,6 +100,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -150,6 +151,9 @@ void inet_sock_destruct(struct sock *sk) + return; + } + ++ if (sock_flag(sk, SOCK_MPTCP)) ++ mptcp_disable_static_key(); ++ + WARN_ON(atomic_read(&sk->sk_rmem_alloc)); + WARN_ON(refcount_read(&sk->sk_wmem_alloc)); + WARN_ON(sk->sk_wmem_queued); +@@ -227,6 +231,8 @@ int inet_listen(struct socket *sock, int backlog) + tcp_fastopen_init_key_once(sock_net(sk)); + } + ++ mptcp_init_listen(sk); ++ + err = inet_csk_listen_start(sk, backlog); + if (err) + goto out; +@@ -244,8 +250,7 @@ int inet_listen(struct socket *sock, int backlog) + * Create an inet socket. + */ + +-static int inet_create(struct net *net, struct socket *sock, int protocol, +- int kern) ++int inet_create(struct net *net, struct socket *sock, int protocol, int kern) + { + struct sock *sk; + struct inet_protosw *answer; +@@ -739,6 +744,24 @@ int inet_accept(struct socket *sock, struct socket *newsock, int flags, + lock_sock(sk2); + + sock_rps_record_flow(sk2); ++ ++ if (sk2->sk_protocol == IPPROTO_TCP && mptcp(tcp_sk(sk2))) { ++ struct mptcp_tcp_sock *mptcp; ++ ++ mptcp_for_each_sub(tcp_sk(sk2)->mpcb, mptcp) { ++ sock_rps_record_flow(mptcp_to_sock(mptcp)); ++ } ++ ++ if (tcp_sk(sk2)->mpcb->master_sk) { ++ struct sock *sk_it = tcp_sk(sk2)->mpcb->master_sk; ++ ++ write_lock_bh(&sk_it->sk_callback_lock); ++ rcu_assign_pointer(sk_it->sk_wq, &newsock->wq); ++ sk_it->sk_socket = newsock; ++ write_unlock_bh(&sk_it->sk_callback_lock); ++ } ++ } ++ + WARN_ON(!((1 << sk2->sk_state) & + (TCPF_ESTABLISHED | TCPF_SYN_RECV | + TCPF_CLOSE_WAIT | TCPF_CLOSE))); +@@ -1978,6 +2001,9 @@ static int __init inet_init(void) + if (init_ipv4_mibs()) + panic("%s: Cannot init ipv4 mibs\n", __func__); + ++ /* We must initialize MPTCP before TCP. */ ++ mptcp_init(); ++ + /* Setup TCP slab cache for open requests. */ + tcp_init(); + +diff --git a/net/ipv4/inet_connection_sock.c b/net/ipv4/inet_connection_sock.c +index 85a88425edc4..f3de2d6eb1a4 100644 +--- a/net/ipv4/inet_connection_sock.c ++++ b/net/ipv4/inet_connection_sock.c +@@ -19,6 +19,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -730,7 +731,10 @@ static void reqsk_timer_handler(struct timer_list *t) + int max_retries, thresh; + u8 defer_accept; + +- if (inet_sk_state_load(sk_listener) != TCP_LISTEN) ++ if (!is_meta_sk(sk_listener) && inet_sk_state_load(sk_listener) != TCP_LISTEN) ++ goto drop; ++ ++ if (is_meta_sk(sk_listener) && !mptcp_can_new_subflow(sk_listener)) + goto drop; + + max_retries = icsk->icsk_syn_retries ? : net->ipv4.sysctl_tcp_synack_retries; +@@ -819,7 +823,9 @@ struct sock *inet_csk_clone_lock(const struct sock *sk, + const struct request_sock *req, + const gfp_t priority) + { +- struct sock *newsk = sk_clone_lock(sk, priority); ++ struct sock *newsk; ++ ++ newsk = sk_clone_lock(sk, priority); + + if (newsk) { + struct inet_connection_sock *newicsk = inet_csk(newsk); +@@ -1019,7 +1025,14 @@ void inet_csk_listen_stop(struct sock *sk) + */ + while ((req = reqsk_queue_remove(queue, sk)) != NULL) { + struct sock *child = req->sk; ++ bool mutex_taken = false; ++ struct mptcp_cb *mpcb = tcp_sk(child)->mpcb; + ++ if (is_meta_sk(child)) { ++ WARN_ON(refcount_inc_not_zero(&mpcb->mpcb_refcnt) == 0); ++ mutex_lock(&mpcb->mpcb_mutex); ++ mutex_taken = true; ++ } + local_bh_disable(); + bh_lock_sock(child); + WARN_ON(sock_owned_by_user(child)); +@@ -1029,6 +1042,10 @@ void inet_csk_listen_stop(struct sock *sk) + reqsk_put(req); + bh_unlock_sock(child); + local_bh_enable(); ++ if (mutex_taken) { ++ mutex_unlock(&mpcb->mpcb_mutex); ++ mptcp_mpcb_put(mpcb); ++ } + sock_put(child); + + cond_resched(); +diff --git a/net/ipv4/ip_sockglue.c b/net/ipv4/ip_sockglue.c +index aa3fd61818c4..8b3e955ec165 100644 +--- a/net/ipv4/ip_sockglue.c ++++ b/net/ipv4/ip_sockglue.c +@@ -44,6 +44,8 @@ + #endif + #include + ++#include ++ + #include + #include + +@@ -657,7 +659,7 @@ static int do_ip_setsockopt(struct sock *sk, int level, + break; + old = rcu_dereference_protected(inet->inet_opt, + lockdep_sock_is_held(sk)); +- if (inet->is_icsk) { ++ if (inet->is_icsk && !is_meta_sk(sk)) { + struct inet_connection_sock *icsk = inet_csk(sk); + #if IS_ENABLED(CONFIG_IPV6) + if (sk->sk_family == PF_INET || +@@ -751,6 +753,20 @@ static int do_ip_setsockopt(struct sock *sk, int level, + inet->tos = val; + sk->sk_priority = rt_tos2priority(val); + sk_dst_reset(sk); ++ /* Update TOS on mptcp subflow */ ++ if (is_meta_sk(sk)) { ++ struct mptcp_tcp_sock *mptcp; ++ ++ mptcp_for_each_sub(tcp_sk(sk)->mpcb, mptcp) { ++ struct sock *sk_it = mptcp_to_sock(mptcp); ++ ++ if (inet_sk(sk_it)->tos != inet_sk(sk)->tos) { ++ inet_sk(sk_it)->tos = inet_sk(sk)->tos; ++ sk_it->sk_priority = sk->sk_priority; ++ sk_dst_reset(sk_it); ++ } ++ } ++ } + } + break; + case IP_TTL: +diff --git a/net/ipv4/syncookies.c b/net/ipv4/syncookies.c +index 2b45d1455592..f988be944eda 100644 +--- a/net/ipv4/syncookies.c ++++ b/net/ipv4/syncookies.c +@@ -12,6 +12,8 @@ + #include + #include + #include ++#include ++#include + #include + #include + #include +@@ -175,7 +177,8 @@ u32 __cookie_v4_init_sequence(const struct iphdr *iph, const struct tcphdr *th, + } + EXPORT_SYMBOL_GPL(__cookie_v4_init_sequence); + +-__u32 cookie_v4_init_sequence(const struct sk_buff *skb, __u16 *mssp) ++__u32 cookie_v4_init_sequence(struct request_sock *req, const struct sock *sk, ++ const struct sk_buff *skb, __u16 *mssp) + { + const struct iphdr *iph = ip_hdr(skb); + const struct tcphdr *th = tcp_hdr(skb); +@@ -200,14 +203,33 @@ int __cookie_v4_check(const struct iphdr *iph, const struct tcphdr *th, + + struct sock *tcp_get_cookie_sock(struct sock *sk, struct sk_buff *skb, + struct request_sock *req, ++ const struct mptcp_options_received *mopt, + struct dst_entry *dst, u32 tsoff) + { + struct inet_connection_sock *icsk = inet_csk(sk); + struct sock *child; + bool own_req; ++#ifdef CONFIG_MPTCP ++ int ret; ++#endif + + child = icsk->icsk_af_ops->syn_recv_sock(sk, skb, req, dst, + NULL, &own_req); ++ ++#ifdef CONFIG_MPTCP ++ if (!child) ++ goto listen_overflow; ++ ++ ret = mptcp_check_req_master(sk, child, req, skb, mopt, 0, tsoff); ++ if (ret < 0) ++ return NULL; ++ ++ if (!ret) ++ return tcp_sk(child)->mpcb->master_sk; ++ ++listen_overflow: ++#endif ++ + if (child) { + refcount_set(&req->rsk_refcnt, 1); + tcp_sk(child)->tsoffset = tsoff; +@@ -284,6 +306,7 @@ struct sock *cookie_v4_check(struct sock *sk, struct sk_buff *skb) + { + struct ip_options *opt = &TCP_SKB_CB(skb)->header.h4.opt; + struct tcp_options_received tcp_opt; ++ struct mptcp_options_received mopt; + struct inet_request_sock *ireq; + struct tcp_request_sock *treq; + struct tcp_sock *tp = tcp_sk(sk); +@@ -313,7 +336,8 @@ struct sock *cookie_v4_check(struct sock *sk, struct sk_buff *skb) + + /* check for timestamp cookie support */ + memset(&tcp_opt, 0, sizeof(tcp_opt)); +- tcp_parse_options(sock_net(sk), skb, &tcp_opt, 0, NULL); ++ mptcp_init_mp_opt(&mopt); ++ tcp_parse_options(sock_net(sk), skb, &tcp_opt, &mopt, 0, NULL, NULL); + + if (tcp_opt.saw_tstamp && tcp_opt.rcv_tsecr) { + tsoff = secure_tcp_ts_off(sock_net(sk), +@@ -326,7 +350,12 @@ struct sock *cookie_v4_check(struct sock *sk, struct sk_buff *skb) + goto out; + + ret = NULL; +- req = inet_reqsk_alloc(&tcp_request_sock_ops, sk, false); /* for safety */ ++#ifdef CONFIG_MPTCP ++ if (mopt.saw_mpc) ++ req = inet_reqsk_alloc(&mptcp_request_sock_ops, sk, false); /* for safety */ ++ else ++#endif ++ req = inet_reqsk_alloc(&tcp_request_sock_ops, sk, false); /* for safety */ + if (!req) + goto out; + +@@ -346,6 +375,8 @@ struct sock *cookie_v4_check(struct sock *sk, struct sk_buff *skb) + ireq->sack_ok = tcp_opt.sack_ok; + ireq->wscale_ok = tcp_opt.wscale_ok; + ireq->tstamp_ok = tcp_opt.saw_tstamp; ++ ireq->mptcp_rqsk = 0; ++ ireq->saw_mpc = 0; + req->ts_recent = tcp_opt.saw_tstamp ? tcp_opt.rcv_tsval : 0; + treq->snt_synack = 0; + treq->tfo_listener = false; +@@ -354,6 +385,9 @@ struct sock *cookie_v4_check(struct sock *sk, struct sk_buff *skb) + + ireq->ir_iif = inet_request_bound_dev_if(sk, skb); + ++ if (mopt.saw_mpc) ++ mptcp_cookies_reqsk_init(req, &mopt, skb); ++ + /* We throwed the options of the initial SYN away, so we hope + * the ACK carries the same options again (see RFC1122 4.2.3.8) + */ +@@ -392,15 +426,15 @@ struct sock *cookie_v4_check(struct sock *sk, struct sk_buff *skb) + (req->rsk_window_clamp > full_space || req->rsk_window_clamp == 0)) + req->rsk_window_clamp = full_space; + +- tcp_select_initial_window(sk, full_space, req->mss, +- &req->rsk_rcv_wnd, &req->rsk_window_clamp, +- ireq->wscale_ok, &rcv_wscale, +- dst_metric(&rt->dst, RTAX_INITRWND)); ++ tp->ops->select_initial_window(sk, full_space, req->mss, ++ &req->rsk_rcv_wnd, &req->rsk_window_clamp, ++ ireq->wscale_ok, &rcv_wscale, ++ dst_metric(&rt->dst, RTAX_INITRWND)); + + ireq->rcv_wscale = rcv_wscale; + ireq->ecn_ok = cookie_ecn_ok(&tcp_opt, sock_net(sk), &rt->dst); + +- ret = tcp_get_cookie_sock(sk, skb, req, &rt->dst, tsoff); ++ ret = tcp_get_cookie_sock(sk, skb, req, &mopt, &rt->dst, tsoff); + /* ip_queue_xmit() depends on our flow being setup + * Normal sockets get it right from inet_csk_route_child_sock() + */ +diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c +index 9f53d25e047e..ae9ba8f2ced1 100644 +--- a/net/ipv4/tcp.c ++++ b/net/ipv4/tcp.c +@@ -270,6 +270,7 @@ + + #include + #include ++#include + #include + #include + #include +@@ -400,6 +401,23 @@ static u64 tcp_compute_delivery_rate(const struct tcp_sock *tp) + return rate64; + } + ++const struct tcp_sock_ops tcp_specific = { ++ .__select_window = __tcp_select_window, ++ .select_window = tcp_select_window, ++ .select_initial_window = tcp_select_initial_window, ++ .init_buffer_space = tcp_init_buffer_space, ++ .set_rto = tcp_set_rto, ++ .should_expand_sndbuf = tcp_should_expand_sndbuf, ++ .send_fin = tcp_send_fin, ++ .write_xmit = tcp_write_xmit, ++ .send_active_reset = tcp_send_active_reset, ++ .write_wakeup = tcp_write_wakeup, ++ .retransmit_timer = tcp_retransmit_timer, ++ .time_wait = tcp_time_wait, ++ .cleanup_rbuf = tcp_cleanup_rbuf, ++ .set_cong_ctrl = __tcp_set_congestion_control, ++}; ++ + /* Address-family independent initialization for a tcp_sock. + * + * NOTE: A lot of things set to zero explicitly by call to +@@ -453,6 +471,11 @@ void tcp_init_sock(struct sock *sk) + WRITE_ONCE(sk->sk_sndbuf, sock_net(sk)->ipv4.sysctl_tcp_wmem[1]); + WRITE_ONCE(sk->sk_rcvbuf, sock_net(sk)->ipv4.sysctl_tcp_rmem[1]); + ++ tp->ops = &tcp_specific; ++ ++ /* Initialize MPTCP-specific stuff and function-pointers */ ++ mptcp_init_tcp_sock(sk); ++ + sk_sockets_allocated_inc(sk); + sk->sk_route_forced_caps = NETIF_F_GSO; + } +@@ -484,7 +507,7 @@ static inline bool tcp_stream_is_readable(const struct tcp_sock *tp, + return true; + if (tcp_rmem_pressure(sk)) + return true; +- if (tcp_receive_window(tp) <= inet_csk(sk)->icsk_ack.rcv_mss) ++ if (tcp_receive_window_now(tp) <= inet_csk(sk)->icsk_ack.rcv_mss) + return true; + } + if (sk->sk_prot->stream_memory_read) +@@ -787,6 +810,7 @@ ssize_t tcp_splice_read(struct socket *sock, loff_t *ppos, + int ret; + + sock_rps_record_flow(sk); ++ + /* + * We can't seek on a socket input + */ +@@ -797,6 +821,16 @@ ssize_t tcp_splice_read(struct socket *sock, loff_t *ppos, + + lock_sock(sk); + ++#ifdef CONFIG_MPTCP ++ if (mptcp(tcp_sk(sk))) { ++ struct mptcp_tcp_sock *mptcp; ++ ++ mptcp_for_each_sub(tcp_sk(sk)->mpcb, mptcp) { ++ sock_rps_record_flow(mptcp_to_sock(mptcp)); ++ } ++ } ++#endif ++ + timeo = sock_rcvtimeo(sk, sock->file->f_flags & O_NONBLOCK); + while (tss.len) { + ret = __tcp_splice_read(sk, &tss); +@@ -912,8 +946,7 @@ struct sk_buff *sk_stream_alloc_skb(struct sock *sk, int size, gfp_t gfp, + return NULL; + } + +-static unsigned int tcp_xmit_size_goal(struct sock *sk, u32 mss_now, +- int large_allowed) ++unsigned int tcp_xmit_size_goal(struct sock *sk, u32 mss_now, int large_allowed) + { + struct tcp_sock *tp = tcp_sk(sk); + u32 new_size_goal, size_goal; +@@ -941,8 +974,13 @@ static int tcp_send_mss(struct sock *sk, int *size_goal, int flags) + { + int mss_now; + +- mss_now = tcp_current_mss(sk); +- *size_goal = tcp_xmit_size_goal(sk, mss_now, !(flags & MSG_OOB)); ++ if (mptcp(tcp_sk(sk))) { ++ mss_now = mptcp_current_mss(sk); ++ *size_goal = mptcp_xmit_size_goal(sk, mss_now, !(flags & MSG_OOB)); ++ } else { ++ mss_now = tcp_current_mss(sk); ++ *size_goal = tcp_xmit_size_goal(sk, mss_now, !(flags & MSG_OOB)); ++ } + + return mss_now; + } +@@ -982,12 +1020,34 @@ ssize_t do_tcp_sendpages(struct sock *sk, struct page *page, int offset, + * is fully established. + */ + if (((1 << sk->sk_state) & ~(TCPF_ESTABLISHED | TCPF_CLOSE_WAIT)) && +- !tcp_passive_fastopen(sk)) { ++ !tcp_passive_fastopen(mptcp(tp) && tp->mpcb->master_sk ? ++ tp->mpcb->master_sk : sk)) { + err = sk_stream_wait_connect(sk, &timeo); + if (err != 0) + goto out_err; + } + ++ if (mptcp(tp)) { ++ struct mptcp_tcp_sock *mptcp; ++ ++ /* We must check this with socket-lock hold because we iterate ++ * over the subflows. ++ */ ++ if (!mptcp_can_sendpage(sk)) { ++ ssize_t ret; ++ ++ release_sock(sk); ++ ret = sock_no_sendpage(sk->sk_socket, page, offset, ++ size, flags); ++ lock_sock(sk); ++ return ret; ++ } ++ ++ mptcp_for_each_sub(tp->mpcb, mptcp) { ++ sock_rps_record_flow(mptcp_to_sock(mptcp)); ++ } ++ } ++ + sk_clear_bit(SOCKWQ_ASYNC_NOSPACE, sk); + + mss_now = tcp_send_mss(sk, &size_goal, flags); +@@ -1109,7 +1169,8 @@ ssize_t do_tcp_sendpages(struct sock *sk, struct page *page, int offset, + int tcp_sendpage_locked(struct sock *sk, struct page *page, int offset, + size_t size, int flags) + { +- if (!(sk->sk_route_caps & NETIF_F_SG)) ++ /* If MPTCP is enabled, we check it later after establishment */ ++ if (!mptcp(tcp_sk(sk)) && !(sk->sk_route_caps & NETIF_F_SG)) + return sock_no_sendpage_locked(sk, page, offset, size, flags); + + tcp_rate_check_app_limited(sk); /* is sending application-limited? */ +@@ -1231,12 +1292,21 @@ int tcp_sendmsg_locked(struct sock *sk, struct msghdr *msg, size_t size) + * is fully established. + */ + if (((1 << sk->sk_state) & ~(TCPF_ESTABLISHED | TCPF_CLOSE_WAIT)) && +- !tcp_passive_fastopen(sk)) { ++ !tcp_passive_fastopen(mptcp(tp) && tp->mpcb->master_sk ? ++ tp->mpcb->master_sk : sk)) { + err = sk_stream_wait_connect(sk, &timeo); + if (err != 0) + goto do_error; + } + ++ if (mptcp(tp)) { ++ struct mptcp_tcp_sock *mptcp; ++ ++ mptcp_for_each_sub(tp->mpcb, mptcp) { ++ sock_rps_record_flow(mptcp_to_sock(mptcp)); ++ } ++ } ++ + if (unlikely(tp->repair)) { + if (tp->repair_queue == TCP_RECV_QUEUE) { + copied = tcp_send_rcvq(sk, msg, size); +@@ -1529,7 +1599,7 @@ static int tcp_peek_sndq(struct sock *sk, struct msghdr *msg, int len) + * calculation of whether or not we must ACK for the sake of + * a window update. + */ +-static void tcp_cleanup_rbuf(struct sock *sk, int copied) ++void tcp_cleanup_rbuf(struct sock *sk, int copied) + { + struct tcp_sock *tp = tcp_sk(sk); + bool time_to_ack = false; +@@ -1568,11 +1638,11 @@ static void tcp_cleanup_rbuf(struct sock *sk, int copied) + * in states, where we will not receive more. It is useless. + */ + if (copied > 0 && !time_to_ack && !(sk->sk_shutdown & RCV_SHUTDOWN)) { +- __u32 rcv_window_now = tcp_receive_window(tp); ++ __u32 rcv_window_now = tcp_receive_window_now(tp); + + /* Optimize, __tcp_select_window() is not cheap. */ + if (2*rcv_window_now <= tp->window_clamp) { +- __u32 new_window = __tcp_select_window(sk); ++ __u32 new_window = tp->ops->__select_window(sk); + + /* Send ACK now, if this read freed lots of space + * in our buffer. Certainly, new_window is new window. +@@ -1688,7 +1758,7 @@ int tcp_read_sock(struct sock *sk, read_descriptor_t *desc, + /* Clean up data we have read: This will do ACK frames. */ + if (copied > 0) { + tcp_recv_skb(sk, seq, &offset); +- tcp_cleanup_rbuf(sk, copied); ++ tp->ops->cleanup_rbuf(sk, copied); + } + return copied; + } +@@ -1979,6 +2049,16 @@ int tcp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int nonblock, + + lock_sock(sk); + ++#ifdef CONFIG_MPTCP ++ if (mptcp(tp)) { ++ struct mptcp_tcp_sock *mptcp; ++ ++ mptcp_for_each_sub(tp->mpcb, mptcp) { ++ sock_rps_record_flow(mptcp_to_sock(mptcp)); ++ } ++ } ++#endif ++ + err = -ENOTCONN; + if (sk->sk_state == TCP_LISTEN) + goto out; +@@ -2097,7 +2177,7 @@ int tcp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int nonblock, + } + } + +- tcp_cleanup_rbuf(sk, copied); ++ tp->ops->cleanup_rbuf(sk, copied); + + if (copied >= target) { + /* Do not sleep, just process backlog. */ +@@ -2189,7 +2269,7 @@ int tcp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int nonblock, + */ + + /* Clean up data we have read: This will do ACK frames. */ +- tcp_cleanup_rbuf(sk, copied); ++ tp->ops->cleanup_rbuf(sk, copied); + + release_sock(sk); + +@@ -2248,8 +2328,11 @@ void tcp_set_state(struct sock *sk, int state) + + switch (state) { + case TCP_ESTABLISHED: +- if (oldstate != TCP_ESTABLISHED) ++ if (oldstate != TCP_ESTABLISHED) { + TCP_INC_STATS(sock_net(sk), TCP_MIB_CURRESTAB); ++ if (is_meta_sk(sk)) ++ MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_CURRESTAB); ++ } + break; + + case TCP_CLOSE: +@@ -2262,8 +2345,11 @@ void tcp_set_state(struct sock *sk, int state) + inet_put_port(sk); + /* fall through */ + default: +- if (oldstate == TCP_ESTABLISHED) ++ if (oldstate == TCP_ESTABLISHED) { + TCP_DEC_STATS(sock_net(sk), TCP_MIB_CURRESTAB); ++ if (is_meta_sk(sk)) ++ MPTCP_DEC_STATS(sock_net(sk), MPTCP_MIB_CURRESTAB); ++ } + } + + /* Change state AFTER socket is unhashed to avoid closed +@@ -2297,7 +2383,7 @@ void tcp_set_state(struct sock *sk, int state) + [TCP_NEW_SYN_RECV] = TCP_CLOSE, /* should not happen ! */ + }; + +-static int tcp_close_state(struct sock *sk) ++int tcp_close_state(struct sock *sk) + { + int next = (int)new_state[sk->sk_state]; + int ns = next & TCP_STATE_MASK; +@@ -2327,7 +2413,7 @@ void tcp_shutdown(struct sock *sk, int how) + TCPF_SYN_RECV | TCPF_CLOSE_WAIT)) { + /* Clear out any half completed packets. FIN if needed. */ + if (tcp_close_state(sk)) +- tcp_send_fin(sk); ++ tcp_sk(sk)->ops->send_fin(sk); + } + } + EXPORT_SYMBOL(tcp_shutdown); +@@ -2352,6 +2438,17 @@ void tcp_close(struct sock *sk, long timeout) + int data_was_unread = 0; + int state; + ++ if (is_meta_sk(sk)) { ++ /* TODO: Currently forcing timeout to 0 because ++ * sk_stream_wait_close will complain during lockdep because ++ * of the mpcb_mutex (circular lock dependency through ++ * inet_csk_listen_stop()). ++ * We should find a way to get rid of the mpcb_mutex. ++ */ ++ mptcp_close(sk, 0); ++ return; ++ } ++ + lock_sock(sk); + sk->sk_shutdown = SHUTDOWN_MASK; + +@@ -2396,7 +2493,7 @@ void tcp_close(struct sock *sk, long timeout) + /* Unread data was tossed, zap the connection. */ + NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPABORTONCLOSE); + tcp_set_state(sk, TCP_CLOSE); +- tcp_send_active_reset(sk, sk->sk_allocation); ++ tcp_sk(sk)->ops->send_active_reset(sk, sk->sk_allocation); + } else if (sock_flag(sk, SOCK_LINGER) && !sk->sk_lingertime) { + /* Check zero linger _after_ checking for unread data. */ + sk->sk_prot->disconnect(sk, 0); +@@ -2470,7 +2567,7 @@ void tcp_close(struct sock *sk, long timeout) + struct tcp_sock *tp = tcp_sk(sk); + if (tp->linger2 < 0) { + tcp_set_state(sk, TCP_CLOSE); +- tcp_send_active_reset(sk, GFP_ATOMIC); ++ tp->ops->send_active_reset(sk, GFP_ATOMIC); + __NET_INC_STATS(sock_net(sk), + LINUX_MIB_TCPABORTONLINGER); + } else { +@@ -2480,7 +2577,8 @@ void tcp_close(struct sock *sk, long timeout) + inet_csk_reset_keepalive_timer(sk, + tmo - TCP_TIMEWAIT_LEN); + } else { +- tcp_time_wait(sk, TCP_FIN_WAIT2, tmo); ++ tcp_sk(sk)->ops->time_wait(sk, TCP_FIN_WAIT2, ++ tmo); + goto out; + } + } +@@ -2489,7 +2587,7 @@ void tcp_close(struct sock *sk, long timeout) + sk_mem_reclaim(sk); + if (tcp_check_oom(sk, 0)) { + tcp_set_state(sk, TCP_CLOSE); +- tcp_send_active_reset(sk, GFP_ATOMIC); ++ tcp_sk(sk)->ops->send_active_reset(sk, GFP_ATOMIC); + __NET_INC_STATS(sock_net(sk), + LINUX_MIB_TCPABORTONMEMORY); + } else if (!check_net(sock_net(sk))) { +@@ -2521,15 +2619,6 @@ void tcp_close(struct sock *sk, long timeout) + } + EXPORT_SYMBOL(tcp_close); + +-/* These states need RST on ABORT according to RFC793 */ +- +-static inline bool tcp_need_reset(int state) +-{ +- return (1 << state) & +- (TCPF_ESTABLISHED | TCPF_CLOSE_WAIT | TCPF_FIN_WAIT1 | +- TCPF_FIN_WAIT2 | TCPF_SYN_RECV); +-} +- + static void tcp_rtx_queue_purge(struct sock *sk) + { + struct rb_node *p = rb_first(&sk->tcp_rtx_queue); +@@ -2551,6 +2640,10 @@ void tcp_write_queue_purge(struct sock *sk) + { + struct sk_buff *skb; + ++ if (mptcp(tcp_sk(sk)) && !is_meta_sk(sk) && ++ !tcp_rtx_and_write_queues_empty(sk)) ++ mptcp_reinject_data(sk, 0); ++ + tcp_chrono_stop(sk, TCP_CHRONO_BUSY); + while ((skb = __skb_dequeue(&sk->sk_write_queue)) != NULL) { + tcp_skb_tsorted_anchor_cleanup(skb); +@@ -2569,6 +2662,36 @@ void tcp_write_queue_purge(struct sock *sk) + inet_csk(sk)->icsk_backoff = 0; + } + ++void tcp_reset_vars(struct sock *sk) ++{ ++ struct inet_connection_sock *icsk = inet_csk(sk); ++ struct tcp_sock *tp = tcp_sk(sk); ++ ++ tp->srtt_us = 0; ++ tp->mdev_us = jiffies_to_usecs(TCP_TIMEOUT_INIT); ++ tp->rcv_rtt_last_tsecr = 0; ++ icsk->icsk_probes_tstamp = 0; ++ icsk->icsk_rto = TCP_TIMEOUT_INIT; ++ tp->snd_ssthresh = TCP_INFINITE_SSTHRESH; ++ tp->snd_cwnd = TCP_INIT_CWND; ++ tp->snd_cwnd_cnt = 0; ++ tp->delivered = 0; ++ tp->delivered_ce = 0; ++ tp->is_sack_reneg = 0; ++ tcp_clear_retrans(tp); ++ tp->segs_in = 0; ++ tp->segs_out = 0; ++ tp->bytes_sent = 0; ++ tp->bytes_acked = 0; ++ tp->bytes_received = 0; ++ tp->bytes_retrans = 0; ++ tp->total_retrans = 0; ++ tp->data_segs_in = 0; ++ tp->data_segs_out = 0; ++ /* There's a bubble in the pipe until at least the first ACK. */ ++ tp->app_limited = ~0U; ++} ++ + int tcp_disconnect(struct sock *sk, int flags) + { + struct inet_sock *inet = inet_sk(sk); +@@ -2591,7 +2714,7 @@ int tcp_disconnect(struct sock *sk, int flags) + /* The last check adjusts for discrepancy of Linux wrt. RFC + * states + */ +- tcp_send_active_reset(sk, gfp_any()); ++ tp->ops->send_active_reset(sk, gfp_any()); + sk->sk_err = ECONNRESET; + } else if (old_state == TCP_SYN_SENT) + sk->sk_err = ECONNRESET; +@@ -2613,11 +2736,15 @@ int tcp_disconnect(struct sock *sk, int flags) + if (!(sk->sk_userlocks & SOCK_BINDADDR_LOCK)) + inet_reset_saddr(sk); + ++ if (is_meta_sk(sk)) { ++ mptcp_disconnect(sk); ++ } else { ++ if (tp->inside_tk_table) ++ mptcp_hash_remove_bh(tp); ++ } ++ + sk->sk_shutdown = 0; + sock_reset_flag(sk, SOCK_DONE); +- tp->srtt_us = 0; +- tp->mdev_us = jiffies_to_usecs(TCP_TIMEOUT_INIT); +- tp->rcv_rtt_last_tsecr = 0; + + seq = tp->write_seq + tp->max_window + 2; + if (!seq) +@@ -2627,21 +2754,14 @@ int tcp_disconnect(struct sock *sk, int flags) + icsk->icsk_backoff = 0; + tp->snd_cwnd = 2; + icsk->icsk_probes_out = 0; +- icsk->icsk_probes_tstamp = 0; +- icsk->icsk_rto = TCP_TIMEOUT_INIT; +- tp->snd_ssthresh = TCP_INFINITE_SSTHRESH; +- tp->snd_cwnd = TCP_INIT_CWND; +- tp->snd_cwnd_cnt = 0; + tp->window_clamp = 0; +- tp->delivered = 0; +- tp->delivered_ce = 0; ++ ++ tcp_reset_vars(sk); ++ + if (icsk->icsk_ca_ops->release) + icsk->icsk_ca_ops->release(sk); + memset(icsk->icsk_ca_priv, 0, sizeof(icsk->icsk_ca_priv)); + tcp_set_ca_state(sk, TCP_CA_Open); +- tp->is_sack_reneg = 0; +- tcp_clear_retrans(tp); +- tp->total_retrans = 0; + inet_csk_delack_init(sk); + /* Initialize rcv_mss to TCP_MIN_MSS to avoid division by 0 + * issue in __tcp_select_window() +@@ -2653,14 +2773,6 @@ int tcp_disconnect(struct sock *sk, int flags) + sk->sk_rx_dst = NULL; + tcp_saved_syn_free(tp); + tp->compressed_ack = 0; +- tp->segs_in = 0; +- tp->segs_out = 0; +- tp->bytes_sent = 0; +- tp->bytes_acked = 0; +- tp->bytes_received = 0; +- tp->bytes_retrans = 0; +- tp->data_segs_in = 0; +- tp->data_segs_out = 0; + tp->duplicate_sack[0].start_seq = 0; + tp->duplicate_sack[0].end_seq = 0; + tp->dsack_dups = 0; +@@ -2669,8 +2781,6 @@ int tcp_disconnect(struct sock *sk, int flags) + tp->sacked_out = 0; + tp->tlp_high_seq = 0; + tp->last_oow_ack_time = 0; +- /* There's a bubble in the pipe until at least the first ACK. */ +- tp->app_limited = ~0U; + tp->rack.mstamp = 0; + tp->rack.advanced = 0; + tp->rack.reo_wnd_steps = 1; +@@ -2704,7 +2814,7 @@ int tcp_disconnect(struct sock *sk, int flags) + static inline bool tcp_can_repair_sock(const struct sock *sk) + { + return ns_capable(sock_net(sk)->user_ns, CAP_NET_ADMIN) && +- (sk->sk_state != TCP_LISTEN); ++ (sk->sk_state != TCP_LISTEN) && !sock_flag(sk, SOCK_MPTCP); + } + + static int tcp_repair_set_window(struct tcp_sock *tp, char __user *optbuf, int len) +@@ -2735,6 +2845,7 @@ static int tcp_repair_set_window(struct tcp_sock *tp, char __user *optbuf, int l + + tp->rcv_wnd = opt.rcv_wnd; + tp->rcv_wup = opt.rcv_wup; ++ tp->rcv_right_edge = tp->rcv_wup + tp->rcv_wnd; + + return 0; + } +@@ -2873,6 +2984,61 @@ static int do_tcp_setsockopt(struct sock *sk, int level, + + return tcp_fastopen_reset_cipher(net, sk, key, backup_key); + } ++#ifdef CONFIG_MPTCP ++ case MPTCP_SCHEDULER: { ++ char name[MPTCP_SCHED_NAME_MAX]; ++ ++ if (optlen < 1) ++ return -EINVAL; ++ ++ /* Cannot be used if MPTCP is not used or we already have ++ * established an MPTCP-connection. ++ */ ++ if (mptcp_init_failed || !sysctl_mptcp_enabled || ++ sk->sk_state != TCP_CLOSE) ++ return -EPERM; ++ ++ val = strncpy_from_user(name, optval, ++ min_t(long, MPTCP_SCHED_NAME_MAX - 1, ++ optlen)); ++ ++ if (val < 0) ++ return -EFAULT; ++ name[val] = 0; ++ ++ lock_sock(sk); ++ err = mptcp_set_scheduler(sk, name); ++ release_sock(sk); ++ return err; ++ } ++ ++ case MPTCP_PATH_MANAGER: { ++ char name[MPTCP_PM_NAME_MAX]; ++ ++ if (optlen < 1) ++ return -EINVAL; ++ ++ /* Cannot be used if MPTCP is not used or we already have ++ * established an MPTCP-connection. ++ */ ++ if (mptcp_init_failed || !sysctl_mptcp_enabled || ++ sk->sk_state != TCP_CLOSE) ++ return -EPERM; ++ ++ val = strncpy_from_user(name, optval, ++ min_t(long, MPTCP_PM_NAME_MAX - 1, ++ optlen)); ++ ++ if (val < 0) ++ return -EFAULT; ++ name[val] = 0; ++ ++ lock_sock(sk); ++ err = mptcp_set_path_manager(sk, name); ++ release_sock(sk); ++ return err; ++ } ++#endif + default: + /* fallthru */ + break; +@@ -3062,6 +3228,12 @@ static int do_tcp_setsockopt(struct sock *sk, int level, + break; + + case TCP_DEFER_ACCEPT: ++ /* An established MPTCP-connection (mptcp(tp) only returns true ++ * if the socket is established) should not use DEFER on new ++ * subflows. ++ */ ++ if (mptcp(tp)) ++ break; + /* Translate value in seconds to number of retransmits */ + icsk->icsk_accept_queue.rskq_defer_accept = + secs_to_retrans(val, TCP_TIMEOUT_INIT / HZ, +@@ -3089,7 +3261,7 @@ static int do_tcp_setsockopt(struct sock *sk, int level, + (TCPF_ESTABLISHED | TCPF_CLOSE_WAIT) && + inet_csk_ack_scheduled(sk)) { + icsk->icsk_ack.pending |= ICSK_ACK_PUSHED; +- tcp_cleanup_rbuf(sk, 1); ++ tp->ops->cleanup_rbuf(sk, 1); + if (!(val & 1)) + inet_csk_enter_pingpong_mode(sk); + } +@@ -3099,7 +3271,10 @@ static int do_tcp_setsockopt(struct sock *sk, int level, + #ifdef CONFIG_TCP_MD5SIG + case TCP_MD5SIG: + case TCP_MD5SIG_EXT: +- err = tp->af_specific->md5_parse(sk, optname, optval, optlen); ++ if (!sock_flag(sk, SOCK_MPTCP)) ++ err = tp->af_specific->md5_parse(sk, optname, optval, optlen); ++ else ++ err = -EINVAL; + break; + #endif + case TCP_USER_TIMEOUT: +@@ -3155,6 +3330,32 @@ static int do_tcp_setsockopt(struct sock *sk, int level, + tp->notsent_lowat = val; + sk->sk_write_space(sk); + break; ++#ifdef CONFIG_MPTCP ++ case MPTCP_ENABLED: ++ if (mptcp_init_failed || !sysctl_mptcp_enabled || ++ sk->sk_state != TCP_CLOSE ++#ifdef CONFIG_TCP_MD5SIG ++ || rcu_access_pointer(tp->md5sig_info) ++#endif ++ ) { ++ err = -EPERM; ++ break; ++ } ++ ++ if (val) ++ mptcp_enable_sock(sk); ++ else ++ mptcp_disable_sock(sk); ++ break; ++ case MPTCP_INFO: ++ if (mptcp_init_failed || !sysctl_mptcp_enabled) { ++ err = -EPERM; ++ break; ++ } ++ ++ tp->record_master_info = !!(val & MPTCP_INFO_FLAG_SAVE_MASTER); ++ break; ++#endif + case TCP_INQ: + if (val > 1 || val < 0) + err = -EINVAL; +@@ -3219,7 +3420,7 @@ static void tcp_get_info_chrono_stats(const struct tcp_sock *tp, + } + + /* Return information about state of tcp endpoint in API format. */ +-void tcp_get_info(struct sock *sk, struct tcp_info *info) ++void tcp_get_info(struct sock *sk, struct tcp_info *info, bool no_lock) + { + const struct tcp_sock *tp = tcp_sk(sk); /* iff sk_type == SOCK_STREAM */ + const struct inet_connection_sock *icsk = inet_csk(sk); +@@ -3256,7 +3457,8 @@ void tcp_get_info(struct sock *sk, struct tcp_info *info) + return; + } + +- slow = lock_sock_fast(sk); ++ if (!no_lock) ++ slow = lock_sock_fast(sk); + + info->tcpi_ca_state = icsk->icsk_ca_state; + info->tcpi_retransmits = icsk->icsk_retransmits; +@@ -3332,7 +3534,9 @@ void tcp_get_info(struct sock *sk, struct tcp_info *info) + info->tcpi_reord_seen = tp->reord_seen; + info->tcpi_rcv_ooopack = tp->rcv_ooopack; + info->tcpi_snd_wnd = tp->snd_wnd; +- unlock_sock_fast(sk, slow); ++ ++ if (!no_lock) ++ unlock_sock_fast(sk, slow); + } + EXPORT_SYMBOL_GPL(tcp_get_info); + +@@ -3479,7 +3683,7 @@ static int do_tcp_getsockopt(struct sock *sk, int level, + if (get_user(len, optlen)) + return -EFAULT; + +- tcp_get_info(sk, &info); ++ tcp_get_info(sk, &info, false); + + len = min_t(unsigned int, len, sizeof(info)); + if (put_user(len, optlen)) +@@ -3668,6 +3872,87 @@ static int do_tcp_getsockopt(struct sock *sk, int level, + } + return 0; + } ++#ifdef CONFIG_MPTCP ++ case MPTCP_SCHEDULER: ++ if (get_user(len, optlen)) ++ return -EFAULT; ++ len = min_t(unsigned int, len, MPTCP_SCHED_NAME_MAX); ++ if (put_user(len, optlen)) ++ return -EFAULT; ++ ++ lock_sock(sk); ++ if (mptcp(tcp_sk(sk))) { ++ struct mptcp_cb *mpcb = tcp_sk(mptcp_meta_sk(sk))->mpcb; ++ ++ if (copy_to_user(optval, mpcb->sched_ops->name, len)) { ++ release_sock(sk); ++ return -EFAULT; ++ } ++ } else { ++ if (copy_to_user(optval, tcp_sk(sk)->mptcp_sched_name, ++ len)) { ++ release_sock(sk); ++ return -EFAULT; ++ } ++ } ++ release_sock(sk); ++ return 0; ++ ++ case MPTCP_PATH_MANAGER: ++ if (get_user(len, optlen)) ++ return -EFAULT; ++ len = min_t(unsigned int, len, MPTCP_PM_NAME_MAX); ++ if (put_user(len, optlen)) ++ return -EFAULT; ++ ++ lock_sock(sk); ++ if (mptcp(tcp_sk(sk))) { ++ struct mptcp_cb *mpcb = tcp_sk(mptcp_meta_sk(sk))->mpcb; ++ ++ if (copy_to_user(optval, mpcb->pm_ops->name, len)) { ++ release_sock(sk); ++ return -EFAULT; ++ } ++ } else { ++ if (copy_to_user(optval, tcp_sk(sk)->mptcp_pm_name, ++ len)) { ++ release_sock(sk); ++ return -EFAULT; ++ } ++ } ++ release_sock(sk); ++ return 0; ++ ++ case MPTCP_ENABLED: ++ if (sk->sk_state != TCP_SYN_SENT) ++ val = mptcp(tp) ? 1 : 0; ++ else ++ val = sock_flag(sk, SOCK_MPTCP) ? 1 : 0; ++ break; ++ case MPTCP_INFO: ++ { ++ int ret; ++ ++ if (!mptcp(tp)) ++ return -EINVAL; ++ ++ if (get_user(len, optlen)) ++ return -EFAULT; ++ ++ len = min_t(unsigned int, len, sizeof(struct mptcp_info)); ++ ++ lock_sock(sk); ++ ret = mptcp_get_info(sk, optval, len); ++ release_sock(sk); ++ ++ if (ret) ++ return ret; ++ ++ if (put_user(len, optlen)) ++ return -EFAULT; ++ return 0; ++ } ++#endif + #ifdef CONFIG_MMU + case TCP_ZEROCOPY_RECEIVE: { + struct tcp_zerocopy_receive zc; +@@ -3873,7 +4158,9 @@ void tcp_done(struct sock *sk) + if (sk->sk_state == TCP_SYN_SENT || sk->sk_state == TCP_SYN_RECV) + TCP_INC_STATS(sock_net(sk), TCP_MIB_ATTEMPTFAILS); + ++ WARN_ON(sk->sk_state == TCP_CLOSE); + tcp_set_state(sk, TCP_CLOSE); ++ + tcp_clear_xmit_timers(sk); + if (req) + reqsk_fastopen_remove(sk, req, false); +@@ -3889,6 +4176,8 @@ void tcp_done(struct sock *sk) + + int tcp_abort(struct sock *sk, int err) + { ++ struct sock *meta_sk = mptcp(tcp_sk(sk)) ? mptcp_meta_sk(sk) : sk; ++ + if (!sk_fullsock(sk)) { + if (sk->sk_state == TCP_NEW_SYN_RECV) { + struct request_sock *req = inet_reqsk(sk); +@@ -3902,7 +4191,7 @@ int tcp_abort(struct sock *sk, int err) + } + + /* Don't race with userspace socket closes such as tcp_close. */ +- lock_sock(sk); ++ lock_sock(meta_sk); + + if (sk->sk_state == TCP_LISTEN) { + tcp_set_state(sk, TCP_CLOSE); +@@ -3911,7 +4200,7 @@ int tcp_abort(struct sock *sk, int err) + + /* Don't race with BH socket closes such as inet_csk_listen_stop. */ + local_bh_disable(); +- bh_lock_sock(sk); ++ bh_lock_sock(meta_sk); + + if (!sock_flag(sk, SOCK_DEAD)) { + sk->sk_err = err; +@@ -3919,14 +4208,14 @@ int tcp_abort(struct sock *sk, int err) + smp_wmb(); + sk->sk_error_report(sk); + if (tcp_need_reset(sk->sk_state)) +- tcp_send_active_reset(sk, GFP_ATOMIC); ++ tcp_sk(sk)->ops->send_active_reset(sk, GFP_ATOMIC); + tcp_done(sk); + } + +- bh_unlock_sock(sk); ++ bh_unlock_sock(meta_sk); + local_bh_enable(); + tcp_write_queue_purge(sk); +- release_sock(sk); ++ release_sock(meta_sk); + return 0; + } + EXPORT_SYMBOL_GPL(tcp_abort); +diff --git a/net/ipv4/tcp_cong.c b/net/ipv4/tcp_cong.c +index 6d5600889dcf..247c1168b6a5 100644 +--- a/net/ipv4/tcp_cong.c ++++ b/net/ipv4/tcp_cong.c +@@ -337,13 +337,19 @@ int tcp_set_allowed_congestion_control(char *val) + return ret; + } + ++int tcp_set_congestion_control(struct sock *sk, const char *name, bool load, ++ bool reinit, bool cap_net_admin) ++{ ++ return tcp_sk(sk)->ops->set_cong_ctrl(sk, name, load, reinit, cap_net_admin); ++} ++ + /* Change congestion control for socket. If load is false, then it is the + * responsibility of the caller to call tcp_init_congestion_control or + * tcp_reinit_congestion_control (if the current congestion control was + * already initialized. + */ +-int tcp_set_congestion_control(struct sock *sk, const char *name, bool load, +- bool reinit, bool cap_net_admin) ++int __tcp_set_congestion_control(struct sock *sk, const char *name, bool load, ++ bool reinit, bool cap_net_admin) + { + struct inet_connection_sock *icsk = inet_csk(sk); + const struct tcp_congestion_ops *ca; +diff --git a/net/ipv4/tcp_diag.c b/net/ipv4/tcp_diag.c +index 549506162dde..e5a530e0b1c5 100644 +--- a/net/ipv4/tcp_diag.c ++++ b/net/ipv4/tcp_diag.c +@@ -31,7 +31,7 @@ static void tcp_diag_get_info(struct sock *sk, struct inet_diag_msg *r, + r->idiag_wqueue = READ_ONCE(tp->write_seq) - tp->snd_una; + } + if (info) +- tcp_get_info(sk, info); ++ tcp_get_info(sk, info, false); + } + + #ifdef CONFIG_TCP_MD5SIG +diff --git a/net/ipv4/tcp_fastopen.c b/net/ipv4/tcp_fastopen.c +index a5ec77a5ad6f..f9fb4a268b9b 100644 +--- a/net/ipv4/tcp_fastopen.c ++++ b/net/ipv4/tcp_fastopen.c +@@ -9,6 +9,7 @@ + #include + #include + #include ++#include + + void tcp_fastopen_init_key_once(struct net *net) + { +@@ -136,8 +137,6 @@ static bool __tcp_fastopen_cookie_gen_cipher(struct request_sock *req, + const siphash_key_t *key, + struct tcp_fastopen_cookie *foc) + { +- BUILD_BUG_ON(TCP_FASTOPEN_COOKIE_SIZE != sizeof(u64)); +- + if (req->rsk_ops->family == AF_INET) { + const struct iphdr *iph = ip_hdr(syn); + +@@ -258,8 +257,9 @@ static struct sock *tcp_fastopen_create_child(struct sock *sk, + { + struct tcp_sock *tp; + struct request_sock_queue *queue = &inet_csk(sk)->icsk_accept_queue; +- struct sock *child; ++ struct sock *child, *meta_sk; + bool own_req; ++ int ret; + + child = inet_csk(sk)->icsk_af_ops->syn_recv_sock(sk, skb, req, NULL, + NULL, &own_req); +@@ -294,15 +294,27 @@ static struct sock *tcp_fastopen_create_child(struct sock *sk, + + refcount_set(&req->rsk_refcnt, 2); + +- /* Now finish processing the fastopen child socket. */ +- tcp_init_transfer(child, BPF_SOCK_OPS_PASSIVE_ESTABLISHED_CB); +- + tp->rcv_nxt = TCP_SKB_CB(skb)->seq + 1; + + tcp_fastopen_add_skb(child, skb); + + tcp_rsk(req)->rcv_nxt = tp->rcv_nxt; + tp->rcv_wup = tp->rcv_nxt; ++ tp->rcv_right_edge = tp->rcv_wup + tp->rcv_wnd; ++ ++ meta_sk = child; ++ ret = mptcp_check_req_fastopen(meta_sk, req); ++ if (ret < 0) ++ return NULL; ++ ++ if (ret == 0) { ++ child = tcp_sk(meta_sk)->mpcb->master_sk; ++ tp = tcp_sk(child); ++ } ++ ++ /* Now finish processing the fastopen child socket. */ ++ tcp_init_transfer(child, BPF_SOCK_OPS_PASSIVE_ESTABLISHED_CB); ++ + /* tcp_conn_request() is sending the SYNACK, + * and queues the child into listener accept queue. + */ +diff --git a/net/ipv4/tcp_input.c b/net/ipv4/tcp_input.c +index c0fcfa296468..dae2ce9656b8 100644 +--- a/net/ipv4/tcp_input.c ++++ b/net/ipv4/tcp_input.c +@@ -76,35 +76,15 @@ + #include + #include + #include ++#include ++#include ++#include + #include + #include + #include + + int sysctl_tcp_max_orphans __read_mostly = NR_FILE; + +-#define FLAG_DATA 0x01 /* Incoming frame contained data. */ +-#define FLAG_WIN_UPDATE 0x02 /* Incoming ACK was a window update. */ +-#define FLAG_DATA_ACKED 0x04 /* This ACK acknowledged new data. */ +-#define FLAG_RETRANS_DATA_ACKED 0x08 /* "" "" some of which was retransmitted. */ +-#define FLAG_SYN_ACKED 0x10 /* This ACK acknowledged SYN. */ +-#define FLAG_DATA_SACKED 0x20 /* New SACK. */ +-#define FLAG_ECE 0x40 /* ECE in this ACK */ +-#define FLAG_LOST_RETRANS 0x80 /* This ACK marks some retransmission lost */ +-#define FLAG_SLOWPATH 0x100 /* Do not skip RFC checks for window update.*/ +-#define FLAG_ORIG_SACK_ACKED 0x200 /* Never retransmitted data are (s)acked */ +-#define FLAG_SND_UNA_ADVANCED 0x400 /* Snd_una was changed (!= FLAG_DATA_ACKED) */ +-#define FLAG_DSACKING_ACK 0x800 /* SACK blocks contained D-SACK info */ +-#define FLAG_SET_XMIT_TIMER 0x1000 /* Set TLP or RTO timer */ +-#define FLAG_SACK_RENEGING 0x2000 /* snd_una advanced to a sacked seq */ +-#define FLAG_UPDATE_TS_RECENT 0x4000 /* tcp_replace_ts_recent() */ +-#define FLAG_NO_CHALLENGE_ACK 0x8000 /* do not call tcp_send_challenge_ack() */ +-#define FLAG_ACK_MAYBE_DELAYED 0x10000 /* Likely a delayed ACK */ +- +-#define FLAG_ACKED (FLAG_DATA_ACKED|FLAG_SYN_ACKED) +-#define FLAG_NOT_DUP (FLAG_DATA|FLAG_WIN_UPDATE|FLAG_ACKED) +-#define FLAG_CA_ALERT (FLAG_DATA_SACKED|FLAG_ECE|FLAG_DSACKING_ACK) +-#define FLAG_FORWARD_PROGRESS (FLAG_ACKED|FLAG_DATA_SACKED) +- + #define TCP_REMNANT (TCP_FLAG_FIN|TCP_FLAG_URG|TCP_FLAG_SYN|TCP_FLAG_PSH) + #define TCP_HP_BITS (~(TCP_RESERVED_BITS|TCP_FLAG_PSH)) + +@@ -349,8 +329,12 @@ static void tcp_sndbuf_expand(struct sock *sk) + per_mss = roundup_pow_of_two(per_mss) + + SKB_DATA_ALIGN(sizeof(struct sk_buff)); + +- nr_segs = max_t(u32, TCP_INIT_CWND, tp->snd_cwnd); +- nr_segs = max_t(u32, nr_segs, tp->reordering + 1); ++ if (mptcp(tp)) { ++ nr_segs = mptcp_check_snd_buf(tp); ++ } else { ++ nr_segs = max_t(u32, TCP_INIT_CWND, tp->snd_cwnd); ++ nr_segs = max_t(u32, nr_segs, tp->reordering + 1); ++ } + + /* Fast Recovery (RFC 5681 3.2) : + * Cubic needs 1.7 factor, rounded to 2 to include +@@ -359,9 +343,17 @@ static void tcp_sndbuf_expand(struct sock *sk) + sndmem = ca_ops->sndbuf_expand ? ca_ops->sndbuf_expand(sk) : 2; + sndmem *= nr_segs * per_mss; + +- if (sk->sk_sndbuf < sndmem) ++ /* MPTCP: after this sndmem is the new contribution of the ++ * current subflow to the aggregated sndbuf */ ++ if (sk->sk_sndbuf < sndmem) { ++ int old_sndbuf = sk->sk_sndbuf; + WRITE_ONCE(sk->sk_sndbuf, + min(sndmem, sock_net(sk)->ipv4.sysctl_tcp_wmem[2])); ++ /* MPTCP: ok, the subflow sndbuf has grown, reflect ++ * this in the aggregate buffer.*/ ++ if (mptcp(tp) && old_sndbuf != sk->sk_sndbuf) ++ mptcp_update_sndbuf(tp); ++ } + } + + /* 2. Tuning advertised window (window_clamp, rcv_ssthresh) +@@ -410,9 +402,14 @@ static int __tcp_grow_window(const struct sock *sk, const struct sk_buff *skb) + static void tcp_grow_window(struct sock *sk, const struct sk_buff *skb) + { + struct tcp_sock *tp = tcp_sk(sk); ++ struct sock *meta_sk = mptcp(tp) ? mptcp_meta_sk(sk) : sk; ++ struct tcp_sock *meta_tp = tcp_sk(meta_sk); + int room; + +- room = min_t(int, tp->window_clamp, tcp_space(sk)) - tp->rcv_ssthresh; ++ if (is_meta_sk(sk)) ++ return; ++ ++ room = min_t(int, meta_tp->window_clamp, tcp_space(meta_sk)) - meta_tp->rcv_ssthresh; + + /* Check #1 */ + if (room > 0 && !tcp_under_memory_pressure(sk)) { +@@ -422,13 +419,13 @@ static void tcp_grow_window(struct sock *sk, const struct sk_buff *skb) + * will fit to rcvbuf in future. + */ + if (tcp_win_from_space(sk, skb->truesize) <= skb->len) +- incr = 2 * tp->advmss; ++ incr = 2 * meta_tp->advmss; + else +- incr = __tcp_grow_window(sk, skb); ++ incr = __tcp_grow_window(meta_sk, skb); + + if (incr) { + incr = max_t(int, incr, 2 * skb->len); +- tp->rcv_ssthresh += min(room, incr); ++ meta_tp->rcv_ssthresh += min(room, incr); + inet_csk(sk)->icsk_ack.quick |= 1; + } + } +@@ -612,7 +609,10 @@ void tcp_rcv_space_adjust(struct sock *sk) + + tcp_mstamp_refresh(tp); + time = tcp_stamp_us_delta(tp->tcp_mstamp, tp->rcvq_space.time); +- if (time < (tp->rcv_rtt_est.rtt_us >> 3) || tp->rcv_rtt_est.rtt_us == 0) ++ if (mptcp(tp)) { ++ if (mptcp_check_rtt(tp, time)) ++ return; ++ } else if (time < (tp->rcv_rtt_est.rtt_us >> 3) || tp->rcv_rtt_est.rtt_us == 0) + return; + + /* Number of bytes copied to user in last RTT */ +@@ -835,7 +835,7 @@ static void tcp_update_pacing_rate(struct sock *sk) + /* Calculate rto without backoff. This is the second half of Van Jacobson's + * routine referred to above. + */ +-static void tcp_set_rto(struct sock *sk) ++void tcp_set_rto(struct sock *sk) + { + const struct tcp_sock *tp = tcp_sk(sk); + /* Old crap is replaced with new one. 8) +@@ -1407,6 +1407,13 @@ static struct sk_buff *tcp_shift_skb_data(struct sock *sk, struct sk_buff *skb, + int len; + int in_sack; + ++ /* For MPTCP we cannot shift skb-data and remove one skb from the ++ * send-queue, because this will make us loose the DSS-option (which ++ * is stored in TCP_SKB_CB(skb)->dss) of the skb we are removing. ++ */ ++ if (mptcp(tp)) ++ goto fallback; ++ + /* Normally R but no L won't result in plain S */ + if (!dup_sack && + (TCP_SKB_CB(skb)->sacked & (TCPCB_LOST|TCPCB_SACKED_RETRANS)) == TCPCB_SACKED_RETRANS) +@@ -2962,7 +2969,7 @@ static bool tcp_ack_update_rtt(struct sock *sk, const int flag, + */ + tcp_update_rtt_min(sk, ca_rtt_us, flag); + tcp_rtt_estimator(sk, seq_rtt_us); +- tcp_set_rto(sk); ++ tp->ops->set_rto(sk); + + /* RFC6298: only reset backoff on valid RTT measurement. */ + inet_csk(sk)->icsk_backoff = 0; +@@ -3030,7 +3037,7 @@ static void tcp_set_xmit_timer(struct sock *sk) + } + + /* If we get here, the whole TSO packet has not been acked. */ +-static u32 tcp_tso_acked(struct sock *sk, struct sk_buff *skb) ++u32 tcp_tso_acked(struct sock *sk, struct sk_buff *skb) + { + struct tcp_sock *tp = tcp_sk(sk); + u32 packets_acked; +@@ -3050,8 +3057,7 @@ static u32 tcp_tso_acked(struct sock *sk, struct sk_buff *skb) + return packets_acked; + } + +-static void tcp_ack_tstamp(struct sock *sk, struct sk_buff *skb, +- u32 prior_snd_una) ++void tcp_ack_tstamp(struct sock *sk, struct sk_buff *skb, u32 prior_snd_una) + { + const struct skb_shared_info *shinfo; + +@@ -3156,6 +3162,8 @@ static int tcp_clean_rtx_queue(struct sock *sk, u32 prior_fack, + */ + if (likely(!(scb->tcp_flags & TCPHDR_SYN))) { + flag |= FLAG_DATA_ACKED; ++ if (mptcp(tp) && mptcp_is_data_seq(skb)) ++ flag |= MPTCP_FLAG_DATA_ACKED; + } else { + flag |= FLAG_SYN_ACKED; + tp->retrans_stamp = 0; +@@ -3276,7 +3284,7 @@ static int tcp_clean_rtx_queue(struct sock *sk, u32 prior_fack, + return flag; + } + +-static void tcp_ack_probe(struct sock *sk) ++void tcp_ack_probe(struct sock *sk) + { + struct inet_connection_sock *icsk = inet_csk(sk); + struct sk_buff *head = tcp_send_head(sk); +@@ -3350,9 +3358,8 @@ static void tcp_cong_control(struct sock *sk, u32 ack, u32 acked_sacked, + /* Check that window update is acceptable. + * The function assumes that snd_una<=ack<=snd_next. + */ +-static inline bool tcp_may_update_window(const struct tcp_sock *tp, +- const u32 ack, const u32 ack_seq, +- const u32 nwin) ++bool tcp_may_update_window(const struct tcp_sock *tp, const u32 ack, ++ const u32 ack_seq, const u32 nwin) + { + return after(ack, tp->snd_una) || + after(ack_seq, tp->snd_wl1) || +@@ -3590,7 +3597,7 @@ static u32 tcp_newly_delivered(struct sock *sk, u32 prior_delivered, int flag) + } + + /* This routine deals with incoming acks, but not outgoing ones. */ +-static int tcp_ack(struct sock *sk, const struct sk_buff *skb, int flag) ++static int tcp_ack(struct sock *sk, struct sk_buff *skb, int flag) + { + struct inet_connection_sock *icsk = inet_csk(sk); + struct tcp_sock *tp = tcp_sk(sk); +@@ -3713,6 +3720,16 @@ static int tcp_ack(struct sock *sk, const struct sk_buff *skb, int flag) + + tcp_rack_update_reo_wnd(sk, &rs); + ++ if (mptcp(tp)) { ++ if (mptcp_fallback_infinite(sk, flag)) { ++ pr_debug("%s resetting flow\n", __func__); ++ mptcp_send_reset(sk); ++ return -1; ++ } ++ ++ mptcp_clean_rtx_infinite(skb, sk); ++ } ++ + if (tp->tlp_high_seq) + tcp_process_tlp_ack(sk, ack, flag); + +@@ -3856,8 +3873,10 @@ static u16 tcp_parse_mss_option(const struct tcphdr *th, u16 user_mss) + */ + void tcp_parse_options(const struct net *net, + const struct sk_buff *skb, +- struct tcp_options_received *opt_rx, int estab, +- struct tcp_fastopen_cookie *foc) ++ struct tcp_options_received *opt_rx, ++ struct mptcp_options_received *mopt, ++ int estab, struct tcp_fastopen_cookie *foc, ++ struct tcp_sock *tp) + { + const unsigned char *ptr; + const struct tcphdr *th = tcp_hdr(skb); +@@ -3943,6 +3962,10 @@ void tcp_parse_options(const struct net *net, + */ + break; + #endif ++ case TCPOPT_MPTCP: ++ mptcp_parse_options(ptr - 2, opsize, mopt, skb, tp); ++ break; ++ + case TCPOPT_FASTOPEN: + tcp_parse_fastopen_option( + opsize - TCPOLEN_FASTOPEN_BASE, +@@ -4010,7 +4033,9 @@ static bool tcp_fast_parse_options(const struct net *net, + return true; + } + +- tcp_parse_options(net, skb, &tp->rx_opt, 1, NULL); ++ tcp_parse_options(net, skb, &tp->rx_opt, ++ mptcp(tp) ? &tp->mptcp->rx_opt : NULL, 1, NULL, tp); ++ + if (tp->rx_opt.saw_tstamp && tp->rx_opt.rcv_tsecr) + tp->rx_opt.rcv_tsecr -= tp->tsoffset; + +@@ -4120,7 +4145,7 @@ static inline bool tcp_paws_discard(const struct sock *sk, + static inline bool tcp_sequence(const struct tcp_sock *tp, u32 seq, u32 end_seq) + { + return !before(end_seq, tp->rcv_wup) && +- !after(seq, tp->rcv_nxt + tcp_receive_window(tp)); ++ !after(seq, tp->rcv_nxt + tcp_receive_window_no_shrink(tp)); + } + + /* When we get a reset we do this. */ +@@ -4169,6 +4194,11 @@ void tcp_fin(struct sock *sk) + { + struct tcp_sock *tp = tcp_sk(sk); + ++ if (is_meta_sk(sk)) { ++ mptcp_fin(sk); ++ return; ++ } ++ + inet_csk_schedule_ack(sk); + + sk->sk_shutdown |= RCV_SHUTDOWN; +@@ -4179,6 +4209,10 @@ void tcp_fin(struct sock *sk) + case TCP_ESTABLISHED: + /* Move to CLOSE_WAIT */ + tcp_set_state(sk, TCP_CLOSE_WAIT); ++ ++ if (mptcp(tp)) ++ mptcp_sub_close_passive(sk); ++ + inet_csk_enter_pingpong_mode(sk); + break; + +@@ -4201,9 +4235,16 @@ void tcp_fin(struct sock *sk) + tcp_set_state(sk, TCP_CLOSING); + break; + case TCP_FIN_WAIT2: ++ if (mptcp(tp)) { ++ /* The socket will get closed by mptcp_data_ready. ++ * We first have to process all data-sequences. ++ */ ++ tp->close_it = 1; ++ break; ++ } + /* Received a FIN -- send ACK and enter TIME_WAIT. */ + tcp_send_ack(sk); +- tcp_time_wait(sk, TCP_TIME_WAIT, 0); ++ tp->ops->time_wait(sk, TCP_TIME_WAIT, 0); + break; + default: + /* Only TCP_LISTEN and TCP_CLOSE are left, in these +@@ -4225,6 +4266,10 @@ void tcp_fin(struct sock *sk) + if (!sock_flag(sk, SOCK_DEAD)) { + sk->sk_state_change(sk); + ++ /* Don't wake up MPTCP-subflows */ ++ if (mptcp(tp)) ++ return; ++ + /* Do not send POLL_HUP for half duplex close. */ + if (sk->sk_shutdown == SHUTDOWN_MASK || + sk->sk_state == TCP_CLOSE) +@@ -4439,6 +4484,9 @@ static bool tcp_try_coalesce(struct sock *sk, + + *fragstolen = false; + ++ if (mptcp(tcp_sk(sk)) && !is_meta_sk(sk)) ++ return false; ++ + /* Its possible this segment overlaps with prior segment in queue */ + if (TCP_SKB_CB(from)->seq != TCP_SKB_CB(to)->end_seq) + return false; +@@ -4493,7 +4541,7 @@ static void tcp_drop(struct sock *sk, struct sk_buff *skb) + /* This one checks to see if we can put data from the + * out_of_order queue into the receive_queue. + */ +-static void tcp_ofo_queue(struct sock *sk) ++void tcp_ofo_queue(struct sock *sk) + { + struct tcp_sock *tp = tcp_sk(sk); + __u32 dsack_high = tp->rcv_nxt; +@@ -4516,7 +4564,14 @@ static void tcp_ofo_queue(struct sock *sk) + p = rb_next(p); + rb_erase(&skb->rbnode, &tp->out_of_order_queue); + +- if (unlikely(!after(TCP_SKB_CB(skb)->end_seq, tp->rcv_nxt))) { ++ /* In case of MPTCP, the segment may be empty if it's a ++ * non-data DATA_FIN. (see beginning of tcp_data_queue) ++ * ++ * But this only holds true for subflows, not for the ++ * meta-socket. ++ */ ++ if (unlikely(!after(TCP_SKB_CB(skb)->end_seq, tp->rcv_nxt) && ++ (is_meta_sk(sk) || !mptcp(tp) || TCP_SKB_CB(skb)->end_seq != TCP_SKB_CB(skb)->seq))) { + tcp_drop(sk, skb); + continue; + } +@@ -4546,6 +4601,9 @@ static void tcp_ofo_queue(struct sock *sk) + static int tcp_try_rmem_schedule(struct sock *sk, struct sk_buff *skb, + unsigned int size) + { ++ if (mptcp(tcp_sk(sk))) ++ sk = mptcp_meta_sk(sk); ++ + if (atomic_read(&sk->sk_rmem_alloc) > sk->sk_rcvbuf || + !sk_rmem_schedule(sk, skb, size)) { + +@@ -4560,7 +4618,7 @@ static int tcp_try_rmem_schedule(struct sock *sk, struct sk_buff *skb, + return 0; + } + +-static void tcp_data_queue_ofo(struct sock *sk, struct sk_buff *skb) ++void tcp_data_queue_ofo(struct sock *sk, struct sk_buff *skb) + { + struct tcp_sock *tp = tcp_sk(sk); + struct rb_node **p, *parent; +@@ -4632,7 +4690,8 @@ static void tcp_data_queue_ofo(struct sock *sk, struct sk_buff *skb) + continue; + } + if (before(seq, TCP_SKB_CB(skb1)->end_seq)) { +- if (!after(end_seq, TCP_SKB_CB(skb1)->end_seq)) { ++ if (!after(end_seq, TCP_SKB_CB(skb1)->end_seq) && ++ (is_meta_sk(sk) || !mptcp(tp) || end_seq != seq)) { + /* All the bits are present. Drop. */ + NET_INC_STATS(sock_net(sk), + LINUX_MIB_TCPOFOMERGE); +@@ -4679,6 +4738,11 @@ static void tcp_data_queue_ofo(struct sock *sk, struct sk_buff *skb) + end_seq); + break; + } ++ /* MPTCP allows non-data data-fin to be in the ofo-queue */ ++ if (mptcp(tp) && !is_meta_sk(sk) && TCP_SKB_CB(skb1)->seq == TCP_SKB_CB(skb1)->end_seq) { ++ skb = skb1; ++ continue; ++ } + rb_erase(&skb1->rbnode, &tp->out_of_order_queue); + tcp_dsack_extend(sk, TCP_SKB_CB(skb1)->seq, + TCP_SKB_CB(skb1)->end_seq); +@@ -4690,7 +4754,7 @@ static void tcp_data_queue_ofo(struct sock *sk, struct sk_buff *skb) + tp->ooo_last_skb = skb; + + add_sack: +- if (tcp_is_sack(tp)) ++ if (tcp_is_sack(tp) && seq != end_seq) + tcp_sack_new_ofo_skb(sk, seq, end_seq); + end: + if (skb) { +@@ -4704,8 +4768,8 @@ static void tcp_data_queue_ofo(struct sock *sk, struct sk_buff *skb) + } + } + +-static int __must_check tcp_queue_rcv(struct sock *sk, struct sk_buff *skb, +- bool *fragstolen) ++int __must_check tcp_queue_rcv(struct sock *sk, struct sk_buff *skb, ++ bool *fragstolen) + { + int eaten; + struct sk_buff *tail = skb_peek_tail(&sk->sk_receive_queue); +@@ -4780,7 +4844,8 @@ void tcp_data_ready(struct sock *sk) + + if (avail < sk->sk_rcvlowat && !tcp_rmem_pressure(sk) && + !sock_flag(sk, SOCK_DONE) && +- tcp_receive_window(tp) > inet_csk(sk)->icsk_ack.rcv_mss) ++ tcp_receive_window_now(tp) > inet_csk(sk)->icsk_ack.rcv_mss && ++ !mptcp(tp)) + return; + + sk->sk_data_ready(sk); +@@ -4792,10 +4857,14 @@ static void tcp_data_queue(struct sock *sk, struct sk_buff *skb) + bool fragstolen; + int eaten; + +- if (TCP_SKB_CB(skb)->seq == TCP_SKB_CB(skb)->end_seq) { ++ /* If no data is present, but a data_fin is in the options, we still ++ * have to call mptcp_queue_skb later on. */ ++ if (TCP_SKB_CB(skb)->seq == TCP_SKB_CB(skb)->end_seq && ++ !(mptcp(tp) && mptcp_is_data_fin(skb))) { + __kfree_skb(skb); + return; + } ++ + skb_dst_drop(skb); + __skb_pull(skb, tcp_hdr(skb)->doff * 4); + +@@ -4806,7 +4875,7 @@ static void tcp_data_queue(struct sock *sk, struct sk_buff *skb) + * Out of sequence packets to the out_of_order_queue. + */ + if (TCP_SKB_CB(skb)->seq == tp->rcv_nxt) { +- if (tcp_receive_window(tp) == 0) { ++ if (tcp_receive_window_no_shrink(tp) == 0) { + NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPZEROWINDOWDROP); + goto out_of_window; + } +@@ -4822,7 +4891,7 @@ static void tcp_data_queue(struct sock *sk, struct sk_buff *skb) + } + + eaten = tcp_queue_rcv(sk, skb, &fragstolen); +- if (skb->len) ++ if (skb->len || mptcp_is_data_fin(skb)) + tcp_event_data_recv(sk, skb); + if (TCP_SKB_CB(skb)->tcp_flags & TCPHDR_FIN) + tcp_fin(sk); +@@ -4844,7 +4913,11 @@ static void tcp_data_queue(struct sock *sk, struct sk_buff *skb) + + if (eaten > 0) + kfree_skb_partial(skb, fragstolen); +- if (!sock_flag(sk, SOCK_DEAD)) ++ if (!sock_flag(sk, SOCK_DEAD) || mptcp(tp)) ++ /* MPTCP: we always have to call data_ready, because ++ * we may be about to receive a data-fin, which still ++ * must get queued. ++ */ + tcp_data_ready(sk); + return; + } +@@ -4864,7 +4937,8 @@ static void tcp_data_queue(struct sock *sk, struct sk_buff *skb) + } + + /* Out of window. F.e. zero window probe. */ +- if (!before(TCP_SKB_CB(skb)->seq, tp->rcv_nxt + tcp_receive_window(tp))) ++ if (!before(TCP_SKB_CB(skb)->seq, ++ tp->rcv_nxt + tcp_receive_window_no_shrink(tp))) + goto out_of_window; + + if (before(TCP_SKB_CB(skb)->seq, tp->rcv_nxt)) { +@@ -4874,7 +4948,7 @@ static void tcp_data_queue(struct sock *sk, struct sk_buff *skb) + /* If window is closed, drop tail of packet. But after + * remembering D-SACK for its head made in previous line. + */ +- if (!tcp_receive_window(tp)) { ++ if (!tcp_receive_window_no_shrink(tp)) { + NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPZEROWINDOWDROP); + goto out_of_window; + } +@@ -5187,7 +5261,7 @@ static int tcp_prune_queue(struct sock *sk) + return -1; + } + +-static bool tcp_should_expand_sndbuf(const struct sock *sk) ++bool tcp_should_expand_sndbuf(const struct sock *sk) + { + const struct tcp_sock *tp = tcp_sk(sk); + +@@ -5222,7 +5296,7 @@ static void tcp_new_space(struct sock *sk) + { + struct tcp_sock *tp = tcp_sk(sk); + +- if (tcp_should_expand_sndbuf(sk)) { ++ if (tp->ops->should_expand_sndbuf(sk)) { + tcp_sndbuf_expand(sk); + tp->snd_cwnd_stamp = tcp_jiffies32; + } +@@ -5236,10 +5310,11 @@ static void tcp_check_space(struct sock *sk) + sock_reset_flag(sk, SOCK_QUEUE_SHRUNK); + /* pairs with tcp_poll() */ + smp_mb(); +- if (sk->sk_socket && +- test_bit(SOCK_NOSPACE, &sk->sk_socket->flags)) { ++ if (mptcp(tcp_sk(sk)) || ++ (sk->sk_socket && ++ test_bit(SOCK_NOSPACE, &sk->sk_socket->flags))) { + tcp_new_space(sk); +- if (!test_bit(SOCK_NOSPACE, &sk->sk_socket->flags)) ++ if (sk->sk_socket && !test_bit(SOCK_NOSPACE, &sk->sk_socket->flags)) + tcp_chrono_stop(sk, TCP_CHRONO_SNDBUF_LIMITED); + } + } +@@ -5258,6 +5333,8 @@ static void __tcp_ack_snd_check(struct sock *sk, int ofo_possible) + { + struct tcp_sock *tp = tcp_sk(sk); + unsigned long rtt, delay; ++ struct sock *meta_sk = mptcp(tp) ? mptcp_meta_sk(sk) : sk; ++ struct tcp_sock *meta_tp = tcp_sk(meta_sk); + + /* More than one full frame received... */ + if (((tp->rcv_nxt - tp->rcv_wup) > inet_csk(sk)->icsk_ack.rcv_mss && +@@ -5266,8 +5343,8 @@ static void __tcp_ack_snd_check(struct sock *sk, int ofo_possible) + * If application uses SO_RCVLOWAT, we want send ack now if + * we have not received enough bytes to satisfy the condition. + */ +- (tp->rcv_nxt - tp->copied_seq < sk->sk_rcvlowat || +- __tcp_select_window(sk) >= tp->rcv_wnd)) || ++ (meta_tp->rcv_nxt - meta_tp->copied_seq < meta_sk->sk_rcvlowat || ++ tp->ops->__select_window(sk) >= tp->rcv_wnd)) || + /* We ACK each frame or... */ + tcp_in_quickack_mode(sk) || + /* Protocol state mandates a one-time immediate ACK */ +@@ -5402,6 +5479,10 @@ static void tcp_urg(struct sock *sk, struct sk_buff *skb, const struct tcphdr *t + { + struct tcp_sock *tp = tcp_sk(sk); + ++ /* MPTCP urgent data is not yet supported */ ++ if (mptcp(tp)) ++ return; ++ + /* Check if we get a new urgent pointer - normally not. */ + if (th->urg) + tcp_check_urg(sk, th); +@@ -5544,9 +5625,15 @@ static bool tcp_validate_incoming(struct sock *sk, struct sk_buff *skb, + goto discard; + } + ++ /* If valid: post process the received MPTCP options. */ ++ if (mptcp(tp) && mptcp_handle_options(sk, th, skb)) ++ goto discard; ++ + return true; + + discard: ++ if (mptcp(tp)) ++ mptcp_reset_mopt(tp); + tcp_drop(sk, skb); + return false; + } +@@ -5603,6 +5690,10 @@ void tcp_rcv_established(struct sock *sk, struct sk_buff *skb) + + tp->rx_opt.saw_tstamp = 0; + ++ /* MPTCP: force slowpath. */ ++ if (mptcp(tp)) ++ goto slow_path; ++ + /* pred_flags is 0xS?10 << 16 + snd_wnd + * if header_prediction is to be made + * 'S' will always be tp->tcp_header_len >> 2 +@@ -5777,7 +5868,7 @@ void tcp_init_transfer(struct sock *sk, int bpf_op) + + tcp_call_bpf(sk, bpf_op, 0, NULL); + tcp_init_congestion_control(sk); +- tcp_init_buffer_space(sk); ++ tcp_sk(sk)->ops->init_buffer_space(sk); + } + + void tcp_finish_connect(struct sock *sk, struct sk_buff *skb) +@@ -5814,17 +5905,24 @@ static bool tcp_rcv_fastopen_synack(struct sock *sk, struct sk_buff *synack, + struct tcp_fastopen_cookie *cookie) + { + struct tcp_sock *tp = tcp_sk(sk); +- struct sk_buff *data = tp->syn_data ? tcp_rtx_queue_head(sk) : NULL; ++ struct sk_buff *data = NULL; + u16 mss = tp->rx_opt.mss_clamp, try_exp = 0; + bool syn_drop = false; + ++ if (tp->syn_data) { ++ if (mptcp(tp)) ++ data = tcp_write_queue_head(mptcp_meta_sk(sk)); ++ else ++ data = tcp_rtx_queue_head(sk); ++ } ++ + if (mss == tp->rx_opt.user_mss) { + struct tcp_options_received opt; + + /* Get original SYNACK MSS value if user MSS sets mss_clamp */ + tcp_clear_options(&opt); + opt.user_mss = opt.mss_clamp = 0; +- tcp_parse_options(sock_net(sk), synack, &opt, 0, NULL); ++ tcp_parse_options(sock_net(sk), synack, &opt, NULL, 0, NULL, NULL); + mss = opt.mss_clamp; + } + +@@ -5848,7 +5946,11 @@ static bool tcp_rcv_fastopen_synack(struct sock *sk, struct sk_buff *synack, + + tcp_fastopen_cache_set(sk, mss, cookie, syn_drop, try_exp); + +- if (data) { /* Retransmit unacked data in SYN */ ++ /* In mptcp case, we do not rely on "retransmit", but instead on ++ * "transmit", because if fastopen data is not acked, the retransmission ++ * becomes the first MPTCP data (see mptcp_rcv_synsent_fastopen). ++ */ ++ if (data && !mptcp(tp)) { /* Retransmit unacked data in SYN */ + skb_rbtree_walk_from(data) { + if (__tcp_retransmit_skb(sk, data, 1)) + break; +@@ -5903,9 +6005,13 @@ static int tcp_rcv_synsent_state_process(struct sock *sk, struct sk_buff *skb, + struct tcp_sock *tp = tcp_sk(sk); + struct tcp_fastopen_cookie foc = { .len = -1 }; + int saved_clamp = tp->rx_opt.mss_clamp; ++ struct mptcp_options_received mopt; + bool fastopen_fail; + +- tcp_parse_options(sock_net(sk), skb, &tp->rx_opt, 0, &foc); ++ mptcp_init_mp_opt(&mopt); ++ ++ tcp_parse_options(sock_net(sk), skb, &tp->rx_opt, ++ mptcp(tp) ? &tp->mptcp->rx_opt : &mopt, 0, &foc, tp); + if (tp->rx_opt.saw_tstamp && tp->rx_opt.rcv_tsecr) + tp->rx_opt.rcv_tsecr -= tp->tsoffset; + +@@ -5966,11 +6072,41 @@ static int tcp_rcv_synsent_state_process(struct sock *sk, struct sk_buff *skb, + tcp_try_undo_spurious_syn(sk); + tcp_ack(sk, skb, FLAG_SLOWPATH); + ++ if (tp->request_mptcp || mptcp(tp)) { ++ int ret; ++ ++ rcu_read_lock(); ++ local_bh_disable(); ++ ret = mptcp_rcv_synsent_state_process(sk, &sk, ++ skb, &mopt); ++ local_bh_enable(); ++ rcu_read_unlock(); ++ ++ /* May have changed if we support MPTCP */ ++ tp = tcp_sk(sk); ++ icsk = inet_csk(sk); ++ ++ if (ret == 1) ++ goto reset_and_undo; ++ if (ret == 2) ++ goto discard; ++ } ++ ++ if (mptcp(tp) && !is_master_tp(tp)) { ++ /* Timer for repeating the ACK until an answer ++ * arrives. Used only when establishing an additional ++ * subflow inside of an MPTCP connection. ++ */ ++ sk_reset_timer(sk, &tp->mptcp->mptcp_ack_timer, ++ jiffies + icsk->icsk_rto); ++ } ++ + /* Ok.. it's good. Set up sequence numbers and + * move to established. + */ + WRITE_ONCE(tp->rcv_nxt, TCP_SKB_CB(skb)->seq + 1); + tp->rcv_wup = TCP_SKB_CB(skb)->seq + 1; ++ tcp_update_rcv_right_edge(tp); + + /* RFC1323: The window in SYN & SYN/ACK segments is + * never scaled. +@@ -5992,6 +6128,11 @@ static int tcp_rcv_synsent_state_process(struct sock *sk, struct sk_buff *skb, + tp->tcp_header_len = sizeof(struct tcphdr); + } + ++ if (mptcp(tp)) { ++ tp->tcp_header_len += MPTCP_SUB_LEN_DSM_ALIGN; ++ tp->advmss -= MPTCP_SUB_LEN_DSM_ALIGN; ++ } ++ + tcp_sync_mss(sk, icsk->icsk_pmtu_cookie); + tcp_initialize_rcv_mss(sk); + +@@ -6015,9 +6156,12 @@ static int tcp_rcv_synsent_state_process(struct sock *sk, struct sk_buff *skb, + } + if (fastopen_fail) + return -1; +- if (sk->sk_write_pending || ++ /* With MPTCP we cannot send data on the third ack due to the ++ * lack of option-space to combine with an MP_CAPABLE. ++ */ ++ if (!mptcp(tp) && (sk->sk_write_pending || + icsk->icsk_accept_queue.rskq_defer_accept || +- inet_csk_in_pingpong_mode(sk)) { ++ inet_csk_in_pingpong_mode(sk))) { + /* Save one ACK. Data will be ready after + * several ticks, if write_pending is set. + * +@@ -6056,6 +6200,7 @@ static int tcp_rcv_synsent_state_process(struct sock *sk, struct sk_buff *skb, + tcp_paws_reject(&tp->rx_opt, 0)) + goto discard_and_undo; + ++ /* TODO - check this here for MPTCP */ + if (th->syn) { + /* We see SYN without ACK. It is attempt of + * simultaneous connect with crossed SYNs. +@@ -6072,9 +6217,15 @@ static int tcp_rcv_synsent_state_process(struct sock *sk, struct sk_buff *skb, + tp->tcp_header_len = sizeof(struct tcphdr); + } + ++ if (mptcp(tp)) { ++ tp->tcp_header_len += MPTCP_SUB_LEN_DSM_ALIGN; ++ tp->advmss -= MPTCP_SUB_LEN_DSM_ALIGN; ++ } ++ + WRITE_ONCE(tp->rcv_nxt, TCP_SKB_CB(skb)->seq + 1); + WRITE_ONCE(tp->copied_seq, tp->rcv_nxt); + tp->rcv_wup = TCP_SKB_CB(skb)->seq + 1; ++ tcp_update_rcv_right_edge(tp); + + /* RFC1323: The window in SYN & SYN/ACK segments is + * never scaled. +@@ -6162,6 +6313,7 @@ static void tcp_rcv_synrecv_state_fastopen(struct sock *sk) + */ + + int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb) ++ __releases(&sk->sk_lock.slock) + { + struct tcp_sock *tp = tcp_sk(sk); + struct inet_connection_sock *icsk = inet_csk(sk); +@@ -6204,6 +6356,16 @@ int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb) + tp->rx_opt.saw_tstamp = 0; + tcp_mstamp_refresh(tp); + queued = tcp_rcv_synsent_state_process(sk, skb, th); ++ if (is_meta_sk(sk)) { ++ sk = tcp_sk(sk)->mpcb->master_sk; ++ tp = tcp_sk(sk); ++ ++ /* Need to call it here, because it will announce new ++ * addresses, which can only be done after the third ack ++ * of the 3-way handshake. ++ */ ++ mptcp_update_metasocket(tp->meta_sk); ++ } + if (queued >= 0) + return queued; + +@@ -6276,6 +6438,8 @@ int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb) + + if (tp->rx_opt.tstamp_ok) + tp->advmss -= TCPOLEN_TSTAMP_ALIGNED; ++ if (mptcp(tp)) ++ tp->advmss -= MPTCP_SUB_LEN_DSM_ALIGN; + + if (!inet_csk(sk)->icsk_ca_ops->cong_control) + tcp_update_pacing_rate(sk); +@@ -6285,6 +6449,30 @@ int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb) + + tcp_initialize_rcv_mss(sk); + tcp_fast_path_on(tp); ++ ++ /* Send an ACK when establishing a new MPTCP subflow, i.e. ++ * using an MP_JOIN subtype. ++ */ ++ if (mptcp(tp)) { ++ if (is_master_tp(tp)) { ++ mptcp_update_metasocket(mptcp_meta_sk(sk)); ++ } else { ++ struct sock *meta_sk = mptcp_meta_sk(sk); ++ ++ tcp_send_ack(sk); ++ ++ /* Update RTO as it might be worse/better */ ++ mptcp_set_rto(sk); ++ ++ /* If the new RTO would fire earlier, pull it in! */ ++ if (tcp_sk(meta_sk)->packets_out && ++ icsk->icsk_timeout > inet_csk(meta_sk)->icsk_rto + jiffies) { ++ tcp_rearm_rto(meta_sk); ++ } ++ ++ mptcp_push_pending_frames(mptcp_meta_sk(sk)); ++ } ++ } + break; + + case TCP_FIN_WAIT1: { +@@ -6325,7 +6513,8 @@ int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb) + tmo = tcp_fin_time(sk); + if (tmo > TCP_TIMEWAIT_LEN) { + inet_csk_reset_keepalive_timer(sk, tmo - TCP_TIMEWAIT_LEN); +- } else if (th->fin || sock_owned_by_user(sk)) { ++ } else if (th->fin || mptcp_is_data_fin(skb) || ++ sock_owned_by_user(sk)) { + /* Bad case. We could lose such FIN otherwise. + * It is not a big problem, but it looks confusing + * and not so rare event. We still can lose it now, +@@ -6334,7 +6523,7 @@ int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb) + */ + inet_csk_reset_keepalive_timer(sk, tmo); + } else { +- tcp_time_wait(sk, TCP_FIN_WAIT2, tmo); ++ tp->ops->time_wait(sk, TCP_FIN_WAIT2, tmo); + goto discard; + } + break; +@@ -6342,7 +6531,7 @@ int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb) + + case TCP_CLOSING: + if (tp->snd_una == tp->write_seq) { +- tcp_time_wait(sk, TCP_TIME_WAIT, 0); ++ tp->ops->time_wait(sk, TCP_TIME_WAIT, 0); + goto discard; + } + break; +@@ -6354,6 +6543,9 @@ int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb) + goto discard; + } + break; ++ case TCP_CLOSE: ++ if (tp->mp_killed) ++ goto discard; + } + + /* step 6: check the URG bit */ +@@ -6375,7 +6567,8 @@ int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb) + */ + if (sk->sk_shutdown & RCV_SHUTDOWN) { + if (TCP_SKB_CB(skb)->end_seq != TCP_SKB_CB(skb)->seq && +- after(TCP_SKB_CB(skb)->end_seq - th->fin, tp->rcv_nxt)) { ++ after(TCP_SKB_CB(skb)->end_seq - th->fin, tp->rcv_nxt) && ++ !mptcp(tp)) { + NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPABORTONDATA); + tcp_reset(sk); + return 1; +@@ -6477,6 +6670,8 @@ static void tcp_openreq_init(struct request_sock *req, + ireq->wscale_ok = rx_opt->wscale_ok; + ireq->acked = 0; + ireq->ecn_ok = 0; ++ ireq->mptcp_rqsk = 0; ++ ireq->saw_mpc = 0; + ireq->ir_rmt_port = tcp_hdr(skb)->source; + ireq->ir_num = ntohs(tcp_hdr(skb)->dest); + ireq->ir_mark = inet_request_mark(sk, skb); +@@ -6602,12 +6797,17 @@ int tcp_conn_request(struct request_sock_ops *rsk_ops, + /* TW buckets are converted to open requests without + * limitations, they conserve resources and peer is + * evidently real one. ++ * ++ * MPTCP: new subflows cannot be established in a stateless manner. + */ +- if ((net->ipv4.sysctl_tcp_syncookies == 2 || ++ if (((!is_meta_sk(sk) && net->ipv4.sysctl_tcp_syncookies == 2) || + inet_csk_reqsk_queue_is_full(sk)) && !isn) { + want_cookie = tcp_syn_flood_action(sk, rsk_ops->slab_name); + if (!want_cookie) + goto drop; ++ ++ if (is_meta_sk(sk)) ++ goto drop; + } + + if (sk_acceptq_is_full(sk)) { +@@ -6625,8 +6825,8 @@ int tcp_conn_request(struct request_sock_ops *rsk_ops, + tcp_clear_options(&tmp_opt); + tmp_opt.mss_clamp = af_ops->mss_clamp; + tmp_opt.user_mss = tp->rx_opt.user_mss; +- tcp_parse_options(sock_net(sk), skb, &tmp_opt, 0, +- want_cookie ? NULL : &foc); ++ tcp_parse_options(sock_net(sk), skb, &tmp_opt, NULL, 0, ++ want_cookie ? NULL : &foc, NULL); + + if (want_cookie && !tmp_opt.saw_tstamp) + tcp_clear_options(&tmp_opt); +@@ -6641,7 +6841,8 @@ int tcp_conn_request(struct request_sock_ops *rsk_ops, + /* Note: tcp_v6_init_req() might override ir_iif for link locals */ + inet_rsk(req)->ir_iif = inet_request_bound_dev_if(sk, skb); + +- af_ops->init_req(req, sk, skb); ++ if (af_ops->init_req(req, sk, skb, want_cookie)) ++ goto drop_and_free; + + if (security_inet_conn_request(sk, skb, req)) + goto drop_and_free; +@@ -6677,7 +6878,7 @@ int tcp_conn_request(struct request_sock_ops *rsk_ops, + tcp_ecn_create_request(req, skb, sk, dst); + + if (want_cookie) { +- isn = cookie_init_sequence(af_ops, sk, skb, &req->mss); ++ isn = cookie_init_sequence(af_ops, req, sk, skb, &req->mss); + req->cookie_ts = tmp_opt.tstamp_ok; + if (!tmp_opt.tstamp_ok) + inet_rsk(req)->ecn_ok = 0; +@@ -6692,17 +6893,25 @@ int tcp_conn_request(struct request_sock_ops *rsk_ops, + fastopen_sk = tcp_try_fastopen(sk, skb, req, &foc, dst); + } + if (fastopen_sk) { ++ struct sock *meta_sk = fastopen_sk; ++ ++ if (mptcp(tcp_sk(fastopen_sk))) ++ meta_sk = mptcp_meta_sk(fastopen_sk); + af_ops->send_synack(fastopen_sk, dst, &fl, req, + &foc, TCP_SYNACK_FASTOPEN); + /* Add the child socket directly into the accept queue */ +- if (!inet_csk_reqsk_queue_add(sk, req, fastopen_sk)) { ++ if (!inet_csk_reqsk_queue_add(sk, req, meta_sk)) { + reqsk_fastopen_remove(fastopen_sk, req, false); + bh_unlock_sock(fastopen_sk); ++ if (meta_sk != fastopen_sk) ++ bh_unlock_sock(meta_sk); + sock_put(fastopen_sk); + goto drop_and_free; + } + sk->sk_data_ready(sk); + bh_unlock_sock(fastopen_sk); ++ if (meta_sk != fastopen_sk) ++ bh_unlock_sock(meta_sk); + sock_put(fastopen_sk); + } else { + tcp_rsk(req)->tfo_listener = false; +diff --git a/net/ipv4/tcp_ipv4.c b/net/ipv4/tcp_ipv4.c +index 2ce85e52aea7..2e76c006ad16 100644 +--- a/net/ipv4/tcp_ipv4.c ++++ b/net/ipv4/tcp_ipv4.c +@@ -62,6 +62,8 @@ + #include + #include + #include ++#include ++#include + #include + #include + #include +@@ -209,6 +211,8 @@ int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len) + struct ip_options_rcu *inet_opt; + struct inet_timewait_death_row *tcp_death_row = &sock_net(sk)->ipv4.tcp_death_row; + ++ mptcp_init_connect(sk); ++ + if (addr_len < sizeof(struct sockaddr_in)) + return -EINVAL; + +@@ -430,7 +434,7 @@ int tcp_v4_err(struct sk_buff *icmp_skb, u32 info) + struct inet_sock *inet; + const int type = icmp_hdr(icmp_skb)->type; + const int code = icmp_hdr(icmp_skb)->code; +- struct sock *sk; ++ struct sock *sk, *meta_sk; + struct sk_buff *skb; + struct request_sock *fastopen; + u32 seq, snd_una; +@@ -460,13 +464,19 @@ int tcp_v4_err(struct sk_buff *icmp_skb, u32 info) + return 0; + } + +- bh_lock_sock(sk); ++ tp = tcp_sk(sk); ++ if (mptcp(tp)) ++ meta_sk = mptcp_meta_sk(sk); ++ else ++ meta_sk = sk; ++ ++ bh_lock_sock(meta_sk); + /* If too many ICMPs get dropped on busy + * servers this needs to be solved differently. + * We do take care of PMTU discovery (RFC1191) special case : + * we can receive locally generated ICMP messages while socket is held. + */ +- if (sock_owned_by_user(sk)) { ++ if (sock_owned_by_user(meta_sk)) { + if (!(type == ICMP_DEST_UNREACH && code == ICMP_FRAG_NEEDED)) + __NET_INC_STATS(net, LINUX_MIB_LOCKDROPPEDICMPS); + } +@@ -479,7 +489,6 @@ int tcp_v4_err(struct sk_buff *icmp_skb, u32 info) + } + + icsk = inet_csk(sk); +- tp = tcp_sk(sk); + /* XXX (TFO) - tp->snd_una should be ISN (tcp_create_openreq_child() */ + fastopen = rcu_dereference(tp->fastopen_rsk); + snd_una = fastopen ? tcp_rsk(fastopen)->snt_isn : tp->snd_una; +@@ -513,11 +522,13 @@ int tcp_v4_err(struct sk_buff *icmp_skb, u32 info) + goto out; + + WRITE_ONCE(tp->mtu_info, info); +- if (!sock_owned_by_user(sk)) { ++ if (!sock_owned_by_user(meta_sk)) { + tcp_v4_mtu_reduced(sk); + } else { + if (!test_and_set_bit(TCP_MTU_REDUCED_DEFERRED, &sk->sk_tsq_flags)) + sock_hold(sk); ++ if (mptcp(tp)) ++ mptcp_tsq_flags(sk); + } + goto out; + } +@@ -531,7 +542,7 @@ int tcp_v4_err(struct sk_buff *icmp_skb, u32 info) + !icsk->icsk_backoff || fastopen) + break; + +- if (sock_owned_by_user(sk)) ++ if (sock_owned_by_user(meta_sk)) + break; + + skb = tcp_rtx_queue_head(sk); +@@ -555,7 +566,7 @@ int tcp_v4_err(struct sk_buff *icmp_skb, u32 info) + } else { + /* RTO revert clocked out retransmission. + * Will retransmit now */ +- tcp_retransmit_timer(sk); ++ tcp_sk(sk)->ops->retransmit_timer(sk); + } + + break; +@@ -575,7 +586,7 @@ int tcp_v4_err(struct sk_buff *icmp_skb, u32 info) + if (fastopen && !fastopen->sk) + break; + +- if (!sock_owned_by_user(sk)) { ++ if (!sock_owned_by_user(meta_sk)) { + sk->sk_err = err; + + sk->sk_error_report(sk); +@@ -604,7 +615,7 @@ int tcp_v4_err(struct sk_buff *icmp_skb, u32 info) + */ + + inet = inet_sk(sk); +- if (!sock_owned_by_user(sk) && inet->recverr) { ++ if (!sock_owned_by_user(meta_sk) && inet->recverr) { + sk->sk_err = err; + sk->sk_error_report(sk); + } else { /* Only an error on timeout */ +@@ -612,7 +623,7 @@ int tcp_v4_err(struct sk_buff *icmp_skb, u32 info) + } + + out: +- bh_unlock_sock(sk); ++ bh_unlock_sock(meta_sk); + sock_put(sk); + return 0; + } +@@ -648,7 +659,7 @@ void tcp_v4_send_check(struct sock *sk, struct sk_buff *skb) + * Exception: precedence violation. We do not implement it in any case. + */ + +-static void tcp_v4_send_reset(const struct sock *sk, struct sk_buff *skb) ++void tcp_v4_send_reset(const struct sock *sk, struct sk_buff *skb) + { + const struct tcphdr *th = tcp_hdr(skb); + struct { +@@ -800,10 +811,10 @@ static void tcp_v4_send_reset(const struct sock *sk, struct sk_buff *skb) + */ + + static void tcp_v4_send_ack(const struct sock *sk, +- struct sk_buff *skb, u32 seq, u32 ack, ++ struct sk_buff *skb, u32 seq, u32 ack, u32 data_ack, + u32 win, u32 tsval, u32 tsecr, int oif, + struct tcp_md5sig_key *key, +- int reply_flags, u8 tos) ++ int reply_flags, u8 tos, int mptcp) + { + const struct tcphdr *th = tcp_hdr(skb); + struct { +@@ -812,6 +823,10 @@ static void tcp_v4_send_ack(const struct sock *sk, + #ifdef CONFIG_TCP_MD5SIG + + (TCPOLEN_MD5SIG_ALIGNED >> 2) + #endif ++#ifdef CONFIG_MPTCP ++ + ((MPTCP_SUB_LEN_DSS >> 2) + ++ (MPTCP_SUB_LEN_ACK >> 2)) ++#endif + ]; + } rep; + struct net *net = sock_net(sk); +@@ -858,6 +873,21 @@ static void tcp_v4_send_ack(const struct sock *sk, + ip_hdr(skb)->daddr, &rep.th); + } + #endif ++#ifdef CONFIG_MPTCP ++ if (mptcp) { ++ int offset = (tsecr) ? 3 : 0; ++ /* Construction of 32-bit data_ack */ ++ rep.opt[offset++] = htonl((TCPOPT_MPTCP << 24) | ++ ((MPTCP_SUB_LEN_DSS + MPTCP_SUB_LEN_ACK) << 16) | ++ (0x20 << 8) | ++ (0x01)); ++ rep.opt[offset] = htonl(data_ack); ++ ++ arg.iov[0].iov_len += MPTCP_SUB_LEN_DSS + MPTCP_SUB_LEN_ACK; ++ rep.th.doff = arg.iov[0].iov_len / 4; ++ } ++#endif /* CONFIG_MPTCP */ ++ + arg.flags = reply_flags; + arg.csum = csum_tcpudp_nofold(ip_hdr(skb)->daddr, + ip_hdr(skb)->saddr, /* XXX */ +@@ -889,28 +919,36 @@ static void tcp_v4_timewait_ack(struct sock *sk, struct sk_buff *skb) + { + struct inet_timewait_sock *tw = inet_twsk(sk); + struct tcp_timewait_sock *tcptw = tcp_twsk(sk); ++ u32 data_ack = 0; ++ int mptcp = 0; ++ ++ if (tcptw->mptcp_tw) { ++ data_ack = (u32)tcptw->mptcp_tw->rcv_nxt; ++ mptcp = 1; ++ } + + tcp_v4_send_ack(sk, skb, +- tcptw->tw_snd_nxt, tcptw->tw_rcv_nxt, ++ tcptw->tw_snd_nxt, tcptw->tw_rcv_nxt, data_ack, + tcptw->tw_rcv_wnd >> tw->tw_rcv_wscale, + tcp_time_stamp_raw() + tcptw->tw_ts_offset, + tcptw->tw_ts_recent, + tw->tw_bound_dev_if, + tcp_twsk_md5_key(tcptw), + tw->tw_transparent ? IP_REPLY_ARG_NOSRCCHECK : 0, +- tw->tw_tos ++ tw->tw_tos, mptcp + ); + + inet_twsk_put(tw); + } + +-static void tcp_v4_reqsk_send_ack(const struct sock *sk, struct sk_buff *skb, +- struct request_sock *req) ++void tcp_v4_reqsk_send_ack(const struct sock *sk, struct sk_buff *skb, ++ struct request_sock *req) + { + /* sk->sk_state == TCP_LISTEN -> for regular TCP_SYN_RECV + * sk->sk_state == TCP_SYN_RECV -> for Fast Open. + */ +- u32 seq = (sk->sk_state == TCP_LISTEN) ? tcp_rsk(req)->snt_isn + 1 : ++ u32 seq = (sk->sk_state == TCP_LISTEN || is_meta_sk(sk)) ? ++ tcp_rsk(req)->snt_isn + 1 : + tcp_sk(sk)->snd_nxt; + + /* RFC 7323 2.3 +@@ -919,7 +957,7 @@ static void tcp_v4_reqsk_send_ack(const struct sock *sk, struct sk_buff *skb, + * Rcv.Wind.Shift bits: + */ + tcp_v4_send_ack(sk, skb, seq, +- tcp_rsk(req)->rcv_nxt, ++ tcp_rsk(req)->rcv_nxt, 0, + req->rsk_rcv_wnd >> inet_rsk(req)->rcv_wscale, + tcp_time_stamp_raw() + tcp_rsk(req)->ts_off, + req->ts_recent, +@@ -927,7 +965,7 @@ static void tcp_v4_reqsk_send_ack(const struct sock *sk, struct sk_buff *skb, + tcp_md5_do_lookup(sk, (union tcp_md5_addr *)&ip_hdr(skb)->saddr, + AF_INET), + inet_rsk(req)->no_srccheck ? IP_REPLY_ARG_NOSRCCHECK : 0, +- ip_hdr(skb)->tos); ++ ip_hdr(skb)->tos, 0); + } + + /* +@@ -935,11 +973,11 @@ static void tcp_v4_reqsk_send_ack(const struct sock *sk, struct sk_buff *skb, + * This still operates on a request_sock only, not on a big + * socket. + */ +-static int tcp_v4_send_synack(const struct sock *sk, struct dst_entry *dst, +- struct flowi *fl, +- struct request_sock *req, +- struct tcp_fastopen_cookie *foc, +- enum tcp_synack_type synack_type) ++int tcp_v4_send_synack(const struct sock *sk, struct dst_entry *dst, ++ struct flowi *fl, ++ struct request_sock *req, ++ struct tcp_fastopen_cookie *foc, ++ enum tcp_synack_type synack_type) + { + const struct inet_request_sock *ireq = inet_rsk(req); + struct flowi4 fl4; +@@ -969,7 +1007,7 @@ static int tcp_v4_send_synack(const struct sock *sk, struct dst_entry *dst, + /* + * IPv4 request_sock destructor. + */ +-static void tcp_v4_reqsk_destructor(struct request_sock *req) ++void tcp_v4_reqsk_destructor(struct request_sock *req) + { + kfree(rcu_dereference_protected(inet_rsk(req)->ireq_opt, 1)); + } +@@ -1354,9 +1392,10 @@ static bool tcp_v4_inbound_md5_hash(const struct sock *sk, + return false; + } + +-static void tcp_v4_init_req(struct request_sock *req, +- const struct sock *sk_listener, +- struct sk_buff *skb) ++static int tcp_v4_init_req(struct request_sock *req, ++ const struct sock *sk_listener, ++ struct sk_buff *skb, ++ bool want_cookie) + { + struct inet_request_sock *ireq = inet_rsk(req); + struct net *net = sock_net(sk_listener); +@@ -1364,6 +1403,8 @@ static void tcp_v4_init_req(struct request_sock *req, + sk_rcv_saddr_set(req_to_sk(req), ip_hdr(skb)->daddr); + sk_daddr_set(req_to_sk(req), ip_hdr(skb)->saddr); + RCU_INIT_POINTER(ireq->ireq_opt, tcp_v4_save_options(net, skb)); ++ ++ return 0; + } + + static struct dst_entry *tcp_v4_route_req(const struct sock *sk, +@@ -1383,7 +1424,7 @@ struct request_sock_ops tcp_request_sock_ops __read_mostly = { + .syn_ack_timeout = tcp_syn_ack_timeout, + }; + +-static const struct tcp_request_sock_ops tcp_request_sock_ipv4_ops = { ++const struct tcp_request_sock_ops tcp_request_sock_ipv4_ops = { + .mss_clamp = TCP_MSS_DEFAULT, + #ifdef CONFIG_TCP_MD5SIG + .req_md5_lookup = tcp_v4_md5_lookup, +@@ -1520,7 +1561,7 @@ struct sock *tcp_v4_syn_recv_sock(const struct sock *sk, struct sk_buff *skb, + } + EXPORT_SYMBOL(tcp_v4_syn_recv_sock); + +-static struct sock *tcp_v4_cookie_check(struct sock *sk, struct sk_buff *skb) ++struct sock *tcp_v4_cookie_check(struct sock *sk, struct sk_buff *skb) + { + #ifdef CONFIG_SYN_COOKIES + const struct tcphdr *th = tcp_hdr(skb); +@@ -1558,6 +1599,9 @@ int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb) + { + struct sock *rsk; + ++ if (is_meta_sk(sk)) ++ return mptcp_v4_do_rcv(sk, skb); ++ + if (sk->sk_state == TCP_ESTABLISHED) { /* Fast path */ + struct dst_entry *dst = sk->sk_rx_dst; + +@@ -1803,6 +1847,10 @@ static void tcp_v4_fill_cb(struct sk_buff *skb, const struct iphdr *iph, + TCP_SKB_CB(skb)->end_seq = (TCP_SKB_CB(skb)->seq + th->syn + th->fin + + skb->len - th->doff * 4); + TCP_SKB_CB(skb)->ack_seq = ntohl(th->ack_seq); ++#ifdef CONFIG_MPTCP ++ TCP_SKB_CB(skb)->mptcp_flags = 0; ++ TCP_SKB_CB(skb)->dss_off = 0; ++#endif + TCP_SKB_CB(skb)->tcp_flags = tcp_flag_byte(th); + TCP_SKB_CB(skb)->tcp_tw_isn = 0; + TCP_SKB_CB(skb)->ip_dsfield = ipv4_get_dsfield(iph); +@@ -1822,8 +1870,8 @@ int tcp_v4_rcv(struct sk_buff *skb) + int sdif = inet_sdif(skb); + const struct iphdr *iph; + const struct tcphdr *th; ++ struct sock *sk, *meta_sk = NULL; + bool refcounted; +- struct sock *sk; + int ret; + + if (skb->pkt_type != PACKET_HOST) +@@ -1877,7 +1925,11 @@ int tcp_v4_rcv(struct sk_buff *skb) + reqsk_put(req); + goto csum_error; + } +- if (unlikely(sk->sk_state != TCP_LISTEN)) { ++ if (unlikely(sk->sk_state != TCP_LISTEN && !is_meta_sk(sk))) { ++ inet_csk_reqsk_queue_drop_and_put(sk, req); ++ goto lookup; ++ } ++ if (unlikely(is_meta_sk(sk) && !mptcp_can_new_subflow(sk))) { + inet_csk_reqsk_queue_drop_and_put(sk, req); + goto lookup; + } +@@ -1886,6 +1938,7 @@ int tcp_v4_rcv(struct sk_buff *skb) + */ + sock_hold(sk); + refcounted = true; ++ + nsk = NULL; + if (!tcp_filter(sk, skb)) { + th = (const struct tcphdr *)skb->data; +@@ -1946,19 +1999,28 @@ int tcp_v4_rcv(struct sk_buff *skb) + + sk_incoming_cpu_update(sk); + +- bh_lock_sock_nested(sk); ++ if (mptcp(tcp_sk(sk))) { ++ meta_sk = mptcp_meta_sk(sk); ++ ++ bh_lock_sock_nested(meta_sk); ++ if (sock_owned_by_user(meta_sk)) ++ mptcp_prepare_for_backlog(sk, skb); ++ } else { ++ meta_sk = sk; ++ bh_lock_sock_nested(sk); ++ } + tcp_segs_in(tcp_sk(sk), skb); + ret = 0; +- if (!sock_owned_by_user(sk)) { ++ if (!sock_owned_by_user(meta_sk)) { + skb_to_free = sk->sk_rx_skb_cache; + sk->sk_rx_skb_cache = NULL; + ret = tcp_v4_do_rcv(sk, skb); + } else { +- if (tcp_add_backlog(sk, skb)) ++ if (tcp_add_backlog(meta_sk, skb)) + goto discard_and_relse; + skb_to_free = NULL; + } +- bh_unlock_sock(sk); ++ bh_unlock_sock(meta_sk); + if (skb_to_free) + __kfree_skb(skb_to_free); + +@@ -1974,6 +2036,19 @@ int tcp_v4_rcv(struct sk_buff *skb) + + tcp_v4_fill_cb(skb, iph, th); + ++#ifdef CONFIG_MPTCP ++ if (!sk && th->syn && !th->ack) { ++ int ret = mptcp_lookup_join(skb, NULL); ++ ++ if (ret < 0) { ++ tcp_v4_send_reset(NULL, skb); ++ goto discard_it; ++ } else if (ret > 0) { ++ return 0; ++ } ++ } ++#endif ++ + if (tcp_checksum_complete(skb)) { + csum_error: + __TCP_INC_STATS(net, TCP_MIB_CSUMERRORS); +@@ -2022,6 +2097,18 @@ int tcp_v4_rcv(struct sk_buff *skb) + refcounted = false; + goto process; + } ++#ifdef CONFIG_MPTCP ++ if (th->syn && !th->ack) { ++ int ret = mptcp_lookup_join(skb, inet_twsk(sk)); ++ ++ if (ret < 0) { ++ tcp_v4_send_reset(NULL, skb); ++ goto discard_it; ++ } else if (ret > 0) { ++ return 0; ++ } ++ } ++#endif + } + /* to ACK */ + /* fall through */ +@@ -2091,7 +2178,12 @@ static int tcp_v4_init_sock(struct sock *sk) + + tcp_init_sock(sk); + +- icsk->icsk_af_ops = &ipv4_specific; ++#ifdef CONFIG_MPTCP ++ if (sock_flag(sk, SOCK_MPTCP)) ++ icsk->icsk_af_ops = &mptcp_v4_specific; ++ else ++#endif ++ icsk->icsk_af_ops = &ipv4_specific; + + #ifdef CONFIG_TCP_MD5SIG + tcp_sk(sk)->af_specific = &tcp_sock_ipv4_specific; +@@ -2110,6 +2202,11 @@ void tcp_v4_destroy_sock(struct sock *sk) + + tcp_cleanup_congestion_control(sk); + ++ if (mptcp(tp)) ++ mptcp_destroy_sock(sk); ++ if (tp->inside_tk_table) ++ mptcp_hash_remove_bh(tp); ++ + tcp_cleanup_ulp(sk); + + /* Cleanup up the write buffer. */ +@@ -2615,6 +2712,11 @@ struct proto tcp_prot = { + .sysctl_rmem_offset = offsetof(struct net, ipv4.sysctl_tcp_rmem), + .max_header = MAX_TCP_HEADER, + .obj_size = sizeof(struct tcp_sock), ++#ifdef CONFIG_MPTCP ++ .useroffset = offsetof(struct tcp_sock, mptcp_sched_name), ++ .usersize = sizeof_field(struct tcp_sock, mptcp_sched_name) + ++ sizeof_field(struct tcp_sock, mptcp_pm_name), ++#endif + .slab_flags = SLAB_TYPESAFE_BY_RCU, + .twsk_prot = &tcp_timewait_sock_ops, + .rsk_prot = &tcp_request_sock_ops, +@@ -2625,6 +2727,9 @@ struct proto tcp_prot = { + .compat_getsockopt = compat_tcp_getsockopt, + #endif + .diag_destroy = tcp_abort, ++#ifdef CONFIG_MPTCP ++ .clear_sk = mptcp_clear_sk, ++#endif + }; + EXPORT_SYMBOL(tcp_prot); + +diff --git a/net/ipv4/tcp_minisocks.c b/net/ipv4/tcp_minisocks.c +index 194743bd3fc1..b35942faf7df 100644 +--- a/net/ipv4/tcp_minisocks.c ++++ b/net/ipv4/tcp_minisocks.c +@@ -19,11 +19,13 @@ + * Jorge Cwik, + */ + ++#include + #include + #include + #include + #include + #include ++#include + #include + #include + #include +@@ -95,10 +97,14 @@ enum tcp_tw_status + struct tcp_options_received tmp_opt; + struct tcp_timewait_sock *tcptw = tcp_twsk((struct sock *)tw); + bool paws_reject = false; ++ struct mptcp_options_received mopt; + + tmp_opt.saw_tstamp = 0; +- if (th->doff > (sizeof(*th) >> 2) && tcptw->tw_ts_recent_stamp) { +- tcp_parse_options(twsk_net(tw), skb, &tmp_opt, 0, NULL); ++ if (th->doff > (sizeof(*th) >> 2) && ++ (tcptw->tw_ts_recent_stamp || tcptw->mptcp_tw)) { ++ mptcp_init_mp_opt(&mopt); ++ ++ tcp_parse_options(twsk_net(tw), skb, &tmp_opt, &mopt, 0, NULL, NULL); + + if (tmp_opt.saw_tstamp) { + if (tmp_opt.rcv_tsecr) +@@ -107,6 +113,11 @@ enum tcp_tw_status + tmp_opt.ts_recent_stamp = tcptw->tw_ts_recent_stamp; + paws_reject = tcp_paws_reject(&tmp_opt, th->rst); + } ++ ++ if (unlikely(mopt.mp_fclose) && tcptw->mptcp_tw) { ++ if (mopt.mptcp_sender_key == tcptw->mptcp_tw->loc_key) ++ return TCP_TW_RST; ++ } + } + + if (tw->tw_substate == TCP_FIN_WAIT2) { +@@ -130,6 +141,16 @@ enum tcp_tw_status + if (!th->ack || + !after(TCP_SKB_CB(skb)->end_seq, tcptw->tw_rcv_nxt) || + TCP_SKB_CB(skb)->end_seq == TCP_SKB_CB(skb)->seq) { ++ /* If mptcp_is_data_fin() returns true, we are sure that ++ * mopt has been initialized - otherwise it would not ++ * be a DATA_FIN. ++ */ ++ if (tcptw->mptcp_tw && tcptw->mptcp_tw->meta_tw && ++ mptcp_is_data_fin(skb) && ++ TCP_SKB_CB(skb)->seq == tcptw->tw_rcv_nxt && ++ mopt.data_seq + 1 == (u32)tcptw->mptcp_tw->rcv_nxt) ++ return TCP_TW_ACK; ++ + inet_twsk_put(tw); + return TCP_TW_SUCCESS; + } +@@ -270,11 +291,25 @@ void tcp_time_wait(struct sock *sk, int state, int timeo) + tw->tw_rcv_wscale = tp->rx_opt.rcv_wscale; + tcptw->tw_rcv_nxt = tp->rcv_nxt; + tcptw->tw_snd_nxt = tp->snd_nxt; +- tcptw->tw_rcv_wnd = tcp_receive_window(tp); ++ /* no need to keep track of the right-most right edge ++ * when in time wait, can directly use the currently ++ * advertised window. ++ */ ++ tcptw->tw_rcv_wnd = tcp_receive_window_now(tp); + tcptw->tw_ts_recent = tp->rx_opt.ts_recent; + tcptw->tw_ts_recent_stamp = tp->rx_opt.ts_recent_stamp; + tcptw->tw_ts_offset = tp->tsoffset; + tcptw->tw_last_oow_ack_time = 0; ++ ++ if (mptcp(tp)) { ++ if (mptcp_init_tw_sock(sk, tcptw)) { ++ inet_twsk_free(tw); ++ goto exit; ++ } ++ } else { ++ tcptw->mptcp_tw = NULL; ++ } ++ + tcptw->tw_tx_delay = tp->tcp_tx_delay; + #if IS_ENABLED(CONFIG_IPV6) + if (tw->tw_family == PF_INET6) { +@@ -336,6 +371,7 @@ void tcp_time_wait(struct sock *sk, int state, int timeo) + NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPTIMEWAITOVERFLOW); + } + ++exit: + tcp_update_metrics(sk); + tcp_done(sk); + } +@@ -343,6 +379,10 @@ void tcp_time_wait(struct sock *sk, int state, int timeo) + + void tcp_twsk_destructor(struct sock *sk) + { ++ struct tcp_timewait_sock *twsk = tcp_twsk(sk); ++ ++ if (twsk->mptcp_tw) ++ mptcp_twsk_destructor(twsk); + #ifdef CONFIG_TCP_MD5SIG + if (static_branch_unlikely(&tcp_md5_needed)) { + struct tcp_timewait_sock *twsk = tcp_twsk(sk); +@@ -386,8 +426,9 @@ void tcp_openreq_init_rwin(struct request_sock *req, + full_space = rcv_wnd * mss; + + /* tcp_full_space because it is guaranteed to be the first packet */ +- tcp_select_initial_window(sk_listener, full_space, +- mss - (ireq->tstamp_ok ? TCPOLEN_TSTAMP_ALIGNED : 0), ++ tp->ops->select_initial_window(sk_listener, full_space, ++ mss - (ireq->tstamp_ok ? TCPOLEN_TSTAMP_ALIGNED : 0) - ++ (ireq->saw_mpc ? MPTCP_SUB_LEN_DSM_ALIGN : 0), + &req->rsk_rcv_wnd, + &req->rsk_window_clamp, + ireq->wscale_ok, +@@ -487,6 +528,8 @@ struct sock *tcp_create_openreq_child(const struct sock *sk, + WRITE_ONCE(newtp->snd_nxt, seq); + newtp->snd_up = seq; + ++ newtp->out_of_order_queue = RB_ROOT; ++ newsk->tcp_rtx_queue = RB_ROOT; + INIT_LIST_HEAD(&newtp->tsq_node); + INIT_LIST_HEAD(&newtp->tsorted_sent_queue); + +@@ -511,6 +554,7 @@ struct sock *tcp_create_openreq_child(const struct sock *sk, + newtp->window_clamp = req->rsk_window_clamp; + newtp->rcv_ssthresh = req->rsk_rcv_wnd; + newtp->rcv_wnd = req->rsk_rcv_wnd; ++ newtp->rcv_right_edge = newtp->rcv_wnd + newtp->rcv_wup; + newtp->rx_opt.wscale_ok = ireq->wscale_ok; + if (newtp->rx_opt.wscale_ok) { + newtp->rx_opt.snd_wscale = ireq->snd_wscale; +@@ -530,6 +574,8 @@ struct sock *tcp_create_openreq_child(const struct sock *sk, + newtp->rx_opt.ts_recent_stamp = 0; + newtp->tcp_header_len = sizeof(struct tcphdr); + } ++ if (ireq->saw_mpc) ++ newtp->tcp_header_len += MPTCP_SUB_LEN_DSM_ALIGN; + if (req->num_timeout) { + newtp->undo_marker = treq->snt_isn; + newtp->retrans_stamp = div_u64(treq->snt_synack, +@@ -547,6 +593,7 @@ struct sock *tcp_create_openreq_child(const struct sock *sk, + tcp_ecn_openreq_child(newtp, req); + newtp->fastopen_req = NULL; + RCU_INIT_POINTER(newtp->fastopen_rsk, NULL); ++ newtp->inside_tk_table = 0; + + __TCP_INC_STATS(sock_net(sk), TCP_MIB_PASSIVEOPENS); + +@@ -570,15 +617,20 @@ struct sock *tcp_check_req(struct sock *sk, struct sk_buff *skb, + bool fastopen, bool *req_stolen) + { + struct tcp_options_received tmp_opt; ++ struct mptcp_options_received mopt; + struct sock *child; + const struct tcphdr *th = tcp_hdr(skb); + __be32 flg = tcp_flag_word(th) & (TCP_FLAG_RST|TCP_FLAG_SYN|TCP_FLAG_ACK); + bool paws_reject = false; + bool own_req; ++ bool meta_locked = false; + + tmp_opt.saw_tstamp = 0; ++ ++ mptcp_init_mp_opt(&mopt); ++ + if (th->doff > (sizeof(struct tcphdr)>>2)) { +- tcp_parse_options(sock_net(sk), skb, &tmp_opt, 0, NULL); ++ tcp_parse_options(sock_net(sk), skb, &tmp_opt, &mopt, 0, NULL, NULL); + + if (tmp_opt.saw_tstamp) { + tmp_opt.ts_recent = req->ts_recent; +@@ -619,7 +671,14 @@ struct sock *tcp_check_req(struct sock *sk, struct sk_buff *skb, + * + * Reset timer after retransmitting SYNACK, similar to + * the idea of fast retransmit in recovery. ++ * ++ * Fall back to TCP if MP_CAPABLE is not set. + */ ++ ++ if (inet_rsk(req)->saw_mpc && !mopt.saw_mpc) ++ inet_rsk(req)->saw_mpc = false; ++ ++ + if (!tcp_oow_rate_limited(sock_net(sk), skb, + LINUX_MIB_TCPACKSKIPPEDSYNRECV, + &tcp_rsk(req)->last_oow_ack_time) && +@@ -767,17 +826,40 @@ struct sock *tcp_check_req(struct sock *sk, struct sk_buff *skb, + * ESTABLISHED STATE. If it will be dropped after + * socket is created, wait for troubles. + */ ++ if (is_meta_sk(sk)) { ++ bh_lock_sock_nested(sk); ++ meta_locked = true; ++ } + child = inet_csk(sk)->icsk_af_ops->syn_recv_sock(sk, skb, req, NULL, + req, &own_req); + if (!child) + goto listen_overflow; + ++ if (own_req && !is_meta_sk(sk)) { ++ int ret = mptcp_check_req_master(sk, child, req, skb, &mopt, 1, 0); ++ if (ret < 0) ++ goto listen_overflow; ++ ++ /* MPTCP-supported */ ++ if (!ret) ++ return tcp_sk(child)->mpcb->master_sk; ++ } else if (own_req) { ++ return mptcp_check_req_child(sk, child, req, skb, &mopt); ++ } ++ ++ if (meta_locked) ++ bh_unlock_sock(sk); ++ + sock_rps_save_rxhash(child, skb); + tcp_synack_rtt_meas(child, req); + *req_stolen = !own_req; ++ + return inet_csk_complete_hashdance(sk, child, req, own_req); + + listen_overflow: ++ if (meta_locked) ++ bh_unlock_sock(sk); ++ + if (!sock_net(sk)->ipv4.sysctl_tcp_abort_on_overflow) { + inet_rsk(req)->acked = 1; + return NULL; +@@ -823,12 +905,13 @@ int tcp_child_process(struct sock *parent, struct sock *child, + { + int ret = 0; + int state = child->sk_state; ++ struct sock *meta_sk = mptcp(tcp_sk(child)) ? mptcp_meta_sk(child) : child; + + /* record NAPI ID of child */ + sk_mark_napi_id(child, skb); + + tcp_segs_in(tcp_sk(child), skb); +- if (!sock_owned_by_user(child)) { ++ if (!sock_owned_by_user(meta_sk)) { + ret = tcp_rcv_state_process(child, skb); + /* Wakeup parent, send SIGIO */ + if (state == TCP_SYN_RECV && child->sk_state != state) +@@ -838,10 +921,14 @@ int tcp_child_process(struct sock *parent, struct sock *child, + * in main socket hash table and lock on listening + * socket does not protect us more. + */ +- __sk_add_backlog(child, skb); ++ if (mptcp(tcp_sk(child))) ++ mptcp_prepare_for_backlog(child, skb); ++ __sk_add_backlog(meta_sk, skb); + } + + bh_unlock_sock(child); ++ if (mptcp(tcp_sk(child))) ++ bh_unlock_sock(meta_sk); + sock_put(child); + return ret; + } +diff --git a/net/ipv4/tcp_output.c b/net/ipv4/tcp_output.c +index 638d7b49ad71..d246e537e686 100644 +--- a/net/ipv4/tcp_output.c ++++ b/net/ipv4/tcp_output.c +@@ -37,6 +37,12 @@ + + #define pr_fmt(fmt) "TCP: " fmt + ++#include ++#include ++#if IS_ENABLED(CONFIG_IPV6) ++#include ++#endif ++#include + #include + + #include +@@ -57,11 +63,8 @@ void tcp_mstamp_refresh(struct tcp_sock *tp) + tp->tcp_mstamp = div_u64(val, NSEC_PER_USEC); + } + +-static bool tcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle, +- int push_one, gfp_t gfp); +- + /* Account for new data that has been sent to the network. */ +-static void tcp_event_new_data_sent(struct sock *sk, struct sk_buff *skb) ++void tcp_event_new_data_sent(struct sock *sk, struct sk_buff *skb) + { + struct inet_connection_sock *icsk = inet_csk(sk); + struct tcp_sock *tp = tcp_sk(sk); +@@ -255,12 +258,16 @@ void tcp_select_initial_window(const struct sock *sk, int __space, __u32 mss, + * value can be stuffed directly into th->window for an outgoing + * frame. + */ +-static u16 tcp_select_window(struct sock *sk) ++u16 tcp_select_window(struct sock *sk) + { + struct tcp_sock *tp = tcp_sk(sk); + u32 old_win = tp->rcv_wnd; +- u32 cur_win = tcp_receive_window(tp); +- u32 new_win = __tcp_select_window(sk); ++ /* The window must never shrink at the meta-level. At the subflow we ++ * have to allow this. Otherwise we may announce a window too large ++ * for the current meta-level sk_rcvbuf. ++ */ ++ u32 cur_win = tcp_receive_window_now(mptcp(tp) ? tcp_sk(mptcp_meta_sk(sk)) : tp); ++ u32 new_win = tp->ops->__select_window(sk); + + /* Never shrink the offered window */ + if (new_win < cur_win) { +@@ -276,8 +283,10 @@ static u16 tcp_select_window(struct sock *sk) + LINUX_MIB_TCPWANTZEROWINDOWADV); + new_win = ALIGN(cur_win, 1 << tp->rx_opt.rcv_wscale); + } ++ + tp->rcv_wnd = new_win; + tp->rcv_wup = tp->rcv_nxt; ++ tcp_update_rcv_right_edge(tp); + + /* Make sure we do not exceed the maximum possible + * scaled window. +@@ -388,7 +397,7 @@ static void tcp_ecn_send(struct sock *sk, struct sk_buff *skb, + /* Constructs common control bits of non-data skb. If SYN/FIN is present, + * auto increment end seqno. + */ +-static void tcp_init_nondata_skb(struct sk_buff *skb, u32 seq, u8 flags) ++void tcp_init_nondata_skb(struct sk_buff *skb, u32 seq, u8 flags) + { + skb->ip_summed = CHECKSUM_PARTIAL; + +@@ -403,7 +412,7 @@ static void tcp_init_nondata_skb(struct sk_buff *skb, u32 seq, u8 flags) + TCP_SKB_CB(skb)->end_seq = seq; + } + +-static inline bool tcp_urg_mode(const struct tcp_sock *tp) ++bool tcp_urg_mode(const struct tcp_sock *tp) + { + return tp->snd_una != tp->snd_up; + } +@@ -414,6 +423,7 @@ static inline bool tcp_urg_mode(const struct tcp_sock *tp) + #define OPTION_WSCALE (1 << 3) + #define OPTION_FAST_OPEN_COOKIE (1 << 8) + #define OPTION_SMC (1 << 9) ++/* Before adding here - take a look at OPTION_MPTCP in include/net/mptcp.h */ + + static void smc_options_write(__be32 *ptr, u16 *options) + { +@@ -430,17 +440,6 @@ static void smc_options_write(__be32 *ptr, u16 *options) + #endif + } + +-struct tcp_out_options { +- u16 options; /* bit field of OPTION_* */ +- u16 mss; /* 0 to disable */ +- u8 ws; /* window scale, 0 to disable */ +- u8 num_sack_blocks; /* number of SACK blocks to include */ +- u8 hash_size; /* bytes in hash_location */ +- __u8 *hash_location; /* temporary pointer, overloaded */ +- __u32 tsval, tsecr; /* need to include OPTION_TS */ +- struct tcp_fastopen_cookie *fastopen_cookie; /* Fast open cookie */ +-}; +- + /* Write previously computed TCP options to the packet. + * + * Beware: Something in the Internet is very sensitive to the ordering of +@@ -455,7 +454,7 @@ struct tcp_out_options { + * (but it may well be that other scenarios fail similarly). + */ + static void tcp_options_write(__be32 *ptr, struct tcp_sock *tp, +- struct tcp_out_options *opts) ++ struct tcp_out_options *opts, struct sk_buff *skb) + { + u16 options = opts->options; /* mungable copy */ + +@@ -549,6 +548,9 @@ static void tcp_options_write(__be32 *ptr, struct tcp_sock *tp, + } + + smc_options_write(ptr, &options); ++ ++ if (unlikely(OPTION_MPTCP & opts->options)) ++ mptcp_options_write(ptr, tp, opts, skb); + } + + static void smc_set_option(const struct tcp_sock *tp, +@@ -635,6 +637,8 @@ static unsigned int tcp_syn_options(struct sock *sk, struct sk_buff *skb, + if (unlikely(!(OPTION_TS & opts->options))) + remaining -= TCPOLEN_SACKPERM_ALIGNED; + } ++ if (tp->request_mptcp || mptcp(tp)) ++ mptcp_syn_options(sk, opts, &remaining); + + if (fastopen && fastopen->cookie.len >= 0) { + u32 need = fastopen->cookie.len; +@@ -718,6 +722,9 @@ static unsigned int tcp_synack_options(const struct sock *sk, + + smc_set_option_cond(tcp_sk(sk), ireq, opts, &remaining); + ++ if (ireq->saw_mpc) ++ mptcp_synack_options(req, opts, &remaining); ++ + return MAX_TCP_OPTION_SPACE - remaining; + } + +@@ -752,14 +759,19 @@ static unsigned int tcp_established_options(struct sock *sk, struct sk_buff *skb + opts->tsecr = tp->rx_opt.ts_recent; + size += TCPOLEN_TSTAMP_ALIGNED; + } ++ if (mptcp(tp)) ++ mptcp_established_options(sk, skb, opts, &size); + + eff_sacks = tp->rx_opt.num_sacks + tp->rx_opt.dsack; + if (unlikely(eff_sacks)) { +- const unsigned int remaining = MAX_TCP_OPTION_SPACE - size; +- opts->num_sack_blocks = +- min_t(unsigned int, eff_sacks, +- (remaining - TCPOLEN_SACK_BASE_ALIGNED) / +- TCPOLEN_SACK_PERBLOCK); ++ const unsigned remaining = MAX_TCP_OPTION_SPACE - size; ++ if (remaining < TCPOLEN_SACK_BASE_ALIGNED) ++ opts->num_sack_blocks = 0; ++ else ++ opts->num_sack_blocks = ++ min_t(unsigned int, eff_sacks, ++ (remaining - TCPOLEN_SACK_BASE_ALIGNED) / ++ TCPOLEN_SACK_PERBLOCK); + if (likely(opts->num_sack_blocks)) + size += TCPOLEN_SACK_BASE_ALIGNED + + opts->num_sack_blocks * TCPOLEN_SACK_PERBLOCK; +@@ -802,19 +814,31 @@ static void tcp_tsq_write(struct sock *sk) + tcp_xmit_retransmit_queue(sk); + } + +- tcp_write_xmit(sk, tcp_current_mss(sk), tp->nonagle, +- 0, GFP_ATOMIC); ++ tcp_sk(sk)->ops->write_xmit(sk, tcp_current_mss(sk), ++ tcp_sk(sk)->nonagle, 0, GFP_ATOMIC); + } + } + + static void tcp_tsq_handler(struct sock *sk) + { +- bh_lock_sock(sk); +- if (!sock_owned_by_user(sk)) ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct sock *meta_sk = mptcp(tp) ? mptcp_meta_sk(sk) : sk; ++ ++ bh_lock_sock(meta_sk); ++ if (!sock_owned_by_user(meta_sk)) { + tcp_tsq_write(sk); +- else if (!test_and_set_bit(TCP_TSQ_DEFERRED, &sk->sk_tsq_flags)) +- sock_hold(sk); +- bh_unlock_sock(sk); ++ ++ if (mptcp(tp)) ++ tcp_tsq_write(meta_sk); ++ } else { ++ if (!test_and_set_bit(TCP_TSQ_DEFERRED, &sk->sk_tsq_flags)) ++ sock_hold(sk); ++ ++ if ((mptcp(tp)) && (sk->sk_state != TCP_CLOSE)) ++ mptcp_tsq_flags(sk); ++ } ++ ++ bh_unlock_sock(meta_sk); + } + /* + * One tasklet per cpu tries to send more skbs. +@@ -851,7 +875,9 @@ static void tcp_tasklet_func(unsigned long data) + #define TCP_DEFERRED_ALL (TCPF_TSQ_DEFERRED | \ + TCPF_WRITE_TIMER_DEFERRED | \ + TCPF_DELACK_TIMER_DEFERRED | \ +- TCPF_MTU_REDUCED_DEFERRED) ++ TCPF_MTU_REDUCED_DEFERRED | \ ++ TCPF_PATH_MANAGER_DEFERRED |\ ++ TCPF_SUB_DEFERRED) + /** + * tcp_release_cb - tcp release_sock() callback + * @sk: socket +@@ -874,6 +900,9 @@ void tcp_release_cb(struct sock *sk) + if (flags & TCPF_TSQ_DEFERRED) { + tcp_tsq_write(sk); + __sock_put(sk); ++ ++ if (mptcp(tcp_sk(sk))) ++ tcp_tsq_write(mptcp_meta_sk(sk)); + } + /* Here begins the tricky part : + * We are called from release_sock() with : +@@ -898,6 +927,13 @@ void tcp_release_cb(struct sock *sk) + inet_csk(sk)->icsk_af_ops->mtu_reduced(sk); + __sock_put(sk); + } ++ if (flags & TCPF_PATH_MANAGER_DEFERRED) { ++ if (tcp_sk(sk)->mpcb->pm_ops->release_sock) ++ tcp_sk(sk)->mpcb->pm_ops->release_sock(sk); ++ __sock_put(sk); ++ } ++ if (flags & TCPF_SUB_DEFERRED) ++ mptcp_tsq_sub_deferred(sk); + } + EXPORT_SYMBOL(tcp_release_cb); + +@@ -981,8 +1017,8 @@ enum hrtimer_restart tcp_pace_kick(struct hrtimer *timer) + return HRTIMER_NORESTART; + } + +-static void tcp_update_skb_after_send(struct sock *sk, struct sk_buff *skb, +- u64 prior_wstamp) ++void tcp_update_skb_after_send(struct sock *sk, struct sk_buff *skb, ++ u64 prior_wstamp) + { + struct tcp_sock *tp = tcp_sk(sk); + +@@ -1128,10 +1164,10 @@ static int __tcp_transmit_skb(struct sock *sk, struct sk_buff *skb, + } + } + +- tcp_options_write((__be32 *)(th + 1), tp, &opts); ++ tcp_options_write((__be32 *)(th + 1), tp, &opts, skb); + skb_shinfo(skb)->gso_type = sk->sk_gso_type; + if (likely(!(tcb->tcp_flags & TCPHDR_SYN))) { +- th->window = htons(tcp_select_window(sk)); ++ th->window = htons(tp->ops->select_window(sk)); + tcp_ecn_send(sk, skb, th, tcp_header_size); + } else { + /* RFC1323: The window in SYN & SYN/ACK segments +@@ -1189,8 +1225,8 @@ static int __tcp_transmit_skb(struct sock *sk, struct sk_buff *skb, + return err; + } + +-static int tcp_transmit_skb(struct sock *sk, struct sk_buff *skb, int clone_it, +- gfp_t gfp_mask) ++int tcp_transmit_skb(struct sock *sk, struct sk_buff *skb, int clone_it, ++ gfp_t gfp_mask) + { + return __tcp_transmit_skb(sk, skb, clone_it, gfp_mask, + tcp_sk(sk)->rcv_nxt); +@@ -1201,7 +1237,7 @@ static int tcp_transmit_skb(struct sock *sk, struct sk_buff *skb, int clone_it, + * NOTE: probe0 timer is not checked, do not forget tcp_push_pending_frames, + * otherwise socket can stall. + */ +-static void tcp_queue_skb(struct sock *sk, struct sk_buff *skb) ++void tcp_queue_skb(struct sock *sk, struct sk_buff *skb) + { + struct tcp_sock *tp = tcp_sk(sk); + +@@ -1214,7 +1250,7 @@ static void tcp_queue_skb(struct sock *sk, struct sk_buff *skb) + } + + /* Initialize TSO segments for a packet. */ +-static void tcp_set_skb_tso_segs(struct sk_buff *skb, unsigned int mss_now) ++void tcp_set_skb_tso_segs(struct sk_buff *skb, unsigned int mss_now) + { + if (skb->len <= mss_now) { + /* Avoid the costly divide in the normal +@@ -1231,7 +1267,7 @@ static void tcp_set_skb_tso_segs(struct sk_buff *skb, unsigned int mss_now) + /* Pcount in the middle of the write queue got changed, we need to do various + * tweaks to fix counters + */ +-static void tcp_adjust_pcount(struct sock *sk, const struct sk_buff *skb, int decr) ++void tcp_adjust_pcount(struct sock *sk, const struct sk_buff *skb, int decr) + { + struct tcp_sock *tp = tcp_sk(sk); + +@@ -1400,7 +1436,7 @@ int tcp_fragment(struct sock *sk, enum tcp_queue tcp_queue, + /* This is similar to __pskb_pull_tail(). The difference is that pulled + * data is not copied, but immediately discarded. + */ +-static int __pskb_trim_head(struct sk_buff *skb, int len) ++int __pskb_trim_head(struct sk_buff *skb, int len) + { + struct skb_shared_info *shinfo; + int i, k, eat; +@@ -1623,6 +1659,7 @@ unsigned int tcp_current_mss(struct sock *sk) + + return mss_now; + } ++EXPORT_SYMBOL(tcp_current_mss); + + /* RFC2861, slow part. Adjust cwnd, after it was not full during one rto. + * As additional protections, we do not touch cwnd in retransmission phases, +@@ -1682,8 +1719,11 @@ static void tcp_cwnd_validate(struct sock *sk, bool is_cwnd_limited) + * 2) not cwnd limited (this else condition) + * 3) no more data to send (tcp_write_queue_empty()) + * 4) application is hitting buffer limit (SOCK_NOSPACE) ++ * 5) For MPTCP subflows, the scheduler determines ++ * sndbuf limited. + */ + if (tcp_write_queue_empty(sk) && sk->sk_socket && ++ !(mptcp(tcp_sk(sk)) && !is_meta_sk(sk)) && + test_bit(SOCK_NOSPACE, &sk->sk_socket->flags) && + (1 << sk->sk_state) & (TCPF_ESTABLISHED | TCPF_CLOSE_WAIT)) + tcp_chrono_start(sk, TCP_CHRONO_SNDBUF_LIMITED); +@@ -1705,8 +1745,8 @@ static bool tcp_minshall_check(const struct tcp_sock *tp) + * But we can avoid doing the divide again given we already have + * skb_pcount = skb->len / mss_now + */ +-static void tcp_minshall_update(struct tcp_sock *tp, unsigned int mss_now, +- const struct sk_buff *skb) ++void tcp_minshall_update(struct tcp_sock *tp, unsigned int mss_now, ++ const struct sk_buff *skb) + { + if (skb->len < tcp_skb_pcount(skb) * mss_now) + tp->snd_sml = TCP_SKB_CB(skb)->end_seq; +@@ -1752,7 +1792,7 @@ static u32 tcp_tso_autosize(const struct sock *sk, unsigned int mss_now, + /* Return the number of segments we want in the skb we are transmitting. + * See if congestion control module wants to decide; otherwise, autosize. + */ +-static u32 tcp_tso_segs(struct sock *sk, unsigned int mss_now) ++u32 tcp_tso_segs(struct sock *sk, unsigned int mss_now) + { + const struct tcp_congestion_ops *ca_ops = inet_csk(sk)->icsk_ca_ops; + u32 min_tso, tso_segs; +@@ -1766,11 +1806,11 @@ static u32 tcp_tso_segs(struct sock *sk, unsigned int mss_now) + } + + /* Returns the portion of skb which can be sent right away */ +-static unsigned int tcp_mss_split_point(const struct sock *sk, +- const struct sk_buff *skb, +- unsigned int mss_now, +- unsigned int max_segs, +- int nonagle) ++unsigned int tcp_mss_split_point(const struct sock *sk, ++ const struct sk_buff *skb, ++ unsigned int mss_now, ++ unsigned int max_segs, ++ int nonagle) + { + const struct tcp_sock *tp = tcp_sk(sk); + u32 partial, needed, window, max_len; +@@ -1800,13 +1840,14 @@ static unsigned int tcp_mss_split_point(const struct sock *sk, + /* Can at least one segment of SKB be sent right now, according to the + * congestion window rules? If so, return how many segments are allowed. + */ +-static inline unsigned int tcp_cwnd_test(const struct tcp_sock *tp, +- const struct sk_buff *skb) ++unsigned int tcp_cwnd_test(const struct tcp_sock *tp, ++ const struct sk_buff *skb) + { + u32 in_flight, cwnd, halfcwnd; + + /* Don't be strict about the congestion window for the final FIN. */ +- if ((TCP_SKB_CB(skb)->tcp_flags & TCPHDR_FIN) && ++ if (skb && ++ (TCP_SKB_CB(skb)->tcp_flags & TCPHDR_FIN) && + tcp_skb_pcount(skb) == 1) + return 1; + +@@ -1821,12 +1862,13 @@ static inline unsigned int tcp_cwnd_test(const struct tcp_sock *tp, + halfcwnd = max(cwnd >> 1, 1U); + return min(halfcwnd, cwnd - in_flight); + } ++EXPORT_SYMBOL(tcp_cwnd_test); + + /* Initialize TSO state of a skb. + * This must be invoked the first time we consider transmitting + * SKB onto the wire. + */ +-static int tcp_init_tso_segs(struct sk_buff *skb, unsigned int mss_now) ++int tcp_init_tso_segs(struct sk_buff *skb, unsigned int mss_now) + { + int tso_segs = tcp_skb_pcount(skb); + +@@ -1841,8 +1883,8 @@ static int tcp_init_tso_segs(struct sk_buff *skb, unsigned int mss_now) + /* Return true if the Nagle test allows this packet to be + * sent now. + */ +-static inline bool tcp_nagle_test(const struct tcp_sock *tp, const struct sk_buff *skb, +- unsigned int cur_mss, int nonagle) ++bool tcp_nagle_test(const struct tcp_sock *tp, const struct sk_buff *skb, ++ unsigned int cur_mss, int nonagle) + { + /* Nagle rule does not apply to frames, which sit in the middle of the + * write_queue (they have no chances to get new data). +@@ -1854,7 +1896,8 @@ static inline bool tcp_nagle_test(const struct tcp_sock *tp, const struct sk_buf + return true; + + /* Don't use the nagle rule for urgent data (or for the final FIN). */ +- if (tcp_urg_mode(tp) || (TCP_SKB_CB(skb)->tcp_flags & TCPHDR_FIN)) ++ if (tcp_urg_mode(tp) || (TCP_SKB_CB(skb)->tcp_flags & TCPHDR_FIN) || ++ mptcp_is_data_fin(skb)) + return true; + + if (!tcp_nagle_check(skb->len < cur_mss, tp, nonagle)) +@@ -1864,9 +1907,8 @@ static inline bool tcp_nagle_test(const struct tcp_sock *tp, const struct sk_buf + } + + /* Does at least the first segment of SKB fit into the send window? */ +-static bool tcp_snd_wnd_test(const struct tcp_sock *tp, +- const struct sk_buff *skb, +- unsigned int cur_mss) ++bool tcp_snd_wnd_test(const struct tcp_sock *tp, const struct sk_buff *skb, ++ unsigned int cur_mss) + { + u32 end_seq = TCP_SKB_CB(skb)->end_seq; + +@@ -1875,6 +1917,7 @@ static bool tcp_snd_wnd_test(const struct tcp_sock *tp, + + return !after(end_seq, tcp_wnd_end(tp)); + } ++EXPORT_SYMBOL(tcp_snd_wnd_test); + + /* Trim TSO SKB to LEN bytes, put the remaining data into a new packet + * which is put after SKB on the list. It is very much like +@@ -2033,7 +2076,8 @@ static bool tcp_tso_should_defer(struct sock *sk, struct sk_buff *skb, + + /* If this packet won't get more data, do not wait. */ + if ((TCP_SKB_CB(skb)->tcp_flags & TCPHDR_FIN) || +- TCP_SKB_CB(skb)->eor) ++ TCP_SKB_CB(skb)->eor || ++ mptcp_is_data_fin(skb)) + goto send_now; + + return true; +@@ -2366,7 +2410,7 @@ void tcp_chrono_stop(struct sock *sk, const enum tcp_chrono type) + * Returns true, if no segments are in flight and we have queued segments, + * but cannot send anything now because of SWS or another problem. + */ +-static bool tcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle, ++bool tcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle, + int push_one, gfp_t gfp) + { + struct tcp_sock *tp = tcp_sk(sk); +@@ -2380,7 +2424,12 @@ static bool tcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle, + sent_pkts = 0; + + tcp_mstamp_refresh(tp); +- if (!push_one) { ++ ++ /* pmtu not yet supported with MPTCP. Should be possible, by early ++ * exiting the loop inside tcp_mtu_probe, making sure that only one ++ * single DSS-mapping gets probed. ++ */ ++ if (!push_one && !mptcp(tp)) { + /* Do MTU probing. */ + result = tcp_mtu_probe(sk); + if (!result) { +@@ -2576,7 +2625,7 @@ void tcp_send_loss_probe(struct sock *sk) + skb = tcp_send_head(sk); + if (skb && tcp_snd_wnd_test(tp, skb, mss)) { + pcount = tp->packets_out; +- tcp_write_xmit(sk, mss, TCP_NAGLE_OFF, 2, GFP_ATOMIC); ++ tp->ops->write_xmit(sk, mss, TCP_NAGLE_OFF, 2, GFP_ATOMIC); + if (tp->packets_out > pcount) + goto probe_sent; + goto rearm_timer; +@@ -2638,8 +2687,8 @@ void __tcp_push_pending_frames(struct sock *sk, unsigned int cur_mss, + if (unlikely(sk->sk_state == TCP_CLOSE)) + return; + +- if (tcp_write_xmit(sk, cur_mss, nonagle, 0, +- sk_gfp_mask(sk, GFP_ATOMIC))) ++ if (tcp_sk(sk)->ops->write_xmit(sk, cur_mss, nonagle, 0, ++ sk_gfp_mask(sk, GFP_ATOMIC))) + tcp_check_probe_timer(sk); + } + +@@ -2652,7 +2701,8 @@ void tcp_push_one(struct sock *sk, unsigned int mss_now) + + BUG_ON(!skb || skb->len < mss_now); + +- tcp_write_xmit(sk, mss_now, TCP_NAGLE_PUSH, 1, sk->sk_allocation); ++ tcp_sk(sk)->ops->write_xmit(sk, mss_now, TCP_NAGLE_PUSH, 1, ++ sk->sk_allocation); + } + + /* This function returns the amount that we can raise the +@@ -2874,6 +2924,10 @@ static void tcp_retrans_try_collapse(struct sock *sk, struct sk_buff *to, + if (TCP_SKB_CB(skb)->tcp_flags & TCPHDR_SYN) + return; + ++ /* Currently not supported for MPTCP - but it should be possible */ ++ if (mptcp(tp)) ++ return; ++ + skb_rbtree_walk_from_safe(skb, tmp) { + if (!tcp_can_collapse(sk, skb)) + break; +@@ -3355,7 +3409,7 @@ struct sk_buff *tcp_make_synack(const struct sock *sk, struct dst_entry *dst, + + /* RFC1323: The window in SYN & SYN/ACK segments is never scaled. */ + th->window = htons(min(req->rsk_rcv_wnd, 65535U)); +- tcp_options_write((__be32 *)(th + 1), NULL, &opts); ++ tcp_options_write((__be32 *)(th + 1), NULL, &opts, skb); + th->doff = (tcp_header_size >> 2); + __TCP_INC_STATS(sock_net(sk), TCP_MIB_OUTSEGS); + +@@ -3437,13 +3491,13 @@ static void tcp_connect_init(struct sock *sk) + if (rcv_wnd == 0) + rcv_wnd = dst_metric(dst, RTAX_INITRWND); + +- tcp_select_initial_window(sk, tcp_full_space(sk), +- tp->advmss - (tp->rx_opt.ts_recent_stamp ? tp->tcp_header_len - sizeof(struct tcphdr) : 0), +- &tp->rcv_wnd, +- &tp->window_clamp, +- sock_net(sk)->ipv4.sysctl_tcp_window_scaling, +- &rcv_wscale, +- rcv_wnd); ++ tp->ops->select_initial_window(sk, tcp_full_space(sk), ++ tp->advmss - (tp->rx_opt.ts_recent_stamp ? tp->tcp_header_len - sizeof(struct tcphdr) : 0), ++ &tp->rcv_wnd, ++ &tp->window_clamp, ++ sock_net(sk)->ipv4.sysctl_tcp_window_scaling, ++ &rcv_wscale, ++ rcv_wnd); + + tp->rx_opt.rcv_wscale = rcv_wscale; + tp->rcv_ssthresh = tp->rcv_wnd; +@@ -3463,11 +3517,43 @@ static void tcp_connect_init(struct sock *sk) + else + tp->rcv_tstamp = tcp_jiffies32; + tp->rcv_wup = tp->rcv_nxt; ++ /* force set rcv_right_edge here at start of connection */ ++ tp->rcv_right_edge = tp->rcv_wup + tp->rcv_wnd; + WRITE_ONCE(tp->copied_seq, tp->rcv_nxt); + + inet_csk(sk)->icsk_rto = tcp_timeout_init(sk); + inet_csk(sk)->icsk_retransmits = 0; + tcp_clear_retrans(tp); ++ ++#ifdef CONFIG_MPTCP ++ if (sock_flag(sk, SOCK_MPTCP) && mptcp_doit(sk)) { ++ if (is_master_tp(tp)) { ++ tp->request_mptcp = 1; ++ mptcp_connect_init(sk); ++ } else if (tp->mptcp) { ++ struct inet_sock *inet = inet_sk(sk); ++ ++ tp->mptcp->snt_isn = tp->write_seq; ++ tp->mptcp->init_rcv_wnd = tp->rcv_wnd; ++ ++ /* Set nonce for new subflows */ ++ if (sk->sk_family == AF_INET) ++ tp->mptcp->mptcp_loc_nonce = mptcp_v4_get_nonce( ++ inet->inet_saddr, ++ inet->inet_daddr, ++ inet->inet_sport, ++ inet->inet_dport); ++#if IS_ENABLED(CONFIG_IPV6) ++ else ++ tp->mptcp->mptcp_loc_nonce = mptcp_v6_get_nonce( ++ inet6_sk(sk)->saddr.s6_addr32, ++ sk->sk_v6_daddr.s6_addr32, ++ inet->inet_sport, ++ inet->inet_dport); ++#endif ++ } ++ } ++#endif + } + + static void tcp_connect_queue_skb(struct sock *sk, struct sk_buff *skb) +@@ -3731,6 +3817,7 @@ void tcp_send_ack(struct sock *sk) + { + __tcp_send_ack(sk, tcp_sk(sk)->rcv_nxt); + } ++EXPORT_SYMBOL_GPL(tcp_send_ack); + + /* This routine sends a packet with an out of date sequence + * number. It assumes the other end will try to ack it. +@@ -3743,7 +3830,7 @@ void tcp_send_ack(struct sock *sk) + * one is with SEG.SEQ=SND.UNA to deliver urgent pointer, another is + * out-of-date with SND.UNA-1 to probe window. + */ +-static int tcp_xmit_probe_skb(struct sock *sk, int urgent, int mib) ++int tcp_xmit_probe_skb(struct sock *sk, int urgent, int mib) + { + struct tcp_sock *tp = tcp_sk(sk); + struct sk_buff *skb; +@@ -3830,7 +3917,7 @@ void tcp_send_probe0(struct sock *sk) + unsigned long timeout; + int err; + +- err = tcp_write_wakeup(sk, LINUX_MIB_TCPWINPROBE); ++ err = tp->ops->write_wakeup(sk, LINUX_MIB_TCPWINPROBE); + + if (tp->packets_out || tcp_write_queue_empty(sk)) { + /* Cancel probe timer, if it is not required. */ +diff --git a/net/ipv4/tcp_timer.c b/net/ipv4/tcp_timer.c +index fa2ae96ecdc4..36199efe2837 100644 +--- a/net/ipv4/tcp_timer.c ++++ b/net/ipv4/tcp_timer.c +@@ -21,6 +21,7 @@ + + #include + #include ++#include + #include + + static u32 tcp_clamp_rto_to_user_timeout(const struct sock *sk) +@@ -65,7 +66,7 @@ u32 tcp_clamp_probe0_to_user_timeout(const struct sock *sk, u32 when) + * Returns: Nothing (void) + */ + +-static void tcp_write_err(struct sock *sk) ++void tcp_write_err(struct sock *sk) + { + sk->sk_err = sk->sk_err_soft ? : ETIMEDOUT; + sk->sk_error_report(sk); +@@ -121,7 +122,7 @@ static int tcp_out_of_resources(struct sock *sk, bool do_reset) + (!tp->snd_wnd && !tp->packets_out)) + do_reset = true; + if (do_reset) +- tcp_send_active_reset(sk, GFP_ATOMIC); ++ tp->ops->send_active_reset(sk, GFP_ATOMIC); + tcp_done(sk); + __NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPABORTONMEMORY); + return 1; +@@ -206,9 +207,9 @@ static unsigned int tcp_model_timeout(struct sock *sk, + * after "boundary" unsuccessful, exponentially backed-off + * retransmissions with an initial RTO of TCP_RTO_MIN. + */ +-static bool retransmits_timed_out(struct sock *sk, +- unsigned int boundary, +- unsigned int timeout) ++bool retransmits_timed_out(struct sock *sk, ++ unsigned int boundary, ++ unsigned int timeout) + { + unsigned int start_ts; + +@@ -228,7 +229,7 @@ static bool retransmits_timed_out(struct sock *sk, + } + + /* A write timeout has occurred. Process the after effects. */ +-static int tcp_write_timeout(struct sock *sk) ++int tcp_write_timeout(struct sock *sk) + { + struct inet_connection_sock *icsk = inet_csk(sk); + struct tcp_sock *tp = tcp_sk(sk); +@@ -243,6 +244,17 @@ static int tcp_write_timeout(struct sock *sk) + sk_rethink_txhash(sk); + } + retry_until = icsk->icsk_syn_retries ? : net->ipv4.sysctl_tcp_syn_retries; ++ ++#ifdef CONFIG_MPTCP ++ /* Stop retransmitting MP_CAPABLE options in SYN if timed out. */ ++ if (tcp_sk(sk)->request_mptcp && ++ icsk->icsk_retransmits >= sysctl_mptcp_syn_retries) { ++ tcp_sk(sk)->request_mptcp = 0; ++ ++ MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_MPCAPABLERETRANSFALLBACK); ++ } ++#endif /* CONFIG_MPTCP */ ++ + expired = icsk->icsk_retransmits >= retry_until; + } else { + if (retransmits_timed_out(sk, net->ipv4.sysctl_tcp_retries1, 0)) { +@@ -338,18 +350,22 @@ static void tcp_delack_timer(struct timer_list *t) + struct inet_connection_sock *icsk = + from_timer(icsk, t, icsk_delack_timer); + struct sock *sk = &icsk->icsk_inet.sk; ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct sock *meta_sk = mptcp(tp) ? mptcp_meta_sk(sk) : sk; + +- bh_lock_sock(sk); +- if (!sock_owned_by_user(sk)) { ++ bh_lock_sock(meta_sk); ++ if (!sock_owned_by_user(meta_sk)) { + tcp_delack_timer_handler(sk); + } else { + icsk->icsk_ack.blocked = 1; +- __NET_INC_STATS(sock_net(sk), LINUX_MIB_DELAYEDACKLOCKED); ++ __NET_INC_STATS(sock_net(meta_sk), LINUX_MIB_DELAYEDACKLOCKED); + /* deleguate our work to tcp_release_cb() */ + if (!test_and_set_bit(TCP_DELACK_TIMER_DEFERRED, &sk->sk_tsq_flags)) + sock_hold(sk); ++ if (mptcp(tp)) ++ mptcp_tsq_flags(sk); + } +- bh_unlock_sock(sk); ++ bh_unlock_sock(meta_sk); + sock_put(sk); + } + +@@ -393,7 +409,12 @@ static void tcp_probe_timer(struct sock *sk) + } + + if (icsk->icsk_probes_out >= max_probes) { +-abort: tcp_write_err(sk); ++abort: ++ tcp_write_err(sk); ++ if (is_meta_sk(sk) && ++ mptcp_in_infinite_mapping_weak(tp->mpcb)) { ++ mptcp_sub_force_close_all(tp->mpcb, NULL); ++ } + } else { + /* Only send another probe if we didn't close things up. */ + tcp_send_probe0(sk); +@@ -614,7 +635,7 @@ void tcp_write_timer_handler(struct sock *sk) + break; + case ICSK_TIME_RETRANS: + icsk->icsk_pending = 0; +- tcp_retransmit_timer(sk); ++ tcp_sk(sk)->ops->retransmit_timer(sk); + break; + case ICSK_TIME_PROBE0: + icsk->icsk_pending = 0; +@@ -631,16 +652,19 @@ static void tcp_write_timer(struct timer_list *t) + struct inet_connection_sock *icsk = + from_timer(icsk, t, icsk_retransmit_timer); + struct sock *sk = &icsk->icsk_inet.sk; ++ struct sock *meta_sk = mptcp(tcp_sk(sk)) ? mptcp_meta_sk(sk) : sk; + +- bh_lock_sock(sk); +- if (!sock_owned_by_user(sk)) { ++ bh_lock_sock(meta_sk); ++ if (!sock_owned_by_user(meta_sk)) { + tcp_write_timer_handler(sk); + } else { + /* delegate our work to tcp_release_cb() */ + if (!test_and_set_bit(TCP_WRITE_TIMER_DEFERRED, &sk->sk_tsq_flags)) + sock_hold(sk); ++ if (mptcp(tcp_sk(sk))) ++ mptcp_tsq_flags(sk); + } +- bh_unlock_sock(sk); ++ bh_unlock_sock(meta_sk); + sock_put(sk); + } + +@@ -670,11 +694,12 @@ static void tcp_keepalive_timer (struct timer_list *t) + struct sock *sk = from_timer(sk, t, sk_timer); + struct inet_connection_sock *icsk = inet_csk(sk); + struct tcp_sock *tp = tcp_sk(sk); ++ struct sock *meta_sk = mptcp(tp) ? mptcp_meta_sk(sk) : sk; + u32 elapsed; + + /* Only process if socket is not in use. */ +- bh_lock_sock(sk); +- if (sock_owned_by_user(sk)) { ++ bh_lock_sock(meta_sk); ++ if (sock_owned_by_user(meta_sk)) { + /* Try again later. */ + inet_csk_reset_keepalive_timer (sk, HZ/20); + goto out; +@@ -686,16 +711,31 @@ static void tcp_keepalive_timer (struct timer_list *t) + } + + tcp_mstamp_refresh(tp); ++ ++ if (tp->send_mp_fclose) { ++ if (icsk->icsk_retransmits >= MPTCP_FASTCLOSE_RETRIES) { ++ tcp_write_err(sk); ++ goto out; ++ } ++ ++ tcp_send_ack(sk); ++ icsk->icsk_retransmits++; ++ ++ icsk->icsk_rto = min(icsk->icsk_rto << 1, TCP_RTO_MAX); ++ elapsed = icsk->icsk_rto; ++ goto resched; ++ } ++ + if (sk->sk_state == TCP_FIN_WAIT2 && sock_flag(sk, SOCK_DEAD)) { + if (tp->linger2 >= 0) { + const int tmo = tcp_fin_time(sk) - TCP_TIMEWAIT_LEN; + + if (tmo > 0) { +- tcp_time_wait(sk, TCP_FIN_WAIT2, tmo); ++ tp->ops->time_wait(sk, TCP_FIN_WAIT2, tmo); + goto out; + } + } +- tcp_send_active_reset(sk, GFP_ATOMIC); ++ tp->ops->send_active_reset(sk, GFP_ATOMIC); + goto death; + } + +@@ -720,11 +760,11 @@ static void tcp_keepalive_timer (struct timer_list *t) + icsk->icsk_probes_out > 0) || + (icsk->icsk_user_timeout == 0 && + icsk->icsk_probes_out >= keepalive_probes(tp))) { +- tcp_send_active_reset(sk, GFP_ATOMIC); ++ tp->ops->send_active_reset(sk, GFP_ATOMIC); + tcp_write_err(sk); + goto out; + } +- if (tcp_write_wakeup(sk, LINUX_MIB_TCPKEEPALIVE) <= 0) { ++ if (tp->ops->write_wakeup(sk, LINUX_MIB_TCPKEEPALIVE) <= 0) { + icsk->icsk_probes_out++; + elapsed = keepalive_intvl_when(tp); + } else { +@@ -748,7 +788,7 @@ static void tcp_keepalive_timer (struct timer_list *t) + tcp_done(sk); + + out: +- bh_unlock_sock(sk); ++ bh_unlock_sock(meta_sk); + sock_put(sk); + } + +diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c +index d1f29a3eb70b..78554dcb8532 100644 +--- a/net/ipv6/addrconf.c ++++ b/net/ipv6/addrconf.c +@@ -967,6 +967,7 @@ void inet6_ifa_finish_destroy(struct inet6_ifaddr *ifp) + + kfree_rcu(ifp, rcu); + } ++EXPORT_SYMBOL(inet6_ifa_finish_destroy); + + static void + ipv6_link_dev_addr(struct inet6_dev *idev, struct inet6_ifaddr *ifp) +diff --git a/net/ipv6/af_inet6.c b/net/ipv6/af_inet6.c +index 942da168f18f..e36520f9dcd5 100644 +--- a/net/ipv6/af_inet6.c ++++ b/net/ipv6/af_inet6.c +@@ -104,8 +104,7 @@ static __inline__ struct ipv6_pinfo *inet6_sk_generic(struct sock *sk) + return (struct ipv6_pinfo *)(((u8 *)sk) + offset); + } + +-static int inet6_create(struct net *net, struct socket *sock, int protocol, +- int kern) ++int inet6_create(struct net *net, struct socket *sock, int protocol, int kern) + { + struct inet_sock *inet; + struct ipv6_pinfo *np; +diff --git a/net/ipv6/ipv6_sockglue.c b/net/ipv6/ipv6_sockglue.c +index 5352c7e68c42..534a9d2e4858 100644 +--- a/net/ipv6/ipv6_sockglue.c ++++ b/net/ipv6/ipv6_sockglue.c +@@ -44,6 +44,8 @@ + #include + #include + #include ++#include ++#include + #include + #include + #include +@@ -221,7 +223,12 @@ static int do_ipv6_setsockopt(struct sock *sk, int level, int optname, + sock_prot_inuse_add(net, &tcp_prot, 1); + local_bh_enable(); + sk->sk_prot = &tcp_prot; +- icsk->icsk_af_ops = &ipv4_specific; ++#ifdef CONFIG_MPTCP ++ if (sock_flag(sk, SOCK_MPTCP)) ++ icsk->icsk_af_ops = &mptcp_v4_specific; ++ else ++#endif ++ icsk->icsk_af_ops = &ipv4_specific; + sk->sk_socket->ops = &inet_stream_ops; + sk->sk_family = PF_INET; + tcp_sync_mss(sk, icsk->icsk_pmtu_cookie); +@@ -345,6 +352,17 @@ static int do_ipv6_setsockopt(struct sock *sk, int level, int optname, + if (val == -1) + val = 0; + np->tclass = val; ++ ++ if (is_meta_sk(sk)) { ++ struct mptcp_tcp_sock *mptcp; ++ ++ mptcp_for_each_sub(tcp_sk(sk)->mpcb, mptcp) { ++ struct sock *sk_it = mptcp_to_sock(mptcp); ++ ++ if (sk_it->sk_family == AF_INET6) ++ inet6_sk(sk_it)->tclass = val; ++ } ++ } + retv = 0; + break; + +diff --git a/net/ipv6/syncookies.c b/net/ipv6/syncookies.c +index ec155844012b..225c015b60a8 100644 +--- a/net/ipv6/syncookies.c ++++ b/net/ipv6/syncookies.c +@@ -15,6 +15,8 @@ + #include + #include + #include ++#include ++#include + #include + + #define COOKIEBITS 24 /* Upper bits store count */ +@@ -106,7 +108,8 @@ u32 __cookie_v6_init_sequence(const struct ipv6hdr *iph, + } + EXPORT_SYMBOL_GPL(__cookie_v6_init_sequence); + +-__u32 cookie_v6_init_sequence(const struct sk_buff *skb, __u16 *mssp) ++__u32 cookie_v6_init_sequence(struct request_sock *req, const struct sock *sk, ++ const struct sk_buff *skb, __u16 *mssp) + { + const struct ipv6hdr *iph = ipv6_hdr(skb); + const struct tcphdr *th = tcp_hdr(skb); +@@ -128,6 +131,7 @@ int __cookie_v6_check(const struct ipv6hdr *iph, const struct tcphdr *th, + struct sock *cookie_v6_check(struct sock *sk, struct sk_buff *skb) + { + struct tcp_options_received tcp_opt; ++ struct mptcp_options_received mopt; + struct inet_request_sock *ireq; + struct tcp_request_sock *treq; + struct ipv6_pinfo *np = inet6_sk(sk); +@@ -157,7 +161,8 @@ struct sock *cookie_v6_check(struct sock *sk, struct sk_buff *skb) + + /* check for timestamp cookie support */ + memset(&tcp_opt, 0, sizeof(tcp_opt)); +- tcp_parse_options(sock_net(sk), skb, &tcp_opt, 0, NULL); ++ mptcp_init_mp_opt(&mopt); ++ tcp_parse_options(sock_net(sk), skb, &tcp_opt, &mopt, 0, NULL, NULL); + + if (tcp_opt.saw_tstamp && tcp_opt.rcv_tsecr) { + tsoff = secure_tcpv6_ts_off(sock_net(sk), +@@ -170,14 +175,27 @@ struct sock *cookie_v6_check(struct sock *sk, struct sk_buff *skb) + goto out; + + ret = NULL; +- req = inet_reqsk_alloc(&tcp6_request_sock_ops, sk, false); ++#ifdef CONFIG_MPTCP ++ if (mopt.saw_mpc) ++ req = inet_reqsk_alloc(&mptcp6_request_sock_ops, sk, false); ++ else ++#endif ++ req = inet_reqsk_alloc(&tcp6_request_sock_ops, sk, false); + if (!req) + goto out; + + ireq = inet_rsk(req); ++ ireq->mptcp_rqsk = 0; ++ ireq->saw_mpc = 0; + treq = tcp_rsk(req); + treq->tfo_listener = false; + ++ /* Must be done before anything else, as it initializes ++ * hash_entry of the MPTCP request-sock. ++ */ ++ if (mopt.saw_mpc) ++ mptcp_cookies_reqsk_init(req, &mopt, skb); ++ + if (security_inet_conn_request(sk, skb, req)) + goto out_free; + +@@ -247,15 +265,15 @@ struct sock *cookie_v6_check(struct sock *sk, struct sk_buff *skb) + (req->rsk_window_clamp > full_space || req->rsk_window_clamp == 0)) + req->rsk_window_clamp = full_space; + +- tcp_select_initial_window(sk, full_space, req->mss, +- &req->rsk_rcv_wnd, &req->rsk_window_clamp, +- ireq->wscale_ok, &rcv_wscale, +- dst_metric(dst, RTAX_INITRWND)); ++ tp->ops->select_initial_window(sk, full_space, req->mss, ++ &req->rsk_rcv_wnd, &req->rsk_window_clamp, ++ ireq->wscale_ok, &rcv_wscale, ++ dst_metric(dst, RTAX_INITRWND)); + + ireq->rcv_wscale = rcv_wscale; + ireq->ecn_ok = cookie_ecn_ok(&tcp_opt, sock_net(sk), dst); + +- ret = tcp_get_cookie_sock(sk, skb, req, dst, tsoff); ++ ret = tcp_get_cookie_sock(sk, skb, req, &mopt, dst, tsoff); + out: + return ret; + out_free: +diff --git a/net/ipv6/tcp_ipv6.c b/net/ipv6/tcp_ipv6.c +index 3903cc0ab188..2f91fddabceb 100644 +--- a/net/ipv6/tcp_ipv6.c ++++ b/net/ipv6/tcp_ipv6.c +@@ -58,6 +58,8 @@ + #include + #include + #include ++#include ++#include + #include + + #include +@@ -67,15 +69,6 @@ + #include + + #include +- +-static void tcp_v6_send_reset(const struct sock *sk, struct sk_buff *skb); +-static void tcp_v6_reqsk_send_ack(const struct sock *sk, struct sk_buff *skb, +- struct request_sock *req); +- +-static int tcp_v6_do_rcv(struct sock *sk, struct sk_buff *skb); +- +-static const struct inet_connection_sock_af_ops ipv6_mapped; +-static const struct inet_connection_sock_af_ops ipv6_specific; + #ifdef CONFIG_TCP_MD5SIG + static const struct tcp_sock_af_ops tcp_sock_ipv6_specific; + static const struct tcp_sock_af_ops tcp_sock_ipv6_mapped_specific; +@@ -99,7 +92,7 @@ static struct ipv6_pinfo *tcp_inet6_sk(const struct sock *sk) + return (struct ipv6_pinfo *)(((u8 *)sk) + offset); + } + +-static void inet6_sk_rx_dst_set(struct sock *sk, const struct sk_buff *skb) ++void inet6_sk_rx_dst_set(struct sock *sk, const struct sk_buff *skb) + { + struct dst_entry *dst = skb_dst(skb); + +@@ -141,7 +134,7 @@ static int tcp_v6_pre_connect(struct sock *sk, struct sockaddr *uaddr, + return BPF_CGROUP_RUN_PROG_INET6_CONNECT(sk, uaddr); + } + +-static int tcp_v6_connect(struct sock *sk, struct sockaddr *uaddr, ++int tcp_v6_connect(struct sock *sk, struct sockaddr *uaddr, + int addr_len) + { + struct sockaddr_in6 *usin = (struct sockaddr_in6 *) uaddr; +@@ -157,6 +150,8 @@ static int tcp_v6_connect(struct sock *sk, struct sockaddr *uaddr, + int err; + struct inet_timewait_death_row *tcp_death_row = &sock_net(sk)->ipv4.tcp_death_row; + ++ mptcp_init_connect(sk); ++ + if (addr_len < SIN6_LEN_RFC2133) + return -EINVAL; + +@@ -236,7 +231,12 @@ static int tcp_v6_connect(struct sock *sk, struct sockaddr *uaddr, + sin.sin_port = usin->sin6_port; + sin.sin_addr.s_addr = usin->sin6_addr.s6_addr32[3]; + +- icsk->icsk_af_ops = &ipv6_mapped; ++#ifdef CONFIG_MPTCP ++ if (sock_flag(sk, SOCK_MPTCP)) ++ icsk->icsk_af_ops = &mptcp_v6_mapped; ++ else ++#endif ++ icsk->icsk_af_ops = &ipv6_mapped; + sk->sk_backlog_rcv = tcp_v4_do_rcv; + #ifdef CONFIG_TCP_MD5SIG + tp->af_specific = &tcp_sock_ipv6_mapped_specific; +@@ -246,7 +246,12 @@ static int tcp_v6_connect(struct sock *sk, struct sockaddr *uaddr, + + if (err) { + icsk->icsk_ext_hdr_len = exthdrlen; +- icsk->icsk_af_ops = &ipv6_specific; ++#ifdef CONFIG_MPTCP ++ if (sock_flag(sk, SOCK_MPTCP)) ++ icsk->icsk_af_ops = &mptcp_v6_specific; ++ else ++#endif ++ icsk->icsk_af_ops = &ipv6_specific; + sk->sk_backlog_rcv = tcp_v6_do_rcv; + #ifdef CONFIG_TCP_MD5SIG + tp->af_specific = &tcp_sock_ipv6_specific; +@@ -340,7 +345,7 @@ static int tcp_v6_connect(struct sock *sk, struct sockaddr *uaddr, + return err; + } + +-static void tcp_v6_mtu_reduced(struct sock *sk) ++void tcp_v6_mtu_reduced(struct sock *sk) + { + struct dst_entry *dst; + u32 mtu; +@@ -376,7 +381,7 @@ static int tcp_v6_err(struct sk_buff *skb, struct inet6_skb_parm *opt, + struct ipv6_pinfo *np; + struct tcp_sock *tp; + __u32 seq, snd_una; +- struct sock *sk; ++ struct sock *sk, *meta_sk; + bool fatal; + int err; + +@@ -402,8 +407,14 @@ static int tcp_v6_err(struct sk_buff *skb, struct inet6_skb_parm *opt, + return 0; + } + +- bh_lock_sock(sk); +- if (sock_owned_by_user(sk) && type != ICMPV6_PKT_TOOBIG) ++ tp = tcp_sk(sk); ++ if (mptcp(tp)) ++ meta_sk = mptcp_meta_sk(sk); ++ else ++ meta_sk = sk; ++ ++ bh_lock_sock(meta_sk); ++ if (sock_owned_by_user(meta_sk) && type != ICMPV6_PKT_TOOBIG) + __NET_INC_STATS(net, LINUX_MIB_LOCKDROPPEDICMPS); + + if (sk->sk_state == TCP_CLOSE) +@@ -414,7 +425,6 @@ static int tcp_v6_err(struct sk_buff *skb, struct inet6_skb_parm *opt, + goto out; + } + +- tp = tcp_sk(sk); + /* XXX (TFO) - tp->snd_una should be ISN (tcp_create_openreq_child() */ + fastopen = rcu_dereference(tp->fastopen_rsk); + snd_una = fastopen ? tcp_rsk(fastopen)->snt_isn : tp->snd_una; +@@ -454,11 +464,15 @@ static int tcp_v6_err(struct sk_buff *skb, struct inet6_skb_parm *opt, + + WRITE_ONCE(tp->mtu_info, mtu); + +- if (!sock_owned_by_user(sk)) ++ if (!sock_owned_by_user(meta_sk)) { + tcp_v6_mtu_reduced(sk); +- else if (!test_and_set_bit(TCP_MTU_REDUCED_DEFERRED, +- &sk->sk_tsq_flags)) +- sock_hold(sk); ++ } else { ++ if (!test_and_set_bit(TCP_MTU_REDUCED_DEFERRED, ++ &sk->sk_tsq_flags)) ++ sock_hold(sk); ++ if (mptcp(tp)) ++ mptcp_tsq_flags(sk); ++ } + goto out; + } + +@@ -473,7 +487,7 @@ static int tcp_v6_err(struct sk_buff *skb, struct inet6_skb_parm *opt, + if (fastopen && !fastopen->sk) + break; + +- if (!sock_owned_by_user(sk)) { ++ if (!sock_owned_by_user(meta_sk)) { + sk->sk_err = err; + sk->sk_error_report(sk); /* Wake people up to see the error (see connect in sock.c) */ + +@@ -483,14 +497,14 @@ static int tcp_v6_err(struct sk_buff *skb, struct inet6_skb_parm *opt, + goto out; + } + +- if (!sock_owned_by_user(sk) && np->recverr) { ++ if (!sock_owned_by_user(meta_sk) && np->recverr) { + sk->sk_err = err; + sk->sk_error_report(sk); + } else + sk->sk_err_soft = err; + + out: +- bh_unlock_sock(sk); ++ bh_unlock_sock(meta_sk); + sock_put(sk); + return 0; + } +@@ -538,8 +552,7 @@ static int tcp_v6_send_synack(const struct sock *sk, struct dst_entry *dst, + return err; + } + +- +-static void tcp_v6_reqsk_destructor(struct request_sock *req) ++void tcp_v6_reqsk_destructor(struct request_sock *req) + { + kfree(inet_rsk(req)->ipv6_opt); + kfree_skb(inet_rsk(req)->pktopts); +@@ -757,9 +770,10 @@ static bool tcp_v6_inbound_md5_hash(const struct sock *sk, + return false; + } + +-static void tcp_v6_init_req(struct request_sock *req, +- const struct sock *sk_listener, +- struct sk_buff *skb) ++static int tcp_v6_init_req(struct request_sock *req, ++ const struct sock *sk_listener, ++ struct sk_buff *skb, ++ bool want_cookie) + { + bool l3_slave = ipv6_l3mdev_skb(TCP_SKB_CB(skb)->header.h6.flags); + struct inet_request_sock *ireq = inet_rsk(req); +@@ -781,6 +795,8 @@ static void tcp_v6_init_req(struct request_sock *req, + refcount_inc(&skb->users); + ireq->pktopts = skb; + } ++ ++ return 0; + } + + static struct dst_entry *tcp_v6_route_req(const struct sock *sk, +@@ -800,7 +816,7 @@ struct request_sock_ops tcp6_request_sock_ops __read_mostly = { + .syn_ack_timeout = tcp_syn_ack_timeout, + }; + +-static const struct tcp_request_sock_ops tcp_request_sock_ipv6_ops = { ++const struct tcp_request_sock_ops tcp_request_sock_ipv6_ops = { + .mss_clamp = IPV6_MIN_MTU - sizeof(struct tcphdr) - + sizeof(struct ipv6hdr), + #ifdef CONFIG_TCP_MD5SIG +@@ -818,9 +834,9 @@ struct request_sock_ops tcp6_request_sock_ops __read_mostly = { + }; + + static void tcp_v6_send_response(const struct sock *sk, struct sk_buff *skb, u32 seq, +- u32 ack, u32 win, u32 tsval, u32 tsecr, ++ u32 ack, u32 data_ack, u32 win, u32 tsval, u32 tsecr, + int oif, struct tcp_md5sig_key *key, int rst, +- u8 tclass, __be32 label, u32 priority) ++ u8 tclass, __be32 label, u32 priority, int mptcp) + { + const struct tcphdr *th = tcp_hdr(skb); + struct tcphdr *t1; +@@ -839,7 +855,10 @@ static void tcp_v6_send_response(const struct sock *sk, struct sk_buff *skb, u32 + if (key) + tot_len += TCPOLEN_MD5SIG_ALIGNED; + #endif +- ++#ifdef CONFIG_MPTCP ++ if (mptcp) ++ tot_len += MPTCP_SUB_LEN_DSS + MPTCP_SUB_LEN_ACK; ++#endif + buff = alloc_skb(MAX_HEADER + sizeof(struct ipv6hdr) + tot_len, + GFP_ATOMIC); + if (!buff) +@@ -877,6 +896,17 @@ static void tcp_v6_send_response(const struct sock *sk, struct sk_buff *skb, u32 + tcp_v6_md5_hash_hdr((__u8 *)topt, key, + &ipv6_hdr(skb)->saddr, + &ipv6_hdr(skb)->daddr, t1); ++ topt += 4; ++ } ++#endif ++#ifdef CONFIG_MPTCP ++ if (mptcp) { ++ /* Construction of 32-bit data_ack */ ++ *topt++ = htonl((TCPOPT_MPTCP << 24) | ++ ((MPTCP_SUB_LEN_DSS + MPTCP_SUB_LEN_ACK) << 16) | ++ (0x20 << 8) | ++ (0x01)); ++ *topt++ = htonl(data_ack); + } + #endif + +@@ -935,7 +965,7 @@ static void tcp_v6_send_response(const struct sock *sk, struct sk_buff *skb, u32 + kfree_skb(buff); + } + +-static void tcp_v6_send_reset(const struct sock *sk, struct sk_buff *skb) ++void tcp_v6_send_reset(const struct sock *sk, struct sk_buff *skb) + { + const struct tcphdr *th = tcp_hdr(skb); + struct ipv6hdr *ipv6h = ipv6_hdr(skb); +@@ -1020,8 +1050,8 @@ static void tcp_v6_send_reset(const struct sock *sk, struct sk_buff *skb) + label = ip6_flowlabel(ipv6h); + } + +- tcp_v6_send_response(sk, skb, seq, ack_seq, 0, 0, 0, oif, key, 1, 0, +- label, priority); ++ tcp_v6_send_response(sk, skb, seq, ack_seq, 0, 0, 0, 0, oif, key, 1, 0, ++ label, priority, 0); + + #ifdef CONFIG_TCP_MD5SIG + out: +@@ -1030,30 +1060,37 @@ static void tcp_v6_send_reset(const struct sock *sk, struct sk_buff *skb) + } + + static void tcp_v6_send_ack(const struct sock *sk, struct sk_buff *skb, u32 seq, +- u32 ack, u32 win, u32 tsval, u32 tsecr, int oif, ++ u32 ack, u32 data_ack, u32 win, u32 tsval, u32 tsecr, int oif, + struct tcp_md5sig_key *key, u8 tclass, +- __be32 label, u32 priority) ++ __be32 label, u32 priority, int mptcp) + { +- tcp_v6_send_response(sk, skb, seq, ack, win, tsval, tsecr, oif, key, 0, +- tclass, label, priority); ++ tcp_v6_send_response(sk, skb, seq, ack, data_ack, win, tsval, tsecr, oif, ++ key, 0, tclass, label, priority, mptcp); + } + + static void tcp_v6_timewait_ack(struct sock *sk, struct sk_buff *skb) + { + struct inet_timewait_sock *tw = inet_twsk(sk); + struct tcp_timewait_sock *tcptw = tcp_twsk(sk); ++ u32 data_ack = 0; ++ int mptcp = 0; + ++ if (tcptw->mptcp_tw) { ++ data_ack = (u32)tcptw->mptcp_tw->rcv_nxt; ++ mptcp = 1; ++ } + tcp_v6_send_ack(sk, skb, tcptw->tw_snd_nxt, tcptw->tw_rcv_nxt, ++ data_ack, + tcptw->tw_rcv_wnd >> tw->tw_rcv_wscale, + tcp_time_stamp_raw() + tcptw->tw_ts_offset, + tcptw->tw_ts_recent, tw->tw_bound_dev_if, tcp_twsk_md5_key(tcptw), +- tw->tw_tclass, cpu_to_be32(tw->tw_flowlabel), tw->tw_priority); ++ tw->tw_tclass, cpu_to_be32(tw->tw_flowlabel), tw->tw_priority, mptcp); + + inet_twsk_put(tw); + } + +-static void tcp_v6_reqsk_send_ack(const struct sock *sk, struct sk_buff *skb, +- struct request_sock *req) ++void tcp_v6_reqsk_send_ack(const struct sock *sk, struct sk_buff *skb, ++ struct request_sock *req) + { + /* sk->sk_state == TCP_LISTEN -> for regular TCP_SYN_RECV + * sk->sk_state == TCP_SYN_RECV -> for Fast Open. +@@ -1063,18 +1100,18 @@ static void tcp_v6_reqsk_send_ack(const struct sock *sk, struct sk_buff *skb, + * exception of segments, MUST be right-shifted by + * Rcv.Wind.Shift bits: + */ +- tcp_v6_send_ack(sk, skb, (sk->sk_state == TCP_LISTEN) ? ++ tcp_v6_send_ack(sk, skb, (sk->sk_state == TCP_LISTEN || is_meta_sk(sk)) ? + tcp_rsk(req)->snt_isn + 1 : tcp_sk(sk)->snd_nxt, +- tcp_rsk(req)->rcv_nxt, ++ tcp_rsk(req)->rcv_nxt, 0, + req->rsk_rcv_wnd >> inet_rsk(req)->rcv_wscale, + tcp_time_stamp_raw() + tcp_rsk(req)->ts_off, + req->ts_recent, sk->sk_bound_dev_if, + tcp_v6_md5_do_lookup(sk, &ipv6_hdr(skb)->saddr), +- 0, 0, sk->sk_priority); ++ 0, 0, sk->sk_priority, 0); + } + + +-static struct sock *tcp_v6_cookie_check(struct sock *sk, struct sk_buff *skb) ++struct sock *tcp_v6_cookie_check(struct sock *sk, struct sk_buff *skb) + { + #ifdef CONFIG_SYN_COOKIES + const struct tcphdr *th = tcp_hdr(skb); +@@ -1100,7 +1137,7 @@ u16 tcp_v6_get_syncookie(struct sock *sk, struct ipv6hdr *iph, + return mss; + } + +-static int tcp_v6_conn_request(struct sock *sk, struct sk_buff *skb) ++int tcp_v6_conn_request(struct sock *sk, struct sk_buff *skb) + { + if (skb->protocol == htons(ETH_P_IP)) + return tcp_v4_conn_request(sk, skb); +@@ -1131,11 +1168,11 @@ static void tcp_v6_restore_cb(struct sk_buff *skb) + sizeof(struct inet6_skb_parm)); + } + +-static struct sock *tcp_v6_syn_recv_sock(const struct sock *sk, struct sk_buff *skb, +- struct request_sock *req, +- struct dst_entry *dst, +- struct request_sock *req_unhash, +- bool *own_req) ++struct sock *tcp_v6_syn_recv_sock(const struct sock *sk, struct sk_buff *skb, ++ struct request_sock *req, ++ struct dst_entry *dst, ++ struct request_sock *req_unhash, ++ bool *own_req) + { + struct inet_request_sock *ireq; + struct ipv6_pinfo *newnp; +@@ -1170,7 +1207,15 @@ static struct sock *tcp_v6_syn_recv_sock(const struct sock *sk, struct sk_buff * + + newnp->saddr = newsk->sk_v6_rcv_saddr; + +- inet_csk(newsk)->icsk_af_ops = &ipv6_mapped; ++#ifdef CONFIG_MPTCP ++ /* We must check on the request-socket because the listener ++ * socket's flag may have been changed halfway through. ++ */ ++ if (!inet_rsk(req)->saw_mpc) ++ inet_csk(newsk)->icsk_af_ops = &mptcp_v6_mapped; ++ else ++#endif ++ inet_csk(newsk)->icsk_af_ops = &ipv6_mapped; + newsk->sk_backlog_rcv = tcp_v4_do_rcv; + #ifdef CONFIG_TCP_MD5SIG + newtp->af_specific = &tcp_sock_ipv6_mapped_specific; +@@ -1217,6 +1262,14 @@ static struct sock *tcp_v6_syn_recv_sock(const struct sock *sk, struct sk_buff * + if (!newsk) + goto out_nonewsk; + ++#ifdef CONFIG_MPTCP ++ /* If the meta_sk is v6-mapped we can end up here with the wrong af_ops. ++ * Just make sure that this subflow is v6. ++ */ ++ if (is_meta_sk(sk)) ++ inet_csk(newsk)->icsk_af_ops = &mptcp_v6_specific; ++#endif ++ + /* + * No need to charge this sock to the relevant IPv6 refcnt debug socks + * count here, tcp_create_openreq_child now does this for us, see the +@@ -1344,7 +1397,7 @@ static struct sock *tcp_v6_syn_recv_sock(const struct sock *sk, struct sk_buff * + * This is because we cannot sleep with the original spinlock + * held. + */ +-static int tcp_v6_do_rcv(struct sock *sk, struct sk_buff *skb) ++int tcp_v6_do_rcv(struct sock *sk, struct sk_buff *skb) + { + struct ipv6_pinfo *np = tcp_inet6_sk(sk); + struct sk_buff *opt_skb = NULL; +@@ -1361,6 +1414,9 @@ static int tcp_v6_do_rcv(struct sock *sk, struct sk_buff *skb) + if (skb->protocol == htons(ETH_P_IP)) + return tcp_v4_do_rcv(sk, skb); + ++ if (is_meta_sk(sk)) ++ return mptcp_v6_do_rcv(sk, skb); ++ + /* + * socket locking is here for SMP purposes as backlog rcv + * is currently called with bh processing disabled. +@@ -1488,6 +1544,10 @@ static void tcp_v6_fill_cb(struct sk_buff *skb, const struct ipv6hdr *hdr, + TCP_SKB_CB(skb)->end_seq = (TCP_SKB_CB(skb)->seq + th->syn + th->fin + + skb->len - th->doff*4); + TCP_SKB_CB(skb)->ack_seq = ntohl(th->ack_seq); ++#ifdef CONFIG_MPTCP ++ TCP_SKB_CB(skb)->mptcp_flags = 0; ++ TCP_SKB_CB(skb)->dss_off = 0; ++#endif + TCP_SKB_CB(skb)->tcp_flags = tcp_flag_byte(th); + TCP_SKB_CB(skb)->tcp_tw_isn = 0; + TCP_SKB_CB(skb)->ip_dsfield = ipv6_get_dsfield(hdr); +@@ -1502,8 +1562,8 @@ INDIRECT_CALLABLE_SCOPE int tcp_v6_rcv(struct sk_buff *skb) + int sdif = inet6_sdif(skb); + const struct tcphdr *th; + const struct ipv6hdr *hdr; ++ struct sock *sk, *meta_sk = NULL; + bool refcounted; +- struct sock *sk; + int ret; + struct net *net = dev_net(skb->dev); + +@@ -1557,12 +1617,17 @@ INDIRECT_CALLABLE_SCOPE int tcp_v6_rcv(struct sk_buff *skb) + reqsk_put(req); + goto csum_error; + } +- if (unlikely(sk->sk_state != TCP_LISTEN)) { ++ if (unlikely(sk->sk_state != TCP_LISTEN && !is_meta_sk(sk))) { ++ inet_csk_reqsk_queue_drop_and_put(sk, req); ++ goto lookup; ++ } ++ if (unlikely(is_meta_sk(sk) && !mptcp_can_new_subflow(sk))) { + inet_csk_reqsk_queue_drop_and_put(sk, req); + goto lookup; + } + sock_hold(sk); + refcounted = true; ++ + nsk = NULL; + if (!tcp_filter(sk, skb)) { + th = (const struct tcphdr *)skb->data; +@@ -1621,19 +1686,28 @@ INDIRECT_CALLABLE_SCOPE int tcp_v6_rcv(struct sk_buff *skb) + + sk_incoming_cpu_update(sk); + +- bh_lock_sock_nested(sk); ++ if (mptcp(tcp_sk(sk))) { ++ meta_sk = mptcp_meta_sk(sk); ++ ++ bh_lock_sock_nested(meta_sk); ++ if (sock_owned_by_user(meta_sk)) ++ mptcp_prepare_for_backlog(sk, skb); ++ } else { ++ meta_sk = sk; ++ bh_lock_sock_nested(sk); ++ } + tcp_segs_in(tcp_sk(sk), skb); + ret = 0; +- if (!sock_owned_by_user(sk)) { ++ if (!sock_owned_by_user(meta_sk)) { + skb_to_free = sk->sk_rx_skb_cache; + sk->sk_rx_skb_cache = NULL; + ret = tcp_v6_do_rcv(sk, skb); + } else { +- if (tcp_add_backlog(sk, skb)) ++ if (tcp_add_backlog(meta_sk, skb)) + goto discard_and_relse; + skb_to_free = NULL; + } +- bh_unlock_sock(sk); ++ bh_unlock_sock(meta_sk); + if (skb_to_free) + __kfree_skb(skb_to_free); + put_and_return: +@@ -1647,6 +1721,19 @@ INDIRECT_CALLABLE_SCOPE int tcp_v6_rcv(struct sk_buff *skb) + + tcp_v6_fill_cb(skb, hdr, th); + ++#ifdef CONFIG_MPTCP ++ if (!sk && th->syn && !th->ack) { ++ int ret = mptcp_lookup_join(skb, NULL); ++ ++ if (ret < 0) { ++ tcp_v6_send_reset(NULL, skb); ++ goto discard_it; ++ } else if (ret > 0) { ++ return 0; ++ } ++ } ++#endif ++ + if (tcp_checksum_complete(skb)) { + csum_error: + __TCP_INC_STATS(net, TCP_MIB_CSUMERRORS); +@@ -1699,6 +1786,18 @@ INDIRECT_CALLABLE_SCOPE int tcp_v6_rcv(struct sk_buff *skb) + refcounted = false; + goto process; + } ++#ifdef CONFIG_MPTCP ++ if (th->syn && !th->ack) { ++ int ret = mptcp_lookup_join(skb, inet_twsk(sk)); ++ ++ if (ret < 0) { ++ tcp_v6_send_reset(NULL, skb); ++ goto discard_it; ++ } else if (ret > 0) { ++ return 0; ++ } ++ } ++#endif + } + /* to ACK */ + /* fall through */ +@@ -1753,13 +1852,13 @@ INDIRECT_CALLABLE_SCOPE void tcp_v6_early_demux(struct sk_buff *skb) + } + } + +-static struct timewait_sock_ops tcp6_timewait_sock_ops = { ++struct timewait_sock_ops tcp6_timewait_sock_ops = { + .twsk_obj_size = sizeof(struct tcp6_timewait_sock), + .twsk_unique = tcp_twsk_unique, + .twsk_destructor = tcp_twsk_destructor, + }; + +-static const struct inet_connection_sock_af_ops ipv6_specific = { ++const struct inet_connection_sock_af_ops ipv6_specific = { + .queue_xmit = inet6_csk_xmit, + .send_check = tcp_v6_send_check, + .rebuild_header = inet6_sk_rebuild_header, +@@ -1790,7 +1889,7 @@ INDIRECT_CALLABLE_SCOPE void tcp_v6_early_demux(struct sk_buff *skb) + /* + * TCP over IPv4 via INET6 API + */ +-static const struct inet_connection_sock_af_ops ipv6_mapped = { ++const struct inet_connection_sock_af_ops ipv6_mapped = { + .queue_xmit = ip_queue_xmit, + .send_check = tcp_v4_send_check, + .rebuild_header = inet_sk_rebuild_header, +@@ -1826,7 +1925,12 @@ static int tcp_v6_init_sock(struct sock *sk) + + tcp_init_sock(sk); + +- icsk->icsk_af_ops = &ipv6_specific; ++#ifdef CONFIG_MPTCP ++ if (sock_flag(sk, SOCK_MPTCP)) ++ icsk->icsk_af_ops = &mptcp_v6_specific; ++ else ++#endif ++ icsk->icsk_af_ops = &ipv6_specific; + + #ifdef CONFIG_TCP_MD5SIG + tcp_sk(sk)->af_specific = &tcp_sock_ipv6_specific; +@@ -1835,7 +1939,7 @@ static int tcp_v6_init_sock(struct sock *sk) + return 0; + } + +-static void tcp_v6_destroy_sock(struct sock *sk) ++void tcp_v6_destroy_sock(struct sock *sk) + { + tcp_v4_destroy_sock(sk); + inet6_destroy_sock(sk); +@@ -2058,6 +2162,11 @@ struct proto tcpv6_prot = { + .sysctl_rmem_offset = offsetof(struct net, ipv4.sysctl_tcp_rmem), + .max_header = MAX_TCP_HEADER, + .obj_size = sizeof(struct tcp6_sock), ++#ifdef CONFIG_MPTCP ++ .useroffset = offsetof(struct tcp_sock, mptcp_sched_name), ++ .usersize = sizeof_field(struct tcp_sock, mptcp_sched_name) + ++ sizeof_field(struct tcp_sock, mptcp_pm_name), ++#endif + .slab_flags = SLAB_TYPESAFE_BY_RCU, + .twsk_prot = &tcp6_timewait_sock_ops, + .rsk_prot = &tcp6_request_sock_ops, +@@ -2068,6 +2177,9 @@ struct proto tcpv6_prot = { + .compat_getsockopt = compat_tcp_getsockopt, + #endif + .diag_destroy = tcp_abort, ++#ifdef CONFIG_MPTCP ++ .clear_sk = mptcp_clear_sk, ++#endif + }; + + /* thinking of making this const? Don't. +diff --git a/net/mptcp/Kconfig b/net/mptcp/Kconfig +new file mode 100644 +index 000000000000..6e05dab4c632 +--- /dev/null ++++ b/net/mptcp/Kconfig +@@ -0,0 +1,154 @@ ++# ++# MPTCP configuration ++# ++config MPTCP ++ bool "MPTCP protocol" ++ depends on (IPV6=y || IPV6=n) ++ select CRYPTO_LIB_SHA256 ++ select CRYPTO ++ ---help--- ++ This replaces the normal TCP stack with a Multipath TCP stack, ++ able to use several paths at once. ++ ++menuconfig MPTCP_PM_ADVANCED ++ bool "MPTCP: advanced path-manager control" ++ depends on MPTCP=y ++ ---help--- ++ Support for selection of different path-managers. You should choose 'Y' here, ++ because otherwise you will not actively create new MPTCP-subflows. ++ ++if MPTCP_PM_ADVANCED ++ ++config MPTCP_FULLMESH ++ tristate "MPTCP Full-Mesh Path-Manager" ++ depends on MPTCP=y ++ ---help--- ++ This path-management module will create a full-mesh among all IP-addresses. ++ ++config MPTCP_NDIFFPORTS ++ tristate "MPTCP ndiff-ports" ++ depends on MPTCP=y ++ ---help--- ++ This path-management module will create multiple subflows between the same ++ pair of IP-addresses, modifying the source-port. You can set the number ++ of subflows via the mptcp_ndiffports-sysctl. ++ ++config MPTCP_BINDER ++ tristate "MPTCP Binder" ++ depends on (MPTCP=y) ++ ---help--- ++ This path-management module works like ndiffports, and adds the sysctl ++ option to set the gateway (and/or path to) per each additional subflow ++ via Loose Source Routing (IPv4 only). ++ ++config MPTCP_NETLINK ++ tristate "MPTCP Netlink Path-Manager" ++ depends on MPTCP=y ++ ---help--- ++ This path-management module is controlled over a Netlink interface. A userspace ++ module can therefore control the establishment of new subflows and the policy ++ to apply over those new subflows for every connection. ++ ++choice ++ prompt "Default MPTCP Path-Manager" ++ default DEFAULT_DUMMY ++ help ++ Select the Path-Manager of your choice ++ ++ config DEFAULT_FULLMESH ++ bool "Full mesh" if MPTCP_FULLMESH=y ++ ++ config DEFAULT_NDIFFPORTS ++ bool "ndiff-ports" if MPTCP_NDIFFPORTS=y ++ ++ config DEFAULT_BINDER ++ bool "binder" if MPTCP_BINDER=y ++ ++ config DEFAULT_NETLINK ++ bool "Netlink" if MPTCP_NETLINK=y ++ ++ config DEFAULT_DUMMY ++ bool "Default" ++ ++endchoice ++ ++endif ++ ++config DEFAULT_MPTCP_PM ++ string ++ default "default" if DEFAULT_DUMMY ++ default "fullmesh" if DEFAULT_FULLMESH ++ default "ndiffports" if DEFAULT_NDIFFPORTS ++ default "binder" if DEFAULT_BINDER ++ default "default" ++ ++menuconfig MPTCP_SCHED_ADVANCED ++ bool "MPTCP: advanced scheduler control" ++ depends on MPTCP=y ++ ---help--- ++ Support for selection of different schedulers. You should choose 'Y' here, ++ if you want to choose a different scheduler than the default one. ++ ++if MPTCP_SCHED_ADVANCED ++ ++config MPTCP_BLEST ++ tristate "MPTCP BLEST" ++ depends on MPTCP=y ++ ---help--- ++ This is an experimental BLocking ESTimation-based (BLEST) scheduler. ++ ++config MPTCP_ROUNDROBIN ++ tristate "MPTCP Round-Robin" ++ depends on (MPTCP=y) ++ ---help--- ++ This is a very simple round-robin scheduler. Probably has bad performance ++ but might be interesting for researchers. ++ ++config MPTCP_REDUNDANT ++ tristate "MPTCP Redundant" ++ depends on (MPTCP=y) ++ ---help--- ++ This scheduler sends all packets redundantly over all subflows to decreases ++ latency and jitter on the cost of lower throughput. ++ ++config MPTCP_ECF ++ tristate "MPTCP ECF" ++ depends on (MPTCP=y) ++ ---help--- ++ This is an experimental Earliest Completion First (ECF) scheduler. ++ ++choice ++ prompt "Default MPTCP Scheduler" ++ default DEFAULT_SCHEDULER ++ help ++ Select the Scheduler of your choice ++ ++ config DEFAULT_SCHEDULER ++ bool "Default" ++ ---help--- ++ This is the default scheduler, sending first on the subflow ++ with the lowest RTT. ++ ++ config DEFAULT_ROUNDROBIN ++ bool "Round-Robin" if MPTCP_ROUNDROBIN=y ++ ---help--- ++ This is the round-rob scheduler, sending in a round-robin ++ fashion.. ++ ++ config DEFAULT_REDUNDANT ++ bool "Redundant" if MPTCP_REDUNDANT=y ++ ---help--- ++ This is the redundant scheduler, sending packets redundantly over ++ all the subflows. ++ ++endchoice ++endif ++ ++config DEFAULT_MPTCP_SCHED ++ string ++ depends on (MPTCP=y) ++ default "default" if DEFAULT_SCHEDULER ++ default "roundrobin" if DEFAULT_ROUNDROBIN ++ default "redundant" if DEFAULT_REDUNDANT ++ default "default" ++ +diff --git a/net/mptcp/Makefile b/net/mptcp/Makefile +new file mode 100644 +index 000000000000..369248a2f68e +--- /dev/null ++++ b/net/mptcp/Makefile +@@ -0,0 +1,25 @@ ++# ++## Makefile for MultiPath TCP support code. ++# ++# ++ ++obj-$(CONFIG_MPTCP) += mptcp.o ++ ++mptcp-y := mptcp_ctrl.o mptcp_ipv4.o mptcp_pm.o \ ++ mptcp_output.o mptcp_input.o mptcp_sched.o ++ ++obj-$(CONFIG_TCP_CONG_LIA) += mptcp_coupled.o ++obj-$(CONFIG_TCP_CONG_OLIA) += mptcp_olia.o ++obj-$(CONFIG_TCP_CONG_WVEGAS) += mptcp_wvegas.o ++obj-$(CONFIG_TCP_CONG_BALIA) += mptcp_balia.o ++obj-$(CONFIG_TCP_CONG_MCTCPDESYNC) += mctcp_desync.o ++obj-$(CONFIG_MPTCP_FULLMESH) += mptcp_fullmesh.o ++obj-$(CONFIG_MPTCP_NDIFFPORTS) += mptcp_ndiffports.o ++obj-$(CONFIG_MPTCP_BINDER) += mptcp_binder.o ++obj-$(CONFIG_MPTCP_NETLINK) += mptcp_netlink.o ++obj-$(CONFIG_MPTCP_ROUNDROBIN) += mptcp_rr.o ++obj-$(CONFIG_MPTCP_REDUNDANT) += mptcp_redundant.o ++obj-$(CONFIG_MPTCP_BLEST) += mptcp_blest.o ++obj-$(CONFIG_MPTCP_ECF) += mptcp_ecf.o ++ ++mptcp-$(subst m,y,$(CONFIG_IPV6)) += mptcp_ipv6.o +diff --git a/net/mptcp/mctcp_desync.c b/net/mptcp/mctcp_desync.c +new file mode 100644 +index 000000000000..f6bf9251d59b +--- /dev/null ++++ b/net/mptcp/mctcp_desync.c +@@ -0,0 +1,193 @@ ++/* ++ * Desynchronized Multi-Channel TCP Congestion Control Algorithm ++ * ++ * Implementation based on publications of "DMCTCP:Desynchronized Multi-Channel ++ * TCP for high speed access networks with tiny buffers" in 23rd international ++ * conference of Computer Communication and Networks (ICCCN), 2014, and ++ * "Exploring parallelism and desynchronization of TCP over high speed networks ++ * with tiny buffers" in Journal of Computer Communications Elsevier, 2015. ++ * ++ * http://ieeexplore.ieee.org/abstract/document/6911722/ ++ * https://doi.org/10.1016/j.comcom.2015.07.010 ++ * ++ * This prototype is for research purpose and is currently experimental code ++ * that only support a single path. Future support of multi-channel over ++ * multi-path requires channels grouping. ++ * ++ * Initial Design and Implementation: ++ * Cheng Cui ++ * ++ * This program is free software; you can redistribute it and/or modify it ++ * under the terms of the GNU General Public License as published by the Free ++ * Software Foundation; either version 2 of the License, or (at your option) ++ * any later version. ++ */ ++#include ++#include ++#include ++ ++enum { ++ MASTER_CHANNEL = 1, ++ INI_MIN_CWND = 2, ++}; ++ ++/* private congestion control structure: ++ * off_tstamp: the last backoff timestamp for loss synchronization event ++ * off_subfid: the subflow which was backoff on off_tstamp ++ */ ++struct mctcp_desync { ++ u64 off_tstamp; ++ u8 off_subfid; ++}; ++ ++static inline int mctcp_cc_sk_can_send(const struct sock *sk) ++{ ++ return mptcp_sk_can_send(sk) && tcp_sk(sk)->srtt_us; ++} ++ ++static void mctcp_desync_init(struct sock *sk) ++{ ++ if (mptcp(tcp_sk(sk))) { ++ struct mctcp_desync *ca = inet_csk_ca(mptcp_meta_sk(sk)); ++ ca->off_tstamp = 0; ++ ca->off_subfid = 0; ++ } ++ /* If we do not mptcp, behave like reno: return */ ++} ++ ++static void mctcp_desync_cong_avoid(struct sock *sk, u32 ack, u32 acked) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ ++ if (!mptcp(tp)) { ++ tcp_reno_cong_avoid(sk, ack, acked); ++ return; ++ } else if (!tcp_is_cwnd_limited(sk)) { ++ return; ++ } else { ++ const struct mctcp_desync *ca = inet_csk_ca(mptcp_meta_sk(sk)); ++ const u8 subfid = tp->mptcp->path_index; ++ ++ /* current aggregated cwnd */ ++ u32 agg_cwnd = 0; ++ u32 min_cwnd = 0xffffffff; ++ u8 min_cwnd_subfid = 0; ++ ++ /* In "safe" area, increase */ ++ if (tcp_in_slow_start(tp)) { ++ if (ca->off_subfid) { ++ /* passed initial phase, allow slow start */ ++ tcp_slow_start(tp, acked); ++ } else if (MASTER_CHANNEL == tp->mptcp->path_index) { ++ /* master channel is normal slow start in ++ * initial phase */ ++ tcp_slow_start(tp, acked); ++ } else { ++ /* secondary channels increase slowly until ++ * the initial phase passed ++ */ ++ tp->snd_ssthresh = tp->snd_cwnd = INI_MIN_CWND; ++ } ++ return; ++ } else { ++ /* In dangerous area, increase slowly and linearly. */ ++ const struct mptcp_tcp_sock *mptcp; ++ ++ /* get total cwnd and the subflow that has min cwnd */ ++ mptcp_for_each_sub(tp->mpcb, mptcp) { ++ const struct sock *sub_sk = mptcp_to_sock(mptcp); ++ ++ if (mctcp_cc_sk_can_send(sub_sk)) { ++ const struct tcp_sock *sub_tp = ++ tcp_sk(sub_sk); ++ agg_cwnd += sub_tp->snd_cwnd; ++ if(min_cwnd > sub_tp->snd_cwnd) { ++ min_cwnd = sub_tp->snd_cwnd; ++ min_cwnd_subfid = ++ sub_tp->mptcp->path_index; ++ } ++ } ++ } ++ /* the smallest subflow grows faster than others */ ++ if (subfid == min_cwnd_subfid) { ++ tcp_cong_avoid_ai(tp, min_cwnd, acked); ++ } else { ++ tcp_cong_avoid_ai(tp, agg_cwnd - min_cwnd, ++ acked); ++ } ++ } ++ } ++} ++ ++static u32 mctcp_desync_ssthresh(struct sock *sk) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ ++ if (!mptcp(tp)) { ++ return max(tp->snd_cwnd >> 1U, 2U); ++ } else { ++ struct mctcp_desync *ca = inet_csk_ca(mptcp_meta_sk(sk)); ++ const u8 subfid = tp->mptcp->path_index; ++ const struct mptcp_tcp_sock *mptcp; ++ u32 max_cwnd = 0; ++ u8 max_cwnd_subfid = 0; ++ ++ /* Find the subflow that has the max cwnd. */ ++ mptcp_for_each_sub(tp->mpcb, mptcp) { ++ const struct sock *sub_sk = mptcp_to_sock(mptcp); ++ ++ if (mctcp_cc_sk_can_send(sub_sk)) { ++ const struct tcp_sock *sub_tp = tcp_sk(sub_sk); ++ if (max_cwnd < sub_tp->snd_cwnd) { ++ max_cwnd = sub_tp->snd_cwnd; ++ max_cwnd_subfid = ++ sub_tp->mptcp->path_index; ++ } ++ } ++ } ++ /* Use high resolution clock. */ ++ if (subfid == max_cwnd_subfid) { ++ u64 now = tcp_clock_us(); ++ u32 delta = tcp_stamp_us_delta(now, ca->off_tstamp); ++ ++ if (delta < (tp->srtt_us >> 3)) { ++ /* desynchronize */ ++ return tp->snd_cwnd; ++ } else { ++ ca->off_tstamp = now; ++ ca->off_subfid = subfid; ++ return max(max_cwnd >> 1U, 2U); ++ } ++ } else { ++ return tp->snd_cwnd; ++ } ++ } ++} ++ ++static struct tcp_congestion_ops mctcp_desync = { ++ .init = mctcp_desync_init, ++ .ssthresh = mctcp_desync_ssthresh, ++ .undo_cwnd = tcp_reno_undo_cwnd, ++ .cong_avoid = mctcp_desync_cong_avoid, ++ .owner = THIS_MODULE, ++ .name = "mctcpdesync", ++}; ++ ++static int __init mctcp_desync_register(void) ++{ ++ BUILD_BUG_ON(sizeof(struct mctcp_desync) > ICSK_CA_PRIV_SIZE); ++ return tcp_register_congestion_control(&mctcp_desync); ++} ++ ++static void __exit mctcp_desync_unregister(void) ++{ ++ tcp_unregister_congestion_control(&mctcp_desync); ++} ++ ++module_init(mctcp_desync_register); ++module_exit(mctcp_desync_unregister); ++ ++MODULE_AUTHOR("Cheng Cui"); ++MODULE_LICENSE("GPL"); ++MODULE_DESCRIPTION("MCTCP: DESYNCHRONIZED MULTICHANNEL TCP CONGESTION CONTROL"); ++MODULE_VERSION("1.0"); +diff --git a/net/mptcp/mptcp_balia.c b/net/mptcp/mptcp_balia.c +new file mode 100644 +index 000000000000..179b53dea020 +--- /dev/null ++++ b/net/mptcp/mptcp_balia.c +@@ -0,0 +1,261 @@ ++/* ++ * MPTCP implementation - Balia Congestion Control ++ * (Balanced Linked Adaptation Algorithm) ++ * ++ * Analysis, Design and Implementation: ++ * Qiuyu Peng ++ * Anwar Walid ++ * Jaehyun Hwang ++ * Steven H. Low ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License ++ * as published by the Free Software Foundation; either version ++ * 2 of the License, or (at your option) any later version. ++ */ ++ ++#include ++#include ++ ++#include ++ ++/* The variable 'rate' (i.e., x_r) will be scaled ++ * e.g., from B/s to KB/s, MB/s, or GB/s ++ * if max_rate > 2^rate_scale_limit ++ */ ++ ++static int rate_scale_limit = 25; ++static int alpha_scale = 10; ++static int scale_num = 5; ++ ++struct mptcp_balia { ++ u64 ai; ++ u64 md; ++ bool forced_update; ++}; ++ ++static inline int mptcp_balia_sk_can_send(const struct sock *sk) ++{ ++ return mptcp_sk_can_send(sk) && tcp_sk(sk)->srtt_us; ++} ++ ++static inline u64 mptcp_get_ai(const struct sock *meta_sk) ++{ ++ return ((struct mptcp_balia *)inet_csk_ca(meta_sk))->ai; ++} ++ ++static inline void mptcp_set_ai(const struct sock *meta_sk, u64 ai) ++{ ++ ((struct mptcp_balia *)inet_csk_ca(meta_sk))->ai = ai; ++} ++ ++static inline u64 mptcp_get_md(const struct sock *meta_sk) ++{ ++ return ((struct mptcp_balia *)inet_csk_ca(meta_sk))->md; ++} ++ ++static inline void mptcp_set_md(const struct sock *meta_sk, u64 md) ++{ ++ ((struct mptcp_balia *)inet_csk_ca(meta_sk))->md = md; ++} ++ ++static inline u64 mptcp_balia_scale(u64 val, int scale) ++{ ++ return (u64) val << scale; ++} ++ ++static inline bool mptcp_get_forced(const struct sock *meta_sk) ++{ ++ return ((struct mptcp_balia *)inet_csk_ca(meta_sk))->forced_update; ++} ++ ++static inline void mptcp_set_forced(const struct sock *meta_sk, bool force) ++{ ++ ((struct mptcp_balia *)inet_csk_ca(meta_sk))->forced_update = force; ++} ++ ++static void mptcp_balia_recalc_ai(const struct sock *sk) ++{ ++ const struct tcp_sock *tp = tcp_sk(sk); ++ const struct mptcp_cb *mpcb = tp->mpcb; ++ struct mptcp_tcp_sock *mptcp; ++ u64 max_rate = 0, rate = 0, sum_rate = 0; ++ u64 alpha, ai = tp->snd_cwnd, md = (tp->snd_cwnd >> 1); ++ int num_scale_down = 0; ++ ++ if (!mpcb) ++ return; ++ ++ /* Find max_rate first */ ++ mptcp_for_each_sub(mpcb, mptcp) { ++ const struct sock *sub_sk = mptcp_to_sock(mptcp); ++ struct tcp_sock *sub_tp = tcp_sk(sub_sk); ++ u64 tmp; ++ ++ if (!mptcp_balia_sk_can_send(sub_sk)) ++ continue; ++ ++ tmp = div_u64((u64)tp->mss_cache * sub_tp->snd_cwnd ++ * (USEC_PER_SEC << 3), sub_tp->srtt_us); ++ sum_rate += tmp; ++ ++ if (tp == sub_tp) ++ rate = tmp; ++ ++ if (tmp >= max_rate) ++ max_rate = tmp; ++ } ++ ++ /* At least, the current subflow should be able to send */ ++ if (unlikely(!rate)) ++ goto exit; ++ ++ alpha = div64_u64(max_rate, rate); ++ ++ /* Scale down max_rate if it is too high (e.g., >2^25) */ ++ while (max_rate > mptcp_balia_scale(1, rate_scale_limit)) { ++ max_rate >>= scale_num; ++ num_scale_down++; ++ } ++ ++ if (num_scale_down) { ++ sum_rate = 0; ++ mptcp_for_each_sub(mpcb, mptcp) { ++ const struct sock *sub_sk = mptcp_to_sock(mptcp); ++ struct tcp_sock *sub_tp = tcp_sk(sub_sk); ++ u64 tmp; ++ ++ if (!mptcp_balia_sk_can_send(sub_sk)) ++ continue; ++ ++ tmp = div_u64((u64)tp->mss_cache * sub_tp->snd_cwnd ++ * (USEC_PER_SEC << 3), sub_tp->srtt_us); ++ tmp >>= (scale_num * num_scale_down); ++ ++ sum_rate += tmp; ++ } ++ rate >>= (scale_num * num_scale_down); ++ } ++ ++ /* (sum_rate)^2 * 10 * w_r ++ * ai = ------------------------------------ ++ * (x_r + max_rate) * (4x_r + max_rate) ++ */ ++ sum_rate *= sum_rate; ++ ++ ai = div64_u64(sum_rate * 10, rate + max_rate); ++ ai = div64_u64(ai * tp->snd_cwnd, (rate << 2) + max_rate); ++ ++ if (unlikely(!ai)) ++ ai = tp->snd_cwnd; ++ ++ md = ((tp->snd_cwnd >> 1) * min(mptcp_balia_scale(alpha, alpha_scale), ++ mptcp_balia_scale(3, alpha_scale) >> 1)) ++ >> alpha_scale; ++ ++exit: ++ mptcp_set_ai(sk, ai); ++ mptcp_set_md(sk, md); ++} ++ ++static void mptcp_balia_init(struct sock *sk) ++{ ++ if (mptcp(tcp_sk(sk))) { ++ mptcp_set_forced(sk, 0); ++ mptcp_set_ai(sk, 0); ++ mptcp_set_md(sk, 0); ++ } ++} ++ ++static void mptcp_balia_cwnd_event(struct sock *sk, enum tcp_ca_event event) ++{ ++ if (event == CA_EVENT_COMPLETE_CWR || event == CA_EVENT_LOSS) ++ mptcp_balia_recalc_ai(sk); ++} ++ ++static void mptcp_balia_set_state(struct sock *sk, u8 ca_state) ++{ ++ if (!mptcp(tcp_sk(sk))) ++ return; ++ ++ mptcp_set_forced(sk, 1); ++} ++ ++static void mptcp_balia_cong_avoid(struct sock *sk, u32 ack, u32 acked) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ int snd_cwnd; ++ ++ if (!mptcp(tp)) { ++ tcp_reno_cong_avoid(sk, ack, acked); ++ return; ++ } ++ ++ if (!tcp_is_cwnd_limited(sk)) ++ return; ++ ++ if (tcp_in_slow_start(tp)) { ++ /* In "safe" area, increase. */ ++ tcp_slow_start(tp, acked); ++ mptcp_balia_recalc_ai(sk); ++ return; ++ } ++ ++ if (mptcp_get_forced(mptcp_meta_sk(sk))) { ++ mptcp_balia_recalc_ai(sk); ++ mptcp_set_forced(sk, 0); ++ } ++ ++ snd_cwnd = (int)mptcp_get_ai(sk); ++ ++ if (tp->snd_cwnd_cnt >= snd_cwnd) { ++ if (tp->snd_cwnd < tp->snd_cwnd_clamp) { ++ tp->snd_cwnd++; ++ mptcp_balia_recalc_ai(sk); ++ } ++ ++ tp->snd_cwnd_cnt = 0; ++ } else { ++ tp->snd_cwnd_cnt++; ++ } ++} ++ ++static u32 mptcp_balia_ssthresh(struct sock *sk) ++{ ++ const struct tcp_sock *tp = tcp_sk(sk); ++ ++ if (unlikely(!mptcp(tp))) ++ return tcp_reno_ssthresh(sk); ++ else ++ return max((u32)(tp->snd_cwnd - mptcp_get_md(sk)), 1U); ++} ++ ++static struct tcp_congestion_ops mptcp_balia = { ++ .init = mptcp_balia_init, ++ .ssthresh = mptcp_balia_ssthresh, ++ .cong_avoid = mptcp_balia_cong_avoid, ++ .undo_cwnd = tcp_reno_undo_cwnd, ++ .cwnd_event = mptcp_balia_cwnd_event, ++ .set_state = mptcp_balia_set_state, ++ .owner = THIS_MODULE, ++ .name = "balia", ++}; ++ ++static int __init mptcp_balia_register(void) ++{ ++ BUILD_BUG_ON(sizeof(struct mptcp_balia) > ICSK_CA_PRIV_SIZE); ++ return tcp_register_congestion_control(&mptcp_balia); ++} ++ ++static void __exit mptcp_balia_unregister(void) ++{ ++ tcp_unregister_congestion_control(&mptcp_balia); ++} ++ ++module_init(mptcp_balia_register); ++module_exit(mptcp_balia_unregister); ++ ++MODULE_AUTHOR("Jaehyun Hwang, Anwar Walid, Qiuyu Peng, Steven H. Low"); ++MODULE_LICENSE("GPL"); ++MODULE_DESCRIPTION("MPTCP BALIA CONGESTION CONTROL ALGORITHM"); ++MODULE_VERSION("0.1"); +diff --git a/net/mptcp/mptcp_binder.c b/net/mptcp/mptcp_binder.c +new file mode 100644 +index 000000000000..7f34a8d00274 +--- /dev/null ++++ b/net/mptcp/mptcp_binder.c +@@ -0,0 +1,494 @@ ++#include ++ ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define MPTCP_GW_MAX_LISTS 10 ++#define MPTCP_GW_LIST_MAX_LEN 6 ++#define MPTCP_GW_SYSCTL_MAX_LEN (15 * MPTCP_GW_LIST_MAX_LEN * \ ++ MPTCP_GW_MAX_LISTS) ++ ++struct mptcp_gw_list { ++ struct in_addr list[MPTCP_GW_MAX_LISTS][MPTCP_GW_LIST_MAX_LEN]; ++ u8 len[MPTCP_GW_MAX_LISTS]; ++}; ++ ++struct binder_priv { ++ /* Worker struct for subflow establishment */ ++ struct work_struct subflow_work; ++ ++ struct mptcp_cb *mpcb; ++ ++ /* Prevent multiple sub-sockets concurrently iterating over sockets */ ++ spinlock_t *flow_lock; ++}; ++ ++static struct mptcp_gw_list *mptcp_gws; ++static rwlock_t mptcp_gws_lock; ++ ++static int mptcp_binder_ndiffports __read_mostly = 1; ++ ++static char sysctl_mptcp_binder_gateways[MPTCP_GW_SYSCTL_MAX_LEN] __read_mostly; ++ ++static int mptcp_get_avail_list_ipv4(struct sock *sk) ++{ ++ int i, j, list_taken, opt_ret, opt_len; ++ unsigned char *opt_ptr, *opt_end_ptr, opt[MAX_IPOPTLEN]; ++ ++ for (i = 0; i < MPTCP_GW_MAX_LISTS; ++i) { ++ struct mptcp_tcp_sock *mptcp; ++ ++ if (mptcp_gws->len[i] == 0) ++ goto error; ++ ++ mptcp_debug("mptcp_get_avail_list_ipv4: List %i\n", i); ++ list_taken = 0; ++ ++ /* Loop through all sub-sockets in this connection */ ++ mptcp_for_each_sub(tcp_sk(sk)->mpcb, mptcp) { ++ sk = mptcp_to_sock(mptcp); ++ ++ mptcp_debug("mptcp_get_avail_list_ipv4: Next sock\n"); ++ ++ /* Reset length and options buffer, then retrieve ++ * from socket ++ */ ++ opt_len = MAX_IPOPTLEN; ++ memset(opt, 0, MAX_IPOPTLEN); ++ opt_ret = ip_getsockopt(sk, IPPROTO_IP, ++ IP_OPTIONS, (char __user *)opt, (int __user *)&opt_len); ++ if (opt_ret < 0) { ++ mptcp_debug("%s: MPTCP subsocket getsockopt() IP_OPTIONS failed, error %d\n", ++ __func__, opt_ret); ++ goto error; ++ } ++ ++ /* If socket has no options, it has no stake in this list */ ++ if (opt_len <= 0) ++ continue; ++ ++ /* Iterate options buffer */ ++ for (opt_ptr = &opt[0]; opt_ptr < &opt[opt_len]; opt_ptr++) { ++ if (*opt_ptr == IPOPT_LSRR) { ++ mptcp_debug("mptcp_get_avail_list_ipv4: LSRR options found\n"); ++ goto sock_lsrr; ++ } ++ } ++ continue; ++ ++sock_lsrr: ++ /* Pointer to the 2nd to last address */ ++ opt_end_ptr = opt_ptr+(*(opt_ptr+1))-4; ++ ++ /* Addresses start 3 bytes after type offset */ ++ opt_ptr += 3; ++ j = 0; ++ ++ /* Different length lists cannot be the same */ ++ if ((opt_end_ptr-opt_ptr)/4 != mptcp_gws->len[i]) ++ continue; ++ ++ /* Iterate if we are still inside options list ++ * and sysctl list ++ */ ++ while (opt_ptr < opt_end_ptr && j < mptcp_gws->len[i]) { ++ /* If there is a different address, this list must ++ * not be set on this socket ++ */ ++ if (memcmp(&mptcp_gws->list[i][j], opt_ptr, 4)) ++ break; ++ ++ /* Jump 4 bytes to next address */ ++ opt_ptr += 4; ++ j++; ++ } ++ ++ /* Reached the end without a differing address, lists ++ * are therefore identical. ++ */ ++ if (j == mptcp_gws->len[i]) { ++ mptcp_debug("mptcp_get_avail_list_ipv4: List already used\n"); ++ list_taken = 1; ++ break; ++ } ++ } ++ ++ /* Free list found if not taken by a socket */ ++ if (!list_taken) { ++ mptcp_debug("mptcp_get_avail_list_ipv4: List free\n"); ++ break; ++ } ++ } ++ ++ if (i >= MPTCP_GW_MAX_LISTS) ++ goto error; ++ ++ return i; ++error: ++ return -1; ++} ++ ++/* The list of addresses is parsed each time a new connection is opened, ++ * to make sure it's up to date. In case of error, all the lists are ++ * marked as unavailable and the subflow's fingerprint is set to 0. ++ */ ++static void mptcp_v4_add_lsrr(struct sock *sk, struct in_addr addr) ++{ ++ int i, j, ret; ++ unsigned char opt[MAX_IPOPTLEN] = {0}; ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct binder_priv *fmp = (struct binder_priv *)&tp->mpcb->mptcp_pm[0]; ++ ++ /* Read lock: multiple sockets can read LSRR addresses at the same ++ * time, but writes are done in mutual exclusion. ++ * Spin lock: must search for free list for one socket at a time, or ++ * multiple sockets could take the same list. ++ */ ++ read_lock(&mptcp_gws_lock); ++ spin_lock(fmp->flow_lock); ++ ++ i = mptcp_get_avail_list_ipv4(sk); ++ ++ /* Execution enters here only if a free path is found. ++ */ ++ if (i >= 0) { ++ opt[0] = IPOPT_NOP; ++ opt[1] = IPOPT_LSRR; ++ opt[2] = sizeof(mptcp_gws->list[i][0].s_addr) * ++ (mptcp_gws->len[i] + 1) + 3; ++ opt[3] = IPOPT_MINOFF; ++ for (j = 0; j < mptcp_gws->len[i]; ++j) ++ memcpy(opt + 4 + ++ (j * sizeof(mptcp_gws->list[i][0].s_addr)), ++ &mptcp_gws->list[i][j].s_addr, ++ sizeof(mptcp_gws->list[i][0].s_addr)); ++ /* Final destination must be part of IP_OPTIONS parameter. */ ++ memcpy(opt + 4 + (j * sizeof(addr.s_addr)), &addr.s_addr, ++ sizeof(addr.s_addr)); ++ ++ /* setsockopt must be inside the lock, otherwise another ++ * subflow could fail to see that we have taken a list. ++ */ ++ ret = ip_setsockopt(sk, IPPROTO_IP, IP_OPTIONS, (char __user *)opt, ++ 4 + sizeof(mptcp_gws->list[i][0].s_addr) * (mptcp_gws->len[i] + 1)); ++ ++ if (ret < 0) { ++ mptcp_debug("%s: MPTCP subsock setsockopt() IP_OPTIONS failed, error %d\n", ++ __func__, ret); ++ } ++ } ++ ++ spin_unlock(fmp->flow_lock); ++ read_unlock(&mptcp_gws_lock); ++ ++ return; ++} ++ ++/* Parses gateways string for a list of paths to different ++ * gateways, and stores them for use with the Loose Source Routing (LSRR) ++ * socket option. Each list must have "," separated addresses, and the lists ++ * themselves must be separated by "-". Returns -1 in case one or more of the ++ * addresses is not a valid ipv4/6 address. ++ */ ++static int mptcp_parse_gateway_ipv4(char *gateways) ++{ ++ int i, j, k, ret; ++ char *tmp_string = NULL; ++ struct in_addr tmp_addr; ++ ++ tmp_string = kzalloc(16, GFP_KERNEL); ++ if (tmp_string == NULL) ++ return -ENOMEM; ++ ++ write_lock(&mptcp_gws_lock); ++ ++ memset(mptcp_gws, 0, sizeof(struct mptcp_gw_list)); ++ ++ /* A TMP string is used since inet_pton needs a null terminated string ++ * but we do not want to modify the sysctl for obvious reasons. ++ * i will iterate over the SYSCTL string, j will iterate over the ++ * temporary string where each IP is copied into, k will iterate over ++ * the IPs in each list. ++ */ ++ for (i = j = k = 0; ++ i < MPTCP_GW_SYSCTL_MAX_LEN && k < MPTCP_GW_MAX_LISTS; ++ ++i) { ++ if (gateways[i] == '-' || gateways[i] == ',' || gateways[i] == '\0') { ++ /* If the temp IP is empty and the current list is ++ * empty, we are done. ++ */ ++ if (j == 0 && mptcp_gws->len[k] == 0) ++ break; ++ ++ /* Terminate the temp IP string, then if it is ++ * non-empty parse the IP and copy it. ++ */ ++ tmp_string[j] = '\0'; ++ if (j > 0) { ++ mptcp_debug("mptcp_parse_gateway_list tmp: %s i: %d\n", tmp_string, i); ++ ++ ret = in4_pton(tmp_string, strlen(tmp_string), ++ (u8 *)&tmp_addr.s_addr, '\0', ++ NULL); ++ ++ if (ret) { ++ mptcp_debug("mptcp_parse_gateway_list ret: %d s_addr: %pI4\n", ++ ret, ++ &tmp_addr.s_addr); ++ memcpy(&mptcp_gws->list[k][mptcp_gws->len[k]].s_addr, ++ &tmp_addr.s_addr, ++ sizeof(tmp_addr.s_addr)); ++ mptcp_gws->len[k]++; ++ j = 0; ++ tmp_string[j] = '\0'; ++ /* Since we can't impose a limit to ++ * what the user can input, make sure ++ * there are not too many IPs in the ++ * SYSCTL string. ++ */ ++ if (mptcp_gws->len[k] > MPTCP_GW_LIST_MAX_LEN) { ++ mptcp_debug("mptcp_parse_gateway_list too many members in list %i: max %i\n", ++ k, ++ MPTCP_GW_LIST_MAX_LEN); ++ goto error; ++ } ++ } else { ++ goto error; ++ } ++ } ++ ++ if (gateways[i] == '-' || gateways[i] == '\0') ++ ++k; ++ } else { ++ tmp_string[j] = gateways[i]; ++ ++j; ++ } ++ } ++ ++ /* Number of flows is number of gateway lists plus master flow */ ++ mptcp_binder_ndiffports = k+1; ++ ++ write_unlock(&mptcp_gws_lock); ++ kfree(tmp_string); ++ ++ return 0; ++ ++error: ++ memset(mptcp_gws, 0, sizeof(struct mptcp_gw_list)); ++ memset(gateways, 0, sizeof(char) * MPTCP_GW_SYSCTL_MAX_LEN); ++ write_unlock(&mptcp_gws_lock); ++ kfree(tmp_string); ++ return -1; ++} ++ ++/** ++ * Create all new subflows, by doing calls to mptcp_initX_subsockets ++ * ++ * This function uses a goto next_subflow, to allow releasing the lock between ++ * new subflows and giving other processes a chance to do some work on the ++ * socket and potentially finishing the communication. ++ **/ ++static void create_subflow_worker(struct work_struct *work) ++{ ++ const struct binder_priv *pm_priv = container_of(work, ++ struct binder_priv, ++ subflow_work); ++ struct mptcp_cb *mpcb = pm_priv->mpcb; ++ struct sock *meta_sk = mpcb->meta_sk; ++ int iter = 0; ++ ++next_subflow: ++ if (iter) { ++ release_sock(meta_sk); ++ mutex_unlock(&mpcb->mpcb_mutex); ++ ++ cond_resched(); ++ } ++ mutex_lock(&mpcb->mpcb_mutex); ++ lock_sock_nested(meta_sk, SINGLE_DEPTH_NESTING); ++ ++ if (!mptcp(tcp_sk(meta_sk))) ++ goto exit; ++ ++ iter++; ++ ++ if (sock_flag(meta_sk, SOCK_DEAD)) ++ goto exit; ++ ++ if (mpcb->master_sk && ++ !tcp_sk(mpcb->master_sk)->mptcp->fully_established) ++ goto exit; ++ ++ if (mptcp_binder_ndiffports > iter && ++ mptcp_binder_ndiffports > mptcp_subflow_count(mpcb)) { ++ struct mptcp_loc4 loc; ++ struct mptcp_rem4 rem; ++ ++ loc.addr.s_addr = inet_sk(meta_sk)->inet_saddr; ++ loc.loc4_id = 0; ++ loc.low_prio = 0; ++ ++ rem.addr.s_addr = inet_sk(meta_sk)->inet_daddr; ++ rem.port = inet_sk(meta_sk)->inet_dport; ++ rem.rem4_id = 0; /* Default 0 */ ++ ++ mptcp_init4_subsockets(meta_sk, &loc, &rem); ++ ++ goto next_subflow; ++ } ++ ++exit: ++ release_sock(meta_sk); ++ mutex_unlock(&mpcb->mpcb_mutex); ++ mptcp_mpcb_put(mpcb); ++ sock_put(meta_sk); ++} ++ ++static void binder_new_session(const struct sock *meta_sk) ++{ ++ struct mptcp_cb *mpcb = tcp_sk(meta_sk)->mpcb; ++ struct binder_priv *fmp = (struct binder_priv *)&mpcb->mptcp_pm[0]; ++ static DEFINE_SPINLOCK(flow_lock); ++ ++#if IS_ENABLED(CONFIG_IPV6) ++ if (meta_sk->sk_family == AF_INET6 && ++ !mptcp_v6_is_v4_mapped(meta_sk)) { ++ mptcp_fallback_default(mpcb); ++ return; ++ } ++#endif ++ ++ /* Initialize workqueue-struct */ ++ INIT_WORK(&fmp->subflow_work, create_subflow_worker); ++ fmp->mpcb = mpcb; ++ ++ fmp->flow_lock = &flow_lock; ++} ++ ++static void binder_create_subflows(struct sock *meta_sk) ++{ ++ struct mptcp_cb *mpcb = tcp_sk(meta_sk)->mpcb; ++ struct binder_priv *pm_priv = (struct binder_priv *)&mpcb->mptcp_pm[0]; ++ ++ if (mptcp_in_infinite_mapping_weak(mpcb) || ++ mpcb->server_side || sock_flag(meta_sk, SOCK_DEAD)) ++ return; ++ ++ if (!work_pending(&pm_priv->subflow_work)) { ++ sock_hold(meta_sk); ++ refcount_inc(&mpcb->mpcb_refcnt); ++ queue_work(mptcp_wq, &pm_priv->subflow_work); ++ } ++} ++ ++static int binder_get_local_id(const struct sock *meta_sk, sa_family_t family, ++ union inet_addr *addr, bool *low_prio) ++{ ++ return 0; ++} ++ ++/* Callback functions, executed when syctl mptcp.mptcp_gateways is updated. ++ * Inspired from proc_tcp_congestion_control(). ++ */ ++static int proc_mptcp_gateways(struct ctl_table *ctl, int write, ++ void __user *buffer, size_t *lenp, ++ loff_t *ppos) ++{ ++ int ret; ++ struct ctl_table tbl = { ++ .maxlen = MPTCP_GW_SYSCTL_MAX_LEN, ++ }; ++ ++ if (write) { ++ tbl.data = kzalloc(MPTCP_GW_SYSCTL_MAX_LEN, GFP_KERNEL); ++ if (tbl.data == NULL) ++ return -ENOMEM; ++ ret = proc_dostring(&tbl, write, buffer, lenp, ppos); ++ if (ret == 0) { ++ ret = mptcp_parse_gateway_ipv4(tbl.data); ++ memcpy(ctl->data, tbl.data, MPTCP_GW_SYSCTL_MAX_LEN); ++ } ++ kfree(tbl.data); ++ } else { ++ ret = proc_dostring(ctl, write, buffer, lenp, ppos); ++ } ++ ++ ++ return ret; ++} ++ ++static struct mptcp_pm_ops binder __read_mostly = { ++ .new_session = binder_new_session, ++ .fully_established = binder_create_subflows, ++ .get_local_id = binder_get_local_id, ++ .init_subsocket_v4 = mptcp_v4_add_lsrr, ++ .name = "binder", ++ .owner = THIS_MODULE, ++}; ++ ++static struct ctl_table binder_table[] = { ++ { ++ .procname = "mptcp_binder_gateways", ++ .data = &sysctl_mptcp_binder_gateways, ++ .maxlen = sizeof(char) * MPTCP_GW_SYSCTL_MAX_LEN, ++ .mode = 0644, ++ .proc_handler = &proc_mptcp_gateways ++ }, ++ { } ++}; ++ ++static struct ctl_table_header *mptcp_sysctl_binder; ++ ++/* General initialization of MPTCP_PM */ ++static int __init binder_register(void) ++{ ++ mptcp_gws = kzalloc(sizeof(*mptcp_gws), GFP_KERNEL); ++ if (!mptcp_gws) ++ return -ENOMEM; ++ ++ rwlock_init(&mptcp_gws_lock); ++ ++ BUILD_BUG_ON(sizeof(struct binder_priv) > MPTCP_PM_SIZE); ++ ++ mptcp_sysctl_binder = register_net_sysctl(&init_net, "net/mptcp", ++ binder_table); ++ if (!mptcp_sysctl_binder) ++ goto sysctl_fail; ++ ++ if (mptcp_register_path_manager(&binder)) ++ goto pm_failed; ++ ++ return 0; ++ ++pm_failed: ++ unregister_net_sysctl_table(mptcp_sysctl_binder); ++sysctl_fail: ++ kfree(mptcp_gws); ++ ++ return -1; ++} ++ ++static void binder_unregister(void) ++{ ++ mptcp_unregister_path_manager(&binder); ++ unregister_net_sysctl_table(mptcp_sysctl_binder); ++ kfree(mptcp_gws); ++} ++ ++module_init(binder_register); ++module_exit(binder_unregister); ++ ++MODULE_AUTHOR("Luca Boccassi, Duncan Eastoe, Christoph Paasch (ndiffports)"); ++MODULE_LICENSE("GPL"); ++MODULE_DESCRIPTION("BINDER MPTCP"); ++MODULE_VERSION("0.1"); +diff --git a/net/mptcp/mptcp_blest.c b/net/mptcp/mptcp_blest.c +new file mode 100644 +index 000000000000..22e25dd0d44e +--- /dev/null ++++ b/net/mptcp/mptcp_blest.c +@@ -0,0 +1,285 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* MPTCP Scheduler to reduce HoL-blocking and spurious retransmissions. ++ * ++ * Algorithm Design: ++ * Simone Ferlin ++ * Ozgu Alay ++ * Olivier Mehani ++ * Roksana Boreli ++ * ++ * Initial Implementation: ++ * Simone Ferlin ++ * ++ * Additional Authors: ++ * Daniel Weber ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License ++ * as published by the Free Software Foundation; either version ++ * 2 of the License, or (at your option) any later version. ++ */ ++ ++#include ++#include ++ ++static unsigned char lambda __read_mostly = 12; ++module_param(lambda, byte, 0644); ++MODULE_PARM_DESC(lambda, "Divided by 10 for scaling factor of fast flow rate estimation"); ++ ++static unsigned char max_lambda __read_mostly = 13; ++module_param(max_lambda, byte, 0644); ++MODULE_PARM_DESC(max_lambda, "Divided by 10 for maximum scaling factor of fast flow rate estimation"); ++ ++static unsigned char min_lambda __read_mostly = 10; ++module_param(min_lambda, byte, 0644); ++MODULE_PARM_DESC(min_lambda, "Divided by 10 for minimum scaling factor of fast flow rate estimation"); ++ ++static unsigned char dyn_lambda_good = 10; /* 1% */ ++module_param(dyn_lambda_good, byte, 0644); ++MODULE_PARM_DESC(dyn_lambda_good, "Decrease of lambda in positive case."); ++ ++static unsigned char dyn_lambda_bad = 40; /* 4% */ ++module_param(dyn_lambda_bad, byte, 0644); ++MODULE_PARM_DESC(dyn_lambda_bad, "Increase of lambda in negative case."); ++ ++struct blestsched_priv { ++ u32 last_rbuf_opti; ++ u32 min_srtt_us; ++ u32 max_srtt_us; ++}; ++ ++struct blestsched_cb { ++ s16 lambda_1000; /* values range from min_lambda * 100 to max_lambda * 100 */ ++ u32 last_lambda_update; ++}; ++ ++static struct blestsched_priv *blestsched_get_priv(const struct tcp_sock *tp) ++{ ++ return (struct blestsched_priv *)&tp->mptcp->mptcp_sched[0]; ++} ++ ++static struct blestsched_cb *blestsched_get_cb(const struct tcp_sock *tp) ++{ ++ return (struct blestsched_cb *)&tp->mpcb->mptcp_sched[0]; ++} ++ ++static void blestsched_update_lambda(struct sock *meta_sk, struct sock *sk) ++{ ++ struct blestsched_cb *blest_cb = blestsched_get_cb(tcp_sk(meta_sk)); ++ struct blestsched_priv *blest_p = blestsched_get_priv(tcp_sk(sk)); ++ ++ if (tcp_jiffies32 - blest_cb->last_lambda_update < usecs_to_jiffies(blest_p->min_srtt_us >> 3)) ++ return; ++ ++ /* if there have been retransmissions of packets of the slow flow ++ * during the slow flows last RTT => increase lambda ++ * otherwise decrease ++ */ ++ if (tcp_sk(meta_sk)->retrans_stamp) { ++ /* need to slow down on the slow flow */ ++ blest_cb->lambda_1000 += dyn_lambda_bad; ++ } else { ++ /* use the slow flow more */ ++ blest_cb->lambda_1000 -= dyn_lambda_good; ++ } ++ ++ /* cap lambda_1000 to its value range */ ++ blest_cb->lambda_1000 = min_t(s16, blest_cb->lambda_1000, max_lambda * 100); ++ blest_cb->lambda_1000 = max_t(s16, blest_cb->lambda_1000, min_lambda * 100); ++ ++ blest_cb->last_lambda_update = tcp_jiffies32; ++} ++ ++/* how many bytes will sk send during the rtt of another, slower flow? */ ++static u32 blestsched_estimate_bytes(struct sock *sk, u32 time_8) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct blestsched_priv *blest_p = blestsched_get_priv(tp); ++ struct blestsched_cb *blest_cb = blestsched_get_cb(mptcp_meta_tp(tp)); ++ u32 avg_rtt, num_rtts, ca_cwnd, packets; ++ ++ avg_rtt = (blest_p->min_srtt_us + blest_p->max_srtt_us) / 2; ++ if (avg_rtt == 0) ++ num_rtts = 1; /* sanity */ ++ else ++ num_rtts = (time_8 / avg_rtt) + 1; /* round up */ ++ ++ /* during num_rtts, how many bytes will be sent on the flow? ++ * assumes for simplification that Reno is applied as congestion-control ++ */ ++ if (tp->snd_ssthresh == TCP_INFINITE_SSTHRESH) { ++ /* we are in initial slow start */ ++ if (num_rtts > 16) ++ num_rtts = 16; /* cap for sanity */ ++ packets = tp->snd_cwnd * ((1 << num_rtts) - 1); /* cwnd + 2*cwnd + 4*cwnd */ ++ } else { ++ ca_cwnd = max(tp->snd_cwnd, tp->snd_ssthresh + 1); /* assume we jump to CA already */ ++ packets = (ca_cwnd + (num_rtts - 1) / 2) * num_rtts; ++ } ++ ++ return div_u64(((u64)packets) * tp->mss_cache * blest_cb->lambda_1000, 1000); ++} ++ ++static u32 blestsched_estimate_linger_time(struct sock *sk) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct blestsched_priv *blest_p = blestsched_get_priv(tp); ++ u32 estimate, slope, inflight, cwnd; ++ ++ inflight = tcp_packets_in_flight(tp) + 1; /* take into account the new one */ ++ cwnd = tp->snd_cwnd; ++ ++ if (inflight >= cwnd) { ++ estimate = blest_p->max_srtt_us; ++ } else { ++ slope = blest_p->max_srtt_us - blest_p->min_srtt_us; ++ if (cwnd == 0) ++ cwnd = 1; /* sanity */ ++ estimate = blest_p->min_srtt_us + (slope * inflight) / cwnd; ++ } ++ ++ return (tp->srtt_us > estimate) ? tp->srtt_us : estimate; ++} ++ ++/* This is the BLEST scheduler. This function decides on which flow to send ++ * a given MSS. If all subflows are found to be busy or the currently best ++ * subflow is estimated to possibly cause HoL-blocking, NULL is returned. ++ */ ++struct sock *blest_get_available_subflow(struct sock *meta_sk, struct sk_buff *skb, ++ bool zero_wnd_test) ++{ ++ struct mptcp_cb *mpcb = tcp_sk(meta_sk)->mpcb; ++ struct sock *bestsk, *minsk = NULL; ++ struct tcp_sock *meta_tp, *besttp; ++ struct mptcp_tcp_sock *mptcp; ++ struct blestsched_priv *blest_p; ++ u32 min_srtt = U32_MAX; ++ ++ /* Answer data_fin on same subflow!!! */ ++ if (meta_sk->sk_shutdown & RCV_SHUTDOWN && ++ skb && mptcp_is_data_fin(skb)) { ++ mptcp_for_each_sub(mpcb, mptcp) { ++ bestsk = mptcp_to_sock(mptcp); ++ ++ if (tcp_sk(bestsk)->mptcp->path_index == mpcb->dfin_path_index && ++ mptcp_is_available(bestsk, skb, zero_wnd_test)) ++ return bestsk; ++ } ++ } ++ ++ /* First, find the overall best subflow */ ++ mptcp_for_each_sub(mpcb, mptcp) { ++ bestsk = mptcp_to_sock(mptcp); ++ besttp = tcp_sk(bestsk); ++ blest_p = blestsched_get_priv(besttp); ++ ++ /* Set of states for which we are allowed to send data */ ++ if (!mptcp_sk_can_send(bestsk)) ++ continue; ++ ++ /* We do not send data on this subflow unless it is ++ * fully established, i.e. the 4th ack has been received. ++ */ ++ if (besttp->mptcp->pre_established) ++ continue; ++ ++ blest_p->min_srtt_us = min(blest_p->min_srtt_us, besttp->srtt_us); ++ blest_p->max_srtt_us = max(blest_p->max_srtt_us, besttp->srtt_us); ++ ++ /* record minimal rtt */ ++ if (besttp->srtt_us < min_srtt) { ++ min_srtt = besttp->srtt_us; ++ minsk = bestsk; ++ } ++ } ++ ++ /* find the current best subflow according to the default scheduler */ ++ bestsk = get_available_subflow(meta_sk, skb, zero_wnd_test); ++ ++ /* if we decided to use a slower flow, we have the option of not using it at all */ ++ if (bestsk && minsk && bestsk != minsk) { ++ u32 slow_linger_time, fast_bytes, slow_inflight_bytes, slow_bytes, avail_space; ++ u32 buffered_bytes = 0; ++ ++ meta_tp = tcp_sk(meta_sk); ++ besttp = tcp_sk(bestsk); ++ ++ blestsched_update_lambda(meta_sk, bestsk); ++ ++ /* if we send this SKB now, it will be acked in besttp->srtt seconds ++ * during this time: how many bytes will we send on the fast flow? ++ */ ++ slow_linger_time = blestsched_estimate_linger_time(bestsk); ++ fast_bytes = blestsched_estimate_bytes(minsk, slow_linger_time); ++ ++ if (skb) ++ buffered_bytes = skb->len; ++ ++ /* is the required space available in the mptcp meta send window? ++ * we assume that all bytes inflight on the slow path will be acked in besttp->srtt seconds ++ * (just like the SKB if it was sent now) -> that means that those inflight bytes will ++ * keep occupying space in the meta window until then ++ */ ++ slow_inflight_bytes = besttp->write_seq - besttp->snd_una; ++ slow_bytes = buffered_bytes + slow_inflight_bytes; // bytes of this SKB plus those in flight already ++ ++ avail_space = (slow_bytes < meta_tp->snd_wnd) ? (meta_tp->snd_wnd - slow_bytes) : 0; ++ ++ if (fast_bytes > avail_space) { ++ /* sending this SKB on the slow flow means ++ * we wouldn't be able to send all the data we'd like to send on the fast flow ++ * so don't do that ++ */ ++ return NULL; ++ } ++ } ++ ++ return bestsk; ++} ++ ++static void blestsched_init(struct sock *sk) ++{ ++ struct blestsched_priv *blest_p = blestsched_get_priv(tcp_sk(sk)); ++ struct blestsched_cb *blest_cb = blestsched_get_cb(tcp_sk(mptcp_meta_sk(sk))); ++ ++ blest_p->last_rbuf_opti = tcp_jiffies32; ++ blest_p->min_srtt_us = U32_MAX; ++ blest_p->max_srtt_us = 0; ++ ++ if (!blest_cb->lambda_1000) { ++ blest_cb->lambda_1000 = lambda * 100; ++ blest_cb->last_lambda_update = tcp_jiffies32; ++ } ++} ++ ++static struct mptcp_sched_ops mptcp_sched_blest = { ++ .get_subflow = blest_get_available_subflow, ++ .next_segment = mptcp_next_segment, ++ .init = blestsched_init, ++ .name = "blest", ++ .owner = THIS_MODULE, ++}; ++ ++static int __init blest_register(void) ++{ ++ BUILD_BUG_ON(sizeof(struct blestsched_priv) > MPTCP_SCHED_SIZE); ++ BUILD_BUG_ON(sizeof(struct blestsched_cb) > MPTCP_SCHED_DATA_SIZE); ++ ++ if (mptcp_register_scheduler(&mptcp_sched_blest)) ++ return -1; ++ ++ return 0; ++} ++ ++static void blest_unregister(void) ++{ ++ mptcp_unregister_scheduler(&mptcp_sched_blest); ++} ++ ++module_init(blest_register); ++module_exit(blest_unregister); ++ ++MODULE_AUTHOR("Simone Ferlin, Daniel Weber"); ++MODULE_LICENSE("GPL"); ++MODULE_DESCRIPTION("BLEST scheduler for MPTCP, based on default minimum RTT scheduler"); ++MODULE_VERSION("0.95"); +diff --git a/net/mptcp/mptcp_coupled.c b/net/mptcp/mptcp_coupled.c +new file mode 100644 +index 000000000000..9eb7628053f6 +--- /dev/null ++++ b/net/mptcp/mptcp_coupled.c +@@ -0,0 +1,262 @@ ++/* ++ * MPTCP implementation - Linked Increase congestion control Algorithm (LIA) ++ * ++ * Initial Design & Implementation: ++ * Sébastien Barré ++ * ++ * Current Maintainer & Author: ++ * Christoph Paasch ++ * ++ * Additional authors: ++ * Jaakko Korkeaniemi ++ * Gregory Detal ++ * Fabien Duchêne ++ * Andreas Seelinger ++ * Lavkesh Lahngir ++ * Andreas Ripke ++ * Vlad Dogaru ++ * Octavian Purdila ++ * John Ronan ++ * Catalin Nicutar ++ * Brandon Heller ++ * ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License ++ * as published by the Free Software Foundation; either version ++ * 2 of the License, or (at your option) any later version. ++ */ ++#include ++#include ++ ++#include ++ ++/* Scaling is done in the numerator with alpha_scale_num and in the denominator ++ * with alpha_scale_den. ++ * ++ * To downscale, we just need to use alpha_scale. ++ * ++ * We have: alpha_scale = alpha_scale_num / (alpha_scale_den ^ 2) ++ */ ++static int alpha_scale_den = 10; ++static int alpha_scale_num = 32; ++static int alpha_scale = 12; ++ ++struct mptcp_ccc { ++ u64 alpha; ++ bool forced_update; ++}; ++ ++static inline int mptcp_ccc_sk_can_send(const struct sock *sk) ++{ ++ return mptcp_sk_can_send(sk) && tcp_sk(sk)->srtt_us; ++} ++ ++static inline u64 mptcp_get_alpha(const struct sock *meta_sk) ++{ ++ return ((struct mptcp_ccc *)inet_csk_ca(meta_sk))->alpha; ++} ++ ++static inline void mptcp_set_alpha(const struct sock *meta_sk, u64 alpha) ++{ ++ ((struct mptcp_ccc *)inet_csk_ca(meta_sk))->alpha = alpha; ++} ++ ++static inline u64 mptcp_ccc_scale(u32 val, int scale) ++{ ++ return (u64) val << scale; ++} ++ ++static inline bool mptcp_get_forced(const struct sock *meta_sk) ++{ ++ return ((struct mptcp_ccc *)inet_csk_ca(meta_sk))->forced_update; ++} ++ ++static inline void mptcp_set_forced(const struct sock *meta_sk, bool force) ++{ ++ ((struct mptcp_ccc *)inet_csk_ca(meta_sk))->forced_update = force; ++} ++ ++static void mptcp_ccc_recalc_alpha(const struct sock *sk) ++{ ++ const struct mptcp_cb *mpcb = tcp_sk(sk)->mpcb; ++ const struct mptcp_tcp_sock *mptcp; ++ int best_cwnd = 0, best_rtt = 0, can_send = 0; ++ u64 max_numerator = 0, sum_denominator = 0, alpha = 1; ++ ++ if (!mpcb) ++ return; ++ ++ /* Do regular alpha-calculation for multiple subflows */ ++ ++ /* Find the max numerator of the alpha-calculation */ ++ mptcp_for_each_sub(mpcb, mptcp) { ++ const struct sock *sub_sk = mptcp_to_sock(mptcp); ++ struct tcp_sock *sub_tp = tcp_sk(sub_sk); ++ u64 tmp; ++ ++ if (!mptcp_ccc_sk_can_send(sub_sk)) ++ continue; ++ ++ can_send++; ++ ++ /* We need to look for the path, that provides the max-value. ++ * Integer-overflow is not possible here, because ++ * tmp will be in u64. ++ */ ++ tmp = div64_u64(mptcp_ccc_scale(sub_tp->snd_cwnd, ++ alpha_scale_num), (u64)sub_tp->srtt_us * sub_tp->srtt_us); ++ ++ if (tmp >= max_numerator) { ++ max_numerator = tmp; ++ best_cwnd = sub_tp->snd_cwnd; ++ best_rtt = sub_tp->srtt_us; ++ } ++ } ++ ++ /* No subflow is able to send - we don't care anymore */ ++ if (unlikely(!can_send)) ++ goto exit; ++ ++ /* Calculate the denominator */ ++ mptcp_for_each_sub(mpcb, mptcp) { ++ const struct sock *sub_sk = mptcp_to_sock(mptcp); ++ struct tcp_sock *sub_tp = tcp_sk(sub_sk); ++ ++ if (!mptcp_ccc_sk_can_send(sub_sk)) ++ continue; ++ ++ sum_denominator += div_u64( ++ mptcp_ccc_scale(sub_tp->snd_cwnd, ++ alpha_scale_den) * best_rtt, ++ sub_tp->srtt_us); ++ } ++ sum_denominator *= sum_denominator; ++ if (unlikely(!sum_denominator)) { ++ pr_err("%s: sum_denominator == 0\n", __func__); ++ mptcp_for_each_sub(mpcb, mptcp) { ++ const struct sock *sub_sk = mptcp_to_sock(mptcp); ++ struct tcp_sock *sub_tp = tcp_sk(sub_sk); ++ pr_err("%s: pi:%d, state:%d\n, rtt:%u, cwnd: %u", ++ __func__, sub_tp->mptcp->path_index, ++ sub_sk->sk_state, sub_tp->srtt_us, ++ sub_tp->snd_cwnd); ++ } ++ } ++ ++ alpha = div64_u64(mptcp_ccc_scale(best_cwnd, alpha_scale_num), sum_denominator); ++ ++ if (unlikely(!alpha)) ++ alpha = 1; ++ ++exit: ++ mptcp_set_alpha(mptcp_meta_sk(sk), alpha); ++} ++ ++static void mptcp_ccc_init(struct sock *sk) ++{ ++ if (mptcp(tcp_sk(sk))) { ++ mptcp_set_forced(mptcp_meta_sk(sk), 0); ++ mptcp_set_alpha(mptcp_meta_sk(sk), 1); ++ } ++ /* If we do not mptcp, behave like reno: return */ ++} ++ ++static void mptcp_ccc_cwnd_event(struct sock *sk, enum tcp_ca_event event) ++{ ++ if (event == CA_EVENT_LOSS) ++ mptcp_ccc_recalc_alpha(sk); ++} ++ ++static void mptcp_ccc_set_state(struct sock *sk, u8 ca_state) ++{ ++ if (!mptcp(tcp_sk(sk))) ++ return; ++ ++ mptcp_set_forced(mptcp_meta_sk(sk), 1); ++} ++ ++static void mptcp_ccc_cong_avoid(struct sock *sk, u32 ack, u32 acked) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ int snd_cwnd; ++ u64 alpha; ++ ++ if (!mptcp(tp)) { ++ tcp_reno_cong_avoid(sk, ack, acked); ++ return; ++ } ++ ++ if (!tcp_is_cwnd_limited(sk)) ++ return; ++ ++ if (tcp_in_slow_start(tp)) { ++ /* In "safe" area, increase. */ ++ tcp_slow_start(tp, acked); ++ mptcp_ccc_recalc_alpha(sk); ++ return; ++ } ++ ++ if (mptcp_get_forced(mptcp_meta_sk(sk))) { ++ mptcp_ccc_recalc_alpha(sk); ++ mptcp_set_forced(mptcp_meta_sk(sk), 0); ++ } ++ ++ alpha = mptcp_get_alpha(mptcp_meta_sk(sk)); ++ ++ /* This may happen, if at the initialization, the mpcb ++ * was not yet attached to the sock, and thus ++ * initializing alpha failed. ++ */ ++ if (unlikely(!alpha)) ++ alpha = 1; ++ ++ snd_cwnd = (int)div_u64((u64)mptcp_ccc_scale(1, alpha_scale), alpha); ++ ++ /* snd_cwnd_cnt >= max (scale * tot_cwnd / alpha, cwnd) ++ * Thus, we select here the max value. ++ */ ++ if (snd_cwnd < tp->snd_cwnd) ++ snd_cwnd = tp->snd_cwnd; ++ ++ if (tp->snd_cwnd_cnt >= snd_cwnd) { ++ if (tp->snd_cwnd < tp->snd_cwnd_clamp) { ++ tp->snd_cwnd++; ++ mptcp_ccc_recalc_alpha(sk); ++ } ++ ++ tp->snd_cwnd_cnt = 0; ++ } else { ++ tp->snd_cwnd_cnt++; ++ } ++} ++ ++static struct tcp_congestion_ops mptcp_ccc = { ++ .init = mptcp_ccc_init, ++ .ssthresh = tcp_reno_ssthresh, ++ .cong_avoid = mptcp_ccc_cong_avoid, ++ .undo_cwnd = tcp_reno_undo_cwnd, ++ .cwnd_event = mptcp_ccc_cwnd_event, ++ .set_state = mptcp_ccc_set_state, ++ .owner = THIS_MODULE, ++ .name = "lia", ++}; ++ ++static int __init mptcp_ccc_register(void) ++{ ++ BUILD_BUG_ON(sizeof(struct mptcp_ccc) > ICSK_CA_PRIV_SIZE); ++ return tcp_register_congestion_control(&mptcp_ccc); ++} ++ ++static void __exit mptcp_ccc_unregister(void) ++{ ++ tcp_unregister_congestion_control(&mptcp_ccc); ++} ++ ++module_init(mptcp_ccc_register); ++module_exit(mptcp_ccc_unregister); ++ ++MODULE_AUTHOR("Christoph Paasch, Sébastien Barré"); ++MODULE_LICENSE("GPL"); ++MODULE_DESCRIPTION("MPTCP LINKED INCREASE CONGESTION CONTROL ALGORITHM"); ++MODULE_VERSION("0.1"); +diff --git a/net/mptcp/mptcp_ctrl.c b/net/mptcp/mptcp_ctrl.c +new file mode 100644 +index 000000000000..db01ec142111 +--- /dev/null ++++ b/net/mptcp/mptcp_ctrl.c +@@ -0,0 +1,3313 @@ ++/* ++ * MPTCP implementation - MPTCP-control ++ * ++ * Initial Design & Implementation: ++ * Sébastien Barré ++ * ++ * Current Maintainer & Author: ++ * Christoph Paasch ++ * ++ * Additional authors: ++ * Jaakko Korkeaniemi ++ * Gregory Detal ++ * Fabien Duchêne ++ * Andreas Seelinger ++ * Lavkesh Lahngir ++ * Andreas Ripke ++ * Vlad Dogaru ++ * Octavian Purdila ++ * John Ronan ++ * Catalin Nicutar ++ * Brandon Heller ++ * ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License ++ * as published by the Free Software Foundation; either version ++ * 2 of the License, or (at your option) any later version. ++ */ ++ ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#if IS_ENABLED(CONFIG_IPV6) ++#include ++#include ++#endif ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++static struct kmem_cache *mptcp_sock_cache __read_mostly; ++static struct kmem_cache *mptcp_cb_cache __read_mostly; ++static struct kmem_cache *mptcp_tw_cache __read_mostly; ++ ++int sysctl_mptcp_enabled __read_mostly = 1; ++int sysctl_mptcp_version __read_mostly = 0; ++static int min_mptcp_version; ++static int max_mptcp_version = 1; ++int sysctl_mptcp_checksum __read_mostly = 1; ++int sysctl_mptcp_debug __read_mostly; ++EXPORT_SYMBOL(sysctl_mptcp_debug); ++int sysctl_mptcp_syn_retries __read_mostly = 3; ++ ++bool mptcp_init_failed __read_mostly; ++ ++struct static_key mptcp_static_key = STATIC_KEY_INIT_FALSE; ++EXPORT_SYMBOL(mptcp_static_key); ++ ++static void mptcp_key_hash(u8 version, u64 key, u32 *token, u64 *idsn); ++ ++static int proc_mptcp_path_manager(struct ctl_table *ctl, int write, ++ void __user *buffer, size_t *lenp, ++ loff_t *ppos) ++{ ++ char val[MPTCP_PM_NAME_MAX]; ++ struct ctl_table tbl = { ++ .data = val, ++ .maxlen = MPTCP_PM_NAME_MAX, ++ }; ++ int ret; ++ ++ mptcp_get_default_path_manager(val); ++ ++ ret = proc_dostring(&tbl, write, buffer, lenp, ppos); ++ if (write && ret == 0) ++ ret = mptcp_set_default_path_manager(val); ++ return ret; ++} ++ ++static int proc_mptcp_scheduler(struct ctl_table *ctl, int write, ++ void __user *buffer, size_t *lenp, ++ loff_t *ppos) ++{ ++ char val[MPTCP_SCHED_NAME_MAX]; ++ struct ctl_table tbl = { ++ .data = val, ++ .maxlen = MPTCP_SCHED_NAME_MAX, ++ }; ++ int ret; ++ ++ mptcp_get_default_scheduler(val); ++ ++ ret = proc_dostring(&tbl, write, buffer, lenp, ppos); ++ if (write && ret == 0) ++ ret = mptcp_set_default_scheduler(val); ++ return ret; ++} ++ ++static struct ctl_table mptcp_table[] = { ++ { ++ .procname = "mptcp_enabled", ++ .data = &sysctl_mptcp_enabled, ++ .maxlen = sizeof(int), ++ .mode = 0644, ++ .proc_handler = &proc_dointvec ++ }, ++ { ++ .procname = "mptcp_version", ++ .data = &sysctl_mptcp_version, ++ .mode = 0644, ++ .maxlen = sizeof(int), ++ .proc_handler = &proc_dointvec_minmax, ++ .extra1 = &min_mptcp_version, ++ .extra2 = &max_mptcp_version, ++ }, ++ { ++ .procname = "mptcp_checksum", ++ .data = &sysctl_mptcp_checksum, ++ .maxlen = sizeof(int), ++ .mode = 0644, ++ .proc_handler = &proc_dointvec ++ }, ++ { ++ .procname = "mptcp_debug", ++ .data = &sysctl_mptcp_debug, ++ .maxlen = sizeof(int), ++ .mode = 0644, ++ .proc_handler = &proc_dointvec ++ }, ++ { ++ .procname = "mptcp_syn_retries", ++ .data = &sysctl_mptcp_syn_retries, ++ .maxlen = sizeof(int), ++ .mode = 0644, ++ .proc_handler = &proc_dointvec ++ }, ++ { ++ .procname = "mptcp_path_manager", ++ .mode = 0644, ++ .maxlen = MPTCP_PM_NAME_MAX, ++ .proc_handler = proc_mptcp_path_manager, ++ }, ++ { ++ .procname = "mptcp_scheduler", ++ .mode = 0644, ++ .maxlen = MPTCP_SCHED_NAME_MAX, ++ .proc_handler = proc_mptcp_scheduler, ++ }, ++ { } ++}; ++ ++static inline u32 mptcp_hash_tk(u32 token, struct mptcp_hashtable *htable) ++{ ++ return token & htable->mask; ++} ++ ++struct mptcp_hashtable mptcp_tk_htable; ++EXPORT_SYMBOL(mptcp_tk_htable); ++ ++/* The following hash table is used to avoid collision of token */ ++static struct mptcp_hashtable mptcp_reqsk_tk_htb; ++ ++/* Lock, protecting the two hash-tables that hold the token. Namely, ++ * mptcp_reqsk_tk_htb and tk_hashtable ++ */ ++static spinlock_t mptcp_tk_hashlock; ++ ++static bool mptcp_reqsk_find_tk(const u32 token) ++{ ++ const u32 hash = mptcp_hash_tk(token, &mptcp_reqsk_tk_htb); ++ const struct mptcp_request_sock *mtreqsk; ++ const struct hlist_nulls_node *node; ++ ++begin: ++ hlist_nulls_for_each_entry_rcu(mtreqsk, node, ++ &mptcp_reqsk_tk_htb.hashtable[hash], ++ hash_entry) { ++ if (token == mtreqsk->mptcp_loc_token) ++ return true; ++ } ++ /* A request-socket is destroyed by RCU. So, it might have been recycled ++ * and put into another hash-table list. So, after the lookup we may ++ * end up in a different list. So, we may need to restart. ++ * ++ * See also the comment in __inet_lookup_established. ++ */ ++ if (get_nulls_value(node) != hash) ++ goto begin; ++ return false; ++} ++ ++static void mptcp_reqsk_insert_tk(struct request_sock *reqsk, const u32 token) ++{ ++ u32 hash = mptcp_hash_tk(token, &mptcp_reqsk_tk_htb); ++ ++ hlist_nulls_add_head_rcu(&mptcp_rsk(reqsk)->hash_entry, ++ &mptcp_reqsk_tk_htb.hashtable[hash]); ++} ++ ++static void mptcp_reqsk_remove_tk(const struct request_sock *reqsk) ++{ ++ rcu_read_lock(); ++ local_bh_disable(); ++ spin_lock(&mptcp_tk_hashlock); ++ hlist_nulls_del_init_rcu(&mptcp_rsk(reqsk)->hash_entry); ++ spin_unlock(&mptcp_tk_hashlock); ++ local_bh_enable(); ++ rcu_read_unlock(); ++} ++ ++void mptcp_reqsk_destructor(struct request_sock *req) ++{ ++ if (!mptcp_rsk(req)->is_sub) ++ mptcp_reqsk_remove_tk(req); ++} ++ ++static void __mptcp_hash_insert(struct tcp_sock *meta_tp, const u32 token) ++{ ++ u32 hash = mptcp_hash_tk(token, &mptcp_tk_htable); ++ ++ hlist_nulls_add_head_rcu(&meta_tp->tk_table, ++ &mptcp_tk_htable.hashtable[hash]); ++ meta_tp->inside_tk_table = 1; ++} ++ ++static bool mptcp_find_token(u32 token) ++{ ++ const u32 hash = mptcp_hash_tk(token, &mptcp_tk_htable); ++ const struct tcp_sock *meta_tp; ++ const struct hlist_nulls_node *node; ++ ++begin: ++ hlist_nulls_for_each_entry_rcu(meta_tp, node, ++ &mptcp_tk_htable.hashtable[hash], ++ tk_table) { ++ if (token == meta_tp->mptcp_loc_token) ++ return true; ++ } ++ /* A TCP-socket is destroyed by RCU. So, it might have been recycled ++ * and put into another hash-table list. So, after the lookup we may ++ * end up in a different list. So, we may need to restart. ++ * ++ * See also the comment in __inet_lookup_established. ++ */ ++ if (get_nulls_value(node) != hash) ++ goto begin; ++ return false; ++} ++ ++static void mptcp_set_key_reqsk(struct request_sock *req, ++ const struct sk_buff *skb, ++ u32 seed) ++{ ++ const struct inet_request_sock *ireq = inet_rsk(req); ++ struct mptcp_request_sock *mtreq = mptcp_rsk(req); ++ ++ if (skb->protocol == htons(ETH_P_IP)) { ++ mtreq->mptcp_loc_key = mptcp_v4_get_key(ip_hdr(skb)->saddr, ++ ip_hdr(skb)->daddr, ++ htons(ireq->ir_num), ++ ireq->ir_rmt_port, ++ seed); ++#if IS_ENABLED(CONFIG_IPV6) ++ } else { ++ mtreq->mptcp_loc_key = mptcp_v6_get_key(ipv6_hdr(skb)->saddr.s6_addr32, ++ ipv6_hdr(skb)->daddr.s6_addr32, ++ htons(ireq->ir_num), ++ ireq->ir_rmt_port, ++ seed); ++#endif ++ } ++ ++ mptcp_key_hash(mtreq->mptcp_ver, mtreq->mptcp_loc_key, &mtreq->mptcp_loc_token, NULL); ++} ++ ++/* New MPTCP-connection request, prepare a new token for the meta-socket that ++ * will be created in mptcp_check_req_master(), and store the received token. ++ */ ++static void mptcp_reqsk_new_mptcp(struct request_sock *req, ++ const struct sock *sk, ++ const struct mptcp_options_received *mopt, ++ const struct sk_buff *skb) ++{ ++ struct mptcp_request_sock *mtreq = mptcp_rsk(req); ++ ++ inet_rsk(req)->saw_mpc = 1; ++ mtreq->mptcp_ver = mopt->mptcp_ver; ++ ++ rcu_read_lock(); ++ local_bh_disable(); ++ spin_lock(&mptcp_tk_hashlock); ++ do { ++ mptcp_set_key_reqsk(req, skb, mptcp_seed++); ++ } while (mptcp_reqsk_find_tk(mtreq->mptcp_loc_token) || ++ mptcp_find_token(mtreq->mptcp_loc_token)); ++ mptcp_reqsk_insert_tk(req, mtreq->mptcp_loc_token); ++ spin_unlock(&mptcp_tk_hashlock); ++ local_bh_enable(); ++ rcu_read_unlock(); ++ ++ if (mtreq->mptcp_ver == MPTCP_VERSION_0) { ++ mtreq->mptcp_rem_key = mopt->mptcp_sender_key; ++ mtreq->rem_key_set = 1; ++ } ++} ++ ++static int mptcp_reqsk_new_cookie(struct request_sock *req, ++ const struct sock *sk, ++ const struct mptcp_options_received *mopt, ++ const struct sk_buff *skb) ++{ ++ struct mptcp_request_sock *mtreq = mptcp_rsk(req); ++ ++ /* Must happen before mptcp_set_key_reqsk to generate the token with ++ * the proper hash algo. ++ */ ++ mtreq->mptcp_ver = mopt->mptcp_ver; ++ ++ rcu_read_lock(); ++ local_bh_disable(); ++ spin_lock(&mptcp_tk_hashlock); ++ ++ mptcp_set_key_reqsk(req, skb, tcp_rsk(req)->snt_isn); ++ ++ if (mptcp_reqsk_find_tk(mtreq->mptcp_loc_token) || ++ mptcp_find_token(mtreq->mptcp_loc_token)) { ++ spin_unlock(&mptcp_tk_hashlock); ++ local_bh_enable(); ++ rcu_read_unlock(); ++ return false; ++ } ++ ++ inet_rsk(req)->saw_mpc = 1; ++ ++ spin_unlock(&mptcp_tk_hashlock); ++ local_bh_enable(); ++ rcu_read_unlock(); ++ ++ if (mtreq->mptcp_ver == MPTCP_VERSION_0) { ++ mtreq->mptcp_rem_key = mopt->mptcp_sender_key; ++ mtreq->rem_key_set = 1; ++ } ++ ++ return true; ++} ++ ++static void mptcp_set_key_sk(const struct sock *sk) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ const struct inet_sock *isk = inet_sk(sk); ++ ++ if (sk->sk_family == AF_INET) ++ tp->mptcp_loc_key = mptcp_v4_get_key(isk->inet_saddr, ++ isk->inet_daddr, ++ isk->inet_sport, ++ isk->inet_dport, ++ mptcp_seed++); ++#if IS_ENABLED(CONFIG_IPV6) ++ else ++ tp->mptcp_loc_key = mptcp_v6_get_key(inet6_sk(sk)->saddr.s6_addr32, ++ sk->sk_v6_daddr.s6_addr32, ++ isk->inet_sport, ++ isk->inet_dport, ++ mptcp_seed++); ++#endif ++ ++ mptcp_key_hash(tp->mptcp_ver, tp->mptcp_loc_key, &tp->mptcp_loc_token, NULL); ++} ++ ++#ifdef CONFIG_JUMP_LABEL ++static atomic_t mptcp_needed_deferred; ++static atomic_t mptcp_wanted; ++ ++static void mptcp_clear(struct work_struct *work) ++{ ++ int deferred = atomic_xchg(&mptcp_needed_deferred, 0); ++ int wanted; ++ ++ wanted = atomic_add_return(deferred, &mptcp_wanted); ++ if (wanted > 0) ++ static_key_enable(&mptcp_static_key); ++ else ++ static_key_disable(&mptcp_static_key); ++} ++ ++static DECLARE_WORK(mptcp_work, mptcp_clear); ++#endif ++ ++static void mptcp_enable_static_key_bh(void) ++{ ++#ifdef CONFIG_JUMP_LABEL ++ int wanted; ++ ++ while (1) { ++ wanted = atomic_read(&mptcp_wanted); ++ if (wanted <= 0) ++ break; ++ if (atomic_cmpxchg(&mptcp_wanted, wanted, wanted + 1) == wanted) ++ return; ++ } ++ atomic_inc(&mptcp_needed_deferred); ++ schedule_work(&mptcp_work); ++#else ++ static_key_slow_inc(&mptcp_static_key); ++#endif ++} ++ ++static void mptcp_enable_static_key(void) ++{ ++#ifdef CONFIG_JUMP_LABEL ++ atomic_inc(&mptcp_wanted); ++ static_key_enable(&mptcp_static_key); ++#else ++ static_key_slow_inc(&mptcp_static_key); ++#endif ++} ++ ++void mptcp_disable_static_key(void) ++{ ++#ifdef CONFIG_JUMP_LABEL ++ int wanted; ++ ++ while (1) { ++ wanted = atomic_read(&mptcp_wanted); ++ if (wanted <= 1) ++ break; ++ if (atomic_cmpxchg(&mptcp_wanted, wanted, wanted - 1) == wanted) ++ return; ++ } ++ atomic_dec(&mptcp_needed_deferred); ++ schedule_work(&mptcp_work); ++#else ++ static_key_slow_dec(&mptcp_static_key); ++#endif ++} ++ ++void mptcp_enable_sock(struct sock *sk) ++{ ++ if (!sock_flag(sk, SOCK_MPTCP)) { ++ sock_set_flag(sk, SOCK_MPTCP); ++ tcp_sk(sk)->mptcp_ver = sysctl_mptcp_version; ++ ++ /* Necessary here, because MPTCP can be enabled/disabled through ++ * a setsockopt. ++ */ ++ if (sk->sk_family == AF_INET) ++ inet_csk(sk)->icsk_af_ops = &mptcp_v4_specific; ++#if IS_ENABLED(CONFIG_IPV6) ++ else if (mptcp_v6_is_v4_mapped(sk)) ++ inet_csk(sk)->icsk_af_ops = &mptcp_v6_mapped; ++ else ++ inet_csk(sk)->icsk_af_ops = &mptcp_v6_specific; ++#endif ++ ++ mptcp_enable_static_key(); ++ } ++} ++ ++void mptcp_disable_sock(struct sock *sk) ++{ ++ if (sock_flag(sk, SOCK_MPTCP)) { ++ sock_reset_flag(sk, SOCK_MPTCP); ++ ++ /* Necessary here, because MPTCP can be enabled/disabled through ++ * a setsockopt. ++ */ ++ if (sk->sk_family == AF_INET) ++ inet_csk(sk)->icsk_af_ops = &ipv4_specific; ++#if IS_ENABLED(CONFIG_IPV6) ++ else if (mptcp_v6_is_v4_mapped(sk)) ++ inet_csk(sk)->icsk_af_ops = &ipv6_mapped; ++ else ++ inet_csk(sk)->icsk_af_ops = &ipv6_specific; ++#endif ++ ++ mptcp_disable_static_key(); ++ } ++} ++ ++void mptcp_connect_init(struct sock *sk) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ ++ rcu_read_lock(); ++ local_bh_disable(); ++ spin_lock(&mptcp_tk_hashlock); ++ do { ++ mptcp_set_key_sk(sk); ++ } while (mptcp_reqsk_find_tk(tp->mptcp_loc_token) || ++ mptcp_find_token(tp->mptcp_loc_token)); ++ ++ __mptcp_hash_insert(tp, tp->mptcp_loc_token); ++ spin_unlock(&mptcp_tk_hashlock); ++ local_bh_enable(); ++ rcu_read_unlock(); ++ ++ MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_MPCAPABLEACTIVE); ++} ++ ++/** ++ * This function increments the refcount of the mpcb struct. ++ * It is the responsibility of the caller to decrement when releasing ++ * the structure. ++ */ ++struct sock *mptcp_hash_find(const struct net *net, const u32 token) ++{ ++ const u32 hash = mptcp_hash_tk(token, &mptcp_tk_htable); ++ const struct tcp_sock *meta_tp; ++ struct sock *meta_sk = NULL; ++ const struct hlist_nulls_node *node; ++ ++ rcu_read_lock(); ++ local_bh_disable(); ++begin: ++ hlist_nulls_for_each_entry_rcu(meta_tp, node, ++ &mptcp_tk_htable.hashtable[hash], ++ tk_table) { ++ meta_sk = (struct sock *)meta_tp; ++ if (token == meta_tp->mptcp_loc_token && ++ net_eq(net, sock_net(meta_sk))) { ++ if (unlikely(!refcount_inc_not_zero(&meta_sk->sk_refcnt))) ++ goto out; ++ if (unlikely(token != meta_tp->mptcp_loc_token || ++ !net_eq(net, sock_net(meta_sk)))) { ++ sock_gen_put(meta_sk); ++ goto begin; ++ } ++ goto found; ++ } ++ } ++ /* A TCP-socket is destroyed by RCU. So, it might have been recycled ++ * and put into another hash-table list. So, after the lookup we may ++ * end up in a different list. So, we may need to restart. ++ * ++ * See also the comment in __inet_lookup_established. ++ */ ++ if (get_nulls_value(node) != hash) ++ goto begin; ++out: ++ meta_sk = NULL; ++found: ++ local_bh_enable(); ++ rcu_read_unlock(); ++ return meta_sk; ++} ++EXPORT_SYMBOL_GPL(mptcp_hash_find); ++ ++void mptcp_hash_remove_bh(struct tcp_sock *meta_tp) ++{ ++ /* remove from the token hashtable */ ++ rcu_read_lock(); ++ local_bh_disable(); ++ spin_lock(&mptcp_tk_hashlock); ++ hlist_nulls_del_init_rcu(&meta_tp->tk_table); ++ meta_tp->inside_tk_table = 0; ++ spin_unlock(&mptcp_tk_hashlock); ++ local_bh_enable(); ++ rcu_read_unlock(); ++} ++ ++struct sock *mptcp_select_ack_sock(const struct sock *meta_sk) ++{ ++ const struct tcp_sock *meta_tp = tcp_sk(meta_sk); ++ struct sock *rttsk = NULL, *lastsk = NULL; ++ u32 min_time = 0, last_active = 0; ++ struct mptcp_tcp_sock *mptcp; ++ ++ mptcp_for_each_sub(meta_tp->mpcb, mptcp) { ++ struct sock *sk = mptcp_to_sock(mptcp); ++ struct tcp_sock *tp = tcp_sk(sk); ++ u32 elapsed; ++ ++ if (!mptcp_sk_can_send_ack(sk) || tp->pf) ++ continue; ++ ++ elapsed = keepalive_time_elapsed(tp); ++ ++ /* We take the one with the lowest RTT within a reasonable ++ * (meta-RTO)-timeframe ++ */ ++ if (elapsed < inet_csk(meta_sk)->icsk_rto) { ++ if (!min_time || tp->srtt_us < min_time) { ++ min_time = tp->srtt_us; ++ rttsk = sk; ++ } ++ continue; ++ } ++ ++ /* Otherwise, we just take the most recent active */ ++ if (!rttsk && (!last_active || elapsed < last_active)) { ++ last_active = elapsed; ++ lastsk = sk; ++ } ++ } ++ ++ if (rttsk) ++ return rttsk; ++ ++ return lastsk; ++} ++EXPORT_SYMBOL(mptcp_select_ack_sock); ++ ++static void mptcp_sock_def_error_report(struct sock *sk) ++{ ++ const struct mptcp_cb *mpcb = tcp_sk(sk)->mpcb; ++ struct tcp_sock *tp = tcp_sk(sk); ++ ++ if (!sock_flag(sk, SOCK_DEAD)) { ++ if (tp->send_mp_fclose && sk->sk_err == ETIMEDOUT) { ++ /* Called by the keep alive timer (tcp_write_timeout), ++ * when the limit of fastclose retransmissions has been ++ * reached. Send a TCP RST to clear the status of any ++ * stateful firewall (typically conntrack) which are ++ * not aware of mptcp and cannot understand the ++ * fastclose option. ++ */ ++ tp->ops->send_active_reset(sk, GFP_ATOMIC); ++ } ++ } ++ ++ /* record this info that can be used by PM after the sf close */ ++ tp->mptcp->sk_err = sk->sk_err; ++ ++ if (!tp->tcp_disconnect && mptcp_in_infinite_mapping_weak(mpcb)) { ++ struct sock *meta_sk = mptcp_meta_sk(sk); ++ ++ meta_sk->sk_err = sk->sk_err; ++ meta_sk->sk_err_soft = sk->sk_err_soft; ++ ++ if (!sock_flag(meta_sk, SOCK_DEAD)) ++ meta_sk->sk_error_report(meta_sk); ++ ++ WARN(meta_sk->sk_state == TCP_CLOSE, ++ "Meta already closed i_rcv %u i_snd %u send_i %u flags %#lx\n", ++ mpcb->infinite_mapping_rcv, mpcb->infinite_mapping_snd, ++ mpcb->send_infinite_mapping, meta_sk->sk_flags); ++ ++ if (meta_sk->sk_state != TCP_CLOSE) ++ tcp_done(meta_sk); ++ } ++ ++ sk->sk_err = 0; ++ return; ++} ++ ++void mptcp_mpcb_put(struct mptcp_cb *mpcb) ++{ ++ if (refcount_dec_and_test(&mpcb->mpcb_refcnt)) { ++ mptcp_cleanup_path_manager(mpcb); ++ mptcp_cleanup_scheduler(mpcb); ++ kfree(mpcb->master_info); ++ kmem_cache_free(mptcp_cb_cache, mpcb); ++ } ++} ++EXPORT_SYMBOL(mptcp_mpcb_put); ++ ++static void mptcp_mpcb_cleanup(struct mptcp_cb *mpcb) ++{ ++ struct mptcp_tw *mptw; ++ ++ /* The mpcb is disappearing - we can make the final ++ * update to the rcv_nxt of the time-wait-sock and remove ++ * its reference to the mpcb. ++ */ ++ spin_lock_bh(&mpcb->mpcb_list_lock); ++ list_for_each_entry_rcu(mptw, &mpcb->tw_list, list) { ++ list_del_rcu(&mptw->list); ++ mptw->in_list = 0; ++ mptcp_mpcb_put(mpcb); ++ rcu_assign_pointer(mptw->mpcb, NULL); ++ } ++ spin_unlock_bh(&mpcb->mpcb_list_lock); ++ ++ mptcp_mpcb_put(mpcb); ++} ++ ++static void mptcp_sock_destruct(struct sock *sk) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ ++ if (!is_meta_sk(sk)) { ++ BUG_ON(!hlist_unhashed(&tp->mptcp->cb_list)); ++ ++ kmem_cache_free(mptcp_sock_cache, tp->mptcp); ++ tp->mptcp = NULL; ++ ++ /* Taken when mpcb pointer was set */ ++ sock_put(mptcp_meta_sk(sk)); ++ mptcp_mpcb_put(tp->mpcb); ++ } else { ++ mptcp_debug("%s destroying meta-sk token %#x\n", __func__, ++ tcp_sk(sk)->mpcb->mptcp_loc_token); ++ ++ mptcp_mpcb_cleanup(tp->mpcb); ++ } ++ ++ WARN_ON(!static_key_false(&mptcp_static_key)); ++ ++ /* Must be called here, because this will decrement the jump-label. */ ++ inet_sock_destruct(sk); ++} ++ ++void mptcp_destroy_sock(struct sock *sk) ++{ ++ if (is_meta_sk(sk)) { ++ struct mptcp_tcp_sock *mptcp; ++ struct hlist_node *tmp; ++ ++ __skb_queue_purge(&tcp_sk(sk)->mpcb->reinject_queue); ++ ++ /* We have to close all remaining subflows. Normally, they ++ * should all be about to get closed. But, if the kernel is ++ * forcing a closure (e.g., tcp_write_err), the subflows might ++ * not have been closed properly (as we are waiting for the ++ * DATA_ACK of the DATA_FIN). ++ */ ++ mptcp_for_each_sub_safe(tcp_sk(sk)->mpcb, mptcp, tmp) { ++ struct sock *sk_it = mptcp_to_sock(mptcp); ++ ++ /* Already did call tcp_close - waiting for graceful ++ * closure, or if we are retransmitting fast-close on ++ * the subflow. The reset (or timeout) will kill the ++ * subflow.. ++ */ ++ if (tcp_sk(sk_it)->closing || ++ tcp_sk(sk_it)->send_mp_fclose) ++ continue; ++ ++ /* Allow the delayed work first to prevent time-wait state */ ++ if (delayed_work_pending(&tcp_sk(sk_it)->mptcp->work)) ++ continue; ++ ++ mptcp_sub_close(sk_it, 0); ++ } ++ } else { ++ mptcp_del_sock(sk); ++ } ++} ++ ++static void mptcp_set_state(struct sock *sk) ++{ ++ struct sock *meta_sk = mptcp_meta_sk(sk); ++ ++ /* Meta is not yet established - wake up the application */ ++ if ((1 << meta_sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV) && ++ sk->sk_state == TCP_ESTABLISHED) { ++ tcp_set_state(meta_sk, TCP_ESTABLISHED); ++ ++ if (!sock_flag(meta_sk, SOCK_DEAD)) { ++ meta_sk->sk_state_change(meta_sk); ++ sk_wake_async(meta_sk, SOCK_WAKE_IO, POLL_OUT); ++ } ++ ++ tcp_sk(meta_sk)->lsndtime = tcp_jiffies32; ++ } ++ ++ if (sk->sk_state == TCP_CLOSE) { ++ if (!sock_flag(sk, SOCK_DEAD)) ++ mptcp_sub_close(sk, 0); ++ } ++} ++ ++static int mptcp_set_congestion_control(struct sock *meta_sk, const char *name, ++ bool load, bool reinit, bool cap_net_admin) ++{ ++ struct mptcp_tcp_sock *mptcp; ++ int err, result = 0; ++ ++ result = __tcp_set_congestion_control(meta_sk, name, load, reinit, cap_net_admin); ++ ++ tcp_sk(meta_sk)->mpcb->tcp_ca_explicit_set = true; ++ ++ mptcp_for_each_sub(tcp_sk(meta_sk)->mpcb, mptcp) { ++ struct sock *sk_it = mptcp_to_sock(mptcp); ++ ++ err = __tcp_set_congestion_control(sk_it, name, load, reinit, cap_net_admin); ++ if (err) ++ result = err; ++ } ++ return result; ++} ++ ++static void mptcp_assign_congestion_control(struct sock *sk) ++{ ++ struct inet_connection_sock *icsk = inet_csk(sk); ++ struct inet_connection_sock *meta_icsk = inet_csk(mptcp_meta_sk(sk)); ++ const struct tcp_congestion_ops *ca = meta_icsk->icsk_ca_ops; ++ ++ /* Congestion control is the same as meta. Thus, it has been ++ * try_module_get'd by tcp_assign_congestion_control. ++ * Congestion control on meta was not explicitly configured by ++ * application, leave default or route based. ++ */ ++ if (icsk->icsk_ca_ops == ca || ++ !tcp_sk(mptcp_meta_sk(sk))->mpcb->tcp_ca_explicit_set) ++ return; ++ ++ /* Use the same congestion control as set on the meta-sk */ ++ if (!try_module_get(ca->owner)) { ++ /* This should never happen. The congestion control is linked ++ * to the meta-socket (through tcp_assign_congestion_control) ++ * who "holds" the refcnt on the module. ++ */ ++ WARN(1, "Could not get the congestion control!"); ++ return; ++ } ++ module_put(icsk->icsk_ca_ops->owner); ++ icsk->icsk_ca_ops = ca; ++ ++ /* Clear out private data before diag gets it and ++ * the ca has not been initialized. ++ */ ++ if (ca->get_info) ++ memset(icsk->icsk_ca_priv, 0, sizeof(icsk->icsk_ca_priv)); ++ ++ return; ++} ++ ++siphash_key_t mptcp_secret __read_mostly; ++u32 mptcp_seed = 0; ++ ++#define SHA256_DIGEST_WORDS (SHA256_DIGEST_SIZE / 4) ++ ++static void mptcp_key_sha256(const u64 key, u32 *token, u64 *idsn) ++{ ++ u32 mptcp_hashed_key[SHA256_DIGEST_WORDS]; ++ struct sha256_state state; ++ ++ sha256_init(&state); ++ sha256_update(&state, (const u8 *)&key, sizeof(key)); ++ sha256_final(&state, (u8 *)mptcp_hashed_key); ++ ++ if (token) ++ *token = mptcp_hashed_key[0]; ++ if (idsn) ++ *idsn = ntohll(*((__be64 *)&mptcp_hashed_key[6])); ++} ++ ++static void mptcp_hmac_sha256(const u8 *key_1, const u8 *key_2, u8 *hash_out, ++ int arg_num, va_list list) ++{ ++ u8 input[SHA256_BLOCK_SIZE + SHA256_DIGEST_SIZE]; ++ struct sha256_state state; ++ int index, msg_length; ++ int length = 0; ++ u8 *msg; ++ int i; ++ ++ /* Generate key xored with ipad */ ++ memset(input, 0x36, SHA256_BLOCK_SIZE); ++ for (i = 0; i < 8; i++) ++ input[i] ^= key_1[i]; ++ for (i = 0; i < 8; i++) ++ input[i + 8] ^= key_2[i]; ++ ++ index = SHA256_BLOCK_SIZE; ++ msg_length = 0; ++ for (i = 0; i < arg_num; i++) { ++ length = va_arg(list, int); ++ msg = va_arg(list, u8 *); ++ BUG_ON(index + length >= sizeof(input)); /* Message is too long */ ++ memcpy(&input[index], msg, length); ++ index += length; ++ msg_length += length; ++ } ++ ++ sha256_init(&state); ++ sha256_update(&state, input, SHA256_BLOCK_SIZE + msg_length); ++ sha256_final(&state, &input[SHA256_BLOCK_SIZE]); ++ ++ /* Prepare second part of hmac */ ++ memset(input, 0x5C, SHA256_BLOCK_SIZE); ++ for (i = 0; i < 8; i++) ++ input[i] ^= key_1[i]; ++ for (i = 0; i < 8; i++) ++ input[i + 8] ^= key_2[i]; ++ ++ sha256_init(&state); ++ sha256_update(&state, input, sizeof(input)); ++ sha256_final(&state, hash_out); ++} ++ ++static void mptcp_key_sha1(u64 key, u32 *token, u64 *idsn) ++{ ++ u32 workspace[SHA_WORKSPACE_WORDS]; ++ u32 mptcp_hashed_key[SHA_DIGEST_WORDS]; ++ u8 input[64]; ++ int i; ++ ++ memset(workspace, 0, sizeof(workspace)); ++ ++ /* Initialize input with appropriate padding */ ++ memset(&input[9], 0, sizeof(input) - 10); /* -10, because the last byte ++ * is explicitly set too ++ */ ++ memcpy(input, &key, sizeof(key)); /* Copy key to the msg beginning */ ++ input[8] = 0x80; /* Padding: First bit after message = 1 */ ++ input[63] = 0x40; /* Padding: Length of the message = 64 bits */ ++ ++ sha_init(mptcp_hashed_key); ++ sha_transform(mptcp_hashed_key, input, workspace); ++ ++ for (i = 0; i < 5; i++) ++ mptcp_hashed_key[i] = (__force u32)cpu_to_be32(mptcp_hashed_key[i]); ++ ++ if (token) ++ *token = mptcp_hashed_key[0]; ++ if (idsn) ++ *idsn = ntohll(*((__be64 *)&mptcp_hashed_key[3])); ++} ++ ++static void mptcp_key_hash(u8 version, u64 key, u32 *token, u64 *idsn) ++{ ++ if (version == MPTCP_VERSION_0) ++ mptcp_key_sha1(key, token, idsn); ++ else if (version >= MPTCP_VERSION_1) ++ mptcp_key_sha256(key, token, idsn); ++} ++ ++static void mptcp_hmac_sha1(const u8 *key_1, const u8 *key_2, u32 *hash_out, ++ int arg_num, va_list list) ++{ ++ u32 workspace[SHA_WORKSPACE_WORDS]; ++ u8 input[128]; /* 2 512-bit blocks */ ++ int i; ++ int index; ++ int length; ++ u8 *msg; ++ ++ memset(workspace, 0, sizeof(workspace)); ++ ++ /* Generate key xored with ipad */ ++ memset(input, 0x36, 64); ++ for (i = 0; i < 8; i++) ++ input[i] ^= key_1[i]; ++ for (i = 0; i < 8; i++) ++ input[i + 8] ^= key_2[i]; ++ ++ index = 64; ++ for (i = 0; i < arg_num; i++) { ++ length = va_arg(list, int); ++ msg = va_arg(list, u8 *); ++ BUG_ON(index + length > 125); /* Message is too long */ ++ memcpy(&input[index], msg, length); ++ index += length; ++ } ++ ++ input[index] = 0x80; /* Padding: First bit after message = 1 */ ++ memset(&input[index + 1], 0, (126 - index)); ++ ++ /* Padding: Length of the message = 512 + message length (bits) */ ++ input[126] = 0x02; ++ input[127] = ((index - 64) * 8); /* Message length (bits) */ ++ ++ sha_init(hash_out); ++ sha_transform(hash_out, input, workspace); ++ memset(workspace, 0, sizeof(workspace)); ++ ++ sha_transform(hash_out, &input[64], workspace); ++ memset(workspace, 0, sizeof(workspace)); ++ ++ for (i = 0; i < 5; i++) ++ hash_out[i] = (__force u32)cpu_to_be32(hash_out[i]); ++ ++ /* Prepare second part of hmac */ ++ memset(input, 0x5C, 64); ++ for (i = 0; i < 8; i++) ++ input[i] ^= key_1[i]; ++ for (i = 0; i < 8; i++) ++ input[i + 8] ^= key_2[i]; ++ ++ memcpy(&input[64], hash_out, 20); ++ input[84] = 0x80; ++ memset(&input[85], 0, 41); ++ ++ /* Padding: Length of the message = 512 + 160 bits */ ++ input[126] = 0x02; ++ input[127] = 0xA0; ++ ++ sha_init(hash_out); ++ sha_transform(hash_out, input, workspace); ++ memset(workspace, 0, sizeof(workspace)); ++ ++ sha_transform(hash_out, &input[64], workspace); ++ ++ for (i = 0; i < 5; i++) ++ hash_out[i] = (__force u32)cpu_to_be32(hash_out[i]); ++} ++ ++void mptcp_hmac(u8 ver, const u8 *key_1, const u8 *key_2, u8 *hash_out, ++ int arg_num, ...) ++{ ++ va_list args; ++ ++ va_start(args, arg_num); ++ if (ver == MPTCP_VERSION_0) ++ mptcp_hmac_sha1(key_1, key_2, (u32 *)hash_out, arg_num, args); ++ else if (ver >= MPTCP_VERSION_1) ++ mptcp_hmac_sha256(key_1, key_2, hash_out, arg_num, args); ++ va_end(args); ++} ++EXPORT_SYMBOL(mptcp_hmac); ++ ++static void mptcp_mpcb_inherit_sockopts(struct sock *meta_sk, struct sock *master_sk) ++{ ++ /* Socket-options handled by sk_clone_lock while creating the meta-sk. ++ * ====== ++ * SO_SNDBUF, SO_SNDBUFFORCE, SO_RCVBUF, SO_RCVBUFFORCE, SO_RCVLOWAT, ++ * SO_RCVTIMEO, SO_SNDTIMEO, SO_ATTACH_FILTER, SO_DETACH_FILTER, ++ * TCP_NODELAY, TCP_CORK ++ * ++ * Socket-options handled in this function here ++ * ====== ++ * TCP_DEFER_ACCEPT ++ * SO_KEEPALIVE ++ * ++ * Socket-options on the todo-list ++ * ====== ++ * SO_BINDTODEVICE - should probably prevent creation of new subsocks ++ * across other devices. - what about the api-draft? ++ * SO_DEBUG ++ * SO_REUSEADDR - probably we don't care about this ++ * SO_DONTROUTE, SO_BROADCAST ++ * SO_OOBINLINE ++ * SO_LINGER ++ * SO_TIMESTAMP* - I don't think this is of concern for a SOCK_STREAM ++ * SO_PASSSEC - I don't think this is of concern for a SOCK_STREAM ++ * SO_RXQ_OVFL ++ * TCP_COOKIE_TRANSACTIONS ++ * TCP_MAXSEG ++ * TCP_THIN_* - Handled by sk_clone_lock, but we need to support this ++ * in mptcp_meta_retransmit_timer. AND we need to check ++ * what is about the subsockets. ++ * TCP_LINGER2 ++ * TCP_WINDOW_CLAMP ++ * TCP_USER_TIMEOUT ++ * TCP_MD5SIG ++ * ++ * Socket-options of no concern for the meta-socket (but for the subsocket) ++ * ====== ++ * SO_PRIORITY ++ * SO_MARK ++ * TCP_CONGESTION ++ * TCP_SYNCNT ++ * TCP_QUICKACK ++ */ ++ ++ /* DEFER_ACCEPT should not be set on the meta, as we want to accept new subflows directly */ ++ inet_csk(meta_sk)->icsk_accept_queue.rskq_defer_accept = 0; ++ ++ /* Keepalives are handled entirely at the MPTCP-layer */ ++ if (sock_flag(meta_sk, SOCK_KEEPOPEN)) { ++ inet_csk_reset_keepalive_timer(meta_sk, ++ keepalive_time_when(tcp_sk(meta_sk))); ++ sock_reset_flag(master_sk, SOCK_KEEPOPEN); ++ inet_csk_delete_keepalive_timer(master_sk); ++ } ++ ++ /* Do not propagate subflow-errors up to the MPTCP-layer */ ++ inet_sk(master_sk)->recverr = 0; ++} ++ ++/* Called without holding lock on meta_sk */ ++static void mptcp_sub_inherit_sockopts(const struct sock *meta_sk, struct sock *sub_sk) ++{ ++ __u8 meta_tos; ++ ++ /* IP_TOS also goes to the subflow. */ ++ meta_tos = READ_ONCE(inet_sk(meta_sk)->tos); ++ if (inet_sk(sub_sk)->tos != meta_tos) { ++ inet_sk(sub_sk)->tos = meta_tos; ++ sub_sk->sk_priority = meta_sk->sk_priority; ++ sk_dst_reset(sub_sk); ++ } ++ ++ /* IPV6_TCLASS */ ++ if (sub_sk->sk_family == AF_INET6 && meta_sk->sk_family == AF_INET6) ++ inet6_sk(sub_sk)->tclass = inet6_sk(meta_sk)->tclass; ++ ++ /* Inherit SO_REUSEADDR */ ++ sub_sk->sk_reuse = meta_sk->sk_reuse; ++ ++ /* Inherit SO_MARK: can be used for routing or filtering */ ++ sub_sk->sk_mark = meta_sk->sk_mark; ++ ++ /* Inherit snd/rcv-buffer locks */ ++ sub_sk->sk_userlocks = meta_sk->sk_userlocks & ~SOCK_BINDPORT_LOCK; ++ ++ /* Nagle/Cork is forced off on the subflows. It is handled at the meta-layer */ ++ tcp_sk(sub_sk)->nonagle = TCP_NAGLE_OFF|TCP_NAGLE_PUSH; ++ ++ /* Keepalives are handled entirely at the MPTCP-layer */ ++ if (sock_flag(sub_sk, SOCK_KEEPOPEN)) { ++ sock_reset_flag(sub_sk, SOCK_KEEPOPEN); ++ inet_csk_delete_keepalive_timer(sub_sk); ++ } ++ ++ /* Do not propagate subflow-errors up to the MPTCP-layer */ ++ inet_sk(sub_sk)->recverr = 0; ++} ++ ++void mptcp_prepare_for_backlog(struct sock *sk, struct sk_buff *skb) ++{ ++ /* In case of success (in mptcp_backlog_rcv) and error (in kfree_skb) of ++ * sk_add_backlog, we will decrement the sk refcount. ++ */ ++ sock_hold(sk); ++ skb->sk = sk; ++ skb->destructor = sock_efree; ++} ++ ++int mptcp_backlog_rcv(struct sock *meta_sk, struct sk_buff *skb) ++{ ++ /* skb-sk may be NULL if we receive a packet immediatly after the ++ * SYN/ACK + MP_CAPABLE. ++ */ ++ struct sock *sk = skb->sk ? skb->sk : meta_sk; ++ int ret = 0; ++ ++ if (unlikely(!refcount_inc_not_zero(&sk->sk_refcnt))) { ++ kfree_skb(skb); ++ return 0; ++ } ++ ++ /* Decrement sk refcnt when calling the skb destructor. ++ * Refcnt is incremented and skb destructor is set in tcp_v{4,6}_rcv via ++ * mptcp_prepare_for_backlog() here above. ++ */ ++ skb_orphan(skb); ++ ++ if (sk->sk_family == AF_INET) ++ ret = tcp_v4_do_rcv(sk, skb); ++#if IS_ENABLED(CONFIG_IPV6) ++ else ++ ret = tcp_v6_do_rcv(sk, skb); ++#endif ++ ++ sock_put(sk); ++ return ret; ++} ++ ++static void mptcp_init_buffer_space(struct sock *sk) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct sock *meta_sk = mptcp_meta_sk(sk); ++ struct tcp_sock *meta_tp = tcp_sk(meta_sk); ++ int space; ++ ++ tcp_init_buffer_space(sk); ++ ++ if (is_master_tp(tp)) { ++ meta_tp->rcvq_space.space = meta_tp->rcv_wnd; ++ tcp_mstamp_refresh(meta_tp); ++ meta_tp->rcvq_space.time = meta_tp->tcp_mstamp; ++ meta_tp->rcvq_space.seq = meta_tp->copied_seq; ++ ++ /* If there is only one subflow, we just use regular TCP ++ * autotuning. User-locks are handled already by ++ * tcp_init_buffer_space ++ */ ++ meta_tp->window_clamp = tp->window_clamp; ++ meta_tp->rcv_ssthresh = tp->rcv_ssthresh; ++ meta_sk->sk_rcvbuf = sk->sk_rcvbuf; ++ meta_sk->sk_sndbuf = sk->sk_sndbuf; ++ ++ return; ++ } ++ ++ if (meta_sk->sk_userlocks & SOCK_RCVBUF_LOCK) ++ goto snd_buf; ++ ++ /* Adding a new subflow to the rcv-buffer space. We make a simple ++ * addition, to give some space to allow traffic on the new subflow. ++ * Autotuning will increase it further later on. ++ */ ++ space = min(meta_sk->sk_rcvbuf + sk->sk_rcvbuf, ++ sock_net(meta_sk)->ipv4.sysctl_tcp_rmem[2]); ++ if (space > meta_sk->sk_rcvbuf) { ++ meta_tp->window_clamp += tp->window_clamp; ++ meta_tp->rcv_ssthresh += tp->rcv_ssthresh; ++ meta_sk->sk_rcvbuf = space; ++ } ++ ++snd_buf: ++ if (meta_sk->sk_userlocks & SOCK_SNDBUF_LOCK) ++ return; ++ ++ /* Adding a new subflow to the send-buffer space. We make a simple ++ * addition, to give some space to allow traffic on the new subflow. ++ * Autotuning will increase it further later on. ++ */ ++ space = min(meta_sk->sk_sndbuf + sk->sk_sndbuf, ++ sock_net(meta_sk)->ipv4.sysctl_tcp_wmem[2]); ++ if (space > meta_sk->sk_sndbuf) { ++ meta_sk->sk_sndbuf = space; ++ meta_sk->sk_write_space(meta_sk); ++ } ++} ++ ++struct lock_class_key meta_key; ++char *meta_key_name = "sk_lock-AF_INET-MPTCP"; ++struct lock_class_key meta_slock_key; ++char *meta_slock_key_name = "slock-AF_INET-MPTCP"; ++ ++static const struct tcp_sock_ops mptcp_meta_specific = { ++ .__select_window = __mptcp_select_window, ++ .select_window = mptcp_select_window, ++ .select_initial_window = mptcp_select_initial_window, ++ .init_buffer_space = mptcp_init_buffer_space, ++ .set_rto = mptcp_tcp_set_rto, ++ .should_expand_sndbuf = mptcp_should_expand_sndbuf, ++ .send_fin = mptcp_send_fin, ++ .write_xmit = mptcp_write_xmit, ++ .send_active_reset = mptcp_send_active_reset, ++ .write_wakeup = mptcp_write_wakeup, ++ .retransmit_timer = mptcp_meta_retransmit_timer, ++ .time_wait = mptcp_time_wait, ++ .cleanup_rbuf = mptcp_cleanup_rbuf, ++ .set_cong_ctrl = mptcp_set_congestion_control, ++}; ++ ++static const struct tcp_sock_ops mptcp_sub_specific = { ++ .__select_window = __mptcp_select_window, ++ .select_window = mptcp_select_window, ++ .select_initial_window = mptcp_select_initial_window, ++ .init_buffer_space = mptcp_init_buffer_space, ++ .set_rto = mptcp_tcp_set_rto, ++ .should_expand_sndbuf = mptcp_should_expand_sndbuf, ++ .send_fin = tcp_send_fin, ++ .write_xmit = tcp_write_xmit, ++ .send_active_reset = tcp_send_active_reset, ++ .write_wakeup = tcp_write_wakeup, ++ .retransmit_timer = mptcp_sub_retransmit_timer, ++ .time_wait = tcp_time_wait, ++ .cleanup_rbuf = tcp_cleanup_rbuf, ++ .set_cong_ctrl = __tcp_set_congestion_control, ++}; ++ ++void mptcp_initialize_recv_vars(struct tcp_sock *meta_tp, struct mptcp_cb *mpcb, ++ __u64 remote_key) ++{ ++ u64 idsn; ++ ++ mpcb->mptcp_rem_key = remote_key; ++ mpcb->rem_key_set = 1; ++ mptcp_key_hash(mpcb->mptcp_ver, mpcb->mptcp_rem_key, &mpcb->mptcp_rem_token, &idsn); ++ ++ idsn++; ++ mpcb->rcv_high_order[0] = idsn >> 32; ++ mpcb->rcv_high_order[1] = mpcb->rcv_high_order[0] + 1; ++ meta_tp->copied_seq = (u32)idsn; ++ meta_tp->rcv_nxt = (u32)idsn; ++ meta_tp->rcv_wup = (u32)idsn; ++ meta_tp->rcv_right_edge = meta_tp->rcv_wup + meta_tp->rcv_wnd; ++ ++ meta_tp->snd_wl1 = meta_tp->rcv_nxt - 1; ++} ++ ++static int mptcp_alloc_mpcb(struct sock *meta_sk, __u64 remote_key, ++ int rem_key_set, __u8 mptcp_ver, u32 window) ++{ ++ struct mptcp_cb *mpcb; ++ struct sock *master_sk; ++ struct inet_connection_sock *meta_icsk = inet_csk(meta_sk); ++ struct tcp_sock *master_tp, *meta_tp = tcp_sk(meta_sk); ++ u64 snd_idsn; ++ ++ dst_release(meta_sk->sk_rx_dst); ++ meta_sk->sk_rx_dst = NULL; ++ /* This flag is set to announce sock_lock_init to ++ * reclassify the lock-class of the master socket. ++ */ ++ meta_tp->is_master_sk = 1; ++ master_sk = sk_clone_lock(meta_sk, GFP_ATOMIC | __GFP_ZERO); ++ meta_tp->is_master_sk = 0; ++ if (!master_sk) ++ goto err_alloc_master; ++ ++ /* Same as in inet_csk_clone_lock - need to init to 0 */ ++ memset(&inet_csk(master_sk)->icsk_accept_queue, 0, ++ sizeof(inet_csk(master_sk)->icsk_accept_queue)); ++ ++ master_tp = tcp_sk(master_sk); ++ master_tp->inside_tk_table = 0; ++ ++ mpcb = kmem_cache_zalloc(mptcp_cb_cache, GFP_ATOMIC); ++ if (!mpcb) ++ goto err_alloc_mpcb; ++ ++ /* Store the mptcp version agreed on initial handshake */ ++ mpcb->mptcp_ver = mptcp_ver; ++ ++ /* Store the keys and generate the peer's token */ ++ mpcb->mptcp_loc_key = meta_tp->mptcp_loc_key; ++ mpcb->mptcp_loc_token = meta_tp->mptcp_loc_token; ++ ++ /* Generate Initial data-sequence-numbers */ ++ mptcp_key_hash(mpcb->mptcp_ver, mpcb->mptcp_loc_key, NULL, &snd_idsn); ++ snd_idsn++; ++ mpcb->snd_high_order[0] = snd_idsn >> 32; ++ mpcb->snd_high_order[1] = mpcb->snd_high_order[0] - 1; ++ ++ mpcb->meta_sk = meta_sk; ++ mpcb->master_sk = master_sk; ++ ++ skb_queue_head_init(&mpcb->reinject_queue); ++ mutex_init(&mpcb->mpcb_mutex); ++ ++ /* Init time-wait stuff */ ++ INIT_LIST_HEAD(&mpcb->tw_list); ++ ++ INIT_HLIST_HEAD(&mpcb->callback_list); ++ INIT_HLIST_HEAD(&mpcb->conn_list); ++ spin_lock_init(&mpcb->mpcb_list_lock); ++ ++ mpcb->orig_sk_rcvbuf = meta_sk->sk_rcvbuf; ++ mpcb->orig_sk_sndbuf = meta_sk->sk_sndbuf; ++ mpcb->orig_window_clamp = meta_tp->window_clamp; ++ ++ /* The meta is directly linked - set refcnt to 1 */ ++ refcount_set(&mpcb->mpcb_refcnt, 1); ++ ++ if (!meta_tp->inside_tk_table) { ++ /* Adding the meta_tp in the token hashtable - coming from server-side */ ++ rcu_read_lock(); ++ local_bh_disable(); ++ spin_lock(&mptcp_tk_hashlock); ++ ++ /* With lockless listeners, we might process two ACKs at the ++ * same time. With TCP, inet_csk_complete_hashdance takes care ++ * of this. But, for MPTCP this would be too late if we add ++ * this MPTCP-socket in the token table (new subflows might ++ * come in and match on this socket here. ++ * So, we need to check if someone else already added the token ++ * and revert in that case. The other guy won the race... ++ */ ++ if (mptcp_find_token(mpcb->mptcp_loc_token)) { ++ spin_unlock(&mptcp_tk_hashlock); ++ local_bh_enable(); ++ rcu_read_unlock(); ++ ++ goto err_insert_token; ++ } ++ __mptcp_hash_insert(meta_tp, mpcb->mptcp_loc_token); ++ ++ spin_unlock(&mptcp_tk_hashlock); ++ local_bh_enable(); ++ rcu_read_unlock(); ++ } ++ ++#if IS_ENABLED(CONFIG_IPV6) ++ if (meta_icsk->icsk_af_ops == &mptcp_v6_mapped) { ++ struct tcp6_sock *master_tp6 = (struct tcp6_sock *)master_sk; ++ struct ipv6_pinfo *newnp, *np = inet6_sk(meta_sk); ++ ++ inet_sk(master_sk)->pinet6 = &master_tp6->inet6; ++ ++ newnp = inet6_sk(master_sk); ++ memcpy(newnp, np, sizeof(struct ipv6_pinfo)); ++ ++ newnp->ipv6_mc_list = NULL; ++ newnp->ipv6_ac_list = NULL; ++ newnp->ipv6_fl_list = NULL; ++ newnp->pktoptions = NULL; ++ newnp->opt = NULL; ++ ++ newnp->rxopt.all = 0; ++ newnp->repflow = 0; ++ np->rxopt.all = 0; ++ np->repflow = 0; ++ } else if (meta_sk->sk_family == AF_INET6) { ++ struct tcp6_sock *master_tp6 = (struct tcp6_sock *)master_sk; ++ struct ipv6_pinfo *newnp, *np = inet6_sk(meta_sk); ++ struct ipv6_txoptions *opt; ++ ++ inet_sk(master_sk)->pinet6 = &master_tp6->inet6; ++ ++ /* The following heavily inspired from tcp_v6_syn_recv_sock() */ ++ newnp = inet6_sk(master_sk); ++ memcpy(newnp, np, sizeof(struct ipv6_pinfo)); ++ ++ newnp->ipv6_mc_list = NULL; ++ newnp->ipv6_ac_list = NULL; ++ newnp->ipv6_fl_list = NULL; ++ newnp->pktoptions = NULL; ++ newnp->opt = NULL; ++ ++ newnp->rxopt.all = 0; ++ newnp->repflow = 0; ++ np->rxopt.all = 0; ++ np->repflow = 0; ++ ++ opt = rcu_dereference(np->opt); ++ if (opt) { ++ opt = ipv6_dup_options(master_sk, opt); ++ RCU_INIT_POINTER(newnp->opt, opt); ++ } ++ inet_csk(master_sk)->icsk_ext_hdr_len = 0; ++ if (opt) ++ inet_csk(master_sk)->icsk_ext_hdr_len = opt->opt_nflen + ++ opt->opt_flen; ++ } ++#endif ++ ++ meta_tp->mptcp = NULL; ++ ++ meta_tp->write_seq = (u32)snd_idsn; ++ meta_tp->snd_sml = meta_tp->write_seq; ++ meta_tp->snd_una = meta_tp->write_seq; ++ meta_tp->snd_nxt = meta_tp->write_seq; ++ meta_tp->pushed_seq = meta_tp->write_seq; ++ meta_tp->snd_up = meta_tp->write_seq; ++ ++ if (rem_key_set) ++ mptcp_initialize_recv_vars(meta_tp, mpcb, remote_key); ++ ++ meta_tp->snd_wnd = window; ++ meta_tp->retrans_stamp = 0; /* Set in tcp_connect() */ ++ ++ meta_tp->packets_out = 0; ++ meta_icsk->icsk_probes_out = 0; ++ ++ rcu_assign_pointer(inet_sk(meta_sk)->inet_opt, NULL); ++ ++ /* Set mptcp-pointers */ ++ master_tp->mpcb = mpcb; ++ master_tp->meta_sk = meta_sk; ++ meta_tp->mpcb = mpcb; ++ meta_tp->meta_sk = meta_sk; ++ ++ /* Initialize the queues */ ++ master_tp->out_of_order_queue = RB_ROOT; ++ master_sk->tcp_rtx_queue = RB_ROOT; ++ INIT_LIST_HEAD(&master_tp->tsq_node); ++ INIT_LIST_HEAD(&master_tp->tsorted_sent_queue); ++ ++ master_tp->fastopen_req = NULL; ++ ++ master_sk->sk_tsq_flags = 0; ++ /* icsk_bind_hash inherited from the meta, but it will be properly set in ++ * mptcp_create_master_sk. Same operation is done in inet_csk_clone_lock. ++ */ ++ inet_csk(master_sk)->icsk_bind_hash = NULL; ++ ++ /* Init the accept_queue structure, we support a queue of 32 pending ++ * connections, it does not need to be huge, since we only store here ++ * pending subflow creations. ++ */ ++ reqsk_queue_alloc(&meta_icsk->icsk_accept_queue); ++ meta_sk->sk_max_ack_backlog = 32; ++ meta_sk->sk_ack_backlog = 0; ++ ++ if (!sock_flag(meta_sk, SOCK_MPTCP)) { ++ mptcp_enable_static_key_bh(); ++ sock_set_flag(meta_sk, SOCK_MPTCP); ++ } ++ ++ /* Redefine function-pointers as the meta-sk is now fully ready */ ++ meta_tp->mpc = 1; ++ meta_tp->ops = &mptcp_meta_specific; ++ ++ meta_sk->sk_backlog_rcv = mptcp_backlog_rcv; ++ meta_sk->sk_destruct = mptcp_sock_destruct; ++ ++ /* Meta-level retransmit timer */ ++ meta_icsk->icsk_rto *= 2; /* Double of initial - rto */ ++ ++ tcp_init_xmit_timers(master_sk); ++ /* Has been set for sending out the SYN */ ++ inet_csk_clear_xmit_timer(meta_sk, ICSK_TIME_RETRANS); ++ ++ mptcp_mpcb_inherit_sockopts(meta_sk, master_sk); ++ ++ mptcp_init_path_manager(mpcb); ++ mptcp_init_scheduler(mpcb); ++ ++ if (!try_module_get(inet_csk(master_sk)->icsk_ca_ops->owner)) ++ tcp_assign_congestion_control(master_sk); ++ ++ master_tp->saved_syn = NULL; ++ ++ mptcp_debug("%s: created mpcb with token %#x\n", ++ __func__, mpcb->mptcp_loc_token); ++ ++ return 0; ++ ++err_insert_token: ++ kmem_cache_free(mptcp_cb_cache, mpcb); ++ ++err_alloc_mpcb: ++ inet_sk(master_sk)->inet_opt = NULL; ++ master_sk->sk_state = TCP_CLOSE; ++ sock_orphan(master_sk); ++ bh_unlock_sock(master_sk); ++ sk_free(master_sk); ++ ++err_alloc_master: ++ return -ENOBUFS; ++} ++ ++/* Called without holding lock on mpcb */ ++static u8 mptcp_set_new_pathindex(struct mptcp_cb *mpcb) ++{ ++ int i; ++ ++ /* Start at 1, because 0 is reserved for the meta-sk */ ++ for (i = 1; i < sizeof(mpcb->path_index_bits) * 8; i++) { ++ if (!test_and_set_bit(i, &mpcb->path_index_bits)) ++ break; ++ } ++ ++ if (i == sizeof(mpcb->path_index_bits) * 8) ++ return 0; ++ return i; ++} ++ ++/* May be called without holding the meta-level lock */ ++int mptcp_add_sock(struct sock *meta_sk, struct sock *sk, u8 loc_id, u8 rem_id, ++ gfp_t flags) ++{ ++ struct mptcp_cb *mpcb = tcp_sk(meta_sk)->mpcb; ++ struct tcp_sock *tp = tcp_sk(sk); ++ ++ tp->mptcp = kmem_cache_zalloc(mptcp_sock_cache, flags); ++ if (!tp->mptcp) ++ return -ENOMEM; ++ ++ tp->mptcp->path_index = mptcp_set_new_pathindex(mpcb); ++ /* No more space for more subflows? */ ++ if (!tp->mptcp->path_index) { ++ kmem_cache_free(mptcp_sock_cache, tp->mptcp); ++ return -EPERM; ++ } ++ ++ INIT_HLIST_NODE(&tp->mptcp->cb_list); ++ ++ tp->mptcp->tp = tp; ++ tp->mpcb = mpcb; ++ tp->meta_sk = meta_sk; ++ ++ if (!sock_flag(sk, SOCK_MPTCP)) { ++ mptcp_enable_static_key_bh(); ++ sock_set_flag(sk, SOCK_MPTCP); ++ } ++ ++ tp->mpc = 1; ++ tp->ops = &mptcp_sub_specific; ++ ++ tp->mptcp->loc_id = loc_id; ++ tp->mptcp->rem_id = rem_id; ++ if (mpcb->sched_ops->init) ++ mpcb->sched_ops->init(sk); ++ ++ /* The corresponding sock_put is in mptcp_sock_destruct(). It cannot be ++ * included in mptcp_del_sock(), because the mpcb must remain alive ++ * until the last subsocket is completely destroyed. ++ */ ++ sock_hold(meta_sk); ++ refcount_inc(&mpcb->mpcb_refcnt); ++ ++ spin_lock_bh(&mpcb->mpcb_list_lock); ++ hlist_add_head_rcu(&tp->mptcp->node, &mpcb->conn_list); ++ spin_unlock_bh(&mpcb->mpcb_list_lock); ++ ++ tp->mptcp->attached = 1; ++ ++ mptcp_sub_inherit_sockopts(meta_sk, sk); ++ INIT_DELAYED_WORK(&tp->mptcp->work, mptcp_sub_close_wq); ++ ++ /* Properly inherit CC from the meta-socket */ ++ mptcp_assign_congestion_control(sk); ++ ++ /* As we successfully allocated the mptcp_tcp_sock, we have to ++ * change the function-pointers here (for sk_destruct to work correctly) ++ */ ++ sk->sk_error_report = mptcp_sock_def_error_report; ++ sk->sk_data_ready = mptcp_data_ready; ++ sk->sk_write_space = mptcp_write_space; ++ sk->sk_state_change = mptcp_set_state; ++ sk->sk_destruct = mptcp_sock_destruct; ++ ++ if (sk->sk_family == AF_INET) ++ mptcp_debug("%s: token %#x pi %d, src_addr:%pI4:%d dst_addr:%pI4:%d\n", ++ __func__ , mpcb->mptcp_loc_token, ++ tp->mptcp->path_index, ++ &((struct inet_sock *)tp)->inet_saddr, ++ ntohs(((struct inet_sock *)tp)->inet_sport), ++ &((struct inet_sock *)tp)->inet_daddr, ++ ntohs(((struct inet_sock *)tp)->inet_dport)); ++#if IS_ENABLED(CONFIG_IPV6) ++ else ++ mptcp_debug("%s: token %#x pi %d, src_addr:%pI6:%d dst_addr:%pI6:%d\n", ++ __func__ , mpcb->mptcp_loc_token, ++ tp->mptcp->path_index, &inet6_sk(sk)->saddr, ++ ntohs(((struct inet_sock *)tp)->inet_sport), ++ &sk->sk_v6_daddr, ++ ntohs(((struct inet_sock *)tp)->inet_dport)); ++#endif ++ ++ return 0; ++} ++ ++void mptcp_del_sock(struct sock *sk) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct mptcp_cb *mpcb; ++ ++ if (!tp->mptcp || !tp->mptcp->attached) ++ return; ++ ++ mpcb = tp->mpcb; ++ ++ if (mpcb->sched_ops->release) ++ mpcb->sched_ops->release(sk); ++ ++ if (mpcb->pm_ops->delete_subflow) ++ mpcb->pm_ops->delete_subflow(sk); ++ ++ mptcp_debug("%s: Removing subsock tok %#x pi:%d state %d is_meta? %d\n", ++ __func__, mpcb->mptcp_loc_token, tp->mptcp->path_index, ++ sk->sk_state, is_meta_sk(sk)); ++ ++ spin_lock_bh(&mpcb->mpcb_list_lock); ++ hlist_del_init_rcu(&tp->mptcp->node); ++ spin_unlock_bh(&mpcb->mpcb_list_lock); ++ ++ tp->mptcp->attached = 0; ++ mpcb->path_index_bits &= ~(1 << tp->mptcp->path_index); ++ ++ if (!tcp_write_queue_empty(sk) || !tcp_rtx_queue_empty(sk)) ++ mptcp_reinject_data(sk, 0); ++ ++ if (is_master_tp(tp)) { ++ struct sock *meta_sk = mptcp_meta_sk(sk); ++ struct tcp_sock *meta_tp = tcp_sk(meta_sk); ++ ++ if (meta_tp->record_master_info && ++ !sock_flag(meta_sk, SOCK_DEAD)) { ++ mpcb->master_info = kmalloc(sizeof(*mpcb->master_info), ++ GFP_ATOMIC); ++ ++ if (mpcb->master_info) ++ tcp_get_info(sk, mpcb->master_info, true); ++ } ++ ++ mpcb->master_sk = NULL; ++ } else if (tp->mptcp->pre_established) { ++ sk_stop_timer(sk, &tp->mptcp->mptcp_ack_timer); ++ } ++} ++ ++/* Updates the MPTCP-session based on path-manager information (e.g., addresses, ++ * low-prio flows,...). ++ */ ++void mptcp_update_metasocket(const struct sock *meta_sk) ++{ ++ if (tcp_sk(meta_sk)->mpcb->pm_ops->new_session) ++ tcp_sk(meta_sk)->mpcb->pm_ops->new_session(meta_sk); ++} ++ ++/* Clean up the receive buffer for full frames taken by the user, ++ * then send an ACK if necessary. COPIED is the number of bytes ++ * tcp_recvmsg has given to the user so far, it speeds up the ++ * calculation of whether or not we must ACK for the sake of ++ * a window update. ++ * (inspired from tcp_cleanup_rbuf()) ++ */ ++void mptcp_cleanup_rbuf(struct sock *meta_sk, int copied) ++{ ++ struct tcp_sock *meta_tp = tcp_sk(meta_sk); ++ bool recheck_rcv_window = false; ++ struct mptcp_tcp_sock *mptcp; ++ __u32 rcv_window_now = 0; ++ ++ if (copied > 0 && !(meta_sk->sk_shutdown & RCV_SHUTDOWN)) { ++ rcv_window_now = tcp_receive_window_now(meta_tp); ++ ++ /* Optimize, __mptcp_select_window() is not cheap. */ ++ if (2 * rcv_window_now <= meta_tp->window_clamp) ++ recheck_rcv_window = true; ++ } ++ ++ mptcp_for_each_sub(meta_tp->mpcb, mptcp) { ++ struct sock *sk = mptcp_to_sock(mptcp); ++ struct tcp_sock *tp = tcp_sk(sk); ++ const struct inet_connection_sock *icsk = inet_csk(sk); ++ ++ if (!mptcp_sk_can_send_ack(sk)) ++ continue; ++ ++ if (!inet_csk_ack_scheduled(sk)) ++ goto second_part; ++ /* Delayed ACKs frequently hit locked sockets during bulk ++ * receive. ++ */ ++ if (icsk->icsk_ack.blocked || ++ /* Once-per-two-segments ACK was not sent by tcp_input.c */ ++ tp->rcv_nxt - tp->rcv_wup > icsk->icsk_ack.rcv_mss || ++ /* If this read emptied read buffer, we send ACK, if ++ * connection is not bidirectional, user drained ++ * receive buffer and there was a small segment ++ * in queue. ++ */ ++ (copied > 0 && ++ ((icsk->icsk_ack.pending & ICSK_ACK_PUSHED2) || ++ ((icsk->icsk_ack.pending & ICSK_ACK_PUSHED) && ++ !icsk->icsk_ack.pingpong)) && ++ !atomic_read(&meta_sk->sk_rmem_alloc))) { ++ tcp_send_ack(sk); ++ continue; ++ } ++ ++second_part: ++ /* This here is the second part of tcp_cleanup_rbuf */ ++ if (recheck_rcv_window) { ++ __u32 new_window = tp->ops->__select_window(sk); ++ ++ /* Send ACK now, if this read freed lots of space ++ * in our buffer. Certainly, new_window is new window. ++ * We can advertise it now, if it is not less than ++ * current one. ++ * "Lots" means "at least twice" here. ++ */ ++ if (new_window && new_window >= 2 * rcv_window_now) ++ tcp_send_ack(sk); ++ } ++ } ++} ++ ++static int mptcp_sub_send_fin(struct sock *sk) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct sk_buff *skb = tcp_write_queue_tail(sk); ++ int mss_now; ++ ++ /* Optimization, tack on the FIN if we have a queue of ++ * unsent frames. But be careful about outgoing SACKS ++ * and IP options. ++ */ ++ mss_now = tcp_current_mss(sk); ++ ++ if (tcp_send_head(sk) != NULL) { ++ TCP_SKB_CB(skb)->tcp_flags |= TCPHDR_FIN; ++ TCP_SKB_CB(skb)->end_seq++; ++ tp->write_seq++; ++ } else { ++ skb = alloc_skb_fclone(MAX_TCP_HEADER, GFP_ATOMIC); ++ if (!skb) ++ return 1; ++ ++ INIT_LIST_HEAD(&skb->tcp_tsorted_anchor); ++ skb_reserve(skb, MAX_TCP_HEADER); ++ /* FIN eats a sequence byte, write_seq advanced by tcp_queue_skb(). */ ++ tcp_init_nondata_skb(skb, tp->write_seq, ++ TCPHDR_ACK | TCPHDR_FIN); ++ tcp_queue_skb(sk, skb); ++ } ++ __tcp_push_pending_frames(sk, mss_now, TCP_NAGLE_OFF); ++ ++ return 0; ++} ++ ++static void mptcp_sub_close_doit(struct sock *sk) ++{ ++ struct sock *meta_sk = mptcp_meta_sk(sk); ++ struct tcp_sock *tp = tcp_sk(sk); ++ ++ if (sock_flag(sk, SOCK_DEAD)) ++ return; ++ ++ if (meta_sk->sk_shutdown == SHUTDOWN_MASK || sk->sk_state == TCP_CLOSE) { ++ tp->closing = 1; ++ tcp_close(sk, 0); ++ } else if (tcp_close_state(sk)) { ++ sk->sk_shutdown |= SEND_SHUTDOWN; ++ tcp_send_fin(sk); ++ } ++} ++ ++void mptcp_sub_close_wq(struct work_struct *work) ++{ ++ struct tcp_sock *tp = container_of(work, struct mptcp_tcp_sock, work.work)->tp; ++ struct sock *sk = (struct sock *)tp; ++ struct mptcp_cb *mpcb = tp->mpcb; ++ struct sock *meta_sk = mptcp_meta_sk(sk); ++ ++ mutex_lock(&mpcb->mpcb_mutex); ++ lock_sock_nested(meta_sk, SINGLE_DEPTH_NESTING); ++ ++ mptcp_sub_close_doit(sk); ++ ++ release_sock(meta_sk); ++ mutex_unlock(&mpcb->mpcb_mutex); ++ mptcp_mpcb_put(mpcb); ++ sock_put(sk); ++} ++ ++void mptcp_sub_close(struct sock *sk, unsigned long delay) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct delayed_work *work = &tcp_sk(sk)->mptcp->work; ++ ++ /* We are already closing - e.g., call from sock_def_error_report upon ++ * tcp_disconnect in tcp_close. ++ */ ++ if (tp->closing) ++ return; ++ ++ /* Work already scheduled ? */ ++ if (work_pending(&work->work)) { ++ /* Work present - who will be first ? */ ++ if (jiffies + delay > work->timer.expires) ++ return; ++ ++ /* Try canceling - if it fails, work will be executed soon */ ++ if (!cancel_delayed_work(work)) ++ return; ++ sock_put(sk); ++ mptcp_mpcb_put(tp->mpcb); ++ } ++ ++ if (!delay) { ++ unsigned char old_state = sk->sk_state; ++ ++ /* We directly send the FIN. Because it may take so a long time, ++ * untile the work-queue will get scheduled... ++ * ++ * If mptcp_sub_send_fin returns 1, it failed and thus we reset ++ * the old state so that tcp_close will finally send the fin ++ * in user-context. ++ */ ++ if (!sk->sk_err && old_state != TCP_CLOSE && ++ tcp_close_state(sk) && mptcp_sub_send_fin(sk)) { ++ if (old_state == TCP_ESTABLISHED) ++ TCP_INC_STATS(sock_net(sk), TCP_MIB_CURRESTAB); ++ sk->sk_state = old_state; ++ } ++ } ++ ++ sock_hold(sk); ++ refcount_inc(&tp->mpcb->mpcb_refcnt); ++ queue_delayed_work(mptcp_wq, work, delay); ++} ++ ++void mptcp_sub_force_close(struct sock *sk) ++{ ++ /* The below tcp_done may have freed the socket, if he is already dead. ++ * Thus, we are not allowed to access it afterwards. That's why ++ * we have to store the dead-state in this local variable. ++ */ ++ int sock_is_dead = sock_flag(sk, SOCK_DEAD); ++ ++ tcp_sk(sk)->mp_killed = 1; ++ ++ if (sk->sk_state != TCP_CLOSE) ++ tcp_done(sk); ++ ++ if (!sock_is_dead) ++ mptcp_sub_close(sk, 0); ++} ++EXPORT_SYMBOL(mptcp_sub_force_close); ++ ++/* Update the mpcb send window, based on the contributions ++ * of each subflow ++ */ ++void mptcp_update_sndbuf(const struct tcp_sock *tp) ++{ ++ struct sock *meta_sk = tp->meta_sk; ++ int new_sndbuf = 0, old_sndbuf = meta_sk->sk_sndbuf; ++ struct mptcp_tcp_sock *mptcp; ++ ++ mptcp_for_each_sub(tp->mpcb, mptcp) { ++ struct sock *sk = mptcp_to_sock(mptcp); ++ ++ if (!mptcp_sk_can_send(sk)) ++ continue; ++ ++ new_sndbuf += sk->sk_sndbuf; ++ ++ if (new_sndbuf > sock_net(meta_sk)->ipv4.sysctl_tcp_wmem[2] || ++ new_sndbuf < 0) { ++ new_sndbuf = sock_net(meta_sk)->ipv4.sysctl_tcp_wmem[2]; ++ break; ++ } ++ } ++ meta_sk->sk_sndbuf = max(min(new_sndbuf, ++ sock_net(meta_sk)->ipv4.sysctl_tcp_wmem[2]), ++ meta_sk->sk_sndbuf); ++ ++ /* The subflow's call to sk_write_space in tcp_new_space ends up in ++ * mptcp_write_space. ++ * It has nothing to do with waking up the application. ++ * So, we do it here. ++ */ ++ if (old_sndbuf != meta_sk->sk_sndbuf) ++ meta_sk->sk_write_space(meta_sk); ++} ++ ++/* Similar to: tcp_close */ ++void mptcp_close(struct sock *meta_sk, long timeout) ++{ ++ struct tcp_sock *meta_tp = tcp_sk(meta_sk); ++ struct mptcp_cb *mpcb = meta_tp->mpcb; ++ struct mptcp_tcp_sock *mptcp; ++ struct sk_buff *skb; ++ int data_was_unread = 0; ++ int state; ++ ++ mptcp_debug("%s: Close of meta_sk with tok %#x\n", ++ __func__, mpcb->mptcp_loc_token); ++ ++ WARN_ON(refcount_inc_not_zero(&mpcb->mpcb_refcnt) == 0); ++ mutex_lock(&mpcb->mpcb_mutex); ++ lock_sock_nested(meta_sk, SINGLE_DEPTH_NESTING); ++ ++ if (meta_tp->inside_tk_table) ++ /* Detach the mpcb from the token hashtable */ ++ mptcp_hash_remove_bh(meta_tp); ++ ++ meta_sk->sk_shutdown = SHUTDOWN_MASK; ++ /* We need to flush the recv. buffs. We do this only on the ++ * descriptor close, not protocol-sourced closes, because the ++ * reader process may not have drained the data yet! ++ */ ++ while ((skb = __skb_dequeue(&meta_sk->sk_receive_queue)) != NULL) { ++ u32 len = TCP_SKB_CB(skb)->end_seq - TCP_SKB_CB(skb)->seq; ++ ++ if (TCP_SKB_CB(skb)->tcp_flags & TCPHDR_FIN) ++ len--; ++ data_was_unread += len; ++ __kfree_skb(skb); ++ } ++ ++ sk_mem_reclaim(meta_sk); ++ ++ /* If socket has been already reset (e.g. in tcp_reset()) - kill it. */ ++ if (meta_sk->sk_state == TCP_CLOSE) { ++ struct mptcp_tcp_sock *mptcp; ++ struct hlist_node *tmp; ++ ++ mptcp_for_each_sub_safe(mpcb, mptcp, tmp) { ++ struct sock *sk_it = mptcp_to_sock(mptcp); ++ ++ if (tcp_sk(sk_it)->send_mp_fclose) ++ continue; ++ mptcp_sub_close(sk_it, 0); ++ } ++ goto adjudge_to_death; ++ } ++ ++ if (data_was_unread) { ++ /* Unread data was tossed, zap the connection. */ ++ NET_INC_STATS(sock_net(meta_sk), LINUX_MIB_TCPABORTONCLOSE); ++ tcp_set_state(meta_sk, TCP_CLOSE); ++ tcp_sk(meta_sk)->ops->send_active_reset(meta_sk, ++ meta_sk->sk_allocation); ++ } else if (sock_flag(meta_sk, SOCK_LINGER) && !meta_sk->sk_lingertime) { ++ /* Check zero linger _after_ checking for unread data. */ ++ meta_sk->sk_prot->disconnect(meta_sk, 0); ++ NET_INC_STATS(sock_net(meta_sk), LINUX_MIB_TCPABORTONDATA); ++ } else if (tcp_close_state(meta_sk)) { ++ mptcp_send_fin(meta_sk); ++ } else if (meta_tp->snd_una == meta_tp->write_seq) { ++ struct mptcp_tcp_sock *mptcp; ++ struct hlist_node *tmp; ++ ++ /* The DATA_FIN has been sent and acknowledged ++ * (e.g., by sk_shutdown). Close all the other subflows ++ */ ++ mptcp_for_each_sub_safe(mpcb, mptcp, tmp) { ++ struct sock *sk_it = mptcp_to_sock(mptcp); ++ unsigned long delay = 0; ++ /* If we are the passive closer, don't trigger ++ * subflow-fin until the subflow has been finned ++ * by the peer. - thus we add a delay ++ */ ++ if (mpcb->passive_close && ++ sk_it->sk_state == TCP_ESTABLISHED) ++ delay = inet_csk(sk_it)->icsk_rto << 3; ++ ++ mptcp_sub_close(sk_it, delay); ++ } ++ } ++ ++ sk_stream_wait_close(meta_sk, timeout); ++ ++adjudge_to_death: ++ state = meta_sk->sk_state; ++ sock_hold(meta_sk); ++ sock_orphan(meta_sk); ++ ++ /* socket will be freed after mptcp_close - we have to prevent ++ * access from the subflows. ++ */ ++ mptcp_for_each_sub(mpcb, mptcp) { ++ struct sock *sk_it = mptcp_to_sock(mptcp); ++ ++ /* Similar to sock_orphan, but we don't set it DEAD, because ++ * the callbacks are still set and must be called. ++ */ ++ write_lock_bh(&sk_it->sk_callback_lock); ++ sk_set_socket(sk_it, NULL); ++ sk_it->sk_wq = NULL; ++ write_unlock_bh(&sk_it->sk_callback_lock); ++ } ++ ++ if (mpcb->pm_ops->close_session) ++ mpcb->pm_ops->close_session(meta_sk); ++ ++ /* It is the last release_sock in its life. It will remove backlog. */ ++ release_sock(meta_sk); ++ ++ /* Now socket is owned by kernel and we acquire BH lock ++ * to finish close. No need to check for user refs. ++ */ ++ local_bh_disable(); ++ bh_lock_sock(meta_sk); ++ WARN_ON(sock_owned_by_user(meta_sk)); ++ ++ percpu_counter_inc(meta_sk->sk_prot->orphan_count); ++ ++ /* Have we already been destroyed by a softirq or backlog? */ ++ if (state != TCP_CLOSE && meta_sk->sk_state == TCP_CLOSE) ++ goto out; ++ ++ /* This is a (useful) BSD violating of the RFC. There is a ++ * problem with TCP as specified in that the other end could ++ * keep a socket open forever with no application left this end. ++ * We use a 3 minute timeout (about the same as BSD) then kill ++ * our end. If they send after that then tough - BUT: long enough ++ * that we won't make the old 4*rto = almost no time - whoops ++ * reset mistake. ++ * ++ * Nope, it was not mistake. It is really desired behaviour ++ * f.e. on http servers, when such sockets are useless, but ++ * consume significant resources. Let's do it with special ++ * linger2 option. --ANK ++ */ ++ ++ if (meta_sk->sk_state == TCP_FIN_WAIT2) { ++ if (meta_tp->linger2 < 0) { ++ tcp_set_state(meta_sk, TCP_CLOSE); ++ meta_tp->ops->send_active_reset(meta_sk, GFP_ATOMIC); ++ __NET_INC_STATS(sock_net(meta_sk), ++ LINUX_MIB_TCPABORTONLINGER); ++ } else { ++ const int tmo = tcp_fin_time(meta_sk); ++ ++ if (tmo > TCP_TIMEWAIT_LEN) { ++ inet_csk_reset_keepalive_timer(meta_sk, ++ tmo - TCP_TIMEWAIT_LEN); ++ } else { ++ meta_tp->ops->time_wait(meta_sk, TCP_FIN_WAIT2, ++ tmo); ++ goto out; ++ } ++ } ++ } ++ if (meta_sk->sk_state != TCP_CLOSE) { ++ sk_mem_reclaim(meta_sk); ++ if (tcp_check_oom(meta_sk, 0)) { ++ if (net_ratelimit()) ++ pr_info("MPTCP: out of memory: force closing socket\n"); ++ tcp_set_state(meta_sk, TCP_CLOSE); ++ meta_tp->ops->send_active_reset(meta_sk, GFP_ATOMIC); ++ __NET_INC_STATS(sock_net(meta_sk), ++ LINUX_MIB_TCPABORTONMEMORY); ++ } ++ } ++ ++ ++ if (meta_sk->sk_state == TCP_CLOSE) ++ inet_csk_destroy_sock(meta_sk); ++ /* Otherwise, socket is reprieved until protocol close. */ ++ ++out: ++ bh_unlock_sock(meta_sk); ++ local_bh_enable(); ++ mutex_unlock(&mpcb->mpcb_mutex); ++ mptcp_mpcb_put(mpcb); ++ sock_put(meta_sk); /* Taken by sock_hold */ ++} ++ ++void mptcp_disconnect(struct sock *meta_sk) ++{ ++ struct tcp_sock *meta_tp = tcp_sk(meta_sk); ++ struct mptcp_tcp_sock *mptcp; ++ struct hlist_node *tmp; ++ ++ __skb_queue_purge(&meta_tp->mpcb->reinject_queue); ++ ++ if (meta_tp->inside_tk_table) ++ mptcp_hash_remove_bh(meta_tp); ++ ++ local_bh_disable(); ++ mptcp_for_each_sub_safe(meta_tp->mpcb, mptcp, tmp) { ++ struct sock *subsk = mptcp_to_sock(mptcp); ++ ++ if (spin_is_locked(&subsk->sk_lock.slock)) ++ bh_unlock_sock(subsk); ++ ++ tcp_sk(subsk)->tcp_disconnect = 1; ++ ++ meta_sk->sk_prot->disconnect(subsk, O_NONBLOCK); ++ ++ sock_orphan(subsk); ++ ++ percpu_counter_inc(meta_sk->sk_prot->orphan_count); ++ ++ inet_csk_destroy_sock(subsk); ++ } ++ local_bh_enable(); ++ ++ mptcp_mpcb_cleanup(meta_tp->mpcb); ++ meta_tp->meta_sk = NULL; ++ ++ meta_tp->send_mp_fclose = 0; ++ meta_tp->mpc = 0; ++ meta_tp->ops = &tcp_specific; ++#if IS_ENABLED(CONFIG_IPV6) ++ if (meta_sk->sk_family == AF_INET6) ++ meta_sk->sk_backlog_rcv = tcp_v6_do_rcv; ++ else ++ meta_sk->sk_backlog_rcv = tcp_v4_do_rcv; ++#else ++ meta_sk->sk_backlog_rcv = tcp_v4_do_rcv; ++#endif ++ meta_sk->sk_destruct = inet_sock_destruct; ++} ++ ++ ++/* Returns True if we should enable MPTCP for that socket. */ ++bool mptcp_doit(struct sock *sk) ++{ ++ const struct dst_entry *dst = __sk_dst_get(sk); ++ ++ /* Don't do mptcp over loopback */ ++ if (sk->sk_family == AF_INET && ++ (ipv4_is_loopback(inet_sk(sk)->inet_daddr) || ++ ipv4_is_loopback(inet_sk(sk)->inet_saddr))) ++ return false; ++#if IS_ENABLED(CONFIG_IPV6) ++ if (sk->sk_family == AF_INET6 && ++ (ipv6_addr_loopback(&sk->sk_v6_daddr) || ++ ipv6_addr_loopback(&inet6_sk(sk)->saddr))) ++ return false; ++#endif ++ if (mptcp_v6_is_v4_mapped(sk) && ++ ipv4_is_loopback(inet_sk(sk)->inet_saddr)) ++ return false; ++ ++#ifdef CONFIG_TCP_MD5SIG ++ /* If TCP_MD5SIG is enabled, do not do MPTCP - there is no Option-Space */ ++ if (tcp_sk(sk)->af_specific->md5_lookup(sk, sk)) ++ return false; ++#endif ++ ++ if (dst->dev && (dst->dev->flags & IFF_NOMULTIPATH)) ++ return false; ++ ++ return true; ++} ++ ++int mptcp_create_master_sk(struct sock *meta_sk, __u64 remote_key, ++ int rem_key_set, __u8 mptcp_ver, u32 window) ++{ ++ struct tcp_sock *master_tp; ++ struct sock *master_sk; ++ ++ if (mptcp_alloc_mpcb(meta_sk, remote_key, rem_key_set, mptcp_ver, window)) ++ goto err_alloc_mpcb; ++ ++ master_sk = tcp_sk(meta_sk)->mpcb->master_sk; ++ master_tp = tcp_sk(master_sk); ++ ++ if (mptcp_add_sock(meta_sk, master_sk, 0, 0, GFP_ATOMIC)) ++ goto err_add_sock; ++ ++ if (__inet_inherit_port(meta_sk, master_sk) < 0) ++ goto err_add_sock; ++ ++ meta_sk->sk_prot->unhash(meta_sk); ++ inet_ehash_nolisten(master_sk, NULL); ++ ++ master_tp->mptcp->init_rcv_wnd = master_tp->rcv_wnd; ++ ++ return 0; ++ ++err_add_sock: ++ inet_csk_prepare_forced_close(master_sk); ++ tcp_done(master_sk); ++ ++err_alloc_mpcb: ++ return -ENOBUFS; ++} ++ ++static int __mptcp_check_req_master(struct sock *child, ++ const struct mptcp_options_received *mopt, ++ struct request_sock *req) ++{ ++ struct tcp_sock *child_tp = tcp_sk(child); ++ struct sock *meta_sk = child; ++ struct mptcp_cb *mpcb; ++ struct mptcp_request_sock *mtreq; ++ ++ /* Never contained an MP_CAPABLE */ ++ if (!inet_rsk(req)->mptcp_rqsk) ++ return 1; ++ ++ mtreq = mptcp_rsk(req); ++ ++ if (!inet_rsk(req)->saw_mpc) { ++ /* Fallback to regular TCP, because we saw one SYN without ++ * MP_CAPABLE. In tcp_check_req we continue the regular path. ++ * But, the socket has been added to the reqsk_tk_htb, so we ++ * must still remove it. ++ */ ++ MPTCP_INC_STATS(sock_net(meta_sk), MPTCP_MIB_MPCAPABLEPASSIVEFALLBACK); ++ mptcp_reqsk_remove_tk(req); ++ return 1; ++ } ++ ++ /* mopt can be NULL when coming from FAST-OPEN */ ++ if (mopt && mopt->saw_mpc && mtreq->mptcp_ver == MPTCP_VERSION_1) { ++ mtreq->mptcp_rem_key = mopt->mptcp_sender_key; ++ mtreq->rem_key_set = 1; ++ } ++ ++ MPTCP_INC_STATS(sock_net(meta_sk), MPTCP_MIB_MPCAPABLEPASSIVEACK); ++ ++ /* Just set this values to pass them to mptcp_alloc_mpcb */ ++ child_tp->mptcp_loc_key = mtreq->mptcp_loc_key; ++ child_tp->mptcp_loc_token = mtreq->mptcp_loc_token; ++ ++ if (mptcp_create_master_sk(meta_sk, mtreq->mptcp_rem_key, ++ mtreq->rem_key_set, mtreq->mptcp_ver, ++ child_tp->snd_wnd)) { ++ inet_csk_prepare_forced_close(meta_sk); ++ tcp_done(meta_sk); ++ ++ return -ENOBUFS; ++ } ++ ++ child = tcp_sk(child)->mpcb->master_sk; ++ child_tp = tcp_sk(child); ++ mpcb = child_tp->mpcb; ++ ++ child_tp->mptcp->snt_isn = tcp_rsk(req)->snt_isn; ++ child_tp->mptcp->rcv_isn = tcp_rsk(req)->rcv_isn; ++ ++ mpcb->dss_csum = mtreq->dss_csum; ++ mpcb->server_side = 1; ++ ++ /* Needs to be done here additionally, because when accepting a ++ * new connection we pass by __reqsk_free and not reqsk_free. ++ */ ++ mptcp_reqsk_remove_tk(req); ++ ++ /* Hold when creating the meta-sk in tcp_vX_syn_recv_sock. */ ++ sock_put(meta_sk); ++ ++ return 0; ++} ++ ++int mptcp_check_req_fastopen(struct sock *child, struct request_sock *req) ++{ ++ struct sock *meta_sk = child, *master_sk; ++ struct sk_buff *skb; ++ u32 new_mapping; ++ int ret; ++ ++ ret = __mptcp_check_req_master(child, NULL, req); ++ if (ret) ++ return ret; ++ ++ master_sk = tcp_sk(meta_sk)->mpcb->master_sk; ++ ++ /* We need to rewind copied_seq as it is set to IDSN + 1 and as we have ++ * pre-MPTCP data in the receive queue. ++ */ ++ tcp_sk(meta_sk)->copied_seq -= tcp_sk(master_sk)->rcv_nxt - ++ tcp_rsk(req)->rcv_isn - 1; ++ ++ /* Map subflow sequence number to data sequence numbers. We need to map ++ * these data to [IDSN - len - 1, IDSN[. ++ */ ++ new_mapping = tcp_sk(meta_sk)->copied_seq - tcp_rsk(req)->rcv_isn - 1; ++ ++ /* There should be only one skb: the SYN + data. */ ++ skb_queue_walk(&meta_sk->sk_receive_queue, skb) { ++ TCP_SKB_CB(skb)->seq += new_mapping; ++ TCP_SKB_CB(skb)->end_seq += new_mapping; ++ } ++ ++ /* With fastopen we change the semantics of the relative subflow ++ * sequence numbers to deal with middleboxes that could add/remove ++ * multiple bytes in the SYN. We chose to start counting at rcv_nxt - 1 ++ * instead of the regular TCP ISN. ++ */ ++ tcp_sk(master_sk)->mptcp->rcv_isn = tcp_sk(master_sk)->rcv_nxt - 1; ++ ++ /* We need to update copied_seq of the master_sk to account for the ++ * already moved data to the meta receive queue. ++ */ ++ tcp_sk(master_sk)->copied_seq = tcp_sk(master_sk)->rcv_nxt; ++ ++ /* Handled by the master_sk */ ++ tcp_sk(meta_sk)->fastopen_rsk = NULL; ++ ++ return 0; ++} ++ ++int mptcp_check_req_master(struct sock *sk, struct sock *child, ++ struct request_sock *req, const struct sk_buff *skb, ++ const struct mptcp_options_received *mopt, ++ int drop, u32 tsoff) ++{ ++ struct sock *meta_sk = child; ++ int ret; ++ ++ ret = __mptcp_check_req_master(child, mopt, req); ++ if (ret) ++ return ret; ++ child = tcp_sk(child)->mpcb->master_sk; ++ ++ sock_rps_save_rxhash(child, skb); ++ ++ /* drop indicates that we come from tcp_check_req and thus need to ++ * handle the request-socket fully. ++ */ ++ if (drop) { ++ tcp_synack_rtt_meas(child, req); ++ ++ inet_csk_reqsk_queue_drop(sk, req); ++ reqsk_queue_removed(&inet_csk(sk)->icsk_accept_queue, req); ++ if (!inet_csk_reqsk_queue_add(sk, req, meta_sk)) { ++ bh_unlock_sock(meta_sk); ++ /* No sock_put() of the meta needed. The reference has ++ * already been dropped in __mptcp_check_req_master(). ++ */ ++ sock_put(child); ++ return -1; ++ } ++ } else { ++ /* Thus, we come from syn-cookies */ ++ refcount_set(&req->rsk_refcnt, 1); ++ tcp_sk(meta_sk)->tsoffset = tsoff; ++ if (!inet_csk_reqsk_queue_add(sk, req, meta_sk)) { ++ bh_unlock_sock(meta_sk); ++ /* No sock_put() of the meta needed. The reference has ++ * already been dropped in __mptcp_check_req_master(). ++ */ ++ sock_put(child); ++ reqsk_put(req); ++ return -1; ++ } ++ } ++ ++ return 0; ++} ++ ++/* May be called without holding the meta-level lock */ ++struct sock *mptcp_check_req_child(struct sock *meta_sk, ++ struct sock *child, ++ struct request_sock *req, ++ struct sk_buff *skb, ++ const struct mptcp_options_received *mopt) ++{ ++ const struct mptcp_cb *mpcb = tcp_sk(meta_sk)->mpcb; ++ struct mptcp_request_sock *mtreq = mptcp_rsk(req); ++ struct tcp_sock *child_tp = tcp_sk(child); ++ u8 hash_mac_check[SHA256_DIGEST_SIZE]; ++ ++ if (!mopt->join_ack) { ++ MPTCP_INC_STATS(sock_net(meta_sk), MPTCP_MIB_JOINACKFAIL); ++ goto teardown; ++ } ++ ++ mptcp_hmac(mpcb->mptcp_ver, (u8 *)&mpcb->mptcp_rem_key, ++ (u8 *)&mpcb->mptcp_loc_key, hash_mac_check, 2, ++ 4, (u8 *)&mtreq->mptcp_rem_nonce, ++ 4, (u8 *)&mtreq->mptcp_loc_nonce); ++ ++ if (memcmp(hash_mac_check, (char *)&mopt->mptcp_recv_mac, 20)) { ++ MPTCP_INC_STATS(sock_net(meta_sk), MPTCP_MIB_JOINACKMAC); ++ goto teardown; ++ } ++ ++ /* Point it to the same struct socket and wq as the meta_sk */ ++ sk_set_socket(child, meta_sk->sk_socket); ++ child->sk_wq = meta_sk->sk_wq; ++ ++ if (mptcp_add_sock(meta_sk, child, mtreq->loc_id, mtreq->rem_id, GFP_ATOMIC)) { ++ /* Has been inherited, but now child_tp->mptcp is NULL */ ++ child_tp->mpc = 0; ++ child_tp->ops = &tcp_specific; ++ ++ /* TODO when we support acking the third ack for new subflows, ++ * we should silently discard this third ack, by returning NULL. ++ * ++ * Maybe, at the retransmission we will have enough memory to ++ * fully add the socket to the meta-sk. ++ */ ++ goto teardown; ++ } ++ ++ /* The child is a clone of the meta socket, we must now reset ++ * some of the fields ++ */ ++ child_tp->mptcp->rcv_low_prio = mtreq->rcv_low_prio; ++ ++ /* We should allow proper increase of the snd/rcv-buffers. Thus, we ++ * use the original values instead of the bloated up ones from the ++ * clone. ++ */ ++ child->sk_sndbuf = mpcb->orig_sk_sndbuf; ++ child->sk_rcvbuf = mpcb->orig_sk_rcvbuf; ++ ++ child_tp->mptcp->slave_sk = 1; ++ child_tp->mptcp->snt_isn = tcp_rsk(req)->snt_isn; ++ child_tp->mptcp->rcv_isn = tcp_rsk(req)->rcv_isn; ++ child_tp->mptcp->init_rcv_wnd = req->rsk_rcv_wnd; ++ ++ child->sk_tsq_flags = 0; ++ ++ child_tp->packets_out = 0; ++ ++ tcp_reset_vars(child); ++ ++ sock_rps_save_rxhash(child, skb); ++ tcp_synack_rtt_meas(child, req); ++ ++ if (mpcb->pm_ops->established_subflow) ++ mpcb->pm_ops->established_subflow(child); ++ ++ /* Subflows do not use the accept queue, as they ++ * are attached immediately to the mpcb. ++ */ ++ inet_csk_reqsk_queue_drop(meta_sk, req); ++ reqsk_queue_removed(&inet_csk(meta_sk)->icsk_accept_queue, req); ++ ++ /* The refcnt is initialized to 2, because regular TCP will put him ++ * in the socket's listener queue. However, we do not have a listener-queue. ++ * So, we need to make sure that this request-sock indeed gets destroyed. ++ */ ++ reqsk_put(req); ++ ++ MPTCP_INC_STATS(sock_net(meta_sk), MPTCP_MIB_JOINACKRX); ++ ++ if (inet_sk(child)->inet_sport != inet_sk(meta_sk)->inet_sport) ++ MPTCP_INC_STATS(sock_net(meta_sk), MPTCP_MIB_JOINALTERNATEPORT); ++ ++ return child; ++ ++teardown: ++ req->rsk_ops->send_reset(meta_sk, skb); ++ ++ /* Drop this request - sock creation failed. */ ++ inet_csk_reqsk_queue_drop(meta_sk, req); ++ reqsk_queue_removed(&inet_csk(meta_sk)->icsk_accept_queue, req); ++ inet_csk_prepare_forced_close(child); ++ tcp_done(child); ++ bh_unlock_sock(meta_sk); ++ return meta_sk; ++} ++ ++int mptcp_init_tw_sock(struct sock *sk, struct tcp_timewait_sock *tw) ++{ ++ struct mptcp_tw *mptw; ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct mptcp_cb *mpcb = tp->mpcb; ++ ++ /* A subsocket in tw can only receive data. So, if we are in ++ * infinite-receive, then we should not reply with a data-ack or act ++ * upon general MPTCP-signaling. We prevent this by simply not creating ++ * the mptcp_tw_sock. ++ */ ++ if (mpcb->infinite_mapping_rcv) { ++ tw->mptcp_tw = NULL; ++ return 0; ++ } ++ ++ /* Alloc MPTCP-tw-sock */ ++ mptw = kmem_cache_alloc(mptcp_tw_cache, GFP_ATOMIC); ++ if (!mptw) { ++ tw->mptcp_tw = NULL; ++ return -ENOBUFS; ++ } ++ ++ refcount_inc(&mpcb->mpcb_refcnt); ++ ++ tw->mptcp_tw = mptw; ++ mptw->loc_key = mpcb->mptcp_loc_key; ++ mptw->meta_tw = mpcb->in_time_wait; ++ mptw->rcv_nxt = mptcp_get_rcv_nxt_64(mptcp_meta_tp(tp)); ++ if (mptw->meta_tw && mpcb->mptw_state != TCP_TIME_WAIT) ++ mptw->rcv_nxt++; ++ rcu_assign_pointer(mptw->mpcb, mpcb); ++ ++ spin_lock_bh(&mpcb->mpcb_list_lock); ++ list_add_rcu(&mptw->list, &tp->mpcb->tw_list); ++ mptw->in_list = 1; ++ spin_unlock_bh(&mpcb->mpcb_list_lock); ++ ++ return 0; ++} ++ ++void mptcp_twsk_destructor(struct tcp_timewait_sock *tw) ++{ ++ struct mptcp_cb *mpcb; ++ ++ rcu_read_lock(); ++ local_bh_disable(); ++ mpcb = rcu_dereference(tw->mptcp_tw->mpcb); ++ ++ /* If we are still holding a ref to the mpcb, we have to remove ourself ++ * from the list and drop the ref properly. ++ */ ++ if (mpcb && refcount_inc_not_zero(&mpcb->mpcb_refcnt)) { ++ spin_lock(&mpcb->mpcb_list_lock); ++ if (tw->mptcp_tw->in_list) { ++ list_del_rcu(&tw->mptcp_tw->list); ++ tw->mptcp_tw->in_list = 0; ++ /* Put, because we added it to the list */ ++ mptcp_mpcb_put(mpcb); ++ } ++ spin_unlock(&mpcb->mpcb_list_lock); ++ ++ /* Second time, because we increased it above */ ++ mptcp_mpcb_put(mpcb); ++ } ++ ++ local_bh_enable(); ++ rcu_read_unlock(); ++ ++ kmem_cache_free(mptcp_tw_cache, tw->mptcp_tw); ++} ++ ++/* Updates the rcv_nxt of the time-wait-socks and allows them to ack a ++ * data-fin. ++ */ ++void mptcp_time_wait(struct sock *meta_sk, int state, int timeo) ++{ ++ struct tcp_sock *meta_tp = tcp_sk(meta_sk); ++ struct mptcp_tw *mptw; ++ ++ if (mptcp_in_infinite_mapping_weak(meta_tp->mpcb)) { ++ struct mptcp_tcp_sock *mptcp; ++ struct hlist_node *tmp; ++ ++ mptcp_for_each_sub_safe(meta_tp->mpcb, mptcp, tmp) { ++ struct sock *sk_it = mptcp_to_sock(mptcp); ++ ++ if (sk_it->sk_state == TCP_CLOSE) ++ continue; ++ ++ tcp_sk(sk_it)->ops->time_wait(sk_it, state, timeo); ++ } ++ } ++ ++ /* Used for sockets that go into tw after the meta ++ * (see mptcp_init_tw_sock()) ++ */ ++ meta_tp->mpcb->in_time_wait = 1; ++ meta_tp->mpcb->mptw_state = state; ++ ++ /* Update the time-wait-sock's information */ ++ rcu_read_lock(); ++ local_bh_disable(); ++ list_for_each_entry_rcu(mptw, &meta_tp->mpcb->tw_list, list) { ++ mptw->meta_tw = 1; ++ mptw->rcv_nxt = mptcp_get_rcv_nxt_64(meta_tp); ++ ++ /* We want to ack a DATA_FIN, but are yet in FIN_WAIT_2 - ++ * pretend as if the DATA_FIN has already reached us, that way ++ * the checks in tcp_timewait_state_process will be good as the ++ * DATA_FIN comes in. ++ */ ++ if (state != TCP_TIME_WAIT) ++ mptw->rcv_nxt++; ++ } ++ local_bh_enable(); ++ rcu_read_unlock(); ++ ++ if (meta_sk->sk_state != TCP_CLOSE) ++ tcp_done(meta_sk); ++} ++ ++void mptcp_tsq_flags(struct sock *sk) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct sock *meta_sk = mptcp_meta_sk(sk); ++ ++ /* It will be handled as a regular deferred-call */ ++ if (is_meta_sk(sk)) ++ return; ++ ++ if (hlist_unhashed(&tp->mptcp->cb_list)) { ++ hlist_add_head(&tp->mptcp->cb_list, &tp->mpcb->callback_list); ++ /* We need to hold it here, as the sock_hold is not assured ++ * by the release_sock as it is done in regular TCP. ++ * ++ * The subsocket may get inet_csk_destroy'd while it is inside ++ * the callback_list. ++ */ ++ sock_hold(sk); ++ } ++ ++ if (!test_and_set_bit(MPTCP_SUB_DEFERRED, &meta_sk->sk_tsq_flags)) ++ sock_hold(meta_sk); ++} ++ ++void mptcp_tsq_sub_deferred(struct sock *meta_sk) ++{ ++ struct tcp_sock *meta_tp = tcp_sk(meta_sk); ++ struct mptcp_tcp_sock *mptcp; ++ struct hlist_node *tmp; ++ ++ __sock_put(meta_sk); ++ hlist_for_each_entry_safe(mptcp, tmp, &meta_tp->mpcb->callback_list, cb_list) { ++ struct tcp_sock *tp = mptcp->tp; ++ struct sock *sk = (struct sock *)tp; ++ ++ hlist_del_init(&mptcp->cb_list); ++ sk->sk_prot->release_cb(sk); ++ /* Final sock_put (cfr. mptcp_tsq_flags) */ ++ sock_put(sk); ++ } ++} ++ ++/* May be called without holding the meta-level lock */ ++void mptcp_join_reqsk_init(const struct mptcp_cb *mpcb, ++ const struct request_sock *req, ++ struct sk_buff *skb) ++{ ++ struct mptcp_request_sock *mtreq = mptcp_rsk(req); ++ u8 mptcp_hash_mac[SHA256_DIGEST_SIZE]; ++ struct mptcp_options_received mopt; ++ ++ mptcp_init_mp_opt(&mopt); ++ tcp_parse_mptcp_options(skb, &mopt); ++ ++ mtreq->is_sub = 1; ++ inet_rsk(req)->mptcp_rqsk = 1; ++ ++ mtreq->mptcp_rem_nonce = mopt.mptcp_recv_nonce; ++ ++ mptcp_hmac(mpcb->mptcp_ver, (u8 *)&mpcb->mptcp_loc_key, ++ (u8 *)&mpcb->mptcp_rem_key, mptcp_hash_mac, 2, ++ 4, (u8 *)&mtreq->mptcp_loc_nonce, ++ 4, (u8 *)&mtreq->mptcp_rem_nonce); ++ mtreq->mptcp_hash_tmac = *(u64 *)mptcp_hash_mac; ++ ++ mtreq->rem_id = mopt.rem_id; ++ mtreq->rcv_low_prio = mopt.low_prio; ++ inet_rsk(req)->saw_mpc = 1; ++ ++ MPTCP_INC_STATS(sock_net(mpcb->meta_sk), MPTCP_MIB_JOINSYNRX); ++} ++ ++void mptcp_reqsk_init(struct request_sock *req, const struct sock *sk, ++ const struct sk_buff *skb, bool want_cookie) ++{ ++ struct mptcp_options_received mopt; ++ struct mptcp_request_sock *mtreq = mptcp_rsk(req); ++ ++ mptcp_init_mp_opt(&mopt); ++ tcp_parse_mptcp_options(skb, &mopt); ++ ++ mtreq->dss_csum = mopt.dss_csum; ++ ++ if (want_cookie) { ++ if (!mptcp_reqsk_new_cookie(req, sk, &mopt, skb)) ++ /* No key available - back to regular TCP */ ++ inet_rsk(req)->mptcp_rqsk = 0; ++ return; ++ } ++ ++ mptcp_reqsk_new_mptcp(req, sk, &mopt, skb); ++} ++ ++void mptcp_cookies_reqsk_init(struct request_sock *req, ++ struct mptcp_options_received *mopt, ++ struct sk_buff *skb) ++{ ++ struct mptcp_request_sock *mtreq = mptcp_rsk(req); ++ ++ /* Absolutely need to always initialize this. */ ++ mtreq->hash_entry.pprev = NULL; ++ ++ mtreq->mptcp_ver = mopt->mptcp_ver; ++ mtreq->mptcp_rem_key = mopt->mptcp_sender_key; ++ mtreq->mptcp_loc_key = mopt->mptcp_receiver_key; ++ mtreq->rem_key_set = 1; ++ ++ /* Generate the token */ ++ mptcp_key_hash(mtreq->mptcp_ver, mtreq->mptcp_loc_key, &mtreq->mptcp_loc_token, NULL); ++ ++ rcu_read_lock(); ++ local_bh_disable(); ++ spin_lock(&mptcp_tk_hashlock); ++ ++ /* Check, if the key is still free */ ++ if (mptcp_reqsk_find_tk(mtreq->mptcp_loc_token) || ++ mptcp_find_token(mtreq->mptcp_loc_token)) ++ goto out; ++ ++ inet_rsk(req)->saw_mpc = 1; ++ mtreq->is_sub = 0; ++ inet_rsk(req)->mptcp_rqsk = 1; ++ mtreq->dss_csum = mopt->dss_csum; ++ ++out: ++ spin_unlock(&mptcp_tk_hashlock); ++ local_bh_enable(); ++ rcu_read_unlock(); ++} ++ ++int mptcp_conn_request(struct sock *sk, struct sk_buff *skb) ++{ ++ struct mptcp_options_received mopt; ++ ++ mptcp_init_mp_opt(&mopt); ++ tcp_parse_mptcp_options(skb, &mopt); ++ ++ if (mopt.is_mp_join) ++ return mptcp_do_join_short(skb, &mopt, sock_net(sk)); ++ if (mopt.drop_me) ++ goto drop; ++ ++ if (!sock_flag(sk, SOCK_MPTCP)) ++ mopt.saw_mpc = 0; ++ ++ /* If the requested version is higher than what we support, fall back */ ++ if (mopt.saw_mpc && mopt.mptcp_ver > tcp_sk(sk)->mptcp_ver) ++ mopt.saw_mpc = 0; ++ ++ if (skb->protocol == htons(ETH_P_IP)) { ++ if (mopt.saw_mpc) { ++ if (skb_rtable(skb)->rt_flags & ++ (RTCF_BROADCAST | RTCF_MULTICAST)) ++ goto drop; ++ ++ MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_MPCAPABLEPASSIVE); ++ return tcp_conn_request(&mptcp_request_sock_ops, ++ &mptcp_request_sock_ipv4_ops, ++ sk, skb); ++ } ++ ++ return tcp_v4_conn_request(sk, skb); ++#if IS_ENABLED(CONFIG_IPV6) ++ } else { ++ if (mopt.saw_mpc) { ++ if (!ipv6_unicast_destination(skb)) ++ goto drop; ++ ++ MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_MPCAPABLEPASSIVE); ++ return tcp_conn_request(&mptcp6_request_sock_ops, ++ &mptcp_request_sock_ipv6_ops, ++ sk, skb); ++ } ++ ++ return tcp_v6_conn_request(sk, skb); ++#endif ++ } ++drop: ++ NET_INC_STATS(sock_net(sk), LINUX_MIB_LISTENDROPS); ++ return 0; ++} ++ ++int mptcp_finish_handshake(struct sock *child, struct sk_buff *skb) ++ __releases(&child->sk_lock.slock) ++{ ++ int ret; ++ ++ /* We don't call tcp_child_process here, because we hold ++ * already the meta-sk-lock and are sure that it is not owned ++ * by the user. ++ */ ++ tcp_sk(child)->segs_in += max_t(u16, 1, skb_shinfo(skb)->gso_segs); ++ ret = tcp_rcv_state_process(child, skb); ++ bh_unlock_sock(child); ++ sock_put(child); ++ ++ return ret; ++} ++ ++static void __mptcp_get_info(const struct sock *meta_sk, ++ struct mptcp_meta_info *info) ++{ ++ const struct inet_connection_sock *meta_icsk = inet_csk(meta_sk); ++ const struct tcp_sock *meta_tp = tcp_sk(meta_sk); ++ u32 now = tcp_jiffies32; ++ ++ memset(info, 0, sizeof(*info)); ++ ++ info->mptcpi_state = meta_sk->sk_state; ++ info->mptcpi_retransmits = meta_icsk->icsk_retransmits; ++ info->mptcpi_probes = meta_icsk->icsk_probes_out; ++ info->mptcpi_backoff = meta_icsk->icsk_backoff; ++ ++ info->mptcpi_rto = jiffies_to_usecs(meta_icsk->icsk_rto); ++ ++ info->mptcpi_unacked = meta_tp->packets_out; ++ ++ info->mptcpi_last_data_sent = jiffies_to_msecs(now - meta_tp->lsndtime); ++ info->mptcpi_last_data_recv = jiffies_to_msecs(now - meta_icsk->icsk_ack.lrcvtime); ++ info->mptcpi_last_ack_recv = jiffies_to_msecs(now - meta_tp->rcv_tstamp); ++ ++ info->mptcpi_total_retrans = meta_tp->total_retrans; ++ ++ info->mptcpi_bytes_acked = meta_tp->bytes_acked; ++ info->mptcpi_bytes_received = meta_tp->bytes_received; ++} ++ ++static void mptcp_get_sub_info(struct sock *sk, struct mptcp_sub_info *info) ++{ ++ struct inet_sock *inet = inet_sk(sk); ++ ++ memset(info, 0, sizeof(*info)); ++ ++ if (sk->sk_family == AF_INET) { ++ info->src_v4.sin_family = AF_INET; ++ info->src_v4.sin_port = inet->inet_sport; ++ ++ info->src_v4.sin_addr.s_addr = inet->inet_rcv_saddr; ++ if (!info->src_v4.sin_addr.s_addr) ++ info->src_v4.sin_addr.s_addr = inet->inet_saddr; ++ ++ info->dst_v4.sin_family = AF_INET; ++ info->dst_v4.sin_port = inet->inet_dport; ++ info->dst_v4.sin_addr.s_addr = inet->inet_daddr; ++#if IS_ENABLED(CONFIG_IPV6) ++ } else { ++ struct ipv6_pinfo *np = inet6_sk(sk); ++ ++ info->src_v6.sin6_family = AF_INET6; ++ info->src_v6.sin6_port = inet->inet_sport; ++ ++ if (ipv6_addr_any(&sk->sk_v6_rcv_saddr)) ++ info->src_v6.sin6_addr = np->saddr; ++ else ++ info->src_v6.sin6_addr = sk->sk_v6_rcv_saddr; ++ ++ info->dst_v6.sin6_family = AF_INET6; ++ info->dst_v6.sin6_port = inet->inet_dport; ++ info->dst_v6.sin6_addr = sk->sk_v6_daddr; ++#endif ++ } ++} ++ ++int mptcp_get_info(const struct sock *meta_sk, char __user *optval, int optlen) ++{ ++ const struct tcp_sock *meta_tp = tcp_sk(meta_sk); ++ ++ struct mptcp_meta_info meta_info; ++ struct mptcp_info m_info; ++ ++ unsigned int info_len; ++ ++ /* Check again with the lock held */ ++ if (!mptcp(meta_tp)) ++ return -EINVAL; ++ ++ if (copy_from_user(&m_info, optval, optlen)) ++ return -EFAULT; ++ ++ if (m_info.meta_info) { ++ unsigned int len; ++ ++ __mptcp_get_info(meta_sk, &meta_info); ++ ++ /* Need to set this, if user thinks that tcp_info is bigger than ours */ ++ len = min_t(unsigned int, m_info.meta_len, sizeof(meta_info)); ++ m_info.meta_len = len; ++ ++ if (copy_to_user((void __user *)m_info.meta_info, &meta_info, len)) ++ return -EFAULT; ++ } ++ ++ /* Need to set this, if user thinks that tcp_info is bigger than ours */ ++ info_len = min_t(unsigned int, m_info.tcp_info_len, sizeof(struct tcp_info)); ++ m_info.tcp_info_len = info_len; ++ ++ if (m_info.initial) { ++ struct mptcp_cb *mpcb = meta_tp->mpcb; ++ ++ if (mpcb->master_sk) { ++ struct tcp_info info; ++ ++ tcp_get_info(mpcb->master_sk, &info, true); ++ if (copy_to_user((void __user *)m_info.initial, &info, info_len)) ++ return -EFAULT; ++ } else if (meta_tp->record_master_info && mpcb->master_info) { ++ if (copy_to_user((void __user *)m_info.initial, mpcb->master_info, info_len)) ++ return -EFAULT; ++ } else { ++ return meta_tp->record_master_info ? -ENOMEM : -EINVAL; ++ } ++ } ++ ++ if (m_info.subflows) { ++ unsigned int len, sub_len = 0; ++ struct mptcp_tcp_sock *mptcp; ++ char __user *ptr; ++ ++ ptr = (char __user *)m_info.subflows; ++ len = m_info.sub_len; ++ ++ mptcp_for_each_sub(meta_tp->mpcb, mptcp) { ++ struct tcp_info t_info; ++ unsigned int tmp_len; ++ ++ tcp_get_info(mptcp_to_sock(mptcp), &t_info, true); ++ ++ tmp_len = min_t(unsigned int, len, info_len); ++ len -= tmp_len; ++ ++ if (copy_to_user(ptr, &t_info, tmp_len)) ++ return -EFAULT; ++ ++ ptr += tmp_len; ++ sub_len += tmp_len; ++ ++ if (len == 0) ++ break; ++ } ++ ++ m_info.sub_len = sub_len; ++ } ++ ++ if (m_info.subflow_info) { ++ unsigned int len, sub_info_len, total_sub_info_len = 0; ++ struct mptcp_tcp_sock *mptcp; ++ char __user *ptr; ++ ++ ptr = (char __user *)m_info.subflow_info; ++ len = m_info.total_sub_info_len; ++ ++ sub_info_len = min_t(unsigned int, m_info.sub_info_len, ++ sizeof(struct mptcp_sub_info)); ++ m_info.sub_info_len = sub_info_len; ++ ++ mptcp_for_each_sub(meta_tp->mpcb, mptcp) { ++ struct mptcp_sub_info m_sub_info; ++ unsigned int tmp_len; ++ ++ mptcp_get_sub_info(mptcp_to_sock(mptcp), &m_sub_info); ++ ++ tmp_len = min_t(unsigned int, len, sub_info_len); ++ len -= tmp_len; ++ ++ if (copy_to_user(ptr, &m_sub_info, tmp_len)) ++ return -EFAULT; ++ ++ ptr += tmp_len; ++ total_sub_info_len += tmp_len; ++ ++ if (len == 0) ++ break; ++ } ++ ++ m_info.total_sub_info_len = total_sub_info_len; ++ } ++ ++ if (copy_to_user(optval, &m_info, optlen)) ++ return -EFAULT; ++ ++ return 0; ++} ++ ++void mptcp_clear_sk(struct sock *sk, int size) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ ++ /* we do not want to clear tk_table field, because of RCU lookups */ ++ sk_prot_clear_nulls(sk, offsetof(struct tcp_sock, tk_table.next)); ++ ++ size -= offsetof(struct tcp_sock, tk_table.pprev); ++ memset((char *)&tp->tk_table.pprev, 0, size); ++} ++ ++static const struct snmp_mib mptcp_snmp_list[] = { ++ SNMP_MIB_ITEM("MPCapableSYNRX", MPTCP_MIB_MPCAPABLEPASSIVE), ++ SNMP_MIB_ITEM("MPCapableSYNTX", MPTCP_MIB_MPCAPABLEACTIVE), ++ SNMP_MIB_ITEM("MPCapableSYNACKRX", MPTCP_MIB_MPCAPABLEACTIVEACK), ++ SNMP_MIB_ITEM("MPCapableACKRX", MPTCP_MIB_MPCAPABLEPASSIVEACK), ++ SNMP_MIB_ITEM("MPCapableFallbackACK", MPTCP_MIB_MPCAPABLEPASSIVEFALLBACK), ++ SNMP_MIB_ITEM("MPCapableFallbackSYNACK", MPTCP_MIB_MPCAPABLEACTIVEFALLBACK), ++ SNMP_MIB_ITEM("MPCapableRetransFallback", MPTCP_MIB_MPCAPABLERETRANSFALLBACK), ++ SNMP_MIB_ITEM("MPTCPCsumEnabled", MPTCP_MIB_CSUMENABLED), ++ SNMP_MIB_ITEM("MPTCPRetrans", MPTCP_MIB_RETRANSSEGS), ++ SNMP_MIB_ITEM("MPFailRX", MPTCP_MIB_MPFAILRX), ++ SNMP_MIB_ITEM("MPCsumFail", MPTCP_MIB_CSUMFAIL), ++ SNMP_MIB_ITEM("MPFastcloseRX", MPTCP_MIB_FASTCLOSERX), ++ SNMP_MIB_ITEM("MPFastcloseTX", MPTCP_MIB_FASTCLOSETX), ++ SNMP_MIB_ITEM("MPFallbackAckSub", MPTCP_MIB_FBACKSUB), ++ SNMP_MIB_ITEM("MPFallbackAckInit", MPTCP_MIB_FBACKINIT), ++ SNMP_MIB_ITEM("MPFallbackDataSub", MPTCP_MIB_FBDATASUB), ++ SNMP_MIB_ITEM("MPFallbackDataInit", MPTCP_MIB_FBDATAINIT), ++ SNMP_MIB_ITEM("MPRemoveAddrSubDelete", MPTCP_MIB_REMADDRSUB), ++ SNMP_MIB_ITEM("MPJoinNoTokenFound", MPTCP_MIB_JOINNOTOKEN), ++ SNMP_MIB_ITEM("MPJoinAlreadyFallenback", MPTCP_MIB_JOINFALLBACK), ++ SNMP_MIB_ITEM("MPJoinSynTx", MPTCP_MIB_JOINSYNTX), ++ SNMP_MIB_ITEM("MPJoinSynRx", MPTCP_MIB_JOINSYNRX), ++ SNMP_MIB_ITEM("MPJoinSynAckRx", MPTCP_MIB_JOINSYNACKRX), ++ SNMP_MIB_ITEM("MPJoinSynAckHMacFailure", MPTCP_MIB_JOINSYNACKMAC), ++ SNMP_MIB_ITEM("MPJoinAckRx", MPTCP_MIB_JOINACKRX), ++ SNMP_MIB_ITEM("MPJoinAckHMacFailure", MPTCP_MIB_JOINACKMAC), ++ SNMP_MIB_ITEM("MPJoinAckMissing", MPTCP_MIB_JOINACKFAIL), ++ SNMP_MIB_ITEM("MPJoinAckRTO", MPTCP_MIB_JOINACKRTO), ++ SNMP_MIB_ITEM("MPJoinAckRexmit", MPTCP_MIB_JOINACKRXMIT), ++ SNMP_MIB_ITEM("NoDSSInWindow", MPTCP_MIB_NODSSWINDOW), ++ SNMP_MIB_ITEM("DSSNotMatching", MPTCP_MIB_DSSNOMATCH), ++ SNMP_MIB_ITEM("InfiniteMapRx", MPTCP_MIB_INFINITEMAPRX), ++ SNMP_MIB_ITEM("DSSNoMatchTCP", MPTCP_MIB_DSSTCPMISMATCH), ++ SNMP_MIB_ITEM("DSSTrimHead", MPTCP_MIB_DSSTRIMHEAD), ++ SNMP_MIB_ITEM("DSSSplitTail", MPTCP_MIB_DSSSPLITTAIL), ++ SNMP_MIB_ITEM("DSSPurgeOldSubSegs", MPTCP_MIB_PURGEOLD), ++ SNMP_MIB_ITEM("AddAddrRx", MPTCP_MIB_ADDADDRRX), ++ SNMP_MIB_ITEM("AddAddrTx", MPTCP_MIB_ADDADDRTX), ++ SNMP_MIB_ITEM("RemAddrRx", MPTCP_MIB_REMADDRRX), ++ SNMP_MIB_ITEM("RemAddrTx", MPTCP_MIB_REMADDRTX), ++ SNMP_MIB_ITEM("MPJoinAlternatePort", MPTCP_MIB_JOINALTERNATEPORT), ++ SNMP_MIB_ITEM("MPCurrEstab", MPTCP_MIB_CURRESTAB), ++ SNMP_MIB_SENTINEL ++}; ++ ++struct workqueue_struct *mptcp_wq; ++EXPORT_SYMBOL(mptcp_wq); ++ ++/* Output /proc/net/mptcp */ ++static int mptcp_pm_seq_show(struct seq_file *seq, void *v) ++{ ++ struct tcp_sock *meta_tp; ++ const struct net *net = seq->private; ++ unsigned int i, n = 0; ++ ++ seq_printf(seq, " sl loc_tok rem_tok v6 local_address remote_address st ns tx_queue rx_queue inode"); ++ seq_putc(seq, '\n'); ++ ++ for (i = 0; i <= mptcp_tk_htable.mask; i++) { ++ struct hlist_nulls_node *node; ++ rcu_read_lock(); ++ local_bh_disable(); ++ hlist_nulls_for_each_entry_rcu(meta_tp, node, ++ &mptcp_tk_htable.hashtable[i], ++ tk_table) { ++ struct sock *meta_sk = (struct sock *)meta_tp; ++ struct inet_sock *isk = inet_sk(meta_sk); ++ struct mptcp_cb *mpcb = meta_tp->mpcb; ++ ++ if (!mptcp(meta_tp) || !net_eq(net, sock_net(meta_sk))) ++ continue; ++ ++ if (!mpcb) ++ continue; ++ ++ if (capable(CAP_NET_ADMIN)) { ++ seq_printf(seq, "%4d: %04X %04X ", n++, ++ mpcb->mptcp_loc_token, ++ mpcb->mptcp_rem_token); ++ } else { ++ seq_printf(seq, "%4d: %04X %04X ", n++, -1, -1); ++ } ++ if (meta_sk->sk_family == AF_INET || ++ mptcp_v6_is_v4_mapped(meta_sk)) { ++ seq_printf(seq, " 0 %08X:%04X %08X:%04X ", ++ isk->inet_rcv_saddr, ++ ntohs(isk->inet_sport), ++ isk->inet_daddr, ++ ntohs(isk->inet_dport)); ++#if IS_ENABLED(CONFIG_IPV6) ++ } else if (meta_sk->sk_family == AF_INET6) { ++ struct in6_addr *src = &meta_sk->sk_v6_rcv_saddr; ++ struct in6_addr *dst = &meta_sk->sk_v6_daddr; ++ seq_printf(seq, " 1 %08X%08X%08X%08X:%04X %08X%08X%08X%08X:%04X", ++ src->s6_addr32[0], src->s6_addr32[1], ++ src->s6_addr32[2], src->s6_addr32[3], ++ ntohs(isk->inet_sport), ++ dst->s6_addr32[0], dst->s6_addr32[1], ++ dst->s6_addr32[2], dst->s6_addr32[3], ++ ntohs(isk->inet_dport)); ++#endif ++ } ++ ++ seq_printf(seq, " %02X %02X %08X:%08X %lu", ++ meta_sk->sk_state, mptcp_subflow_count(mpcb), ++ meta_tp->write_seq - meta_tp->snd_una, ++ max_t(int, meta_tp->rcv_nxt - ++ meta_tp->copied_seq, 0), ++ sock_i_ino(meta_sk)); ++ seq_putc(seq, '\n'); ++ } ++ ++ local_bh_enable(); ++ rcu_read_unlock(); ++ } ++ ++ return 0; ++} ++ ++static int mptcp_snmp_seq_show(struct seq_file *seq, void *v) ++{ ++ struct net *net = seq->private; ++ int i; ++ ++ for (i = 0; mptcp_snmp_list[i].name != NULL; i++) ++ seq_printf(seq, "%-32s\t%ld\n", mptcp_snmp_list[i].name, ++ snmp_fold_field(net->mptcp.mptcp_statistics, ++ mptcp_snmp_list[i].entry)); ++ ++ return 0; ++} ++ ++static int mptcp_pm_init_net(struct net *net) ++{ ++ net->mptcp.mptcp_statistics = alloc_percpu(struct mptcp_mib); ++ if (!net->mptcp.mptcp_statistics) ++ goto out_mptcp_mibs; ++ ++#ifdef CONFIG_PROC_FS ++ net->mptcp.proc_net_mptcp = proc_net_mkdir(net, "mptcp_net", net->proc_net); ++ if (!net->mptcp.proc_net_mptcp) ++ goto out_proc_net_mptcp; ++ if (!proc_create_net_single("mptcp", S_IRUGO, net->mptcp.proc_net_mptcp, ++ mptcp_pm_seq_show, NULL)) ++ goto out_mptcp_net_mptcp; ++ if (!proc_create_net_single("snmp", S_IRUGO, net->mptcp.proc_net_mptcp, ++ mptcp_snmp_seq_show, NULL)) ++ goto out_mptcp_net_snmp; ++#endif ++ ++ return 0; ++ ++#ifdef CONFIG_PROC_FS ++out_mptcp_net_snmp: ++ remove_proc_entry("mptcp", net->mptcp.proc_net_mptcp); ++out_mptcp_net_mptcp: ++ remove_proc_subtree("mptcp_net", net->proc_net); ++ net->mptcp.proc_net_mptcp = NULL; ++out_proc_net_mptcp: ++ free_percpu(net->mptcp.mptcp_statistics); ++#endif ++out_mptcp_mibs: ++ return -ENOMEM; ++} ++ ++static void mptcp_pm_exit_net(struct net *net) ++{ ++ remove_proc_entry("snmp", net->mptcp.proc_net_mptcp); ++ remove_proc_entry("mptcp", net->mptcp.proc_net_mptcp); ++ remove_proc_subtree("mptcp_net", net->proc_net); ++ free_percpu(net->mptcp.mptcp_statistics); ++} ++ ++static struct pernet_operations mptcp_pm_proc_ops = { ++ .init = mptcp_pm_init_net, ++ .exit = mptcp_pm_exit_net, ++}; ++ ++static unsigned long mptcp_htable_entries __initdata; ++ ++static int __init set_mptcp_htable_entries(char *str) ++{ ++ ssize_t ret; ++ ++ if (!str) ++ return 0; ++ ++ ret = kstrtoul(str, 0, &mptcp_htable_entries); ++ if (ret) ++ return 0; ++ ++ return 1; ++} ++__setup("mptcp_htable_entries=", set_mptcp_htable_entries); ++ ++/* General initialization of mptcp */ ++void __init mptcp_init(void) ++{ ++ unsigned int i; ++ struct ctl_table_header *mptcp_sysctl; ++ ++ mptcp_sock_cache = kmem_cache_create("mptcp_sock", ++ sizeof(struct mptcp_tcp_sock), ++ 0, SLAB_HWCACHE_ALIGN, ++ NULL); ++ if (!mptcp_sock_cache) ++ goto mptcp_sock_cache_failed; ++ ++ mptcp_cb_cache = kmem_cache_create("mptcp_cb", sizeof(struct mptcp_cb), ++ 0, SLAB_TYPESAFE_BY_RCU|SLAB_HWCACHE_ALIGN, ++ NULL); ++ if (!mptcp_cb_cache) ++ goto mptcp_cb_cache_failed; ++ ++ mptcp_tw_cache = kmem_cache_create("mptcp_tw", sizeof(struct mptcp_tw), ++ 0, SLAB_TYPESAFE_BY_RCU|SLAB_HWCACHE_ALIGN, ++ NULL); ++ if (!mptcp_tw_cache) ++ goto mptcp_tw_cache_failed; ++ ++ get_random_bytes(&mptcp_secret, sizeof(mptcp_secret)); ++ ++ mptcp_wq = alloc_workqueue("mptcp_wq", WQ_UNBOUND | WQ_MEM_RECLAIM, 8); ++ if (!mptcp_wq) ++ goto alloc_workqueue_failed; ++ ++ mptcp_tk_htable.hashtable = ++ alloc_large_system_hash("MPTCP tokens", ++ sizeof(mptcp_tk_htable.hashtable[0]), ++ mptcp_htable_entries, ++ 18, /* one slot per 256KB of memory */ ++ 0, ++ NULL, ++ &mptcp_tk_htable.mask, ++ 1024, ++ mptcp_htable_entries ? 0 : 1024 * 1024); ++ ++ for (i = 0; i <= mptcp_tk_htable.mask; i++) ++ INIT_HLIST_NULLS_HEAD(&mptcp_tk_htable.hashtable[i], i); ++ ++ mptcp_reqsk_tk_htb.hashtable = ++ alloc_large_system_hash("MPTCP request tokens", ++ sizeof(mptcp_reqsk_tk_htb.hashtable[0]), ++ mptcp_htable_entries, ++ 18, /* one slot per 256KB of memory */ ++ 0, ++ NULL, ++ &mptcp_reqsk_tk_htb.mask, ++ 1024, ++ mptcp_htable_entries ? 0 : 1024 * 1024); ++ ++ for (i = 0; i <= mptcp_reqsk_tk_htb.mask; i++) ++ INIT_HLIST_NULLS_HEAD(&mptcp_reqsk_tk_htb.hashtable[i], i); ++ ++ ++ spin_lock_init(&mptcp_tk_hashlock); ++ ++ if (register_pernet_subsys(&mptcp_pm_proc_ops)) ++ goto pernet_failed; ++ ++#if IS_ENABLED(CONFIG_IPV6) ++ if (mptcp_pm_v6_init()) ++ goto mptcp_pm_v6_failed; ++#endif ++ if (mptcp_pm_v4_init()) ++ goto mptcp_pm_v4_failed; ++ ++ mptcp_sysctl = register_net_sysctl(&init_net, "net/mptcp", mptcp_table); ++ if (!mptcp_sysctl) ++ goto register_sysctl_failed; ++ ++ if (mptcp_register_path_manager(&mptcp_pm_default)) ++ goto register_pm_failed; ++ ++ if (mptcp_register_scheduler(&mptcp_sched_default)) ++ goto register_sched_failed; ++ ++ pr_info("MPTCP: Unstable branch"); ++ ++ mptcp_init_failed = false; ++ ++ return; ++ ++register_sched_failed: ++ mptcp_unregister_path_manager(&mptcp_pm_default); ++register_pm_failed: ++ unregister_net_sysctl_table(mptcp_sysctl); ++register_sysctl_failed: ++ mptcp_pm_v4_undo(); ++mptcp_pm_v4_failed: ++#if IS_ENABLED(CONFIG_IPV6) ++ mptcp_pm_v6_undo(); ++mptcp_pm_v6_failed: ++#endif ++ unregister_pernet_subsys(&mptcp_pm_proc_ops); ++pernet_failed: ++ destroy_workqueue(mptcp_wq); ++alloc_workqueue_failed: ++ kmem_cache_destroy(mptcp_tw_cache); ++mptcp_tw_cache_failed: ++ kmem_cache_destroy(mptcp_cb_cache); ++mptcp_cb_cache_failed: ++ kmem_cache_destroy(mptcp_sock_cache); ++mptcp_sock_cache_failed: ++ mptcp_init_failed = true; ++} +diff --git a/net/mptcp/mptcp_ecf.c b/net/mptcp/mptcp_ecf.c +new file mode 100644 +index 000000000000..6b976b2b0c72 +--- /dev/null ++++ b/net/mptcp/mptcp_ecf.c +@@ -0,0 +1,195 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* MPTCP ECF Scheduler ++ * ++ * Algorithm Design: ++ * Yeon-sup Lim ++ * Don Towsley ++ * Erich M. Nahum ++ * Richard J. Gibbens ++ * ++ * Initial Implementation: ++ * Yeon-sup Lim ++ * ++ * Additional Authors: ++ * Daniel Weber ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License ++ * as published by the Free Software Foundation; either version ++ * 2 of the License, or (at your option) any later version. ++ */ ++ ++#include ++#include ++ ++static unsigned int mptcp_ecf_r_beta __read_mostly = 4; /* beta = 1/r_beta = 0.25 */ ++module_param(mptcp_ecf_r_beta, int, 0644); ++MODULE_PARM_DESC(mptcp_ecf_r_beta, "beta for ECF"); ++ ++struct ecfsched_priv { ++ u32 last_rbuf_opti; ++}; ++ ++struct ecfsched_cb { ++ u32 switching_margin; /* this is "waiting" in algorithm description */ ++}; ++ ++static struct ecfsched_priv *ecfsched_get_priv(const struct tcp_sock *tp) ++{ ++ return (struct ecfsched_priv *)&tp->mptcp->mptcp_sched[0]; ++} ++ ++static struct ecfsched_cb *ecfsched_get_cb(const struct tcp_sock *tp) ++{ ++ return (struct ecfsched_cb *)&tp->mpcb->mptcp_sched[0]; ++} ++ ++/* This is the ECF scheduler. This function decides on which flow to send ++ * a given MSS. If all subflows are found to be busy or the currently best ++ * subflow is estimated to be slower than waiting for minsk, NULL is returned. ++ */ ++static struct sock *ecf_get_available_subflow(struct sock *meta_sk, ++ struct sk_buff *skb, ++ bool zero_wnd_test) ++{ ++ struct mptcp_cb *mpcb = tcp_sk(meta_sk)->mpcb; ++ struct sock *bestsk, *minsk = NULL; ++ struct tcp_sock *besttp; ++ struct mptcp_tcp_sock *mptcp; ++ struct ecfsched_cb *ecf_cb = ecfsched_get_cb(tcp_sk(meta_sk)); ++ u32 min_srtt = U32_MAX; ++ u32 sub_sndbuf = 0; ++ u32 sub_packets_out = 0; ++ ++ /* Answer data_fin on same subflow!!! */ ++ if (meta_sk->sk_shutdown & RCV_SHUTDOWN && ++ skb && mptcp_is_data_fin(skb)) { ++ mptcp_for_each_sub(mpcb, mptcp) { ++ bestsk = mptcp_to_sock(mptcp); ++ ++ if (tcp_sk(bestsk)->mptcp->path_index == mpcb->dfin_path_index && ++ mptcp_is_available(bestsk, skb, zero_wnd_test)) ++ return bestsk; ++ } ++ } ++ ++ /* First, find the overall best (fastest) subflow */ ++ mptcp_for_each_sub(mpcb, mptcp) { ++ bestsk = mptcp_to_sock(mptcp); ++ besttp = tcp_sk(bestsk); ++ ++ /* Set of states for which we are allowed to send data */ ++ if (!mptcp_sk_can_send(bestsk)) ++ continue; ++ ++ /* We do not send data on this subflow unless it is ++ * fully established, i.e. the 4th ack has been received. ++ */ ++ if (besttp->mptcp->pre_established) ++ continue; ++ ++ sub_sndbuf += bestsk->sk_wmem_queued; ++ sub_packets_out += besttp->packets_out; ++ ++ /* record minimal rtt */ ++ if (besttp->srtt_us < min_srtt) { ++ min_srtt = besttp->srtt_us; ++ minsk = bestsk; ++ } ++ } ++ ++ /* find the current best subflow according to the default scheduler */ ++ bestsk = get_available_subflow(meta_sk, skb, zero_wnd_test); ++ ++ /* if we decided to use a slower flow, we have the option of not using it at all */ ++ if (bestsk && minsk && bestsk != minsk) { ++ u32 mss = tcp_current_mss(bestsk); /* assuming equal MSS */ ++ u32 sndbuf_meta = meta_sk->sk_wmem_queued; ++ u32 sndbuf_minus = sub_sndbuf; ++ u32 sndbuf = 0; ++ ++ u32 cwnd_f = tcp_sk(minsk)->snd_cwnd; ++ u32 srtt_f = tcp_sk(minsk)->srtt_us >> 3; ++ u32 rttvar_f = tcp_sk(minsk)->rttvar_us >> 1; ++ ++ u32 cwnd_s = tcp_sk(bestsk)->snd_cwnd; ++ u32 srtt_s = tcp_sk(bestsk)->srtt_us >> 3; ++ u32 rttvar_s = tcp_sk(bestsk)->rttvar_us >> 1; ++ ++ u32 delta = max(rttvar_f, rttvar_s); ++ ++ u32 x_f; ++ u64 lhs, rhs; /* to avoid overflow, using u64 */ ++ ++ if (tcp_sk(meta_sk)->packets_out > sub_packets_out) ++ sndbuf_minus += (tcp_sk(meta_sk)->packets_out - sub_packets_out) * mss; ++ ++ if (sndbuf_meta > sndbuf_minus) ++ sndbuf = sndbuf_meta - sndbuf_minus; ++ ++ /* we have something to send. ++ * at least one time tx over fastest subflow is required ++ */ ++ x_f = sndbuf > cwnd_f * mss ? sndbuf : cwnd_f * mss; ++ lhs = srtt_f * (x_f + cwnd_f * mss); ++ rhs = cwnd_f * mss * (srtt_s + delta); ++ ++ if (mptcp_ecf_r_beta * lhs < mptcp_ecf_r_beta * rhs + ecf_cb->switching_margin * rhs) { ++ u32 x_s = sndbuf > cwnd_s * mss ? sndbuf : cwnd_s * mss; ++ u64 lhs_s = srtt_s * x_s; ++ u64 rhs_s = cwnd_s * mss * (2 * srtt_f + delta); ++ ++ if (lhs_s >= rhs_s) { ++ /* too slower than fastest */ ++ ecf_cb->switching_margin = 1; ++ return NULL; ++ } ++ } else { ++ /* use slower one */ ++ ecf_cb->switching_margin = 0; ++ } ++ } ++ ++ return bestsk; ++} ++ ++static void ecfsched_init(struct sock *sk) ++{ ++ struct ecfsched_priv *ecf_p = ecfsched_get_priv(tcp_sk(sk)); ++ struct ecfsched_cb *ecf_cb = ecfsched_get_cb(tcp_sk(mptcp_meta_sk(sk))); ++ ++ ecf_p->last_rbuf_opti = tcp_jiffies32; ++ ecf_cb->switching_margin = 0; ++} ++ ++struct mptcp_sched_ops mptcp_sched_ecf = { ++ .get_subflow = ecf_get_available_subflow, ++ .next_segment = mptcp_next_segment, ++ .init = ecfsched_init, ++ .name = "ecf", ++ .owner = THIS_MODULE, ++}; ++ ++static int __init ecf_register(void) ++{ ++ BUILD_BUG_ON(sizeof(struct ecfsched_priv) > MPTCP_SCHED_SIZE); ++ BUILD_BUG_ON(sizeof(struct ecfsched_cb) > MPTCP_SCHED_DATA_SIZE); ++ ++ if (mptcp_register_scheduler(&mptcp_sched_ecf)) ++ return -1; ++ ++ return 0; ++} ++ ++static void ecf_unregister(void) ++{ ++ mptcp_unregister_scheduler(&mptcp_sched_ecf); ++} ++ ++module_init(ecf_register); ++module_exit(ecf_unregister); ++ ++MODULE_AUTHOR("Yeon-sup Lim, Daniel Weber"); ++MODULE_LICENSE("GPL"); ++MODULE_DESCRIPTION("ECF (Earliest Completion First) scheduler for MPTCP, based on default minimum RTT scheduler"); ++MODULE_VERSION("0.95"); +diff --git a/net/mptcp/mptcp_fullmesh.c b/net/mptcp/mptcp_fullmesh.c +new file mode 100644 +index 000000000000..5424960256e6 +--- /dev/null ++++ b/net/mptcp/mptcp_fullmesh.c +@@ -0,0 +1,1938 @@ ++#include ++#include ++ ++#include ++#include ++ ++#if IS_ENABLED(CONFIG_IPV6) ++#include ++#include ++#endif ++ ++enum { ++ MPTCP_EVENT_ADD = 1, ++ MPTCP_EVENT_DEL, ++ MPTCP_EVENT_MOD, ++}; ++ ++#define MPTCP_SUBFLOW_RETRY_DELAY 1000 ++ ++/* Max number of local or remote addresses we can store. ++ * When changing, see the bitfield below in fullmesh_rem4/6. ++ */ ++#define MPTCP_MAX_ADDR 8 ++ ++struct fullmesh_rem4 { ++ u8 rem4_id; ++ u8 bitfield; ++ u8 retry_bitfield; ++ __be16 port; ++ struct in_addr addr; ++}; ++ ++struct fullmesh_rem6 { ++ u8 rem6_id; ++ u8 bitfield; ++ u8 retry_bitfield; ++ __be16 port; ++ struct in6_addr addr; ++}; ++ ++struct mptcp_loc_addr { ++ struct mptcp_loc4 locaddr4[MPTCP_MAX_ADDR]; ++ u8 loc4_bits; ++ u8 next_v4_index; ++ ++ struct mptcp_loc6 locaddr6[MPTCP_MAX_ADDR]; ++ u8 loc6_bits; ++ u8 next_v6_index; ++ struct rcu_head rcu; ++}; ++ ++struct mptcp_addr_event { ++ struct list_head list; ++ unsigned short family; ++ u8 code:7, ++ low_prio:1; ++ int if_idx; ++ union inet_addr addr; ++}; ++ ++struct fullmesh_priv { ++ /* Worker struct for subflow establishment */ ++ struct work_struct subflow_work; ++ /* Delayed worker, when the routing-tables are not yet ready. */ ++ struct delayed_work subflow_retry_work; ++ ++ /* Remote addresses */ ++ struct fullmesh_rem4 remaddr4[MPTCP_MAX_ADDR]; ++ struct fullmesh_rem6 remaddr6[MPTCP_MAX_ADDR]; ++ ++ struct mptcp_cb *mpcb; ++ ++ u16 remove_addrs; /* Addresses to remove */ ++ u8 announced_addrs_v4; /* IPv4 Addresses we did announce */ ++ u8 announced_addrs_v6; /* IPv6 Addresses we did announce */ ++ ++ u8 add_addr; /* Are we sending an add_addr? */ ++ ++ u8 rem4_bits; ++ u8 rem6_bits; ++ ++ /* Have we established the additional subflows for primary pair? */ ++ u8 first_pair:1; ++}; ++ ++struct mptcp_fm_ns { ++ struct mptcp_loc_addr __rcu *local; ++ spinlock_t local_lock; /* Protecting the above pointer */ ++ struct list_head events; ++ struct delayed_work address_worker; ++ ++ struct net *net; ++}; ++ ++static int num_subflows __read_mostly = 1; ++module_param(num_subflows, int, 0644); ++MODULE_PARM_DESC(num_subflows, "choose the number of subflows per pair of IP addresses of MPTCP connection"); ++ ++static int create_on_err __read_mostly; ++module_param(create_on_err, int, 0644); ++MODULE_PARM_DESC(create_on_err, "recreate the subflow upon a timeout"); ++ ++static struct mptcp_pm_ops full_mesh __read_mostly; ++ ++static void full_mesh_create_subflows(struct sock *meta_sk); ++ ++static struct mptcp_fm_ns *fm_get_ns(const struct net *net) ++{ ++ return (struct mptcp_fm_ns *)net->mptcp.path_managers[MPTCP_PM_FULLMESH]; ++} ++ ++static struct fullmesh_priv *fullmesh_get_priv(const struct mptcp_cb *mpcb) ++{ ++ return (struct fullmesh_priv *)&mpcb->mptcp_pm[0]; ++} ++ ++/* Find the first free index in the bitfield */ ++static int __mptcp_find_free_index(u8 bitfield, u8 base) ++{ ++ int i; ++ ++ /* There are anyways no free bits... */ ++ if (bitfield == 0xff) ++ goto exit; ++ ++ i = ffs(~(bitfield >> base)) - 1; ++ if (i < 0) ++ goto exit; ++ ++ /* No free bits when starting at base, try from 0 on */ ++ if (i + base >= sizeof(bitfield) * 8) ++ return __mptcp_find_free_index(bitfield, 0); ++ ++ return i + base; ++exit: ++ return -1; ++} ++ ++static int mptcp_find_free_index(u8 bitfield) ++{ ++ return __mptcp_find_free_index(bitfield, 0); ++} ++ ++static void mptcp_addv4_raddr(struct mptcp_cb *mpcb, ++ const struct in_addr *addr, ++ __be16 port, u8 id) ++{ ++ int i; ++ struct fullmesh_rem4 *rem4; ++ struct fullmesh_priv *fmp = fullmesh_get_priv(mpcb); ++ ++ mptcp_for_each_bit_set(fmp->rem4_bits, i) { ++ rem4 = &fmp->remaddr4[i]; ++ ++ /* Address is already in the list --- continue */ ++ if (rem4->rem4_id == id && ++ rem4->addr.s_addr == addr->s_addr && rem4->port == port) ++ return; ++ ++ /* This may be the case, when the peer is behind a NAT. He is ++ * trying to JOIN, thus sending the JOIN with a certain ID. ++ * However the src_addr of the IP-packet has been changed. We ++ * update the addr in the list, because this is the address as ++ * OUR BOX sees it. ++ */ ++ if (rem4->rem4_id == id && rem4->addr.s_addr != addr->s_addr) { ++ /* update the address */ ++ mptcp_debug("%s: updating old addr:%pI4 to addr %pI4 with id:%d\n", ++ __func__, &rem4->addr.s_addr, ++ &addr->s_addr, id); ++ rem4->addr.s_addr = addr->s_addr; ++ rem4->port = port; ++ mpcb->list_rcvd = 1; ++ return; ++ } ++ } ++ ++ i = mptcp_find_free_index(fmp->rem4_bits); ++ /* Do we have already the maximum number of local/remote addresses? */ ++ if (i < 0) { ++ mptcp_debug("%s: At max num of remote addresses: %d --- not adding address: %pI4\n", ++ __func__, MPTCP_MAX_ADDR, &addr->s_addr); ++ return; ++ } ++ ++ rem4 = &fmp->remaddr4[i]; ++ ++ /* Address is not known yet, store it */ ++ rem4->addr.s_addr = addr->s_addr; ++ rem4->port = port; ++ rem4->bitfield = 0; ++ rem4->retry_bitfield = 0; ++ rem4->rem4_id = id; ++ mpcb->list_rcvd = 1; ++ fmp->rem4_bits |= (1 << i); ++ ++ return; ++} ++ ++static void mptcp_addv6_raddr(struct mptcp_cb *mpcb, ++ const struct in6_addr *addr, ++ __be16 port, u8 id) ++{ ++ int i; ++ struct fullmesh_rem6 *rem6; ++ struct fullmesh_priv *fmp = fullmesh_get_priv(mpcb); ++ ++ mptcp_for_each_bit_set(fmp->rem6_bits, i) { ++ rem6 = &fmp->remaddr6[i]; ++ ++ /* Address is already in the list --- continue */ ++ if (rem6->rem6_id == id && ++ ipv6_addr_equal(&rem6->addr, addr) && rem6->port == port) ++ return; ++ ++ /* This may be the case, when the peer is behind a NAT. He is ++ * trying to JOIN, thus sending the JOIN with a certain ID. ++ * However the src_addr of the IP-packet has been changed. We ++ * update the addr in the list, because this is the address as ++ * OUR BOX sees it. ++ */ ++ if (rem6->rem6_id == id) { ++ /* update the address */ ++ mptcp_debug("%s: updating old addr: %pI6 to addr %pI6 with id:%d\n", ++ __func__, &rem6->addr, addr, id); ++ rem6->addr = *addr; ++ rem6->port = port; ++ mpcb->list_rcvd = 1; ++ return; ++ } ++ } ++ ++ i = mptcp_find_free_index(fmp->rem6_bits); ++ /* Do we have already the maximum number of local/remote addresses? */ ++ if (i < 0) { ++ mptcp_debug("%s: At max num of remote addresses: %d --- not adding address: %pI6\n", ++ __func__, MPTCP_MAX_ADDR, addr); ++ return; ++ } ++ ++ rem6 = &fmp->remaddr6[i]; ++ ++ /* Address is not known yet, store it */ ++ rem6->addr = *addr; ++ rem6->port = port; ++ rem6->bitfield = 0; ++ rem6->retry_bitfield = 0; ++ rem6->rem6_id = id; ++ mpcb->list_rcvd = 1; ++ fmp->rem6_bits |= (1 << i); ++ ++ return; ++} ++ ++static void mptcp_v4_rem_raddress(struct mptcp_cb *mpcb, u8 id) ++{ ++ int i; ++ struct fullmesh_priv *fmp = fullmesh_get_priv(mpcb); ++ ++ mptcp_for_each_bit_set(fmp->rem4_bits, i) { ++ if (fmp->remaddr4[i].rem4_id == id) { ++ /* remove address from bitfield */ ++ fmp->rem4_bits &= ~(1 << i); ++ ++ break; ++ } ++ } ++} ++ ++static void mptcp_v6_rem_raddress(const struct mptcp_cb *mpcb, u8 id) ++{ ++ int i; ++ struct fullmesh_priv *fmp = fullmesh_get_priv(mpcb); ++ ++ mptcp_for_each_bit_set(fmp->rem6_bits, i) { ++ if (fmp->remaddr6[i].rem6_id == id) { ++ /* remove address from bitfield */ ++ fmp->rem6_bits &= ~(1 << i); ++ ++ break; ++ } ++ } ++} ++ ++/* Sets the bitfield of the remote-address field */ ++static void mptcp_v4_set_init_addr_bit(const struct mptcp_cb *mpcb, ++ const struct in_addr *addr, u8 index) ++{ ++ int i; ++ struct fullmesh_priv *fmp = fullmesh_get_priv(mpcb); ++ ++ mptcp_for_each_bit_set(fmp->rem4_bits, i) { ++ if (fmp->remaddr4[i].addr.s_addr == addr->s_addr) { ++ fmp->remaddr4[i].bitfield |= (1 << index); ++ return; ++ } ++ } ++} ++ ++/* Sets the bitfield of the remote-address field */ ++static void mptcp_v6_set_init_addr_bit(struct mptcp_cb *mpcb, ++ const struct in6_addr *addr, u8 index) ++{ ++ int i; ++ struct fullmesh_priv *fmp = fullmesh_get_priv(mpcb); ++ ++ mptcp_for_each_bit_set(fmp->rem6_bits, i) { ++ if (ipv6_addr_equal(&fmp->remaddr6[i].addr, addr)) { ++ fmp->remaddr6[i].bitfield |= (1 << index); ++ return; ++ } ++ } ++} ++ ++static void mptcp_set_init_addr_bit(struct mptcp_cb *mpcb, ++ const union inet_addr *addr, ++ sa_family_t family, u8 id) ++{ ++ if (family == AF_INET) ++ mptcp_v4_set_init_addr_bit(mpcb, &addr->in, id); ++ else ++ mptcp_v6_set_init_addr_bit(mpcb, &addr->in6, id); ++} ++ ++static void mptcp_v4_subflows(struct sock *meta_sk, ++ const struct mptcp_loc4 *loc, ++ struct mptcp_rem4 *rem) ++{ ++ int i; ++ ++ for (i = 1; i < num_subflows; i++) ++ mptcp_init4_subsockets(meta_sk, loc, rem); ++} ++ ++#if IS_ENABLED(CONFIG_IPV6) ++static void mptcp_v6_subflows(struct sock *meta_sk, ++ const struct mptcp_loc6 *loc, ++ struct mptcp_rem6 *rem) ++{ ++ int i; ++ ++ for (i = 1; i < num_subflows; i++) ++ mptcp_init6_subsockets(meta_sk, loc, rem); ++} ++#endif ++ ++static void retry_subflow_worker(struct work_struct *work) ++{ ++ struct delayed_work *delayed_work = container_of(work, ++ struct delayed_work, ++ work); ++ struct fullmesh_priv *fmp = container_of(delayed_work, ++ struct fullmesh_priv, ++ subflow_retry_work); ++ struct mptcp_cb *mpcb = fmp->mpcb; ++ struct sock *meta_sk = mpcb->meta_sk; ++ struct mptcp_loc_addr *mptcp_local; ++ struct mptcp_fm_ns *fm_ns = fm_get_ns(sock_net(meta_sk)); ++ int iter = 0, i; ++ ++ /* We need a local (stable) copy of the address-list. Really, it is not ++ * such a big deal, if the address-list is not 100% up-to-date. ++ */ ++ rcu_read_lock_bh(); ++ mptcp_local = rcu_dereference_bh(fm_ns->local); ++ mptcp_local = kmemdup(mptcp_local, sizeof(*mptcp_local), GFP_ATOMIC); ++ rcu_read_unlock_bh(); ++ ++ if (!mptcp_local) ++ return; ++ ++next_subflow: ++ if (iter) { ++ release_sock(meta_sk); ++ mutex_unlock(&mpcb->mpcb_mutex); ++ ++ cond_resched(); ++ } ++ mutex_lock(&mpcb->mpcb_mutex); ++ lock_sock_nested(meta_sk, SINGLE_DEPTH_NESTING); ++ ++ if (!mptcp(tcp_sk(meta_sk))) ++ goto exit; ++ ++ iter++; ++ ++ if (sock_flag(meta_sk, SOCK_DEAD)) ++ goto exit; ++ ++ mptcp_for_each_bit_set(fmp->rem4_bits, i) { ++ struct fullmesh_rem4 *rem = &fmp->remaddr4[i]; ++ /* Do we need to retry establishing a subflow ? */ ++ if (rem->retry_bitfield) { ++ int i = mptcp_find_free_index(~rem->retry_bitfield); ++ struct mptcp_rem4 rem4; ++ ++ rem->bitfield |= (1 << i); ++ rem->retry_bitfield &= ~(1 << i); ++ ++ rem4.addr = rem->addr; ++ rem4.port = rem->port; ++ rem4.rem4_id = rem->rem4_id; ++ ++ mptcp_init4_subsockets(meta_sk, &mptcp_local->locaddr4[i], &rem4); ++ mptcp_v4_subflows(meta_sk, ++ &mptcp_local->locaddr4[i], ++ &rem4); ++ goto next_subflow; ++ } ++ } ++ ++#if IS_ENABLED(CONFIG_IPV6) ++ mptcp_for_each_bit_set(fmp->rem6_bits, i) { ++ struct fullmesh_rem6 *rem = &fmp->remaddr6[i]; ++ ++ /* Do we need to retry establishing a subflow ? */ ++ if (rem->retry_bitfield) { ++ int i = mptcp_find_free_index(~rem->retry_bitfield); ++ struct mptcp_rem6 rem6; ++ ++ rem->bitfield |= (1 << i); ++ rem->retry_bitfield &= ~(1 << i); ++ ++ rem6.addr = rem->addr; ++ rem6.port = rem->port; ++ rem6.rem6_id = rem->rem6_id; ++ ++ mptcp_init6_subsockets(meta_sk, &mptcp_local->locaddr6[i], &rem6); ++ mptcp_v6_subflows(meta_sk, ++ &mptcp_local->locaddr6[i], ++ &rem6); ++ goto next_subflow; ++ } ++ } ++#endif ++ ++exit: ++ kfree(mptcp_local); ++ release_sock(meta_sk); ++ mutex_unlock(&mpcb->mpcb_mutex); ++ mptcp_mpcb_put(mpcb); ++ sock_put(meta_sk); ++} ++ ++/** ++ * Create all new subflows, by doing calls to mptcp_initX_subsockets ++ * ++ * This function uses a goto next_subflow, to allow releasing the lock between ++ * new subflows and giving other processes a chance to do some work on the ++ * socket and potentially finishing the communication. ++ **/ ++static void create_subflow_worker(struct work_struct *work) ++{ ++ struct fullmesh_priv *fmp = container_of(work, struct fullmesh_priv, ++ subflow_work); ++ struct mptcp_cb *mpcb = fmp->mpcb; ++ struct sock *meta_sk = mpcb->meta_sk; ++ struct mptcp_loc_addr *mptcp_local; ++ const struct mptcp_fm_ns *fm_ns = fm_get_ns(sock_net(meta_sk)); ++ int iter = 0, retry = 0; ++ int i; ++ ++ /* We need a local (stable) copy of the address-list. Really, it is not ++ * such a big deal, if the address-list is not 100% up-to-date. ++ */ ++ rcu_read_lock_bh(); ++ mptcp_local = rcu_dereference_bh(fm_ns->local); ++ mptcp_local = kmemdup(mptcp_local, sizeof(*mptcp_local), GFP_ATOMIC); ++ rcu_read_unlock_bh(); ++ ++ if (!mptcp_local) ++ return; ++ ++next_subflow: ++ if (iter) { ++ release_sock(meta_sk); ++ mutex_unlock(&mpcb->mpcb_mutex); ++ ++ cond_resched(); ++ } ++ mutex_lock(&mpcb->mpcb_mutex); ++ lock_sock_nested(meta_sk, SINGLE_DEPTH_NESTING); ++ ++ if (sock_flag(meta_sk, SOCK_DEAD) || !mptcp(tcp_sk(meta_sk))) ++ goto exit; ++ ++ if (mpcb->master_sk && ++ !tcp_sk(mpcb->master_sk)->mptcp->fully_established) ++ goto exit; ++ ++ /* Create the additional subflows for the first pair */ ++ if (fmp->first_pair == 0 && mpcb->master_sk) { ++ struct mptcp_loc4 loc; ++ struct mptcp_rem4 rem; ++ ++ loc.addr.s_addr = inet_sk(meta_sk)->inet_saddr; ++ loc.loc4_id = 0; ++ loc.low_prio = 0; ++ loc.if_idx = mpcb->master_sk->sk_bound_dev_if; ++ ++ rem.addr.s_addr = inet_sk(meta_sk)->inet_daddr; ++ rem.port = inet_sk(meta_sk)->inet_dport; ++ rem.rem4_id = 0; /* Default 0 */ ++ ++ mptcp_v4_subflows(meta_sk, &loc, &rem); ++ ++ fmp->first_pair = 1; ++ } ++ iter++; ++ ++ mptcp_for_each_bit_set(fmp->rem4_bits, i) { ++ struct fullmesh_rem4 *rem; ++ u8 remaining_bits; ++ ++ rem = &fmp->remaddr4[i]; ++ remaining_bits = ~(rem->bitfield) & mptcp_local->loc4_bits; ++ ++ /* Are there still combinations to handle? */ ++ if (remaining_bits) { ++ int i = mptcp_find_free_index(~remaining_bits); ++ struct mptcp_rem4 rem4; ++ ++ rem->bitfield |= (1 << i); ++ ++ rem4.addr = rem->addr; ++ rem4.port = rem->port; ++ rem4.rem4_id = rem->rem4_id; ++ ++ /* If a route is not yet available then retry once */ ++ if (mptcp_init4_subsockets(meta_sk, &mptcp_local->locaddr4[i], ++ &rem4) == -ENETUNREACH) ++ retry = rem->retry_bitfield |= (1 << i); ++ else ++ mptcp_v4_subflows(meta_sk, ++ &mptcp_local->locaddr4[i], ++ &rem4); ++ goto next_subflow; ++ } ++ } ++ ++#if IS_ENABLED(CONFIG_IPV6) ++ if (fmp->first_pair == 0 && mpcb->master_sk) { ++ struct mptcp_loc6 loc; ++ struct mptcp_rem6 rem; ++ ++ loc.addr = inet6_sk(meta_sk)->saddr; ++ loc.loc6_id = 0; ++ loc.low_prio = 0; ++ loc.if_idx = mpcb->master_sk->sk_bound_dev_if; ++ ++ rem.addr = meta_sk->sk_v6_daddr; ++ rem.port = inet_sk(meta_sk)->inet_dport; ++ rem.rem6_id = 0; /* Default 0 */ ++ ++ mptcp_v6_subflows(meta_sk, &loc, &rem); ++ ++ fmp->first_pair = 1; ++ } ++ mptcp_for_each_bit_set(fmp->rem6_bits, i) { ++ struct fullmesh_rem6 *rem; ++ u8 remaining_bits; ++ ++ rem = &fmp->remaddr6[i]; ++ remaining_bits = ~(rem->bitfield) & mptcp_local->loc6_bits; ++ ++ /* Are there still combinations to handle? */ ++ if (remaining_bits) { ++ int i = mptcp_find_free_index(~remaining_bits); ++ struct mptcp_rem6 rem6; ++ ++ rem->bitfield |= (1 << i); ++ ++ rem6.addr = rem->addr; ++ rem6.port = rem->port; ++ rem6.rem6_id = rem->rem6_id; ++ ++ /* If a route is not yet available then retry once */ ++ if (mptcp_init6_subsockets(meta_sk, &mptcp_local->locaddr6[i], ++ &rem6) == -ENETUNREACH) ++ retry = rem->retry_bitfield |= (1 << i); ++ else ++ mptcp_v6_subflows(meta_sk, ++ &mptcp_local->locaddr6[i], ++ &rem6); ++ goto next_subflow; ++ } ++ } ++#endif ++ ++ if (retry && !delayed_work_pending(&fmp->subflow_retry_work)) { ++ sock_hold(meta_sk); ++ refcount_inc(&mpcb->mpcb_refcnt); ++ queue_delayed_work(mptcp_wq, &fmp->subflow_retry_work, ++ msecs_to_jiffies(MPTCP_SUBFLOW_RETRY_DELAY)); ++ } ++ ++exit: ++ kfree(mptcp_local); ++ release_sock(meta_sk); ++ mutex_unlock(&mpcb->mpcb_mutex); ++ mptcp_mpcb_put(mpcb); ++ sock_put(meta_sk); ++} ++ ++static void announce_remove_addr(u8 addr_id, struct sock *meta_sk) ++{ ++ struct mptcp_cb *mpcb = tcp_sk(meta_sk)->mpcb; ++ struct fullmesh_priv *fmp = fullmesh_get_priv(mpcb); ++ struct sock *sk = mptcp_select_ack_sock(meta_sk); ++ ++ fmp->remove_addrs |= (1 << addr_id); ++ mpcb->addr_signal = 1; ++ ++ if (sk) ++ tcp_send_ack(sk); ++} ++ ++static void update_addr_bitfields(struct sock *meta_sk, ++ const struct mptcp_loc_addr *mptcp_local) ++{ ++ struct mptcp_cb *mpcb = tcp_sk(meta_sk)->mpcb; ++ struct fullmesh_priv *fmp = fullmesh_get_priv(mpcb); ++ int i; ++ ++ /* The bits in announced_addrs_* always match with loc*_bits. So, a ++ * simple & operation unsets the correct bits, because these go from ++ * announced to non-announced ++ */ ++ fmp->announced_addrs_v4 &= mptcp_local->loc4_bits; ++ ++ mptcp_for_each_bit_set(fmp->rem4_bits, i) { ++ fmp->remaddr4[i].bitfield &= mptcp_local->loc4_bits; ++ fmp->remaddr4[i].retry_bitfield &= mptcp_local->loc4_bits; ++ } ++ ++ fmp->announced_addrs_v6 &= mptcp_local->loc6_bits; ++ ++ mptcp_for_each_bit_set(fmp->rem6_bits, i) { ++ fmp->remaddr6[i].bitfield &= mptcp_local->loc6_bits; ++ fmp->remaddr6[i].retry_bitfield &= mptcp_local->loc6_bits; ++ } ++} ++ ++static int mptcp_find_address(const struct mptcp_loc_addr *mptcp_local, ++ sa_family_t family, const union inet_addr *addr, ++ int if_idx) ++{ ++ int i; ++ u8 loc_bits; ++ bool found = false; ++ ++ if (family == AF_INET) ++ loc_bits = mptcp_local->loc4_bits; ++ else ++ loc_bits = mptcp_local->loc6_bits; ++ ++ mptcp_for_each_bit_set(loc_bits, i) { ++ if (family == AF_INET && ++ (!if_idx || mptcp_local->locaddr4[i].if_idx == if_idx) && ++ mptcp_local->locaddr4[i].addr.s_addr == addr->in.s_addr) { ++ found = true; ++ break; ++ } ++ if (family == AF_INET6 && ++ (!if_idx || mptcp_local->locaddr6[i].if_idx == if_idx) && ++ ipv6_addr_equal(&mptcp_local->locaddr6[i].addr, ++ &addr->in6)) { ++ found = true; ++ break; ++ } ++ } ++ ++ if (!found) ++ return -1; ++ ++ return i; ++} ++ ++static int mptcp_find_address_transp(const struct mptcp_loc_addr *mptcp_local, ++ sa_family_t family, int if_idx) ++{ ++ bool found = false; ++ u8 loc_bits; ++ int i; ++ ++ if (family == AF_INET) ++ loc_bits = mptcp_local->loc4_bits; ++ else ++ loc_bits = mptcp_local->loc6_bits; ++ ++ mptcp_for_each_bit_set(loc_bits, i) { ++ if (family == AF_INET && ++ (!if_idx || mptcp_local->locaddr4[i].if_idx == if_idx)) { ++ found = true; ++ break; ++ } ++ if (family == AF_INET6 && ++ (!if_idx || mptcp_local->locaddr6[i].if_idx == if_idx)) { ++ found = true; ++ break; ++ } ++ } ++ ++ if (!found) ++ return -1; ++ ++ return i; ++} ++ ++static void mptcp_address_worker(struct work_struct *work) ++{ ++ const struct delayed_work *delayed_work = container_of(work, ++ struct delayed_work, ++ work); ++ struct mptcp_fm_ns *fm_ns = container_of(delayed_work, ++ struct mptcp_fm_ns, ++ address_worker); ++ struct net *net = fm_ns->net; ++ struct mptcp_addr_event *event = NULL; ++ struct mptcp_loc_addr *mptcp_local, *old; ++ int i, id = -1; /* id is used in the socket-code on a delete-event */ ++ bool success; /* Used to indicate if we succeeded handling the event */ ++ ++next_event: ++ success = false; ++ kfree(event); ++ ++ /* First, let's dequeue an event from our event-list */ ++ rcu_read_lock_bh(); ++ spin_lock(&fm_ns->local_lock); ++ ++ event = list_first_entry_or_null(&fm_ns->events, ++ struct mptcp_addr_event, list); ++ if (!event) { ++ spin_unlock(&fm_ns->local_lock); ++ rcu_read_unlock_bh(); ++ return; ++ } ++ ++ list_del(&event->list); ++ ++ mptcp_local = rcu_dereference_bh(fm_ns->local); ++ ++ if (event->code == MPTCP_EVENT_DEL) { ++ id = mptcp_find_address(mptcp_local, event->family, ++ &event->addr, event->if_idx); ++ ++ /* Not in the list - so we don't care */ ++ if (id < 0) { ++ mptcp_debug("%s could not find id\n", __func__); ++ goto duno; ++ } ++ ++ old = mptcp_local; ++ mptcp_local = kmemdup(mptcp_local, sizeof(*mptcp_local), ++ GFP_ATOMIC); ++ if (!mptcp_local) ++ goto duno; ++ ++ if (event->family == AF_INET) ++ mptcp_local->loc4_bits &= ~(1 << id); ++ else ++ mptcp_local->loc6_bits &= ~(1 << id); ++ ++ rcu_assign_pointer(fm_ns->local, mptcp_local); ++ kfree_rcu(old, rcu); ++ } else { ++ int i = mptcp_find_address(mptcp_local, event->family, ++ &event->addr, event->if_idx); ++ int j = i; ++ ++ if (j < 0) { ++ /* Not in the list, so we have to find an empty slot */ ++ if (event->family == AF_INET) ++ i = __mptcp_find_free_index(mptcp_local->loc4_bits, ++ mptcp_local->next_v4_index); ++ if (event->family == AF_INET6) ++ i = __mptcp_find_free_index(mptcp_local->loc6_bits, ++ mptcp_local->next_v6_index); ++ ++ if (i < 0) { ++ mptcp_debug("%s no more space\n", __func__); ++ goto duno; ++ } ++ ++ /* It might have been a MOD-event. */ ++ event->code = MPTCP_EVENT_ADD; ++ } else { ++ /* Let's check if anything changes */ ++ if (event->family == AF_INET && ++ event->low_prio == mptcp_local->locaddr4[i].low_prio) ++ goto duno; ++ ++ if (event->family == AF_INET6 && ++ event->low_prio == mptcp_local->locaddr6[i].low_prio) ++ goto duno; ++ } ++ ++ old = mptcp_local; ++ mptcp_local = kmemdup(mptcp_local, sizeof(*mptcp_local), ++ GFP_ATOMIC); ++ if (!mptcp_local) ++ goto duno; ++ ++ if (event->family == AF_INET) { ++ mptcp_local->locaddr4[i].addr.s_addr = event->addr.in.s_addr; ++ mptcp_local->locaddr4[i].loc4_id = i + 1; ++ mptcp_local->locaddr4[i].low_prio = event->low_prio; ++ mptcp_local->locaddr4[i].if_idx = event->if_idx; ++ ++ mptcp_debug("%s updated IP %pI4 on ifidx %u prio %u id %u\n", ++ __func__, &event->addr.in.s_addr, ++ event->if_idx, event->low_prio, i + 1); ++ } else { ++ mptcp_local->locaddr6[i].addr = event->addr.in6; ++ mptcp_local->locaddr6[i].loc6_id = i + MPTCP_MAX_ADDR; ++ mptcp_local->locaddr6[i].low_prio = event->low_prio; ++ mptcp_local->locaddr6[i].if_idx = event->if_idx; ++ ++ mptcp_debug("%s updated IP %pI6 on ifidx %u prio %u id %u\n", ++ __func__, &event->addr.in6, ++ event->if_idx, event->low_prio, i + MPTCP_MAX_ADDR); ++ } ++ ++ if (j < 0) { ++ if (event->family == AF_INET) { ++ mptcp_local->loc4_bits |= (1 << i); ++ mptcp_local->next_v4_index = i + 1; ++ } else { ++ mptcp_local->loc6_bits |= (1 << i); ++ mptcp_local->next_v6_index = i + 1; ++ } ++ } ++ ++ rcu_assign_pointer(fm_ns->local, mptcp_local); ++ kfree_rcu(old, rcu); ++ } ++ success = true; ++ ++duno: ++ spin_unlock(&fm_ns->local_lock); ++ rcu_read_unlock_bh(); ++ ++ if (!success) ++ goto next_event; ++ ++ /* Now we iterate over the MPTCP-sockets and apply the event. */ ++ for (i = 0; i <= mptcp_tk_htable.mask; i++) { ++ const struct hlist_nulls_node *node; ++ struct tcp_sock *meta_tp; ++ ++ rcu_read_lock_bh(); ++ hlist_nulls_for_each_entry_rcu(meta_tp, node, ++ &mptcp_tk_htable.hashtable[i], ++ tk_table) { ++ struct sock *meta_sk = (struct sock *)meta_tp, *sk; ++ bool meta_v4 = meta_sk->sk_family == AF_INET; ++ struct mptcp_cb *mpcb; ++ ++ if (sock_net(meta_sk) != net) ++ continue; ++ ++ if (meta_v4) { ++ /* skip IPv6 events if meta is IPv4 */ ++ if (event->family == AF_INET6) ++ continue; ++ } else if (event->family == AF_INET && meta_sk->sk_ipv6only) { ++ /* skip IPv4 events if IPV6_V6ONLY is set */ ++ continue; ++ } ++ ++ if (unlikely(!refcount_inc_not_zero(&meta_sk->sk_refcnt))) ++ continue; ++ ++ bh_lock_sock(meta_sk); ++ ++ mpcb = meta_tp->mpcb; ++ if (!mpcb) ++ goto next; ++ ++ if (!mptcp(meta_tp) || !is_meta_sk(meta_sk) || ++ mptcp_in_infinite_mapping_weak(mpcb)) ++ goto next; ++ ++ /* May be that the pm has changed in-between */ ++ if (mpcb->pm_ops != &full_mesh) ++ goto next; ++ ++ if (sock_owned_by_user(meta_sk)) { ++ if (!test_and_set_bit(MPTCP_PATH_MANAGER_DEFERRED, ++ &meta_sk->sk_tsq_flags)) ++ sock_hold(meta_sk); ++ ++ goto next; ++ } ++ ++ if (event->code == MPTCP_EVENT_ADD) { ++ struct fullmesh_priv *fmp = fullmesh_get_priv(mpcb); ++ ++ fmp->add_addr++; ++ mpcb->addr_signal = 1; ++ ++ sk = mptcp_select_ack_sock(meta_sk); ++ if (sk) ++ tcp_send_ack(sk); ++ ++ full_mesh_create_subflows(meta_sk); ++ } ++ ++ if (event->code == MPTCP_EVENT_DEL) { ++ struct mptcp_tcp_sock *mptcp; ++ struct mptcp_loc_addr *mptcp_local; ++ struct hlist_node *tmp; ++ bool found = false; ++ ++ mptcp_local = rcu_dereference_bh(fm_ns->local); ++ ++ /* In any case, we need to update our bitfields */ ++ if (id >= 0) ++ update_addr_bitfields(meta_sk, mptcp_local); ++ ++ /* Look for the socket and remove him */ ++ mptcp_for_each_sub_safe(mpcb, mptcp, tmp) { ++ struct sock *sk = mptcp_to_sock(mptcp); ++ ++ if ((event->family == AF_INET6 && ++ (sk->sk_family == AF_INET || ++ mptcp_v6_is_v4_mapped(sk))) || ++ (event->family == AF_INET && ++ (sk->sk_family == AF_INET6 && ++ !mptcp_v6_is_v4_mapped(sk)))) ++ continue; ++ ++ if (event->family == AF_INET && ++ (sk->sk_family == AF_INET || ++ mptcp_v6_is_v4_mapped(sk)) && ++ inet_sk(sk)->inet_saddr != event->addr.in.s_addr) ++ continue; ++ ++ if (event->family == AF_INET6 && ++ sk->sk_family == AF_INET6 && ++ !ipv6_addr_equal(&inet6_sk(sk)->saddr, &event->addr.in6)) ++ continue; ++ ++ /* Reinject, so that pf = 1 and so we ++ * won't select this one as the ++ * ack-sock. ++ */ ++ mptcp_reinject_data(sk, 0); ++ ++ /* We announce the removal of this id */ ++ announce_remove_addr(tcp_sk(sk)->mptcp->loc_id, meta_sk); ++ ++ mptcp_sub_force_close(sk); ++ found = true; ++ } ++ ++ if (found) ++ goto next; ++ ++ /* The id may have been given by the event, ++ * matching on a local address. And it may not ++ * have matched on one of the above sockets, ++ * because the client never created a subflow. ++ * So, we have to finally remove it here. ++ */ ++ if (id >= 0) { ++ u8 loc_id = id ++ + (event->family == AF_INET ? 1 : MPTCP_MAX_ADDR); ++ announce_remove_addr(loc_id, meta_sk); ++ } ++ } ++ ++ if (event->code == MPTCP_EVENT_MOD) { ++ struct mptcp_tcp_sock *mptcp; ++ ++ mptcp_for_each_sub(mpcb, mptcp) { ++ struct sock *sk = mptcp_to_sock(mptcp); ++ struct tcp_sock *tp = tcp_sk(sk); ++ if (event->family == AF_INET && ++ (sk->sk_family == AF_INET || ++ mptcp_v6_is_v4_mapped(sk)) && ++ inet_sk(sk)->inet_saddr == event->addr.in.s_addr) { ++ if (event->low_prio != tp->mptcp->low_prio) { ++ tp->mptcp->send_mp_prio = 1; ++ tp->mptcp->low_prio = event->low_prio; ++ ++ tcp_send_ack(sk); ++ } ++ } ++ ++ if (event->family == AF_INET6 && ++ sk->sk_family == AF_INET6 && ++ !ipv6_addr_equal(&inet6_sk(sk)->saddr, &event->addr.in6)) { ++ if (event->low_prio != tp->mptcp->low_prio) { ++ tp->mptcp->send_mp_prio = 1; ++ tp->mptcp->low_prio = event->low_prio; ++ ++ tcp_send_ack(sk); ++ } ++ } ++ } ++ } ++next: ++ bh_unlock_sock(meta_sk); ++ sock_put(meta_sk); ++ } ++ rcu_read_unlock_bh(); ++ } ++ goto next_event; ++} ++ ++static struct mptcp_addr_event *lookup_similar_event(const struct net *net, ++ const struct mptcp_addr_event *event) ++{ ++ struct mptcp_addr_event *eventq; ++ struct mptcp_fm_ns *fm_ns = fm_get_ns(net); ++ ++ list_for_each_entry(eventq, &fm_ns->events, list) { ++ if (eventq->family != event->family) ++ continue; ++ if (eventq->if_idx != event->if_idx) ++ continue; ++ if (event->family == AF_INET) { ++ if (eventq->addr.in.s_addr == event->addr.in.s_addr) ++ return eventq; ++ } else { ++ if (ipv6_addr_equal(&eventq->addr.in6, &event->addr.in6)) ++ return eventq; ++ } ++ } ++ return NULL; ++} ++ ++/* We already hold the net-namespace MPTCP-lock */ ++static void add_pm_event(struct net *net, const struct mptcp_addr_event *event) ++{ ++ struct mptcp_addr_event *eventq = lookup_similar_event(net, event); ++ struct mptcp_fm_ns *fm_ns = fm_get_ns(net); ++ ++ if (eventq) { ++ switch (event->code) { ++ case MPTCP_EVENT_DEL: ++ mptcp_debug("%s del old_code %u\n", __func__, eventq->code); ++ list_del(&eventq->list); ++ kfree(eventq); ++ break; ++ case MPTCP_EVENT_ADD: ++ mptcp_debug("%s add old_code %u\n", __func__, eventq->code); ++ eventq->low_prio = event->low_prio; ++ eventq->code = MPTCP_EVENT_ADD; ++ return; ++ case MPTCP_EVENT_MOD: ++ mptcp_debug("%s mod old_code %u\n", __func__, eventq->code); ++ eventq->low_prio = event->low_prio; ++ eventq->code = MPTCP_EVENT_MOD; ++ return; ++ } ++ } ++ ++ /* OK, we have to add the new address to the wait queue */ ++ eventq = kmemdup(event, sizeof(struct mptcp_addr_event), GFP_ATOMIC); ++ if (!eventq) ++ return; ++ ++ list_add_tail(&eventq->list, &fm_ns->events); ++ ++ /* Create work-queue */ ++ if (!delayed_work_pending(&fm_ns->address_worker)) ++ queue_delayed_work(mptcp_wq, &fm_ns->address_worker, ++ msecs_to_jiffies(500)); ++} ++ ++static void addr4_event_handler(const struct in_ifaddr *ifa, unsigned long event, ++ struct net *net) ++{ ++ const struct net_device *netdev = ifa->ifa_dev->dev; ++ struct mptcp_fm_ns *fm_ns = fm_get_ns(net); ++ struct mptcp_addr_event mpevent; ++ ++ if (ifa->ifa_scope > RT_SCOPE_LINK || ++ ipv4_is_loopback(ifa->ifa_local)) ++ return; ++ ++ spin_lock_bh(&fm_ns->local_lock); ++ ++ mpevent.family = AF_INET; ++ mpevent.addr.in.s_addr = ifa->ifa_local; ++ mpevent.low_prio = (netdev->flags & IFF_MPBACKUP) ? 1 : 0; ++ mpevent.if_idx = netdev->ifindex; ++ ++ if (event == NETDEV_DOWN || !netif_running(netdev) || ++ (netdev->flags & IFF_NOMULTIPATH) || !(netdev->flags & IFF_UP)) ++ mpevent.code = MPTCP_EVENT_DEL; ++ else if (event == NETDEV_UP) ++ mpevent.code = MPTCP_EVENT_ADD; ++ else if (event == NETDEV_CHANGE) ++ mpevent.code = MPTCP_EVENT_MOD; ++ ++ mptcp_debug("%s created event for %pI4, code %u prio %u idx %u\n", __func__, ++ &ifa->ifa_local, mpevent.code, mpevent.low_prio, mpevent.if_idx); ++ add_pm_event(net, &mpevent); ++ ++ spin_unlock_bh(&fm_ns->local_lock); ++ return; ++} ++ ++/* React on IPv4-addr add/rem-events */ ++static int mptcp_pm_inetaddr_event(struct notifier_block *this, ++ unsigned long event, void *ptr) ++{ ++ const struct in_ifaddr *ifa = (struct in_ifaddr *)ptr; ++ struct net *net = dev_net(ifa->ifa_dev->dev); ++ ++ if (!(event == NETDEV_UP || event == NETDEV_DOWN || ++ event == NETDEV_CHANGE)) ++ return NOTIFY_DONE; ++ ++ addr4_event_handler(ifa, event, net); ++ ++ return NOTIFY_DONE; ++} ++ ++static struct notifier_block mptcp_pm_inetaddr_notifier = { ++ .notifier_call = mptcp_pm_inetaddr_event, ++}; ++ ++#if IS_ENABLED(CONFIG_IPV6) ++ ++static int inet6_addr_event(struct notifier_block *this, unsigned long event, ++ void *ptr); ++ ++static void addr6_event_handler(const struct inet6_ifaddr *ifa, unsigned long event, ++ struct net *net) ++{ ++ const struct net_device *netdev = ifa->idev->dev; ++ int addr_type = ipv6_addr_type(&ifa->addr); ++ struct mptcp_fm_ns *fm_ns = fm_get_ns(net); ++ struct mptcp_addr_event mpevent; ++ ++ if (ifa->scope > RT_SCOPE_LINK || ++ addr_type == IPV6_ADDR_ANY || ++ (addr_type & IPV6_ADDR_LOOPBACK) || ++ (addr_type & IPV6_ADDR_LINKLOCAL)) ++ return; ++ ++ spin_lock_bh(&fm_ns->local_lock); ++ ++ mpevent.family = AF_INET6; ++ mpevent.addr.in6 = ifa->addr; ++ mpevent.low_prio = (netdev->flags & IFF_MPBACKUP) ? 1 : 0; ++ mpevent.if_idx = netdev->ifindex; ++ ++ if (event == NETDEV_DOWN || !netif_running(netdev) || ++ (netdev->flags & IFF_NOMULTIPATH) || !(netdev->flags & IFF_UP)) ++ mpevent.code = MPTCP_EVENT_DEL; ++ else if (event == NETDEV_UP) ++ mpevent.code = MPTCP_EVENT_ADD; ++ else if (event == NETDEV_CHANGE) ++ mpevent.code = MPTCP_EVENT_MOD; ++ ++ mptcp_debug("%s created event for %pI6, code %u prio %u idx %u\n", __func__, ++ &ifa->addr, mpevent.code, mpevent.low_prio, mpevent.if_idx); ++ add_pm_event(net, &mpevent); ++ ++ spin_unlock_bh(&fm_ns->local_lock); ++ return; ++} ++ ++/* React on IPv6-addr add/rem-events */ ++static int inet6_addr_event(struct notifier_block *this, unsigned long event, ++ void *ptr) ++{ ++ struct inet6_ifaddr *ifa6 = (struct inet6_ifaddr *)ptr; ++ struct net *net = dev_net(ifa6->idev->dev); ++ ++ if (!(event == NETDEV_UP || event == NETDEV_DOWN || ++ event == NETDEV_CHANGE)) ++ return NOTIFY_DONE; ++ ++ addr6_event_handler(ifa6, event, net); ++ ++ return NOTIFY_DONE; ++} ++ ++static struct notifier_block inet6_addr_notifier = { ++ .notifier_call = inet6_addr_event, ++}; ++ ++#endif ++ ++/* React on ifup/down-events */ ++static int netdev_event(struct notifier_block *this, unsigned long event, ++ void *ptr) ++{ ++ const struct net_device *dev = netdev_notifier_info_to_dev(ptr); ++ struct in_device *in_dev; ++#if IS_ENABLED(CONFIG_IPV6) ++ struct inet6_dev *in6_dev; ++#endif ++ ++ if (!(event == NETDEV_UP || event == NETDEV_DOWN || ++ event == NETDEV_CHANGE)) ++ return NOTIFY_DONE; ++ ++ rcu_read_lock(); ++ in_dev = __in_dev_get_rtnl(dev); ++ ++ if (in_dev) { ++ struct in_ifaddr *ifa; ++ ++ in_dev_for_each_ifa_rcu(ifa, in_dev) { ++ mptcp_pm_inetaddr_event(NULL, event, ifa); ++ } ++ } ++ ++#if IS_ENABLED(CONFIG_IPV6) ++ in6_dev = __in6_dev_get(dev); ++ ++ if (in6_dev) { ++ struct inet6_ifaddr *ifa6; ++ list_for_each_entry(ifa6, &in6_dev->addr_list, if_list) ++ inet6_addr_event(NULL, event, ifa6); ++ } ++#endif ++ ++ rcu_read_unlock(); ++ return NOTIFY_DONE; ++} ++ ++static struct notifier_block mptcp_pm_netdev_notifier = { ++ .notifier_call = netdev_event, ++}; ++ ++static void full_mesh_add_raddr(struct mptcp_cb *mpcb, ++ const union inet_addr *addr, ++ sa_family_t family, __be16 port, u8 id) ++{ ++ if (family == AF_INET) ++ mptcp_addv4_raddr(mpcb, &addr->in, port, id); ++ else ++ mptcp_addv6_raddr(mpcb, &addr->in6, port, id); ++} ++ ++static void full_mesh_new_session(const struct sock *meta_sk) ++{ ++ struct mptcp_loc_addr *mptcp_local; ++ struct mptcp_cb *mpcb = tcp_sk(meta_sk)->mpcb; ++ struct fullmesh_priv *fmp = fullmesh_get_priv(mpcb); ++ const struct mptcp_fm_ns *fm_ns = fm_get_ns(sock_net(meta_sk)); ++ struct tcp_sock *master_tp = tcp_sk(mpcb->master_sk); ++ int i, index, if_idx = 0; ++ union inet_addr saddr, daddr; ++ sa_family_t family = AF_INET; ++ bool meta_v4 = meta_sk->sk_family == AF_INET; ++ ++ /* Init local variables necessary for the rest */ ++ if (meta_sk->sk_family == AF_INET || mptcp_v6_is_v4_mapped(meta_sk)) { ++ saddr.ip = inet_sk(meta_sk)->inet_saddr; ++ daddr.ip = inet_sk(meta_sk)->inet_daddr; ++ if_idx = mpcb->master_sk->sk_bound_dev_if; ++ family = AF_INET; ++#if IS_ENABLED(CONFIG_IPV6) ++ } else { ++ saddr.in6 = inet6_sk(meta_sk)->saddr; ++ daddr.in6 = meta_sk->sk_v6_daddr; ++ if_idx = mpcb->master_sk->sk_bound_dev_if; ++ family = AF_INET6; ++#endif ++ } ++ ++ if (inet_sk(meta_sk)->transparent) ++ if_idx = inet_sk(meta_sk)->rx_dst_ifindex; ++ ++ rcu_read_lock_bh(); ++ mptcp_local = rcu_dereference(fm_ns->local); ++ ++ if (inet_sk(meta_sk)->transparent) ++ index = mptcp_find_address_transp(mptcp_local, family, if_idx); ++ else ++ index = mptcp_find_address(mptcp_local, family, &saddr, if_idx); ++ if (index < 0) ++ goto fallback; ++ ++ if (family == AF_INET) ++ master_tp->mptcp->low_prio = mptcp_local->locaddr4[index].low_prio; ++ else ++ master_tp->mptcp->low_prio = mptcp_local->locaddr6[index].low_prio; ++ master_tp->mptcp->send_mp_prio = master_tp->mptcp->low_prio; ++ ++ full_mesh_add_raddr(mpcb, &daddr, family, 0, 0); ++ mptcp_set_init_addr_bit(mpcb, &daddr, family, index); ++ ++ /* Initialize workqueue-struct */ ++ INIT_WORK(&fmp->subflow_work, create_subflow_worker); ++ INIT_DELAYED_WORK(&fmp->subflow_retry_work, retry_subflow_worker); ++ fmp->mpcb = mpcb; ++ ++ if (!meta_v4 && meta_sk->sk_ipv6only) ++ goto skip_ipv4; ++ ++ /* Look for the address among the local addresses */ ++ mptcp_for_each_bit_set(mptcp_local->loc4_bits, i) { ++ __be32 ifa_address = mptcp_local->locaddr4[i].addr.s_addr; ++ ++ /* We do not need to announce the initial subflow's address again */ ++ if (family == AF_INET && ++ (!if_idx || mptcp_local->locaddr4[i].if_idx == if_idx) && ++ saddr.ip == ifa_address) ++ continue; ++ ++ fmp->add_addr++; ++ mpcb->addr_signal = 1; ++ } ++ ++skip_ipv4: ++#if IS_ENABLED(CONFIG_IPV6) ++ /* skip IPv6 addresses if meta-socket is IPv4 */ ++ if (meta_v4) ++ goto skip_ipv6; ++ ++ mptcp_for_each_bit_set(mptcp_local->loc6_bits, i) { ++ const struct in6_addr *ifa6 = &mptcp_local->locaddr6[i].addr; ++ ++ /* We do not need to announce the initial subflow's address again */ ++ if (family == AF_INET6 && ++ (!if_idx || mptcp_local->locaddr6[i].if_idx == if_idx) && ++ ipv6_addr_equal(&saddr.in6, ifa6)) ++ continue; ++ ++ fmp->add_addr++; ++ mpcb->addr_signal = 1; ++ } ++ ++skip_ipv6: ++#endif ++ ++ rcu_read_unlock_bh(); ++ ++ if (family == AF_INET) ++ fmp->announced_addrs_v4 |= (1 << index); ++ else ++ fmp->announced_addrs_v6 |= (1 << index); ++ ++ for (i = fmp->add_addr; i && fmp->add_addr; i--) ++ tcp_send_ack(mpcb->master_sk); ++ ++ if (master_tp->mptcp->send_mp_prio) ++ tcp_send_ack(mpcb->master_sk); ++ ++ return; ++ ++fallback: ++ rcu_read_unlock_bh(); ++ mptcp_fallback_default(mpcb); ++ return; ++} ++ ++static void full_mesh_create_subflows(struct sock *meta_sk) ++{ ++ struct mptcp_cb *mpcb = tcp_sk(meta_sk)->mpcb; ++ struct fullmesh_priv *fmp = fullmesh_get_priv(mpcb); ++ ++ if (mptcp_in_infinite_mapping_weak(mpcb) || ++ mpcb->server_side || sock_flag(meta_sk, SOCK_DEAD)) ++ return; ++ ++ if (mpcb->master_sk && ++ !tcp_sk(mpcb->master_sk)->mptcp->fully_established) ++ return; ++ ++ if (!work_pending(&fmp->subflow_work)) { ++ sock_hold(meta_sk); ++ refcount_inc(&mpcb->mpcb_refcnt); ++ queue_work(mptcp_wq, &fmp->subflow_work); ++ } ++} ++ ++/* Called upon release_sock, if the socket was owned by the user during ++ * a path-management event. ++ */ ++static void full_mesh_release_sock(struct sock *meta_sk) ++{ ++ struct mptcp_loc_addr *mptcp_local; ++ struct mptcp_cb *mpcb = tcp_sk(meta_sk)->mpcb; ++ struct fullmesh_priv *fmp = fullmesh_get_priv(mpcb); ++ const struct mptcp_fm_ns *fm_ns = fm_get_ns(sock_net(meta_sk)); ++ bool meta_v4 = meta_sk->sk_family == AF_INET; ++ struct mptcp_tcp_sock *mptcp; ++ struct hlist_node *tmp; ++ int i; ++ ++ rcu_read_lock_bh(); ++ mptcp_local = rcu_dereference(fm_ns->local); ++ ++ if (!meta_v4 && meta_sk->sk_ipv6only) ++ goto skip_ipv4; ++ ++ /* First, detect modifications or additions */ ++ mptcp_for_each_bit_set(mptcp_local->loc4_bits, i) { ++ struct in_addr ifa = mptcp_local->locaddr4[i].addr; ++ bool found = false; ++ ++ mptcp_for_each_sub(mpcb, mptcp) { ++ struct sock *sk = mptcp_to_sock(mptcp); ++ struct tcp_sock *tp = tcp_sk(sk); ++ ++ if (sk->sk_family == AF_INET6 && ++ !mptcp_v6_is_v4_mapped(sk)) ++ continue; ++ ++ if (inet_sk(sk)->inet_saddr != ifa.s_addr) ++ continue; ++ ++ found = true; ++ ++ if (mptcp_local->locaddr4[i].low_prio != tp->mptcp->low_prio) { ++ tp->mptcp->send_mp_prio = 1; ++ tp->mptcp->low_prio = mptcp_local->locaddr4[i].low_prio; ++ ++ tcp_send_ack(sk); ++ } ++ } ++ ++ if (!found) { ++ struct sock *sk; ++ ++ fmp->add_addr++; ++ mpcb->addr_signal = 1; ++ ++ sk = mptcp_select_ack_sock(meta_sk); ++ if (sk) ++ tcp_send_ack(sk); ++ full_mesh_create_subflows(meta_sk); ++ } ++ } ++ ++skip_ipv4: ++#if IS_ENABLED(CONFIG_IPV6) ++ /* skip IPv6 addresses if meta-socket is IPv4 */ ++ if (meta_v4) ++ goto removal; ++ ++ mptcp_for_each_bit_set(mptcp_local->loc6_bits, i) { ++ struct in6_addr ifa = mptcp_local->locaddr6[i].addr; ++ bool found = false; ++ ++ mptcp_for_each_sub(mpcb, mptcp) { ++ struct sock *sk = mptcp_to_sock(mptcp); ++ struct tcp_sock *tp = tcp_sk(sk); ++ ++ if (sk->sk_family == AF_INET || ++ mptcp_v6_is_v4_mapped(sk)) ++ continue; ++ ++ if (!ipv6_addr_equal(&inet6_sk(sk)->saddr, &ifa)) ++ continue; ++ ++ found = true; ++ ++ if (mptcp_local->locaddr6[i].low_prio != tp->mptcp->low_prio) { ++ tp->mptcp->send_mp_prio = 1; ++ tp->mptcp->low_prio = mptcp_local->locaddr6[i].low_prio; ++ ++ tcp_send_ack(sk); ++ } ++ } ++ ++ if (!found) { ++ struct sock *sk; ++ ++ fmp->add_addr++; ++ mpcb->addr_signal = 1; ++ ++ sk = mptcp_select_ack_sock(meta_sk); ++ if (sk) ++ tcp_send_ack(sk); ++ full_mesh_create_subflows(meta_sk); ++ } ++ } ++ ++removal: ++#endif ++ ++ /* Now, detect address-removals */ ++ mptcp_for_each_sub_safe(mpcb, mptcp, tmp) { ++ struct sock *sk = mptcp_to_sock(mptcp); ++ bool shall_remove = true; ++ ++ if (sk->sk_family == AF_INET || mptcp_v6_is_v4_mapped(sk)) { ++ mptcp_for_each_bit_set(mptcp_local->loc4_bits, i) { ++ if (inet_sk(sk)->inet_saddr == mptcp_local->locaddr4[i].addr.s_addr) { ++ shall_remove = false; ++ break; ++ } ++ } ++ } else { ++ mptcp_for_each_bit_set(mptcp_local->loc6_bits, i) { ++ if (ipv6_addr_equal(&inet6_sk(sk)->saddr, &mptcp_local->locaddr6[i].addr)) { ++ shall_remove = false; ++ break; ++ } ++ } ++ } ++ ++ if (shall_remove) { ++ /* Reinject, so that pf = 1 and so we ++ * won't select this one as the ++ * ack-sock. ++ */ ++ mptcp_reinject_data(sk, 0); ++ ++ announce_remove_addr(tcp_sk(sk)->mptcp->loc_id, ++ meta_sk); ++ ++ mptcp_sub_force_close(sk); ++ } ++ } ++ ++ /* Just call it optimistically. It actually cannot do any harm */ ++ update_addr_bitfields(meta_sk, mptcp_local); ++ ++ rcu_read_unlock_bh(); ++} ++ ++static int full_mesh_get_local_id(const struct sock *meta_sk, ++ sa_family_t family, union inet_addr *addr, ++ bool *low_prio) ++{ ++ struct mptcp_loc_addr *mptcp_local; ++ const struct mptcp_fm_ns *fm_ns = fm_get_ns(sock_net(meta_sk)); ++ int index, id = -1; ++ ++ /* Handle the backup-flows */ ++ rcu_read_lock_bh(); ++ mptcp_local = rcu_dereference(fm_ns->local); ++ ++ index = mptcp_find_address(mptcp_local, family, addr, 0); ++ ++ if (index != -1) { ++ if (family == AF_INET) { ++ id = mptcp_local->locaddr4[index].loc4_id; ++ *low_prio = mptcp_local->locaddr4[index].low_prio; ++ } else { ++ id = mptcp_local->locaddr6[index].loc6_id; ++ *low_prio = mptcp_local->locaddr6[index].low_prio; ++ } ++ } ++ ++ ++ rcu_read_unlock_bh(); ++ ++ return id; ++} ++ ++static void full_mesh_addr_signal(struct sock *sk, unsigned *size, ++ struct tcp_out_options *opts, ++ struct sk_buff *skb) ++{ ++ const struct tcp_sock *tp = tcp_sk(sk); ++ struct mptcp_cb *mpcb = tp->mpcb; ++ struct sock *meta_sk = mpcb->meta_sk; ++ struct fullmesh_priv *fmp = fullmesh_get_priv(mpcb); ++ struct mptcp_loc_addr *mptcp_local; ++ struct mptcp_fm_ns *fm_ns = fm_get_ns(sock_net(sk)); ++ int remove_addr_len; ++ u8 unannouncedv4 = 0, unannouncedv6 = 0; ++ bool meta_v4 = meta_sk->sk_family == AF_INET; ++ ++ mpcb->addr_signal = 0; ++ ++ if (likely(!fmp->add_addr)) ++ goto remove_addr; ++ ++ rcu_read_lock_bh(); ++ mptcp_local = rcu_dereference(fm_ns->local); ++ ++ if (!meta_v4 && meta_sk->sk_ipv6only) ++ goto skip_ipv4; ++ ++ /* IPv4 */ ++ unannouncedv4 = (~fmp->announced_addrs_v4) & mptcp_local->loc4_bits; ++ if (unannouncedv4 && ++ ((mpcb->mptcp_ver == MPTCP_VERSION_0 && ++ MAX_TCP_OPTION_SPACE - *size >= MPTCP_SUB_LEN_ADD_ADDR4_ALIGN) || ++ (mpcb->mptcp_ver >= MPTCP_VERSION_1 && ++ MAX_TCP_OPTION_SPACE - *size >= MPTCP_SUB_LEN_ADD_ADDR4_ALIGN_VER1))) { ++ int ind = mptcp_find_free_index(~unannouncedv4); ++ ++ opts->options |= OPTION_MPTCP; ++ opts->mptcp_options |= OPTION_ADD_ADDR; ++ opts->add_addr4.addr_id = mptcp_local->locaddr4[ind].loc4_id; ++ opts->add_addr4.addr = mptcp_local->locaddr4[ind].addr; ++ opts->add_addr_v4 = 1; ++ if (mpcb->mptcp_ver >= MPTCP_VERSION_1) { ++ u8 mptcp_hash_mac[SHA256_DIGEST_SIZE]; ++ ++ mptcp_hmac(mpcb->mptcp_ver, (u8 *)&mpcb->mptcp_loc_key, ++ (u8 *)&mpcb->mptcp_rem_key, mptcp_hash_mac, 2, ++ 1, (u8 *)&mptcp_local->locaddr4[ind].loc4_id, ++ 4, (u8 *)&opts->add_addr4.addr.s_addr); ++ opts->add_addr4.trunc_mac = *(u64 *)&mptcp_hash_mac[SHA256_DIGEST_SIZE - sizeof(u64)]; ++ } ++ ++ if (skb) { ++ fmp->announced_addrs_v4 |= (1 << ind); ++ fmp->add_addr--; ++ } ++ ++ if (mpcb->mptcp_ver < MPTCP_VERSION_1) ++ *size += MPTCP_SUB_LEN_ADD_ADDR4_ALIGN; ++ if (mpcb->mptcp_ver >= MPTCP_VERSION_1) ++ *size += MPTCP_SUB_LEN_ADD_ADDR4_ALIGN_VER1; ++ ++ goto skip_ipv6; ++ } ++ ++ if (meta_v4) ++ goto skip_ipv6; ++skip_ipv4: ++ /* IPv6 */ ++ unannouncedv6 = (~fmp->announced_addrs_v6) & mptcp_local->loc6_bits; ++ if (unannouncedv6 && ++ ((mpcb->mptcp_ver == MPTCP_VERSION_0 && ++ MAX_TCP_OPTION_SPACE - *size >= MPTCP_SUB_LEN_ADD_ADDR6_ALIGN) || ++ (mpcb->mptcp_ver >= MPTCP_VERSION_1 && ++ MAX_TCP_OPTION_SPACE - *size >= MPTCP_SUB_LEN_ADD_ADDR6_ALIGN_VER1))) { ++ int ind = mptcp_find_free_index(~unannouncedv6); ++ ++ opts->options |= OPTION_MPTCP; ++ opts->mptcp_options |= OPTION_ADD_ADDR; ++ opts->add_addr6.addr_id = mptcp_local->locaddr6[ind].loc6_id; ++ opts->add_addr6.addr = mptcp_local->locaddr6[ind].addr; ++ opts->add_addr_v6 = 1; ++ if (mpcb->mptcp_ver >= MPTCP_VERSION_1) { ++ u8 mptcp_hash_mac[SHA256_DIGEST_SIZE]; ++ ++ mptcp_hmac(mpcb->mptcp_ver, (u8 *)&mpcb->mptcp_loc_key, ++ (u8 *)&mpcb->mptcp_rem_key, mptcp_hash_mac, 2, ++ 1, (u8 *)&mptcp_local->locaddr6[ind].loc6_id, ++ 16, (u8 *)&opts->add_addr6.addr.s6_addr); ++ opts->add_addr6.trunc_mac = *(u64 *)&mptcp_hash_mac[SHA256_DIGEST_SIZE - sizeof(u64)]; ++ } ++ ++ if (skb) { ++ fmp->announced_addrs_v6 |= (1 << ind); ++ fmp->add_addr--; ++ } ++ if (mpcb->mptcp_ver < MPTCP_VERSION_1) ++ *size += MPTCP_SUB_LEN_ADD_ADDR6_ALIGN; ++ if (mpcb->mptcp_ver >= MPTCP_VERSION_1) ++ *size += MPTCP_SUB_LEN_ADD_ADDR6_ALIGN_VER1; ++ } ++ ++skip_ipv6: ++ rcu_read_unlock_bh(); ++ ++ if (!unannouncedv4 && !unannouncedv6 && skb) ++ fmp->add_addr--; ++ ++remove_addr: ++ if (likely(!fmp->remove_addrs)) ++ goto exit; ++ ++ remove_addr_len = mptcp_sub_len_remove_addr_align(fmp->remove_addrs); ++ if (MAX_TCP_OPTION_SPACE - *size < remove_addr_len) ++ goto exit; ++ ++ opts->options |= OPTION_MPTCP; ++ opts->mptcp_options |= OPTION_REMOVE_ADDR; ++ opts->remove_addrs = fmp->remove_addrs; ++ *size += remove_addr_len; ++ if (skb) ++ fmp->remove_addrs = 0; ++ ++exit: ++ mpcb->addr_signal = !!(fmp->add_addr || fmp->remove_addrs); ++} ++ ++static void full_mesh_rem_raddr(struct mptcp_cb *mpcb, u8 rem_id) ++{ ++ mptcp_v4_rem_raddress(mpcb, rem_id); ++ mptcp_v6_rem_raddress(mpcb, rem_id); ++} ++ ++static void full_mesh_delete_subflow(struct sock *sk) ++{ ++ struct fullmesh_priv *fmp = fullmesh_get_priv(tcp_sk(sk)->mpcb); ++ struct mptcp_fm_ns *fm_ns = fm_get_ns(sock_net(sk)); ++ struct sock *meta_sk = mptcp_meta_sk(sk); ++ struct mptcp_loc_addr *mptcp_local; ++ int index, i; ++ ++ if (!create_on_err) ++ return; ++ ++ if (!mptcp_can_new_subflow(meta_sk)) ++ return; ++ ++ rcu_read_lock_bh(); ++ mptcp_local = rcu_dereference_bh(fm_ns->local); ++ ++ if (sk->sk_family == AF_INET || mptcp_v6_is_v4_mapped(sk)) { ++ union inet_addr saddr; ++ ++ saddr.ip = inet_sk(sk)->inet_saddr; ++ index = mptcp_find_address(mptcp_local, AF_INET, &saddr, ++ sk->sk_bound_dev_if); ++ if (index < 0) ++ goto out; ++ ++ mptcp_for_each_bit_set(fmp->rem4_bits, i) { ++ struct fullmesh_rem4 *rem4 = &fmp->remaddr4[i]; ++ ++ if (rem4->addr.s_addr != sk->sk_daddr) ++ continue; ++ ++ if (rem4->port && rem4->port != inet_sk(sk)->inet_dport) ++ continue; ++ ++ rem4->bitfield &= ~(1 << index); ++ } ++#if IS_ENABLED(CONFIG_IPV6) ++ } else { ++ union inet_addr saddr; ++ ++ saddr.in6 = inet6_sk(sk)->saddr; ++ index = mptcp_find_address(mptcp_local, AF_INET6, &saddr, ++ sk->sk_bound_dev_if); ++ if (index < 0) ++ goto out; ++ ++ mptcp_for_each_bit_set(fmp->rem6_bits, i) { ++ struct fullmesh_rem6 *rem6 = &fmp->remaddr6[i]; ++ ++ if (!ipv6_addr_equal(&rem6->addr, &sk->sk_v6_daddr)) ++ continue; ++ ++ if (rem6->port && rem6->port != inet_sk(sk)->inet_dport) ++ continue; ++ ++ rem6->bitfield &= ~(1 << index); ++ } ++#endif ++ } ++ ++out: ++ rcu_read_unlock_bh(); ++ ++ /* re-schedule the creation of failed subflows */ ++ if (tcp_sk(sk)->mptcp->sk_err == ETIMEDOUT || sk->sk_err == ETIMEDOUT) ++ full_mesh_create_subflows(meta_sk); ++} ++ ++/* Output /proc/net/mptcp_fullmesh */ ++static int mptcp_fm_seq_show(struct seq_file *seq, void *v) ++{ ++ const struct net *net = seq->private; ++ struct mptcp_loc_addr *mptcp_local; ++ const struct mptcp_fm_ns *fm_ns = fm_get_ns(net); ++ int i; ++ ++ seq_printf(seq, "Index, Address-ID, Backup, IP-address, if-idx\n"); ++ ++ rcu_read_lock_bh(); ++ mptcp_local = rcu_dereference(fm_ns->local); ++ ++ seq_printf(seq, "IPv4, next v4-index: %u\n", mptcp_local->next_v4_index); ++ ++ mptcp_for_each_bit_set(mptcp_local->loc4_bits, i) { ++ struct mptcp_loc4 *loc4 = &mptcp_local->locaddr4[i]; ++ ++ seq_printf(seq, "%u, %u, %u, %pI4, %u\n", i, loc4->loc4_id, ++ loc4->low_prio, &loc4->addr, loc4->if_idx); ++ } ++ ++ seq_printf(seq, "IPv6, next v6-index: %u\n", mptcp_local->next_v6_index); ++ ++ mptcp_for_each_bit_set(mptcp_local->loc6_bits, i) { ++ struct mptcp_loc6 *loc6 = &mptcp_local->locaddr6[i]; ++ ++ seq_printf(seq, "%u, %u, %u, %pI6, %u\n", i, loc6->loc6_id, ++ loc6->low_prio, &loc6->addr, loc6->if_idx); ++ } ++ rcu_read_unlock_bh(); ++ ++ return 0; ++} ++ ++static int mptcp_fm_init_net(struct net *net) ++{ ++ struct mptcp_loc_addr *mptcp_local; ++ struct mptcp_fm_ns *fm_ns; ++ int err = 0; ++ ++ fm_ns = kzalloc(sizeof(*fm_ns), GFP_KERNEL); ++ if (!fm_ns) ++ return -ENOBUFS; ++ ++ mptcp_local = kzalloc(sizeof(*mptcp_local), GFP_KERNEL); ++ if (!mptcp_local) { ++ err = -ENOBUFS; ++ goto err_mptcp_local; ++ } ++ ++ if (!proc_create_net_single("mptcp_fullmesh", S_IRUGO, net->proc_net, ++ mptcp_fm_seq_show, NULL)) { ++ err = -ENOMEM; ++ goto err_seq_fops; ++ } ++ ++ mptcp_local->next_v4_index = 1; ++ ++ rcu_assign_pointer(fm_ns->local, mptcp_local); ++ INIT_DELAYED_WORK(&fm_ns->address_worker, mptcp_address_worker); ++ INIT_LIST_HEAD(&fm_ns->events); ++ spin_lock_init(&fm_ns->local_lock); ++ fm_ns->net = net; ++ net->mptcp.path_managers[MPTCP_PM_FULLMESH] = fm_ns; ++ ++ return 0; ++err_seq_fops: ++ kfree(mptcp_local); ++err_mptcp_local: ++ kfree(fm_ns); ++ return err; ++} ++ ++static void mptcp_fm_exit_net(struct net *net) ++{ ++ struct mptcp_addr_event *eventq, *tmp; ++ struct mptcp_fm_ns *fm_ns; ++ struct mptcp_loc_addr *mptcp_local; ++ ++ fm_ns = fm_get_ns(net); ++ cancel_delayed_work_sync(&fm_ns->address_worker); ++ ++ rcu_read_lock_bh(); ++ ++ mptcp_local = rcu_dereference_bh(fm_ns->local); ++ kfree_rcu(mptcp_local, rcu); ++ ++ spin_lock(&fm_ns->local_lock); ++ list_for_each_entry_safe(eventq, tmp, &fm_ns->events, list) { ++ list_del(&eventq->list); ++ kfree(eventq); ++ } ++ spin_unlock(&fm_ns->local_lock); ++ ++ rcu_read_unlock_bh(); ++ ++ remove_proc_entry("mptcp_fullmesh", net->proc_net); ++ ++ kfree(fm_ns); ++} ++ ++static struct pernet_operations full_mesh_net_ops = { ++ .init = mptcp_fm_init_net, ++ .exit = mptcp_fm_exit_net, ++}; ++ ++static struct mptcp_pm_ops full_mesh __read_mostly = { ++ .new_session = full_mesh_new_session, ++ .release_sock = full_mesh_release_sock, ++ .fully_established = full_mesh_create_subflows, ++ .new_remote_address = full_mesh_create_subflows, ++ .get_local_id = full_mesh_get_local_id, ++ .addr_signal = full_mesh_addr_signal, ++ .add_raddr = full_mesh_add_raddr, ++ .rem_raddr = full_mesh_rem_raddr, ++ .delete_subflow = full_mesh_delete_subflow, ++ .name = "fullmesh", ++ .owner = THIS_MODULE, ++}; ++ ++/* General initialization of MPTCP_PM */ ++static int __init full_mesh_register(void) ++{ ++ int ret; ++ ++ BUILD_BUG_ON(sizeof(struct fullmesh_priv) > MPTCP_PM_SIZE); ++ ++ ret = register_pernet_subsys(&full_mesh_net_ops); ++ if (ret) ++ goto out; ++ ++ ret = register_inetaddr_notifier(&mptcp_pm_inetaddr_notifier); ++ if (ret) ++ goto err_reg_inetaddr; ++ ret = register_netdevice_notifier(&mptcp_pm_netdev_notifier); ++ if (ret) ++ goto err_reg_netdev; ++ ++#if IS_ENABLED(CONFIG_IPV6) ++ ret = register_inet6addr_notifier(&inet6_addr_notifier); ++ if (ret) ++ goto err_reg_inet6addr; ++#endif ++ ++ ret = mptcp_register_path_manager(&full_mesh); ++ if (ret) ++ goto err_reg_pm; ++ ++out: ++ return ret; ++ ++ ++err_reg_pm: ++#if IS_ENABLED(CONFIG_IPV6) ++ unregister_inet6addr_notifier(&inet6_addr_notifier); ++err_reg_inet6addr: ++#endif ++ unregister_netdevice_notifier(&mptcp_pm_netdev_notifier); ++err_reg_netdev: ++ unregister_inetaddr_notifier(&mptcp_pm_inetaddr_notifier); ++err_reg_inetaddr: ++ unregister_pernet_subsys(&full_mesh_net_ops); ++ goto out; ++} ++ ++static void full_mesh_unregister(void) ++{ ++#if IS_ENABLED(CONFIG_IPV6) ++ unregister_inet6addr_notifier(&inet6_addr_notifier); ++#endif ++ unregister_netdevice_notifier(&mptcp_pm_netdev_notifier); ++ unregister_inetaddr_notifier(&mptcp_pm_inetaddr_notifier); ++ unregister_pernet_subsys(&full_mesh_net_ops); ++ mptcp_unregister_path_manager(&full_mesh); ++} ++ ++module_init(full_mesh_register); ++module_exit(full_mesh_unregister); ++ ++MODULE_AUTHOR("Christoph Paasch"); ++MODULE_LICENSE("GPL"); ++MODULE_DESCRIPTION("Full-Mesh MPTCP"); ++MODULE_VERSION("0.88"); +diff --git a/net/mptcp/mptcp_input.c b/net/mptcp/mptcp_input.c +new file mode 100644 +index 000000000000..ae9cc7209613 +--- /dev/null ++++ b/net/mptcp/mptcp_input.c +@@ -0,0 +1,2546 @@ ++/* ++ * MPTCP implementation - Sending side ++ * ++ * Initial Design & Implementation: ++ * Sébastien Barré ++ * ++ * Current Maintainer & Author: ++ * Christoph Paasch ++ * ++ * Additional authors: ++ * Jaakko Korkeaniemi ++ * Gregory Detal ++ * Fabien Duchêne ++ * Andreas Seelinger ++ * Lavkesh Lahngir ++ * Andreas Ripke ++ * Vlad Dogaru ++ * Octavian Purdila ++ * John Ronan ++ * Catalin Nicutar ++ * Brandon Heller ++ * ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License ++ * as published by the Free Software Foundation; either version ++ * 2 of the License, or (at your option) any later version. ++ */ ++ ++#include ++ ++#include ++#include ++#include ++ ++#include ++ ++/* is seq1 < seq2 ? */ ++static inline bool before64(const u64 seq1, const u64 seq2) ++{ ++ return (s64)(seq1 - seq2) < 0; ++} ++ ++/* is seq1 > seq2 ? */ ++#define after64(seq1, seq2) before64(seq2, seq1) ++ ++static inline void mptcp_become_fully_estab(struct sock *sk) ++{ ++ tcp_sk(sk)->mptcp->fully_established = 1; ++ ++ if (is_master_tp(tcp_sk(sk)) && ++ tcp_sk(sk)->mpcb->pm_ops->fully_established) ++ tcp_sk(sk)->mpcb->pm_ops->fully_established(mptcp_meta_sk(sk)); ++} ++ ++/* Similar to tcp_tso_acked without any memory accounting */ ++static inline int mptcp_tso_acked_reinject(const struct sock *meta_sk, ++ struct sk_buff *skb) ++{ ++ const struct tcp_sock *meta_tp = tcp_sk(meta_sk); ++ u32 packets_acked, len, delta_truesize; ++ ++ BUG_ON(!after(TCP_SKB_CB(skb)->end_seq, meta_tp->snd_una)); ++ ++ packets_acked = tcp_skb_pcount(skb); ++ ++ if (skb_unclone(skb, GFP_ATOMIC)) ++ return 0; ++ ++ len = meta_tp->snd_una - TCP_SKB_CB(skb)->seq; ++ delta_truesize = __pskb_trim_head(skb, len); ++ ++ TCP_SKB_CB(skb)->seq += len; ++ skb->ip_summed = CHECKSUM_PARTIAL; ++ ++ if (delta_truesize) ++ skb->truesize -= delta_truesize; ++ ++ /* Any change of skb->len requires recalculation of tso factor. */ ++ if (tcp_skb_pcount(skb) > 1) ++ tcp_set_skb_tso_segs(skb, tcp_skb_mss(skb)); ++ packets_acked -= tcp_skb_pcount(skb); ++ ++ if (packets_acked) { ++ BUG_ON(tcp_skb_pcount(skb) == 0); ++ BUG_ON(!before(TCP_SKB_CB(skb)->seq, TCP_SKB_CB(skb)->end_seq)); ++ } ++ ++ return packets_acked; ++} ++ ++/* Cleans the meta-socket retransmission queue and the reinject-queue. */ ++static void mptcp_clean_rtx_queue(struct sock *meta_sk, u32 prior_snd_una) ++{ ++ struct sk_buff *skb, *tmp, *next; ++ struct tcp_sock *meta_tp = tcp_sk(meta_sk); ++ struct mptcp_cb *mpcb = meta_tp->mpcb; ++ bool fully_acked = true; ++ bool acked = false; ++ u32 acked_pcount; ++ ++ for (skb = skb_rb_first(&meta_sk->tcp_rtx_queue); skb; skb = next) { ++ struct tcp_skb_cb *scb = TCP_SKB_CB(skb); ++ ++ tcp_ack_tstamp(meta_sk, skb, prior_snd_una); ++ ++ if (after(scb->end_seq, meta_tp->snd_una)) { ++ if (tcp_skb_pcount(skb) == 1 || ++ !after(meta_tp->snd_una, scb->seq)) ++ break; ++ ++ acked_pcount = tcp_tso_acked(meta_sk, skb); ++ if (!acked_pcount) ++ break; ++ fully_acked = false; ++ } else { ++ acked_pcount = tcp_skb_pcount(skb); ++ } ++ ++ acked = true; ++ meta_tp->packets_out -= acked_pcount; ++ meta_tp->retrans_stamp = 0; ++ ++ if (!fully_acked) ++ break; ++ ++ next = skb_rb_next(skb); ++ ++ if (mptcp_is_data_fin(skb)) { ++ struct mptcp_tcp_sock *mptcp; ++ struct hlist_node *tmp; ++ ++ /* DATA_FIN has been acknowledged - now we can close ++ * the subflows ++ */ ++ mptcp_for_each_sub_safe(mpcb, mptcp, tmp) { ++ struct sock *sk_it = mptcp_to_sock(mptcp); ++ unsigned long delay = 0; ++ ++ /* If we are the passive closer, don't trigger ++ * subflow-fin until the subflow has been finned ++ * by the peer - thus we add a delay. ++ */ ++ if (mpcb->passive_close && ++ sk_it->sk_state == TCP_ESTABLISHED) ++ delay = inet_csk(sk_it)->icsk_rto << 3; ++ ++ mptcp_sub_close(sk_it, delay); ++ } ++ } ++ tcp_rtx_queue_unlink_and_free(skb, meta_sk); ++ } ++ /* Remove acknowledged data from the reinject queue */ ++ skb_queue_walk_safe(&mpcb->reinject_queue, skb, tmp) { ++ if (before(meta_tp->snd_una, TCP_SKB_CB(skb)->end_seq)) { ++ if (tcp_skb_pcount(skb) == 1 || ++ !after(meta_tp->snd_una, TCP_SKB_CB(skb)->seq)) ++ break; ++ ++ mptcp_tso_acked_reinject(meta_sk, skb); ++ break; ++ } ++ ++ __skb_unlink(skb, &mpcb->reinject_queue); ++ __kfree_skb(skb); ++ } ++ ++ if (likely(between(meta_tp->snd_up, prior_snd_una, meta_tp->snd_una))) ++ meta_tp->snd_up = meta_tp->snd_una; ++ ++ if (acked) { ++ tcp_rearm_rto(meta_sk); ++ /* Normally this is done in tcp_try_undo_loss - but MPTCP ++ * does not call this function. ++ */ ++ inet_csk(meta_sk)->icsk_retransmits = 0; ++ } ++} ++ ++/* Inspired by tcp_rcv_state_process */ ++/* Returns 0 if processing the packet can continue ++ * -1 if connection was closed with an active reset ++ * 1 if connection was closed and processing should stop. ++ */ ++static int mptcp_rcv_state_process(struct sock *meta_sk, struct sock *sk, ++ const struct sk_buff *skb, u32 data_seq, ++ u16 data_len) ++{ ++ struct tcp_sock *meta_tp = tcp_sk(meta_sk), *tp = tcp_sk(sk); ++ const struct tcphdr *th = tcp_hdr(skb); ++ ++ /* State-machine handling if FIN has been enqueued and he has ++ * been acked (snd_una == write_seq) - it's important that this ++ * here is after sk_wmem_free_skb because otherwise ++ * sk_forward_alloc is wrong upon inet_csk_destroy_sock() ++ */ ++ switch (meta_sk->sk_state) { ++ case TCP_FIN_WAIT1: { ++ struct dst_entry *dst; ++ int tmo; ++ ++ if (meta_tp->snd_una != meta_tp->write_seq) ++ break; ++ ++ tcp_set_state(meta_sk, TCP_FIN_WAIT2); ++ meta_sk->sk_shutdown |= SEND_SHUTDOWN; ++ ++ dst = __sk_dst_get(sk); ++ if (dst) ++ dst_confirm(dst); ++ ++ if (!sock_flag(meta_sk, SOCK_DEAD)) { ++ /* Wake up lingering close() */ ++ meta_sk->sk_state_change(meta_sk); ++ break; ++ } ++ ++ if (meta_tp->linger2 < 0 || ++ (data_len && ++ after(data_seq + data_len - (mptcp_is_data_fin2(skb, tp) ? 1 : 0), ++ meta_tp->rcv_nxt))) { ++ mptcp_send_active_reset(meta_sk, GFP_ATOMIC); ++ tcp_done(meta_sk); ++ NET_INC_STATS(sock_net(meta_sk), LINUX_MIB_TCPABORTONDATA); ++ return -1; ++ } ++ ++ tmo = tcp_fin_time(meta_sk); ++ if (tmo > TCP_TIMEWAIT_LEN) { ++ inet_csk_reset_keepalive_timer(meta_sk, tmo - TCP_TIMEWAIT_LEN); ++ } else if (mptcp_is_data_fin2(skb, tp) || sock_owned_by_user(meta_sk)) { ++ /* Bad case. We could lose such FIN otherwise. ++ * It is not a big problem, but it looks confusing ++ * and not so rare event. We still can lose it now, ++ * if it spins in bh_lock_sock(), but it is really ++ * marginal case. ++ */ ++ inet_csk_reset_keepalive_timer(meta_sk, tmo); ++ } else { ++ meta_tp->ops->time_wait(meta_sk, TCP_FIN_WAIT2, tmo); ++ } ++ break; ++ } ++ case TCP_CLOSING: ++ case TCP_LAST_ACK: ++ if (meta_tp->snd_una == meta_tp->write_seq) { ++ tcp_done(meta_sk); ++ return 1; ++ } ++ break; ++ } ++ ++ /* step 7: process the segment text */ ++ switch (meta_sk->sk_state) { ++ case TCP_FIN_WAIT1: ++ case TCP_FIN_WAIT2: ++ /* RFC 793 says to queue data in these states, ++ * RFC 1122 says we MUST send a reset. ++ * BSD 4.4 also does reset. ++ */ ++ if (meta_sk->sk_shutdown & RCV_SHUTDOWN) { ++ if (TCP_SKB_CB(skb)->end_seq != TCP_SKB_CB(skb)->seq && ++ after(TCP_SKB_CB(skb)->end_seq - th->fin, tp->rcv_nxt) && ++ !mptcp_is_data_fin2(skb, tp)) { ++ NET_INC_STATS(sock_net(meta_sk), LINUX_MIB_TCPABORTONDATA); ++ mptcp_send_active_reset(meta_sk, GFP_ATOMIC); ++ tcp_reset(meta_sk); ++ return -1; ++ } ++ } ++ break; ++ } ++ ++ return 0; ++} ++ ++/** ++ * @return: ++ * i) 1: Everything's fine. ++ * ii) -1: A reset has been sent on the subflow - csum-failure ++ * iii) 0: csum-failure but no reset sent, because it's the last subflow. ++ * Last packet should not be destroyed by the caller because it has ++ * been done here. ++ */ ++static int mptcp_verif_dss_csum(struct sock *sk) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct sk_buff *tmp, *tmp1, *last = NULL; ++ __wsum csum_tcp = 0; /* cumulative checksum of pld + mptcp-header */ ++ int ans = 1, overflowed = 0, offset = 0, dss_csum_added = 0; ++ int iter = 0; ++ u32 next_seq, offset_seq; ++ ++ skb_queue_walk_safe(&sk->sk_receive_queue, tmp, tmp1) { ++ unsigned int csum_len; ++ ++ /* init next seq in first round */ ++ if (!iter) ++ next_seq = TCP_SKB_CB(tmp)->seq; ++ offset_seq = next_seq - TCP_SKB_CB(tmp)->seq; ++ ++ if (before(tp->mptcp->map_subseq + tp->mptcp->map_data_len, TCP_SKB_CB(tmp)->end_seq)) ++ /* Mapping ends in the middle of the packet - ++ * csum only these bytes ++ */ ++ csum_len = tp->mptcp->map_subseq + tp->mptcp->map_data_len - TCP_SKB_CB(tmp)->seq; ++ else ++ csum_len = tmp->len; ++ ++ csum_len -= offset_seq; ++ offset = 0; ++ if (overflowed) { ++ char first_word[4]; ++ first_word[0] = 0; ++ first_word[1] = 0; ++ first_word[2] = 0; ++ first_word[3] = *(tmp->data + offset_seq); ++ csum_tcp = csum_partial(first_word, 4, csum_tcp); ++ offset = 1; ++ csum_len--; ++ overflowed = 0; ++ } ++ ++ csum_tcp = skb_checksum(tmp, offset + offset_seq, csum_len, ++ csum_tcp); ++ ++ /* Was it on an odd-length? Then we have to merge the next byte ++ * correctly (see above) ++ */ ++ if (csum_len != (csum_len & (~1))) ++ overflowed = 1; ++ ++ if (mptcp_is_data_seq(tmp) && !dss_csum_added) { ++ __be32 data_seq = htonl((u32)(tp->mptcp->map_data_seq >> 32)); ++ ++ /* If a 64-bit dss is present, we increase the offset ++ * by 4 bytes, as the high-order 64-bits will be added ++ * in the final csum_partial-call. ++ */ ++ u32 offset = skb_transport_offset(tmp) + ++ TCP_SKB_CB(tmp)->dss_off; ++ if (TCP_SKB_CB(tmp)->mptcp_flags & MPTCPHDR_SEQ64_SET) ++ offset += 4; ++ ++ csum_tcp = skb_checksum(tmp, offset, ++ MPTCP_SUB_LEN_SEQ_CSUM, ++ csum_tcp); ++ ++ csum_tcp = csum_partial(&data_seq, ++ sizeof(data_seq), csum_tcp); ++ ++ dss_csum_added = 1; /* Just do it once */ ++ } else if (mptcp_is_data_mpcapable(tmp) && !dss_csum_added) { ++ u32 offset = skb_transport_offset(tmp) + TCP_SKB_CB(tmp)->dss_off; ++ __be64 data_seq = htonll(tp->mptcp->map_data_seq); ++ __be32 rel_seq = htonl(tp->mptcp->map_subseq - tp->mptcp->rcv_isn); ++ ++ csum_tcp = csum_partial(&data_seq, sizeof(data_seq), csum_tcp); ++ csum_tcp = csum_partial(&rel_seq, sizeof(rel_seq), csum_tcp); ++ ++ csum_tcp = skb_checksum(tmp, offset, 4, csum_tcp); ++ ++ dss_csum_added = 1; ++ } ++ last = tmp; ++ iter++; ++ ++ if (!skb_queue_is_last(&sk->sk_receive_queue, tmp) && ++ !before(TCP_SKB_CB(tmp1)->seq, ++ tp->mptcp->map_subseq + tp->mptcp->map_data_len)) ++ break; ++ next_seq = TCP_SKB_CB(tmp)->end_seq; ++ } ++ ++ /* Now, checksum must be 0 */ ++ if (unlikely(csum_fold(csum_tcp))) { ++ struct mptcp_tcp_sock *mptcp; ++ struct sock *sk_it = NULL; ++ ++ pr_debug("%s csum is wrong: %#x tcp-seq %u dss_csum_added %d overflowed %d iterations %d\n", ++ __func__, csum_fold(csum_tcp), TCP_SKB_CB(last)->seq, ++ dss_csum_added, overflowed, iter); ++ ++ MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_CSUMFAIL); ++ tp->mptcp->send_mp_fail = 1; ++ ++ /* map_data_seq is the data-seq number of the ++ * mapping we are currently checking ++ */ ++ tp->mpcb->csum_cutoff_seq = tp->mptcp->map_data_seq; ++ ++ /* Search for another subflow that is fully established */ ++ mptcp_for_each_sub(tp->mpcb, mptcp) { ++ sk_it = mptcp_to_sock(mptcp); ++ ++ if (sk_it != sk && ++ tcp_sk(sk_it)->mptcp->fully_established) ++ break; ++ ++ sk_it = NULL; ++ } ++ ++ if (sk_it) { ++ mptcp_send_reset(sk); ++ ans = -1; ++ } else { ++ tp->mpcb->send_infinite_mapping = 1; ++ ++ /* Need to purge the rcv-queue as it's no more valid */ ++ while ((tmp = __skb_dequeue(&sk->sk_receive_queue)) != NULL) { ++ tp->copied_seq = TCP_SKB_CB(tmp)->end_seq; ++ kfree_skb(tmp); ++ } ++ ++ mptcp_fallback_close(tp->mpcb, sk); ++ ++ ans = 0; ++ } ++ } ++ ++ return ans; ++} ++ ++static inline void mptcp_prepare_skb(struct sk_buff *skb, ++ const struct sock *sk) ++{ ++ const struct tcp_sock *tp = tcp_sk(sk); ++ struct tcp_skb_cb *tcb = TCP_SKB_CB(skb); ++ u32 inc = 0, end_seq = tcb->end_seq; ++ ++ if (TCP_SKB_CB(skb)->tcp_flags & TCPHDR_FIN) ++ end_seq--; ++ /* If skb is the end of this mapping (end is always at mapping-boundary ++ * thanks to the splitting/trimming), then we need to increase ++ * data-end-seq by 1 if this here is a data-fin. ++ * ++ * We need to do -1 because end_seq includes the subflow-FIN. ++ */ ++ if (tp->mptcp->map_data_fin && ++ end_seq == tp->mptcp->map_subseq + tp->mptcp->map_data_len) { ++ inc = 1; ++ ++ /* We manually set the fin-flag if it is a data-fin. For easy ++ * processing in tcp_recvmsg. ++ */ ++ TCP_SKB_CB(skb)->tcp_flags |= TCPHDR_FIN; ++ } else { ++ /* We may have a subflow-fin with data but without data-fin */ ++ TCP_SKB_CB(skb)->tcp_flags &= ~TCPHDR_FIN; ++ } ++ ++ /* Adapt data-seq's to the packet itself. We kinda transform the ++ * dss-mapping to a per-packet granularity. This is necessary to ++ * correctly handle overlapping mappings coming from different ++ * subflows. Otherwise it would be a complete mess. ++ */ ++ tcb->seq = ((u32)tp->mptcp->map_data_seq) + tcb->seq - tp->mptcp->map_subseq; ++ tcb->end_seq = tcb->seq + skb->len + inc; ++} ++ ++static inline void mptcp_reset_mapping(struct tcp_sock *tp, u32 old_copied_seq) ++{ ++ tp->mptcp->map_data_len = 0; ++ tp->mptcp->map_data_seq = 0; ++ tp->mptcp->map_subseq = 0; ++ tp->mptcp->map_data_fin = 0; ++ tp->mptcp->mapping_present = 0; ++ ++ /* In infinite mapping receiver mode, we have to advance the implied ++ * data-sequence number when we progress the subflow's data. ++ */ ++ if (tp->mpcb->infinite_mapping_rcv) ++ tp->mpcb->infinite_rcv_seq += (tp->copied_seq - old_copied_seq); ++} ++ ++/* The DSS-mapping received on the sk only covers the second half of the skb ++ * (cut at seq). We trim the head from the skb. ++ * Data will be freed upon kfree(). ++ * ++ * Inspired by tcp_trim_head(). ++ */ ++static void mptcp_skb_trim_head(struct sk_buff *skb, struct sock *sk, u32 seq) ++{ ++ int len = seq - TCP_SKB_CB(skb)->seq; ++ u32 new_seq = TCP_SKB_CB(skb)->seq + len; ++ u32 delta_truesize; ++ ++ delta_truesize = __pskb_trim_head(skb, len); ++ ++ TCP_SKB_CB(skb)->seq = new_seq; ++ ++ if (delta_truesize) { ++ skb->truesize -= delta_truesize; ++ atomic_sub(delta_truesize, &sk->sk_rmem_alloc); ++ sk_mem_uncharge(sk, delta_truesize); ++ } ++} ++ ++/* The DSS-mapping received on the sk only covers the first half of the skb ++ * (cut at seq). We create a second skb (@return), and queue it in the rcv-queue ++ * as further packets may resolve the mapping of the second half of data. ++ * ++ * Inspired by tcp_fragment(). ++ */ ++static int mptcp_skb_split_tail(struct sk_buff *skb, struct sock *sk, u32 seq) ++{ ++ struct sk_buff *buff; ++ int nsize; ++ int nlen, len; ++ u8 flags; ++ ++ len = seq - TCP_SKB_CB(skb)->seq; ++ nsize = skb_headlen(skb) - len + tcp_sk(sk)->tcp_header_len; ++ if (nsize < 0) ++ nsize = 0; ++ ++ /* Get a new skb... force flag on. */ ++ buff = alloc_skb(nsize, GFP_ATOMIC); ++ if (buff == NULL) ++ return -ENOMEM; ++ ++ skb_reserve(buff, tcp_sk(sk)->tcp_header_len); ++ skb_reset_transport_header(buff); ++ ++ flags = TCP_SKB_CB(skb)->tcp_flags; ++ TCP_SKB_CB(skb)->tcp_flags = flags & ~(TCPHDR_FIN); ++ TCP_SKB_CB(buff)->tcp_flags = flags; ++ ++ /* We absolutly need to call skb_set_owner_r before refreshing the ++ * truesize of buff, otherwise the moved data will account twice. ++ */ ++ skb_set_owner_r(buff, sk); ++ nlen = skb->len - len - nsize; ++ buff->truesize += nlen; ++ skb->truesize -= nlen; ++ ++ /* Correct the sequence numbers. */ ++ TCP_SKB_CB(buff)->seq = TCP_SKB_CB(skb)->seq + len; ++ TCP_SKB_CB(buff)->end_seq = TCP_SKB_CB(skb)->end_seq; ++ TCP_SKB_CB(skb)->end_seq = TCP_SKB_CB(buff)->seq; ++ ++ skb_split(skb, buff, len); ++ ++ __skb_queue_after(&sk->sk_receive_queue, skb, buff); ++ ++ return 0; ++} ++ ++/* @return: 0 everything is fine. Just continue processing ++ * 1 subflow is broken stop everything ++ * -1 this packet was broken - continue with the next one. ++ */ ++static int mptcp_prevalidate_skb(struct sock *sk, struct sk_buff *skb) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct mptcp_cb *mpcb = tp->mpcb; ++ ++ /* If we are in infinite mode, the subflow-fin is in fact a data-fin. */ ++ if (!skb->len && (TCP_SKB_CB(skb)->tcp_flags & TCPHDR_FIN) && ++ !mptcp_is_data_fin(skb) && !mpcb->infinite_mapping_rcv) { ++ /* Remove a pure subflow-fin from the queue and increase ++ * copied_seq. ++ */ ++ tp->copied_seq = TCP_SKB_CB(skb)->end_seq; ++ __skb_unlink(skb, &sk->sk_receive_queue); ++ __kfree_skb(skb); ++ return -1; ++ } ++ ++ /* If we are not yet fully established and do not know the mapping for ++ * this segment, this path has to fallback to infinite or be torn down. ++ */ ++ if (!tp->mptcp->fully_established && !mptcp_is_data_seq(skb) && ++ !mptcp_is_data_mpcapable(skb) && ++ !tp->mptcp->mapping_present && !mpcb->infinite_mapping_rcv) { ++ pr_debug("%s %#x will fallback - pi %d from %pS, seq %u mptcp-flags %#x\n", ++ __func__, mpcb->mptcp_loc_token, ++ tp->mptcp->path_index, __builtin_return_address(0), ++ TCP_SKB_CB(skb)->seq, TCP_SKB_CB(skb)->mptcp_flags); ++ ++ if (!is_master_tp(tp)) { ++ MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_FBDATASUB); ++ mptcp_send_reset(sk); ++ return 1; ++ } ++ ++ MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_FBDATAINIT); ++ ++ mpcb->infinite_mapping_snd = 1; ++ mpcb->infinite_mapping_rcv = 1; ++ mpcb->infinite_rcv_seq = mptcp_get_rcv_nxt_64(mptcp_meta_tp(tp)); ++ ++ mptcp_fallback_close(mpcb, sk); ++ ++ /* We do a seamless fallback and should not send a inf.mapping. */ ++ mpcb->send_infinite_mapping = 0; ++ tp->mptcp->fully_established = 1; ++ } ++ ++ /* Receiver-side becomes fully established when a whole rcv-window has ++ * been received without the need to fallback due to the previous ++ * condition. ++ */ ++ if (!tp->mptcp->fully_established) { ++ tp->mptcp->init_rcv_wnd -= skb->len; ++ if (tp->mptcp->init_rcv_wnd < 0) ++ mptcp_become_fully_estab(sk); ++ } ++ ++ return 0; ++} ++ ++static void mptcp_restart_sending(struct sock *meta_sk) ++{ ++ struct tcp_sock *meta_tp = tcp_sk(meta_sk); ++ struct mptcp_cb *mpcb = meta_tp->mpcb; ++ struct sk_buff *wq_head, *skb, *tmp; ++ ++ skb = tcp_rtx_queue_head(meta_sk); ++ ++ /* We resend everything that has not been acknowledged, thus we need ++ * to move it from the rtx-tree to the write-queue. ++ */ ++ wq_head = tcp_write_queue_head(meta_sk); ++ ++ skb_rbtree_walk_from_safe(skb, tmp) { ++ list_del(&skb->tcp_tsorted_anchor); ++ tcp_rtx_queue_unlink(skb, meta_sk); ++ INIT_LIST_HEAD(&skb->tcp_tsorted_anchor); ++ ++ if (wq_head) ++ __skb_queue_before(&meta_sk->sk_write_queue, wq_head, skb); ++ else ++ tcp_add_write_queue_tail(meta_sk, skb); ++ } ++ ++ /* We artificially restart the whole send-queue. Thus, ++ * it is as if no packets are in flight ++ */ ++ meta_tp->packets_out = 0; ++ ++ /* If the snd_nxt already wrapped around, we have to ++ * undo the wrapping, as we are restarting from snd_una ++ * on. ++ */ ++ if (meta_tp->snd_nxt < meta_tp->snd_una) { ++ mpcb->snd_high_order[mpcb->snd_hiseq_index] -= 2; ++ mpcb->snd_hiseq_index = mpcb->snd_hiseq_index ? 0 : 1; ++ } ++ meta_tp->snd_nxt = meta_tp->snd_una; ++ ++ /* Trigger a sending on the meta. */ ++ mptcp_push_pending_frames(meta_sk); ++} ++ ++/* @return: 0 everything is fine. Just continue processing ++ * 1 subflow is broken stop everything ++ * -1 this packet was broken - continue with the next one. ++ */ ++static int mptcp_detect_mapping(struct sock *sk, struct sk_buff *skb) ++{ ++ struct tcp_sock *tp = tcp_sk(sk), *meta_tp = mptcp_meta_tp(tp); ++ struct mptcp_cb *mpcb = tp->mpcb; ++ struct tcp_skb_cb *tcb = TCP_SKB_CB(skb); ++ u32 *ptr; ++ u32 data_seq, sub_seq, data_len, tcp_end_seq; ++ bool set_infinite_rcv = false; ++ ++ /* If we are in infinite-mapping-mode, the subflow is guaranteed to be ++ * in-order at the data-level. Thus data-seq-numbers can be inferred ++ * from what is expected at the data-level. ++ */ ++ if (mpcb->infinite_mapping_rcv) { ++ /* copied_seq may be bigger than tcb->seq (e.g., when the peer ++ * retransmits data that actually has already been acknowledged with ++ * newer data, if he did not receive our acks). Thus, we need ++ * to account for this overlap as well. ++ */ ++ tp->mptcp->map_data_seq = mpcb->infinite_rcv_seq - (tp->copied_seq - tcb->seq); ++ tp->mptcp->map_subseq = tcb->seq; ++ tp->mptcp->map_data_len = skb->len; ++ tp->mptcp->map_data_fin = !!(TCP_SKB_CB(skb)->tcp_flags & TCPHDR_FIN); ++ tp->mptcp->mapping_present = 1; ++ return 0; ++ } ++ ++ if (!tp->mptcp->mapping_present && mptcp_is_data_mpcapable(skb)) { ++ __u32 *ptr = (__u32 *)(skb_transport_header(skb) + TCP_SKB_CB(skb)->dss_off); ++ ++ sub_seq = 1 + tp->mptcp->rcv_isn; ++ data_seq = meta_tp->rcv_nxt; ++ data_len = get_unaligned_be16(ptr); ++ } else if (!mptcp_is_data_seq(skb)) { ++ /* No mapping here? ++ * Exit - it is either already set or still on its way ++ */ ++ if (!tp->mptcp->mapping_present && ++ tp->rcv_nxt - tp->copied_seq > 65536) { ++ /* Too many packets without a mapping, ++ * this subflow is broken ++ */ ++ MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_NODSSWINDOW); ++ mptcp_send_reset(sk); ++ return 1; ++ } ++ ++ return 0; ++ } else { ++ /* Well, then the DSS-mapping is there. So, read it! */ ++ ptr = mptcp_skb_set_data_seq(skb, &data_seq, mpcb); ++ ptr++; ++ sub_seq = get_unaligned_be32(ptr) + tp->mptcp->rcv_isn; ++ ptr++; ++ data_len = get_unaligned_be16(ptr); ++ } ++ ++ /* If it's an empty skb with DATA_FIN, sub_seq must get fixed. ++ * The draft sets it to 0, but we really would like to have the ++ * real value, to have an easy handling afterwards here in this ++ * function. ++ */ ++ if (mptcp_is_data_fin(skb) && skb->len == 0) ++ sub_seq = TCP_SKB_CB(skb)->seq; ++ ++ /* If there is already a mapping - we check if it maps with the current ++ * one. If not - we reset. ++ */ ++ if (tp->mptcp->mapping_present && ++ (data_seq != (u32)tp->mptcp->map_data_seq || ++ sub_seq != tp->mptcp->map_subseq || ++ data_len != tp->mptcp->map_data_len + tp->mptcp->map_data_fin || ++ mptcp_is_data_fin(skb) != tp->mptcp->map_data_fin)) { ++ /* Mapping in packet is different from what we want */ ++ pr_debug("%s Mappings do not match!\n", __func__); ++ pr_debug("%s dseq %u mdseq %u, sseq %u msseq %u dlen %u mdlen %u dfin %d mdfin %d\n", ++ __func__, data_seq, (u32)tp->mptcp->map_data_seq, ++ sub_seq, tp->mptcp->map_subseq, data_len, ++ tp->mptcp->map_data_len, mptcp_is_data_fin(skb), ++ tp->mptcp->map_data_fin); ++ MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_DSSNOMATCH); ++ mptcp_send_reset(sk); ++ return 1; ++ } ++ ++ /* If the previous check was good, the current mapping is valid and we exit. */ ++ if (tp->mptcp->mapping_present) ++ return 0; ++ ++ /* Mapping not yet set on this subflow - we set it here! */ ++ ++ if (!data_len) { ++ mpcb->infinite_mapping_rcv = 1; ++ mpcb->send_infinite_mapping = 1; ++ tp->mptcp->fully_established = 1; ++ /* We need to repeat mp_fail's until the sender felt ++ * back to infinite-mapping - here we stop repeating it. ++ */ ++ tp->mptcp->send_mp_fail = 0; ++ ++ /* We have to fixup data_len - it must be the same as skb->len */ ++ data_len = skb->len + (mptcp_is_data_fin(skb) ? 1 : 0); ++ sub_seq = tcb->seq; ++ ++ mptcp_restart_sending(tp->meta_sk); ++ ++ mptcp_fallback_close(mpcb, sk); ++ ++ /* data_seq and so on are set correctly */ ++ ++ /* At this point, the meta-ofo-queue has to be emptied, ++ * as the following data is guaranteed to be in-order at ++ * the data and subflow-level ++ */ ++ skb_rbtree_purge(&meta_tp->out_of_order_queue); ++ ++ set_infinite_rcv = true; ++ MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_INFINITEMAPRX); ++ } ++ ++ /* We are sending mp-fail's and thus are in fallback mode. ++ * Ignore packets which do not announce the fallback and still ++ * want to provide a mapping. ++ */ ++ if (tp->mptcp->send_mp_fail) { ++ tp->copied_seq = TCP_SKB_CB(skb)->end_seq; ++ __skb_unlink(skb, &sk->sk_receive_queue); ++ __kfree_skb(skb); ++ return -1; ++ } ++ ++ /* FIN increased the mapping-length by 1 */ ++ if (mptcp_is_data_fin(skb)) ++ data_len--; ++ ++ /* Subflow-sequences of packet must be ++ * (at least partially) be part of the DSS-mapping's ++ * subflow-sequence-space. ++ * ++ * Basically the mapping is not valid, if either of the ++ * following conditions is true: ++ * ++ * 1. It's not a data_fin and ++ * MPTCP-sub_seq >= TCP-end_seq ++ * ++ * 2. It's a data_fin and TCP-end_seq > TCP-seq and ++ * MPTCP-sub_seq >= TCP-end_seq ++ * ++ * The previous two can be merged into: ++ * TCP-end_seq > TCP-seq and MPTCP-sub_seq >= TCP-end_seq ++ * Because if it's not a data-fin, TCP-end_seq > TCP-seq ++ * ++ * 3. It's a data_fin and skb->len == 0 and ++ * MPTCP-sub_seq > TCP-end_seq ++ * ++ * 4. It's not a data_fin and TCP-end_seq > TCP-seq and ++ * MPTCP-sub_seq + MPTCP-data_len <= TCP-seq ++ */ ++ ++ /* subflow-fin is not part of the mapping - ignore it here ! */ ++ tcp_end_seq = tcb->end_seq; ++ if (tcb->tcp_flags & TCPHDR_FIN) ++ tcp_end_seq--; ++ if ((!before(sub_seq, tcb->end_seq) && after(tcp_end_seq, tcb->seq)) || ++ (mptcp_is_data_fin(skb) && skb->len == 0 && after(sub_seq, tcb->end_seq)) || ++ (!after(sub_seq + data_len, tcb->seq) && after(tcp_end_seq, tcb->seq))) { ++ /* Subflow-sequences of packet is different from what is in the ++ * packet's dss-mapping. The peer is misbehaving - reset ++ */ ++ pr_debug("%s Packet's mapping does not map to the DSS sub_seq %u end_seq %u, tcp_end_seq %u seq %u dfin %u len %u data_len %u copied_seq %u\n", ++ __func__, sub_seq, tcb->end_seq, tcp_end_seq, ++ tcb->seq, mptcp_is_data_fin(skb), ++ skb->len, data_len, tp->copied_seq); ++ MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_DSSTCPMISMATCH); ++ mptcp_send_reset(sk); ++ return 1; ++ } ++ ++ /* Does the DSS had 64-bit seqnum's ? */ ++ if (!(tcb->mptcp_flags & MPTCPHDR_SEQ64_SET)) { ++ /* Wrapped around? */ ++ if (unlikely(after(data_seq, meta_tp->rcv_nxt) && data_seq < meta_tp->rcv_nxt)) { ++ tp->mptcp->map_data_seq = mptcp_get_data_seq_64(mpcb, !mpcb->rcv_hiseq_index, data_seq); ++ } else { ++ /* Else, access the default high-order bits */ ++ tp->mptcp->map_data_seq = mptcp_get_data_seq_64(mpcb, mpcb->rcv_hiseq_index, data_seq); ++ } ++ } else { ++ tp->mptcp->map_data_seq = mptcp_get_data_seq_64(mpcb, (tcb->mptcp_flags & MPTCPHDR_SEQ64_INDEX) ? 1 : 0, data_seq); ++ ++ if (unlikely(tcb->mptcp_flags & MPTCPHDR_SEQ64_OFO)) { ++ /* We make sure that the data_seq is invalid. ++ * It will be dropped later. ++ */ ++ tp->mptcp->map_data_seq += 0xFFFFFFFF; ++ tp->mptcp->map_data_seq += 0xFFFFFFFF; ++ } ++ } ++ ++ if (set_infinite_rcv) ++ mpcb->infinite_rcv_seq = tp->mptcp->map_data_seq; ++ ++ tp->mptcp->map_data_len = data_len; ++ tp->mptcp->map_subseq = sub_seq; ++ tp->mptcp->map_data_fin = mptcp_is_data_fin(skb) ? 1 : 0; ++ tp->mptcp->mapping_present = 1; ++ ++ return 0; ++} ++ ++/* Similar to tcp_sequence(...) */ ++static inline bool mptcp_sequence(const struct tcp_sock *meta_tp, ++ u64 data_seq, u64 end_data_seq) ++{ ++ const struct mptcp_cb *mpcb = meta_tp->mpcb; ++ u64 rcv_wup64; ++ ++ /* Wrap-around? */ ++ if (meta_tp->rcv_wup > meta_tp->rcv_nxt) { ++ rcv_wup64 = ((u64)(mpcb->rcv_high_order[mpcb->rcv_hiseq_index] - 1) << 32) | ++ meta_tp->rcv_wup; ++ } else { ++ rcv_wup64 = mptcp_get_data_seq_64(mpcb, mpcb->rcv_hiseq_index, ++ meta_tp->rcv_wup); ++ } ++ ++ return !before64(end_data_seq, rcv_wup64) && ++ !after64(data_seq, mptcp_get_rcv_nxt_64(meta_tp) + tcp_receive_window_now(meta_tp)); ++} ++ ++/* @return: 0 everything is fine. Just continue processing ++ * -1 this packet was broken - continue with the next one. ++ */ ++static int mptcp_validate_mapping(struct sock *sk, struct sk_buff *skb) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct sk_buff *tmp, *tmp1; ++ u32 tcp_end_seq; ++ ++ if (!tp->mptcp->mapping_present) ++ return 0; ++ ++ /* either, the new skb gave us the mapping and the first segment ++ * in the sub-rcv-queue has to be trimmed ... ++ */ ++ tmp = skb_peek(&sk->sk_receive_queue); ++ if (before(TCP_SKB_CB(tmp)->seq, tp->mptcp->map_subseq) && ++ after(TCP_SKB_CB(tmp)->end_seq, tp->mptcp->map_subseq)) { ++ MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_DSSTRIMHEAD); ++ mptcp_skb_trim_head(tmp, sk, tp->mptcp->map_subseq); ++ } ++ ++ /* ... or the new skb (tail) has to be split at the end. */ ++ tcp_end_seq = TCP_SKB_CB(skb)->end_seq; ++ if (TCP_SKB_CB(skb)->tcp_flags & TCPHDR_FIN) ++ tcp_end_seq--; ++ if (after(tcp_end_seq, tp->mptcp->map_subseq + tp->mptcp->map_data_len)) { ++ u32 seq = tp->mptcp->map_subseq + tp->mptcp->map_data_len; ++ MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_DSSSPLITTAIL); ++ if (mptcp_skb_split_tail(skb, sk, seq)) { /* Allocation failed */ ++ /* TODO : maybe handle this here better. ++ * We now just force meta-retransmission. ++ */ ++ tp->copied_seq = TCP_SKB_CB(skb)->end_seq; ++ __skb_unlink(skb, &sk->sk_receive_queue); ++ __kfree_skb(skb); ++ return -1; ++ } ++ } ++ ++ /* Now, remove old sk_buff's from the receive-queue. ++ * This may happen if the mapping has been lost for these segments and ++ * the next mapping has already been received. ++ */ ++ if (before(TCP_SKB_CB(skb_peek(&sk->sk_receive_queue))->seq, tp->mptcp->map_subseq)) { ++ skb_queue_walk_safe(&sk->sk_receive_queue, tmp1, tmp) { ++ if (!before(TCP_SKB_CB(tmp1)->seq, tp->mptcp->map_subseq)) ++ break; ++ ++ tp->copied_seq = TCP_SKB_CB(tmp1)->end_seq; ++ __skb_unlink(tmp1, &sk->sk_receive_queue); ++ ++ MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_PURGEOLD); ++ /* Impossible that we could free skb here, because his ++ * mapping is known to be valid from previous checks ++ */ ++ __kfree_skb(tmp1); ++ } ++ } ++ ++ return 0; ++} ++ ++/* @return: 0 everything is fine. Just continue processing ++ * 1 subflow is broken stop everything ++ * -1 this mapping has been put in the meta-receive-queue ++ * -2 this mapping has been eaten by the application ++ */ ++static int mptcp_queue_skb(struct sock *sk) ++{ ++ struct tcp_sock *tp = tcp_sk(sk), *meta_tp = mptcp_meta_tp(tp); ++ struct sock *meta_sk = mptcp_meta_sk(sk); ++ struct mptcp_cb *mpcb = tp->mpcb; ++ struct sk_buff *tmp, *tmp1; ++ u64 rcv_nxt64 = mptcp_get_rcv_nxt_64(meta_tp); ++ u32 old_copied_seq = tp->copied_seq; ++ bool data_queued = false; ++ ++ /* Have we not yet received the full mapping? */ ++ if (!tp->mptcp->mapping_present || ++ before(tp->rcv_nxt, tp->mptcp->map_subseq + tp->mptcp->map_data_len)) ++ return 0; ++ ++ /* Is this an overlapping mapping? rcv_nxt >= end_data_seq ++ * OR ++ * This mapping is out of window ++ */ ++ if (!before64(rcv_nxt64, tp->mptcp->map_data_seq + tp->mptcp->map_data_len + tp->mptcp->map_data_fin) || ++ !mptcp_sequence(meta_tp, tp->mptcp->map_data_seq, ++ tp->mptcp->map_data_seq + tp->mptcp->map_data_len + tp->mptcp->map_data_fin)) { ++ skb_queue_walk_safe(&sk->sk_receive_queue, tmp1, tmp) { ++ __skb_unlink(tmp1, &sk->sk_receive_queue); ++ tp->copied_seq = TCP_SKB_CB(tmp1)->end_seq; ++ __kfree_skb(tmp1); ++ ++ if (!skb_queue_empty(&sk->sk_receive_queue) && ++ !before(TCP_SKB_CB(tmp)->seq, ++ tp->mptcp->map_subseq + tp->mptcp->map_data_len)) ++ break; ++ } ++ ++ mptcp_reset_mapping(tp, old_copied_seq); ++ ++ return -1; ++ } ++ ++ /* Record it, because we want to send our data_fin on the same path */ ++ if (tp->mptcp->map_data_fin) { ++ mpcb->dfin_path_index = tp->mptcp->path_index; ++ mpcb->dfin_combined = !!(sk->sk_shutdown & RCV_SHUTDOWN); ++ } ++ ++ /* Verify the checksum */ ++ if (mpcb->dss_csum && !mpcb->infinite_mapping_rcv) { ++ int ret = mptcp_verif_dss_csum(sk); ++ ++ if (ret <= 0) { ++ mptcp_reset_mapping(tp, old_copied_seq); ++ return 1; ++ } ++ } ++ ++ if (before64(rcv_nxt64, tp->mptcp->map_data_seq)) { ++ /* Seg's have to go to the meta-ofo-queue */ ++ skb_queue_walk_safe(&sk->sk_receive_queue, tmp1, tmp) { ++ tp->copied_seq = TCP_SKB_CB(tmp1)->end_seq; ++ mptcp_prepare_skb(tmp1, sk); ++ __skb_unlink(tmp1, &sk->sk_receive_queue); ++ /* MUST be done here, because fragstolen may be true later. ++ * Then, kfree_skb_partial will not account the memory. ++ */ ++ skb_orphan(tmp1); ++ ++ if (!mpcb->in_time_wait) /* In time-wait, do not receive data */ ++ tcp_data_queue_ofo(meta_sk, tmp1); ++ else ++ __kfree_skb(tmp1); ++ ++ if (!skb_queue_empty(&sk->sk_receive_queue) && ++ !before(TCP_SKB_CB(tmp)->seq, ++ tp->mptcp->map_subseq + tp->mptcp->map_data_len)) ++ break; ++ } ++ ++ /* Quick ACK if more 3/4 of the receive window is filled */ ++ if (after64(tp->mptcp->map_data_seq, ++ rcv_nxt64 + 3 * (tcp_receive_window_now(meta_tp) >> 2))) ++ tcp_enter_quickack_mode(sk, TCP_MAX_QUICKACKS); ++ ++ } else { ++ /* Ready for the meta-rcv-queue */ ++ skb_queue_walk_safe(&sk->sk_receive_queue, tmp1, tmp) { ++ int eaten = 0; ++ bool fragstolen = false; ++ u32 old_rcv_nxt = meta_tp->rcv_nxt; ++ ++ tp->copied_seq = TCP_SKB_CB(tmp1)->end_seq; ++ mptcp_prepare_skb(tmp1, sk); ++ __skb_unlink(tmp1, &sk->sk_receive_queue); ++ /* MUST be done here, because fragstolen may be true. ++ * Then, kfree_skb_partial will not account the memory. ++ */ ++ skb_orphan(tmp1); ++ ++ /* This segment has already been received */ ++ if (!after(TCP_SKB_CB(tmp1)->end_seq, meta_tp->rcv_nxt)) { ++ __kfree_skb(tmp1); ++ goto next; ++ } ++ ++ if (mpcb->in_time_wait) /* In time-wait, do not receive data */ ++ eaten = 1; ++ ++ if (!eaten) ++ eaten = tcp_queue_rcv(meta_sk, tmp1, &fragstolen); ++ ++ meta_tp->rcv_nxt = TCP_SKB_CB(tmp1)->end_seq; ++ ++ if (TCP_SKB_CB(tmp1)->tcp_flags & TCPHDR_FIN) ++ mptcp_fin(meta_sk); ++ ++ /* Check if this fills a gap in the ofo queue */ ++ if (!RB_EMPTY_ROOT(&meta_tp->out_of_order_queue)) ++ tcp_ofo_queue(meta_sk); ++ ++ mptcp_check_rcvseq_wrap(meta_tp, old_rcv_nxt); ++ ++ if (eaten) ++ kfree_skb_partial(tmp1, fragstolen); ++ ++ data_queued = true; ++next: ++ if (!skb_queue_empty(&sk->sk_receive_queue) && ++ !before(TCP_SKB_CB(tmp)->seq, ++ tp->mptcp->map_subseq + tp->mptcp->map_data_len)) ++ break; ++ } ++ } ++ ++ inet_csk(meta_sk)->icsk_ack.lrcvtime = tcp_jiffies32; ++ mptcp_reset_mapping(tp, old_copied_seq); ++ ++ return data_queued ? -1 : -2; ++} ++ ++void mptcp_data_ready(struct sock *sk) ++{ ++ struct sock *meta_sk = mptcp_meta_sk(sk); ++ struct sk_buff *skb, *tmp; ++ int queued = 0; ++ ++ tcp_mstamp_refresh(tcp_sk(meta_sk)); ++ ++ /* restart before the check, because mptcp_fin might have changed the ++ * state. ++ */ ++restart: ++ /* If the meta cannot receive data, there is no point in pushing data. ++ * If we are in time-wait, we may still be waiting for the final FIN. ++ * So, we should proceed with the processing. ++ */ ++ if (!mptcp_sk_can_recv(meta_sk) && !tcp_sk(sk)->mpcb->in_time_wait) { ++ skb_queue_purge(&sk->sk_receive_queue); ++ tcp_sk(sk)->copied_seq = tcp_sk(sk)->rcv_nxt; ++ goto exit; ++ } ++ ++ /* Iterate over all segments, detect their mapping (if we don't have ++ * one yet), validate them and push everything one level higher. ++ */ ++ skb_queue_walk_safe(&sk->sk_receive_queue, skb, tmp) { ++ int ret; ++ /* Pre-validation - e.g., early fallback */ ++ ret = mptcp_prevalidate_skb(sk, skb); ++ if (ret < 0) ++ goto restart; ++ else if (ret > 0) ++ break; ++ ++ /* Set the current mapping */ ++ ret = mptcp_detect_mapping(sk, skb); ++ if (ret < 0) ++ goto restart; ++ else if (ret > 0) ++ break; ++ ++ /* Validation */ ++ if (mptcp_validate_mapping(sk, skb) < 0) ++ goto restart; ++ ++ /* Push a level higher */ ++ ret = mptcp_queue_skb(sk); ++ if (ret < 0) { ++ if (ret == -1) ++ queued = ret; ++ goto restart; ++ } else if (ret == 0) { ++ continue; ++ } else { /* ret == 1 */ ++ break; ++ } ++ } ++ ++exit: ++ if (tcp_sk(sk)->close_it && sk->sk_state == TCP_FIN_WAIT2) { ++ tcp_send_ack(sk); ++ tcp_sk(sk)->ops->time_wait(sk, TCP_TIME_WAIT, 0); ++ } ++ ++ if (queued == -1 && !sock_flag(meta_sk, SOCK_DEAD)) ++ meta_sk->sk_data_ready(meta_sk); ++} ++ ++struct mp_join *mptcp_find_join(const struct sk_buff *skb) ++{ ++ const struct tcphdr *th = tcp_hdr(skb); ++ unsigned char *ptr; ++ int length = (th->doff * 4) - sizeof(struct tcphdr); ++ ++ /* Jump through the options to check whether JOIN is there */ ++ ptr = (unsigned char *)(th + 1); ++ while (length > 0) { ++ int opcode = *ptr++; ++ int opsize; ++ ++ switch (opcode) { ++ case TCPOPT_EOL: ++ return NULL; ++ case TCPOPT_NOP: /* Ref: RFC 793 section 3.1 */ ++ length--; ++ continue; ++ default: ++ opsize = *ptr++; ++ if (opsize < 2) /* "silly options" */ ++ return NULL; ++ if (opsize > length) ++ return NULL; /* don't parse partial options */ ++ if (opcode == TCPOPT_MPTCP && ++ ((struct mptcp_option *)(ptr - 2))->sub == MPTCP_SUB_JOIN) { ++ return (struct mp_join *)(ptr - 2); ++ } ++ ptr += opsize - 2; ++ length -= opsize; ++ } ++ } ++ return NULL; ++} ++ ++int mptcp_lookup_join(struct sk_buff *skb, struct inet_timewait_sock *tw) ++{ ++ struct sock *meta_sk; ++ u32 token; ++ bool meta_v4; ++ struct mp_join *join_opt = mptcp_find_join(skb); ++ if (!join_opt) ++ return 0; ++ ++ /* MPTCP structures were not initialized, so return error */ ++ if (mptcp_init_failed) ++ return -1; ++ ++ token = join_opt->u.syn.token; ++ meta_sk = mptcp_hash_find(dev_net(skb_dst(skb)->dev), token); ++ if (!meta_sk) { ++ MPTCP_INC_STATS(dev_net(skb_dst(skb)->dev), MPTCP_MIB_JOINNOTOKEN); ++ mptcp_debug("%s:mpcb not found:%x\n", __func__, token); ++ return -1; ++ } ++ ++ meta_v4 = meta_sk->sk_family == AF_INET; ++ if (meta_v4) { ++ if (skb->protocol == htons(ETH_P_IPV6)) { ++ mptcp_debug("SYN+MP_JOIN with IPV6 address on pure IPV4 meta\n"); ++ sock_put(meta_sk); /* Taken by mptcp_hash_find */ ++ return -1; ++ } ++ } else if (skb->protocol == htons(ETH_P_IP) && meta_sk->sk_ipv6only) { ++ mptcp_debug("SYN+MP_JOIN with IPV4 address on IPV6_V6ONLY meta\n"); ++ sock_put(meta_sk); /* Taken by mptcp_hash_find */ ++ return -1; ++ } ++ ++ /* Coming from time-wait-sock processing in tcp_v4_rcv. ++ * We have to deschedule it before continuing, because otherwise ++ * mptcp_v4_do_rcv will hit again on it inside tcp_v4_hnd_req. ++ */ ++ if (tw) ++ inet_twsk_deschedule_put(tw); ++ ++ /* OK, this is a new syn/join, let's create a new open request and ++ * send syn+ack ++ */ ++ if (skb->protocol == htons(ETH_P_IP)) { ++ tcp_v4_do_rcv(meta_sk, skb); ++#if IS_ENABLED(CONFIG_IPV6) ++ } else { ++ tcp_v6_do_rcv(meta_sk, skb); ++#endif /* CONFIG_IPV6 */ ++ } ++ sock_put(meta_sk); /* Taken by mptcp_hash_find */ ++ return 1; ++} ++ ++int mptcp_do_join_short(struct sk_buff *skb, ++ const struct mptcp_options_received *mopt, ++ struct net *net) ++{ ++ struct sock *meta_sk; ++ u32 token; ++ bool meta_v4; ++ ++ token = mopt->mptcp_rem_token; ++ meta_sk = mptcp_hash_find(net, token); ++ if (!meta_sk) { ++ MPTCP_INC_STATS(dev_net(skb_dst(skb)->dev), MPTCP_MIB_JOINNOTOKEN); ++ mptcp_debug("%s:mpcb not found:%x\n", __func__, token); ++ return -1; ++ } ++ ++ meta_v4 = meta_sk->sk_family == AF_INET; ++ if (meta_v4) { ++ if (skb->protocol == htons(ETH_P_IPV6)) { ++ mptcp_debug("SYN+MP_JOIN with IPV6 address on pure IPV4 meta\n"); ++ sock_put(meta_sk); /* Taken by mptcp_hash_find */ ++ return -1; ++ } ++ } else if (skb->protocol == htons(ETH_P_IP) && meta_sk->sk_ipv6only) { ++ mptcp_debug("SYN+MP_JOIN with IPV4 address on IPV6_V6ONLY meta\n"); ++ sock_put(meta_sk); /* Taken by mptcp_hash_find */ ++ return -1; ++ } ++ ++ /* OK, this is a new syn/join, let's create a new open request and ++ * send syn+ack ++ */ ++ ++ /* mptcp_v4_do_rcv tries to free the skb - we prevent this, as ++ * the skb will finally be freed by tcp_v4_do_rcv (where we are ++ * coming from) ++ */ ++ skb_get(skb); ++ if (skb->protocol == htons(ETH_P_IP)) { ++ tcp_v4_do_rcv(meta_sk, skb); ++#if IS_ENABLED(CONFIG_IPV6) ++ } else { /* IPv6 */ ++ tcp_v6_do_rcv(meta_sk, skb); ++#endif /* CONFIG_IPV6 */ ++ } ++ ++ sock_put(meta_sk); /* Taken by mptcp_hash_find */ ++ return 0; ++} ++ ++/** ++ * Equivalent of tcp_fin() for MPTCP ++ * Can be called only when the FIN is validly part ++ * of the data seqnum space. Not before when we get holes. ++ */ ++void mptcp_fin(struct sock *meta_sk) ++{ ++ struct sock *sk = NULL; ++ struct tcp_sock *meta_tp = tcp_sk(meta_sk); ++ struct mptcp_cb *mpcb = meta_tp->mpcb; ++ struct mptcp_tcp_sock *mptcp; ++ unsigned char state; ++ ++ mptcp_for_each_sub(mpcb, mptcp) { ++ struct sock *sk_it = mptcp_to_sock(mptcp); ++ ++ if (tcp_sk(sk_it)->mptcp->path_index == mpcb->dfin_path_index) { ++ sk = sk_it; ++ break; ++ } ++ } ++ ++ if (!sk || sk->sk_state == TCP_CLOSE) ++ sk = mptcp_select_ack_sock(meta_sk); ++ ++ inet_csk_schedule_ack(sk); ++ ++ if (!mpcb->in_time_wait) { ++ meta_sk->sk_shutdown |= RCV_SHUTDOWN; ++ sock_set_flag(meta_sk, SOCK_DONE); ++ state = meta_sk->sk_state; ++ } else { ++ state = mpcb->mptw_state; ++ } ++ ++ switch (state) { ++ case TCP_SYN_RECV: ++ case TCP_ESTABLISHED: ++ /* Move to CLOSE_WAIT */ ++ tcp_set_state(meta_sk, TCP_CLOSE_WAIT); ++ inet_csk(sk)->icsk_ack.pingpong = 1; ++ break; ++ ++ case TCP_CLOSE_WAIT: ++ case TCP_CLOSING: ++ /* Received a retransmission of the FIN, do ++ * nothing. ++ */ ++ break; ++ case TCP_LAST_ACK: ++ /* RFC793: Remain in the LAST-ACK state. */ ++ break; ++ ++ case TCP_FIN_WAIT1: ++ /* This case occurs when a simultaneous close ++ * happens, we must ack the received FIN and ++ * enter the CLOSING state. ++ */ ++ tcp_send_ack(sk); ++ tcp_set_state(meta_sk, TCP_CLOSING); ++ break; ++ case TCP_FIN_WAIT2: ++ /* Received a FIN -- send ACK and enter TIME_WAIT. */ ++ tcp_send_ack(sk); ++ meta_tp->ops->time_wait(meta_sk, TCP_TIME_WAIT, 0); ++ break; ++ default: ++ /* Only TCP_LISTEN and TCP_CLOSE are left, in these ++ * cases we should never reach this piece of code. ++ */ ++ pr_err("%s: Impossible, meta_sk->sk_state=%d\n", __func__, ++ meta_sk->sk_state); ++ break; ++ } ++ ++ /* It _is_ possible, that we have something out-of-order _after_ FIN. ++ * Probably, we should reset in this case. For now drop them. ++ */ ++ skb_rbtree_purge(&meta_tp->out_of_order_queue); ++ sk_mem_reclaim(meta_sk); ++ ++ if (!sock_flag(meta_sk, SOCK_DEAD)) { ++ meta_sk->sk_state_change(meta_sk); ++ ++ /* Do not send POLL_HUP for half duplex close. */ ++ if (meta_sk->sk_shutdown == SHUTDOWN_MASK || ++ meta_sk->sk_state == TCP_CLOSE) ++ sk_wake_async(meta_sk, SOCK_WAKE_WAITD, POLL_HUP); ++ else ++ sk_wake_async(meta_sk, SOCK_WAKE_WAITD, POLL_IN); ++ } ++ ++ return; ++} ++ ++/* Similar to tcp_xmit_retransmit_queue */ ++static void mptcp_xmit_retransmit_queue(struct sock *meta_sk) ++{ ++ struct tcp_sock *meta_tp = tcp_sk(meta_sk); ++ struct sk_buff *skb, *rtx_head; ++ ++ if (!meta_tp->packets_out) ++ return; ++ ++ skb = rtx_head = tcp_rtx_queue_head(meta_sk); ++ skb_rbtree_walk_from(skb) { ++ if (mptcp_retransmit_skb(meta_sk, skb)) ++ return; ++ ++ if (skb == rtx_head) ++ inet_csk_reset_xmit_timer(meta_sk, ICSK_TIME_RETRANS, ++ inet_csk(meta_sk)->icsk_rto, ++ TCP_RTO_MAX); ++ } ++} ++ ++static void mptcp_snd_una_update(struct tcp_sock *meta_tp, u32 data_ack) ++{ ++ u32 delta = data_ack - meta_tp->snd_una; ++ ++ sock_owned_by_me((struct sock *)meta_tp); ++ meta_tp->bytes_acked += delta; ++ meta_tp->snd_una = data_ack; ++} ++ ++static void mptcp_stop_subflow_chronos(struct sock *meta_sk, ++ const enum tcp_chrono type) ++{ ++ const struct mptcp_cb *mpcb = tcp_sk(meta_sk)->mpcb; ++ struct mptcp_tcp_sock *mptcp; ++ ++ mptcp_for_each_sub(mpcb, mptcp) { ++ struct sock *sk_it = mptcp_to_sock(mptcp); ++ ++ tcp_chrono_stop(sk_it, type); ++ } ++} ++ ++/* Handle the DATA_ACK */ ++static bool mptcp_process_data_ack(struct sock *sk, const struct sk_buff *skb) ++{ ++ struct sock *meta_sk = mptcp_meta_sk(sk); ++ struct tcp_sock *meta_tp = tcp_sk(meta_sk), *tp = tcp_sk(sk); ++ struct tcp_skb_cb *tcb = TCP_SKB_CB(skb); ++ u32 prior_snd_una = meta_tp->snd_una; ++ int prior_packets; ++ u32 nwin, data_ack, data_seq; ++ u16 data_len = 0; ++ ++ /* A valid packet came in - subflow is operational again */ ++ tp->pf = 0; ++ ++ /* Even if there is no data-ack, we stop retransmitting. ++ * Except if this is a SYN/ACK. Then it is just a retransmission ++ */ ++ if (tp->mptcp->pre_established && !tcp_hdr(skb)->syn) { ++ tp->mptcp->pre_established = 0; ++ sk_stop_timer(sk, &tp->mptcp->mptcp_ack_timer); ++ ++ if (meta_tp->mpcb->pm_ops->established_subflow) ++ meta_tp->mpcb->pm_ops->established_subflow(sk); ++ } ++ ++ /* If we are in infinite mapping mode, rx_opt.data_ack has been ++ * set by mptcp_clean_rtx_infinite. ++ */ ++ if (!(tcb->mptcp_flags & MPTCPHDR_ACK) && !tp->mpcb->infinite_mapping_snd) ++ return false; ++ ++ if (unlikely(!tp->mptcp->fully_established) && ++ tp->mptcp->snt_isn + 1 != TCP_SKB_CB(skb)->ack_seq) ++ /* As soon as a subflow-data-ack (not acking syn, thus snt_isn + 1) ++ * includes a data-ack, we are fully established ++ */ ++ mptcp_become_fully_estab(sk); ++ ++ /* After we did the subflow-only processing (stopping timer and marking ++ * subflow as established), check if we can proceed with MPTCP-level ++ * processing. ++ */ ++ if (meta_sk->sk_state == TCP_CLOSE) ++ return false; ++ ++ /* Get the data_seq */ ++ if (mptcp_is_data_seq(skb)) { ++ data_seq = tp->mptcp->rx_opt.data_seq; ++ data_len = tp->mptcp->rx_opt.data_len; ++ } else { ++ data_seq = meta_tp->snd_wl1; ++ } ++ ++ data_ack = tp->mptcp->rx_opt.data_ack; ++ ++ /* If the ack is older than previous acks ++ * then we can probably ignore it. ++ */ ++ if (before(data_ack, prior_snd_una)) ++ goto exit; ++ ++ /* If the ack includes data we haven't sent yet, discard ++ * this segment (RFC793 Section 3.9). ++ */ ++ if (after(data_ack, meta_tp->snd_nxt)) ++ goto exit; ++ ++ /* First valid DATA_ACK, we can stop sending the special MP_CAPABLE */ ++ tp->mpcb->send_mptcpv1_mpcapable = 0; ++ ++ /*** Now, update the window - inspired by tcp_ack_update_window ***/ ++ nwin = ntohs(tcp_hdr(skb)->window); ++ ++ if (likely(!tcp_hdr(skb)->syn)) ++ nwin <<= tp->rx_opt.snd_wscale; ++ ++ if (tcp_may_update_window(meta_tp, data_ack, data_seq, nwin)) { ++ tcp_update_wl(meta_tp, data_seq); ++ ++ /* Draft v09, Section 3.3.5: ++ * [...] It should only update its local receive window values ++ * when the largest sequence number allowed (i.e. DATA_ACK + ++ * receive window) increases. [...] ++ */ ++ if (meta_tp->snd_wnd != nwin && ++ !before(data_ack + nwin, tcp_wnd_end(meta_tp))) { ++ meta_tp->snd_wnd = nwin; ++ ++ if (nwin > meta_tp->max_window) ++ meta_tp->max_window = nwin; ++ } ++ } ++ /*** Done, update the window ***/ ++ ++ /* We passed data and got it acked, remove any soft error ++ * log. Something worked... ++ */ ++ sk->sk_err_soft = 0; ++ inet_csk(meta_sk)->icsk_probes_out = 0; ++ meta_tp->rcv_tstamp = tcp_jiffies32; ++ prior_packets = meta_tp->packets_out; ++ if (!prior_packets) ++ goto no_queue; ++ ++ mptcp_snd_una_update(meta_tp, data_ack); ++ ++ mptcp_clean_rtx_queue(meta_sk, prior_snd_una); ++ ++ /* We are in loss-state, and something got acked, retransmit the whole ++ * queue now! ++ */ ++ if (inet_csk(meta_sk)->icsk_ca_state == TCP_CA_Loss && ++ after(data_ack, prior_snd_una)) { ++ mptcp_xmit_retransmit_queue(meta_sk); ++ inet_csk(meta_sk)->icsk_ca_state = TCP_CA_Open; ++ } ++ ++ /* Simplified version of tcp_new_space, because the snd-buffer ++ * is handled by all the subflows. ++ */ ++ if (sock_flag(meta_sk, SOCK_QUEUE_SHRUNK)) { ++ sock_reset_flag(meta_sk, SOCK_QUEUE_SHRUNK); ++ if (meta_sk->sk_socket && ++ test_bit(SOCK_NOSPACE, &meta_sk->sk_socket->flags)) ++ meta_sk->sk_write_space(meta_sk); ++ ++ if (meta_sk->sk_socket && ++ !test_bit(SOCK_NOSPACE, &meta_sk->sk_socket->flags)) { ++ tcp_chrono_stop(meta_sk, TCP_CHRONO_SNDBUF_LIMITED); ++ mptcp_stop_subflow_chronos(meta_sk, ++ TCP_CHRONO_SNDBUF_LIMITED); ++ } ++ } ++ ++ if (meta_sk->sk_state != TCP_ESTABLISHED) { ++ int ret = mptcp_rcv_state_process(meta_sk, sk, skb, data_seq, data_len); ++ ++ if (ret < 0) ++ return true; ++ else if (ret > 0) ++ return false; ++ } ++ ++exit: ++ mptcp_push_pending_frames(meta_sk); ++ ++ return false; ++ ++no_queue: ++ if (tcp_send_head(meta_sk)) ++ tcp_ack_probe(meta_sk); ++ ++ mptcp_push_pending_frames(meta_sk); ++ ++ return false; ++} ++ ++void mptcp_clean_rtx_infinite(const struct sk_buff *skb, struct sock *sk) ++{ ++ struct tcp_sock *tp = tcp_sk(sk), *meta_tp = tcp_sk(mptcp_meta_sk(sk)); ++ ++ if (!tp->mpcb->infinite_mapping_snd) ++ return; ++ ++ /* The difference between both write_seq's represents the offset between ++ * data-sequence and subflow-sequence. As we are infinite, this must ++ * match. ++ * ++ * Thus, from this difference we can infer the meta snd_una. ++ */ ++ tp->mptcp->rx_opt.data_ack = meta_tp->snd_nxt - tp->snd_nxt + ++ tp->snd_una; ++ ++ mptcp_process_data_ack(sk, skb); ++} ++ ++/**** static functions used by mptcp_parse_options */ ++ ++static void mptcp_send_reset_rem_id(const struct mptcp_cb *mpcb, u8 rem_id) ++{ ++ struct mptcp_tcp_sock *mptcp; ++ struct hlist_node *tmp; ++ ++ mptcp_for_each_sub_safe(mpcb, mptcp, tmp) { ++ struct sock *sk_it = mptcp_to_sock(mptcp); ++ ++ if (tcp_sk(sk_it)->mptcp->rem_id == rem_id) { ++ mptcp_reinject_data(sk_it, 0); ++ mptcp_send_reset(sk_it); ++ } ++ } ++} ++ ++static inline bool is_valid_addropt_opsize(u8 mptcp_ver, ++ struct mp_add_addr *mpadd, ++ int opsize) ++{ ++#if IS_ENABLED(CONFIG_IPV6) ++ if (mptcp_ver < MPTCP_VERSION_1 && mpadd->u_bit.v0.ipver == 6) { ++ return opsize == MPTCP_SUB_LEN_ADD_ADDR6 || ++ opsize == MPTCP_SUB_LEN_ADD_ADDR6 + 2; ++ } ++ if (mptcp_ver >= MPTCP_VERSION_1) ++ return opsize == MPTCP_SUB_LEN_ADD_ADDR6_VER1 || ++ opsize == MPTCP_SUB_LEN_ADD_ADDR6_VER1 + 2 || ++ opsize == MPTCP_SUB_LEN_ADD_ADDR4_VER1 || ++ opsize == MPTCP_SUB_LEN_ADD_ADDR4_VER1 + 2; ++#endif ++ if (mptcp_ver < MPTCP_VERSION_1 && mpadd->u_bit.v0.ipver == 4) { ++ return opsize == MPTCP_SUB_LEN_ADD_ADDR4 || ++ opsize == MPTCP_SUB_LEN_ADD_ADDR4 + 2; ++ } ++ if (mptcp_ver >= MPTCP_VERSION_1) { ++ return opsize == MPTCP_SUB_LEN_ADD_ADDR4_VER1 || ++ opsize == MPTCP_SUB_LEN_ADD_ADDR4_VER1 + 2; ++ } ++ return false; ++} ++ ++void mptcp_parse_options(const uint8_t *ptr, int opsize, ++ struct mptcp_options_received *mopt, ++ const struct sk_buff *skb, ++ struct tcp_sock *tp) ++{ ++ const struct mptcp_option *mp_opt = (struct mptcp_option *)ptr; ++ const struct tcphdr *th = tcp_hdr(skb); ++ ++ /* If the socket is mp-capable we would have a mopt. */ ++ if (!mopt) ++ return; ++ ++ switch (mp_opt->sub) { ++ case MPTCP_SUB_CAPABLE: ++ { ++ const struct mp_capable *mpcapable = (struct mp_capable *)ptr; ++ ++ if (mpcapable->ver == MPTCP_VERSION_0 && ++ ((th->syn && opsize != MPTCP_SUB_LEN_CAPABLE_SYN) || ++ (!th->syn && th->ack && opsize != MPTCP_SUB_LEN_CAPABLE_ACK))) { ++ mptcp_debug("%s: mp_capable v0: bad option size %d\n", ++ __func__, opsize); ++ break; ++ } ++ ++ if (mpcapable->ver == MPTCP_VERSION_1 && ++ ((th->syn && !th->ack && opsize != MPTCPV1_SUB_LEN_CAPABLE_SYN) || ++ (th->syn && th->ack && opsize != MPTCPV1_SUB_LEN_CAPABLE_SYNACK) || ++ (!th->syn && th->ack && opsize != MPTCPV1_SUB_LEN_CAPABLE_ACK && ++ opsize != MPTCPV1_SUB_LEN_CAPABLE_DATA && ++ opsize != MPTCPV1_SUB_LEN_CAPABLE_DATA_CSUM))) { ++ mptcp_debug("%s: mp_capable v1: bad option size %d\n", ++ __func__, opsize); ++ break; ++ } ++ ++ /* MPTCP-RFC 6824: ++ * "If receiving a message with the 'B' flag set to 1, and this ++ * is not understood, then this SYN MUST be silently ignored; ++ */ ++ if (mpcapable->b) { ++ mopt->drop_me = 1; ++ break; ++ } ++ ++ /* MPTCP-RFC 6824: ++ * "An implementation that only supports this method MUST set ++ * bit "H" to 1, and bits "C" through "G" to 0." ++ */ ++ if (!mpcapable->h) ++ break; ++ ++ mopt->saw_mpc = 1; ++ mopt->dss_csum = sysctl_mptcp_checksum || mpcapable->a; ++ ++ if (mpcapable->ver == MPTCP_VERSION_0) { ++ if (opsize == MPTCP_SUB_LEN_CAPABLE_SYN) ++ mopt->mptcp_sender_key = mpcapable->sender_key; ++ ++ if (opsize == MPTCP_SUB_LEN_CAPABLE_ACK) { ++ mopt->mptcp_sender_key = mpcapable->sender_key; ++ mopt->mptcp_receiver_key = mpcapable->receiver_key; ++ } ++ } else if (mpcapable->ver == MPTCP_VERSION_1) { ++ if (opsize == MPTCPV1_SUB_LEN_CAPABLE_SYNACK) ++ mopt->mptcp_sender_key = mpcapable->sender_key; ++ ++ if (opsize == MPTCPV1_SUB_LEN_CAPABLE_ACK) { ++ mopt->mptcp_sender_key = mpcapable->sender_key; ++ mopt->mptcp_receiver_key = mpcapable->receiver_key; ++ } ++ ++ if (opsize == MPTCPV1_SUB_LEN_CAPABLE_DATA || ++ opsize == MPTCPV1_SUB_LEN_CAPABLE_DATA_CSUM) { ++ mopt->mptcp_sender_key = mpcapable->sender_key; ++ mopt->mptcp_receiver_key = mpcapable->receiver_key; ++ ++ TCP_SKB_CB(skb)->mptcp_flags |= MPTCPHDR_MPC_DATA; ++ ++ ptr += sizeof(struct mp_capable); ++ TCP_SKB_CB(skb)->dss_off = (ptr - skb_transport_header(skb)); ++ ++ /* Is a check-sum present? */ ++ if (opsize == MPTCPV1_SUB_LEN_CAPABLE_DATA_CSUM) ++ TCP_SKB_CB(skb)->mptcp_flags |= MPTCPHDR_DSS_CSUM; ++ } ++ } ++ ++ mopt->mptcp_ver = mpcapable->ver; ++ break; ++ } ++ case MPTCP_SUB_JOIN: ++ { ++ const struct mp_join *mpjoin = (struct mp_join *)ptr; ++ ++ if (opsize != MPTCP_SUB_LEN_JOIN_SYN && ++ opsize != MPTCP_SUB_LEN_JOIN_SYNACK && ++ opsize != MPTCP_SUB_LEN_JOIN_ACK) { ++ mptcp_debug("%s: mp_join: bad option size %d\n", ++ __func__, opsize); ++ break; ++ } ++ ++ /* saw_mpc must be set, because in tcp_check_req we assume that ++ * it is set to support falling back to reg. TCP if a rexmitted ++ * SYN has no MP_CAPABLE or MP_JOIN ++ */ ++ switch (opsize) { ++ case MPTCP_SUB_LEN_JOIN_SYN: ++ mopt->is_mp_join = 1; ++ mopt->saw_mpc = 1; ++ mopt->low_prio = mpjoin->b; ++ mopt->rem_id = mpjoin->addr_id; ++ mopt->mptcp_rem_token = mpjoin->u.syn.token; ++ mopt->mptcp_recv_nonce = mpjoin->u.syn.nonce; ++ break; ++ case MPTCP_SUB_LEN_JOIN_SYNACK: ++ mopt->saw_mpc = 1; ++ mopt->low_prio = mpjoin->b; ++ mopt->rem_id = mpjoin->addr_id; ++ mopt->mptcp_recv_tmac = mpjoin->u.synack.mac; ++ mopt->mptcp_recv_nonce = mpjoin->u.synack.nonce; ++ break; ++ case MPTCP_SUB_LEN_JOIN_ACK: ++ mopt->saw_mpc = 1; ++ mopt->join_ack = 1; ++ memcpy(mopt->mptcp_recv_mac, mpjoin->u.ack.mac, 20); ++ break; ++ } ++ break; ++ } ++ case MPTCP_SUB_DSS: ++ { ++ const struct mp_dss *mdss = (struct mp_dss *)ptr; ++ struct tcp_skb_cb *tcb = TCP_SKB_CB(skb); ++ ++ /* We check opsize for the csum and non-csum case. We do this, ++ * because the draft says that the csum SHOULD be ignored if ++ * it has not been negotiated in the MP_CAPABLE but still is ++ * present in the data. ++ * ++ * It will get ignored later in mptcp_queue_skb. ++ */ ++ if (opsize != mptcp_sub_len_dss(mdss, 0) && ++ opsize != mptcp_sub_len_dss(mdss, 1)) { ++ mptcp_debug("%s: mp_dss: bad option size %d\n", ++ __func__, opsize); ++ break; ++ } ++ ++ ptr += 4; ++ ++ if (mdss->A) { ++ tcb->mptcp_flags |= MPTCPHDR_ACK; ++ ++ if (mdss->a) { ++ mopt->data_ack = (u32) get_unaligned_be64(ptr); ++ ptr += MPTCP_SUB_LEN_ACK_64; ++ } else { ++ mopt->data_ack = get_unaligned_be32(ptr); ++ ptr += MPTCP_SUB_LEN_ACK; ++ } ++ } ++ ++ tcb->dss_off = (ptr - skb_transport_header(skb)); ++ ++ if (mdss->M) { ++ if (mdss->m) { ++ u64 data_seq64 = get_unaligned_be64(ptr); ++ ++ tcb->mptcp_flags |= MPTCPHDR_SEQ64_SET; ++ mopt->data_seq = (u32) data_seq64; ++ ++ ptr += 12; /* 64-bit dseq + subseq */ ++ } else { ++ mopt->data_seq = get_unaligned_be32(ptr); ++ ptr += 8; /* 32-bit dseq + subseq */ ++ } ++ mopt->data_len = get_unaligned_be16(ptr); ++ ++ tcb->mptcp_flags |= MPTCPHDR_SEQ; ++ ++ /* Is a check-sum present? */ ++ if (opsize == mptcp_sub_len_dss(mdss, 1)) ++ tcb->mptcp_flags |= MPTCPHDR_DSS_CSUM; ++ ++ /* DATA_FIN only possible with DSS-mapping */ ++ if (mdss->F) ++ tcb->mptcp_flags |= MPTCPHDR_FIN; ++ } ++ ++ break; ++ } ++ case MPTCP_SUB_ADD_ADDR: ++ { ++ struct mp_add_addr *mpadd = (struct mp_add_addr *)ptr; ++ ++ /* If tcp_sock is not available, MPTCP version can't be ++ * retrieved and ADD_ADDR opsize validation is not possible. ++ */ ++ if (!tp || !tp->mpcb) ++ break; ++ ++ if (!is_valid_addropt_opsize(tp->mpcb->mptcp_ver, ++ mpadd, opsize)) { ++ mptcp_debug("%s: mp_add_addr: bad option size %d\n", ++ __func__, opsize); ++ break; ++ } ++ ++ /* We have to manually parse the options if we got two of them. */ ++ if (mopt->saw_add_addr) { ++ mopt->more_add_addr = 1; ++ break; ++ } ++ mopt->saw_add_addr = 1; ++ mopt->add_addr_ptr = ptr; ++ break; ++ } ++ case MPTCP_SUB_REMOVE_ADDR: ++ if ((opsize - MPTCP_SUB_LEN_REMOVE_ADDR) < 0) { ++ mptcp_debug("%s: mp_remove_addr: bad option size %d\n", ++ __func__, opsize); ++ break; ++ } ++ ++ if (mopt->saw_rem_addr) { ++ mopt->more_rem_addr = 1; ++ break; ++ } ++ mopt->saw_rem_addr = 1; ++ mopt->rem_addr_ptr = ptr; ++ break; ++ case MPTCP_SUB_PRIO: ++ { ++ const struct mp_prio *mpprio = (struct mp_prio *)ptr; ++ ++ if (opsize != MPTCP_SUB_LEN_PRIO && ++ opsize != MPTCP_SUB_LEN_PRIO_ADDR) { ++ mptcp_debug("%s: mp_prio: bad option size %d\n", ++ __func__, opsize); ++ break; ++ } ++ ++ mopt->saw_low_prio = 1; ++ mopt->low_prio = mpprio->b; ++ ++ if (opsize == MPTCP_SUB_LEN_PRIO_ADDR) { ++ mopt->saw_low_prio = 2; ++ mopt->prio_addr_id = mpprio->addr_id; ++ } ++ break; ++ } ++ case MPTCP_SUB_FAIL: ++ if (opsize != MPTCP_SUB_LEN_FAIL) { ++ mptcp_debug("%s: mp_fail: bad option size %d\n", ++ __func__, opsize); ++ break; ++ } ++ mopt->mp_fail = 1; ++ break; ++ case MPTCP_SUB_FCLOSE: ++ if (opsize != MPTCP_SUB_LEN_FCLOSE) { ++ mptcp_debug("%s: mp_fclose: bad option size %d\n", ++ __func__, opsize); ++ break; ++ } ++ ++ mopt->mp_fclose = 1; ++ mopt->mptcp_sender_key = ((struct mp_fclose *)ptr)->key; ++ ++ break; ++ default: ++ mptcp_debug("%s: Received unkown subtype: %d\n", ++ __func__, mp_opt->sub); ++ break; ++ } ++} ++ ++/** Parse only MPTCP options */ ++void tcp_parse_mptcp_options(const struct sk_buff *skb, ++ struct mptcp_options_received *mopt) ++{ ++ const struct tcphdr *th = tcp_hdr(skb); ++ int length = (th->doff * 4) - sizeof(struct tcphdr); ++ const unsigned char *ptr = (const unsigned char *)(th + 1); ++ ++ while (length > 0) { ++ int opcode = *ptr++; ++ int opsize; ++ ++ switch (opcode) { ++ case TCPOPT_EOL: ++ return; ++ case TCPOPT_NOP: /* Ref: RFC 793 section 3.1 */ ++ length--; ++ continue; ++ default: ++ opsize = *ptr++; ++ if (opsize < 2) /* "silly options" */ ++ return; ++ if (opsize > length) ++ return; /* don't parse partial options */ ++ if (opcode == TCPOPT_MPTCP) ++ mptcp_parse_options(ptr - 2, opsize, mopt, skb, NULL); ++ } ++ ptr += opsize - 2; ++ length -= opsize; ++ } ++} ++ ++bool mptcp_check_rtt(const struct tcp_sock *tp, int time) ++{ ++ struct mptcp_cb *mpcb = tp->mpcb; ++ struct mptcp_tcp_sock *mptcp; ++ u32 rtt_max = 0; ++ ++ /* In MPTCP, we take the max delay across all flows, ++ * in order to take into account meta-reordering buffers. ++ */ ++ mptcp_for_each_sub(mpcb, mptcp) { ++ struct sock *sk = mptcp_to_sock(mptcp); ++ ++ if (!mptcp_sk_can_recv(sk)) ++ continue; ++ ++ if (rtt_max < tcp_sk(sk)->rcv_rtt_est.rtt_us) ++ rtt_max = tcp_sk(sk)->rcv_rtt_est.rtt_us; ++ } ++ if (time < (rtt_max >> 3) || !rtt_max) ++ return true; ++ ++ return false; ++} ++ ++static void mptcp_handle_add_addr(const unsigned char *ptr, struct sock *sk) ++{ ++ struct mp_add_addr *mpadd = (struct mp_add_addr *)ptr; ++ struct mptcp_cb *mpcb = tcp_sk(sk)->mpcb; ++ union inet_addr addr; ++ sa_family_t family; ++ __be16 port = 0; ++ bool is_v4; ++ ++ if (mpcb->mptcp_ver < MPTCP_VERSION_1) { ++ is_v4 = mpadd->u_bit.v0.ipver == 4; ++ } else { ++ is_v4 = mpadd->len == MPTCP_SUB_LEN_ADD_ADDR4_VER1 || ++ mpadd->len == MPTCP_SUB_LEN_ADD_ADDR4_VER1 + 2; ++ ++ /* TODO: support ADD_ADDRv1 retransmissions */ ++ if (mpadd->u_bit.v1.echo) ++ return; ++ } ++ ++ if (is_v4) { ++ u8 hash_mac_check[SHA256_DIGEST_SIZE]; ++ __be16 hmacport = 0; ++ char *recv_hmac; ++ ++ if (mpcb->mptcp_ver < MPTCP_VERSION_1) ++ goto skip_hmac_v4; ++ ++ recv_hmac = (char *)mpadd->u.v4.mac; ++ if (mpadd->len == MPTCP_SUB_LEN_ADD_ADDR4_VER1) { ++ recv_hmac -= sizeof(mpadd->u.v4.port); ++ } else if (mpadd->len == MPTCP_SUB_LEN_ADD_ADDR4_VER1 + 2) { ++ hmacport = mpadd->u.v4.port; ++ } ++ mptcp_hmac(mpcb->mptcp_ver, (u8 *)&mpcb->mptcp_rem_key, ++ (u8 *)&mpcb->mptcp_loc_key, hash_mac_check, 3, ++ 1, (u8 *)&mpadd->addr_id, ++ 4, (u8 *)&mpadd->u.v4.addr.s_addr, ++ 2, (u8 *)&hmacport); ++ if (memcmp(&hash_mac_check[SHA256_DIGEST_SIZE - sizeof(u64)], recv_hmac, 8) != 0) ++ /* ADD_ADDR2 discarded */ ++ return; ++skip_hmac_v4: ++ if ((mpcb->mptcp_ver == MPTCP_VERSION_0 && ++ mpadd->len == MPTCP_SUB_LEN_ADD_ADDR4 + 2) || ++ (mpcb->mptcp_ver == MPTCP_VERSION_1 && ++ mpadd->len == MPTCP_SUB_LEN_ADD_ADDR4_VER1 + 2)) ++ port = mpadd->u.v4.port; ++ family = AF_INET; ++ addr.in = mpadd->u.v4.addr; ++#if IS_ENABLED(CONFIG_IPV6) ++ } else { ++ u8 hash_mac_check[SHA256_DIGEST_SIZE]; ++ __be16 hmacport = 0; ++ char *recv_hmac; ++ ++ if (mpcb->mptcp_ver < MPTCP_VERSION_1) ++ goto skip_hmac_v6; ++ ++ recv_hmac = (char *)mpadd->u.v6.mac; ++ if (mpadd->len == MPTCP_SUB_LEN_ADD_ADDR6_VER1) { ++ recv_hmac -= sizeof(mpadd->u.v6.port); ++ } else if (mpadd->len == MPTCP_SUB_LEN_ADD_ADDR6_VER1 + 2) { ++ hmacport = mpadd->u.v6.port; ++ } ++ mptcp_hmac(mpcb->mptcp_ver, (u8 *)&mpcb->mptcp_rem_key, ++ (u8 *)&mpcb->mptcp_loc_key, hash_mac_check, 3, ++ 1, (u8 *)&mpadd->addr_id, ++ 16, (u8 *)&mpadd->u.v6.addr.s6_addr, ++ 2, (u8 *)&hmacport); ++ if (memcmp(&hash_mac_check[SHA256_DIGEST_SIZE - sizeof(u64)], recv_hmac, 8) != 0) ++ /* ADD_ADDR2 discarded */ ++ return; ++skip_hmac_v6: ++ if ((mpcb->mptcp_ver == MPTCP_VERSION_0 && ++ mpadd->len == MPTCP_SUB_LEN_ADD_ADDR6 + 2) || ++ (mpcb->mptcp_ver == MPTCP_VERSION_1 && ++ mpadd->len == MPTCP_SUB_LEN_ADD_ADDR6_VER1 + 2)) ++ port = mpadd->u.v6.port; ++ family = AF_INET6; ++ addr.in6 = mpadd->u.v6.addr; ++#endif /* CONFIG_IPV6 */ ++ } ++ ++ if (mpcb->pm_ops->add_raddr) ++ mpcb->pm_ops->add_raddr(mpcb, &addr, family, port, mpadd->addr_id); ++ ++ MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_ADDADDRRX); ++} ++ ++static void mptcp_handle_rem_addr(const unsigned char *ptr, struct sock *sk) ++{ ++ struct mp_remove_addr *mprem = (struct mp_remove_addr *)ptr; ++ int i; ++ u8 rem_id; ++ struct mptcp_cb *mpcb = tcp_sk(sk)->mpcb; ++ ++ for (i = 0; i <= mprem->len - MPTCP_SUB_LEN_REMOVE_ADDR; i++) { ++ rem_id = (&mprem->addrs_id)[i]; ++ ++ if (mpcb->pm_ops->rem_raddr) ++ mpcb->pm_ops->rem_raddr(mpcb, rem_id); ++ mptcp_send_reset_rem_id(mpcb, rem_id); ++ ++ MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_REMADDRSUB); ++ } ++ ++ MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_REMADDRRX); ++} ++ ++static void mptcp_parse_addropt(const struct sk_buff *skb, struct sock *sk) ++{ ++ struct tcphdr *th = tcp_hdr(skb); ++ unsigned char *ptr; ++ int length = (th->doff * 4) - sizeof(struct tcphdr); ++ ++ /* Jump through the options to check whether ADD_ADDR is there */ ++ ptr = (unsigned char *)(th + 1); ++ while (length > 0) { ++ int opcode = *ptr++; ++ int opsize; ++ ++ switch (opcode) { ++ case TCPOPT_EOL: ++ return; ++ case TCPOPT_NOP: ++ length--; ++ continue; ++ default: ++ opsize = *ptr++; ++ if (opsize < 2) ++ return; ++ if (opsize > length) ++ return; /* don't parse partial options */ ++ if (opcode == TCPOPT_MPTCP && ++ ((struct mptcp_option *)ptr)->sub == MPTCP_SUB_ADD_ADDR) { ++ u8 mptcp_ver = tcp_sk(sk)->mpcb->mptcp_ver; ++ struct mp_add_addr *mpadd = (struct mp_add_addr *)ptr; ++ ++ if (!is_valid_addropt_opsize(mptcp_ver, mpadd, ++ opsize)) ++ goto cont; ++ ++ mptcp_handle_add_addr(ptr, sk); ++ } ++ if (opcode == TCPOPT_MPTCP && ++ ((struct mptcp_option *)ptr)->sub == MPTCP_SUB_REMOVE_ADDR) { ++ if ((opsize - MPTCP_SUB_LEN_REMOVE_ADDR) < 0) ++ goto cont; ++ ++ mptcp_handle_rem_addr(ptr, sk); ++ } ++cont: ++ ptr += opsize - 2; ++ length -= opsize; ++ } ++ } ++ return; ++} ++ ++static bool mptcp_mp_fastclose_rcvd(struct sock *sk) ++{ ++ struct mptcp_tcp_sock *mptcp = tcp_sk(sk)->mptcp; ++ struct mptcp_cb *mpcb = tcp_sk(sk)->mpcb; ++ ++ if (likely(!mptcp->rx_opt.mp_fclose)) ++ return false; ++ ++ MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_FASTCLOSERX); ++ mptcp->rx_opt.mp_fclose = 0; ++ if (mptcp->rx_opt.mptcp_sender_key != mpcb->mptcp_loc_key) ++ return false; ++ ++ mptcp_sub_force_close_all(mpcb, NULL); ++ ++ tcp_reset(mptcp_meta_sk(sk)); ++ ++ return true; ++} ++ ++static void mptcp_mp_fail_rcvd(struct sock *sk, const struct tcphdr *th) ++{ ++ struct mptcp_tcp_sock *mptcp = tcp_sk(sk)->mptcp; ++ struct sock *meta_sk = mptcp_meta_sk(sk); ++ struct mptcp_cb *mpcb = tcp_sk(sk)->mpcb; ++ ++ MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_MPFAILRX); ++ mptcp->rx_opt.mp_fail = 0; ++ ++ if (!th->rst && !mpcb->infinite_mapping_snd) { ++ mpcb->send_infinite_mapping = 1; ++ ++ mptcp_restart_sending(meta_sk); ++ ++ mptcp_fallback_close(mpcb, sk); ++ } ++} ++ ++static inline void mptcp_path_array_check(struct sock *meta_sk) ++{ ++ struct mptcp_cb *mpcb = tcp_sk(meta_sk)->mpcb; ++ ++ if (unlikely(mpcb->list_rcvd)) { ++ mpcb->list_rcvd = 0; ++ if (mpcb->pm_ops->new_remote_address) ++ mpcb->pm_ops->new_remote_address(meta_sk); ++ } ++} ++ ++bool mptcp_handle_options(struct sock *sk, const struct tcphdr *th, ++ const struct sk_buff *skb) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct mptcp_options_received *mopt = &tp->mptcp->rx_opt; ++ struct mptcp_cb *mpcb = tp->mpcb; ++ ++ if (tp->mpcb->infinite_mapping_rcv || tp->mpcb->infinite_mapping_snd) ++ return false; ++ ++ if (mptcp_mp_fastclose_rcvd(sk)) ++ return true; ++ ++ if (sk->sk_state == TCP_RST_WAIT && !th->rst) ++ return true; ++ ++ if (mopt->saw_mpc && !tp->mpcb->rem_key_set) ++ mptcp_initialize_recv_vars(mptcp_meta_tp(tp), tp->mpcb, ++ mopt->mptcp_sender_key); ++ ++ if (unlikely(mopt->mp_fail)) ++ mptcp_mp_fail_rcvd(sk, th); ++ ++ /* RFC 6824, Section 3.3: ++ * If a checksum is not present when its use has been negotiated, the ++ * receiver MUST close the subflow with a RST as it is considered broken. ++ */ ++ if ((mptcp_is_data_seq(skb) || mptcp_is_data_mpcapable(skb)) && ++ tp->mpcb->dss_csum && ++ !(TCP_SKB_CB(skb)->mptcp_flags & MPTCPHDR_DSS_CSUM)) { ++ mptcp_send_reset(sk); ++ return true; ++ } ++ ++ /* We have to acknowledge retransmissions of the third ++ * ack. ++ */ ++ if (mopt->join_ack) { ++ tcp_send_delayed_ack(sk); ++ mopt->join_ack = 0; ++ } ++ ++ if (mopt->saw_add_addr || mopt->saw_rem_addr) { ++ if (mopt->more_add_addr || mopt->more_rem_addr) { ++ mptcp_parse_addropt(skb, sk); ++ } else { ++ if (mopt->saw_add_addr) ++ mptcp_handle_add_addr(mopt->add_addr_ptr, sk); ++ if (mopt->saw_rem_addr) ++ mptcp_handle_rem_addr(mopt->rem_addr_ptr, sk); ++ } ++ ++ mopt->more_add_addr = 0; ++ mopt->saw_add_addr = 0; ++ mopt->more_rem_addr = 0; ++ mopt->saw_rem_addr = 0; ++ } ++ if (mopt->saw_low_prio) { ++ if (mopt->saw_low_prio == 1) { ++ tp->mptcp->rcv_low_prio = mopt->low_prio; ++ if (mpcb->pm_ops->prio_changed) ++ mpcb->pm_ops->prio_changed(sk, mopt->low_prio); ++ } else { ++ struct mptcp_tcp_sock *mptcp; ++ ++ mptcp_for_each_sub(tp->mpcb, mptcp) { ++ if (mptcp->rem_id == mopt->prio_addr_id) { ++ mptcp->rcv_low_prio = mopt->low_prio; ++ if (mpcb->pm_ops->prio_changed) ++ mpcb->pm_ops->prio_changed(sk, ++ mopt->low_prio); ++ } ++ } ++ } ++ mopt->saw_low_prio = 0; ++ } ++ ++ if (mptcp_process_data_ack(sk, skb)) ++ return true; ++ ++ mptcp_path_array_check(mptcp_meta_sk(sk)); ++ /* Socket may have been mp_killed by a REMOVE_ADDR */ ++ if (tp->mp_killed) ++ return true; ++ ++ return false; ++} ++ ++static void _mptcp_rcv_synsent_fastopen(struct sock *meta_sk, ++ struct sk_buff *skb, bool rtx_queue) ++{ ++ struct tcp_sock *meta_tp = tcp_sk(meta_sk); ++ struct tcp_sock *master_tp = tcp_sk(meta_tp->mpcb->master_sk); ++ u32 new_mapping = meta_tp->write_seq - master_tp->snd_una; ++ ++ /* If the server only acknowledges partially the data sent in ++ * the SYN, we need to trim the acknowledged part because ++ * we don't want to retransmit this already received data. ++ * When we reach this point, tcp_ack() has already cleaned up ++ * fully acked segments. However, tcp trims partially acked ++ * segments only when retransmitting. Since MPTCP comes into ++ * play only now, we will fake an initial transmit, and ++ * retransmit_skb() will not be called. The following fragment ++ * comes from __tcp_retransmit_skb(). ++ */ ++ if (before(TCP_SKB_CB(skb)->seq, master_tp->snd_una)) { ++ BUG_ON(before(TCP_SKB_CB(skb)->end_seq, master_tp->snd_una)); ++ /* tcp_trim_head can only returns ENOMEM if skb is ++ * cloned. It is not the case here (see ++ * tcp_send_syn_data). ++ */ ++ BUG_ON(tcp_trim_head(meta_sk, skb, master_tp->snd_una - ++ TCP_SKB_CB(skb)->seq)); ++ } ++ ++ TCP_SKB_CB(skb)->seq += new_mapping; ++ TCP_SKB_CB(skb)->end_seq += new_mapping; ++ TCP_SKB_CB(skb)->sacked = 0; ++ ++ list_del(&skb->tcp_tsorted_anchor); ++ ++ if (rtx_queue) ++ tcp_rtx_queue_unlink(skb, meta_sk); ++ ++ INIT_LIST_HEAD(&skb->tcp_tsorted_anchor); ++ ++ if (rtx_queue) ++ tcp_add_write_queue_tail(meta_sk, skb); ++} ++ ++/* In case of fastopen, some data can already be in the write queue. ++ * We need to update the sequence number of the segments as they ++ * were initially TCP sequence numbers. ++ */ ++static void mptcp_rcv_synsent_fastopen(struct sock *meta_sk) ++{ ++ struct tcp_sock *meta_tp = tcp_sk(meta_sk); ++ struct tcp_sock *master_tp = tcp_sk(meta_tp->mpcb->master_sk); ++ struct sk_buff *skb_write_head, *skb_rtx_head, *tmp; ++ ++ skb_write_head = tcp_write_queue_head(meta_sk); ++ skb_rtx_head = tcp_rtx_queue_head(meta_sk); ++ ++ if (!(skb_write_head || skb_rtx_head)) ++ return; ++ ++ /* There should only be one skb in {write, rtx} queue: the data not ++ * acknowledged in the SYN+ACK. In this case, we need to map ++ * this data to data sequence numbers. ++ */ ++ ++ BUG_ON(skb_write_head && skb_rtx_head); ++ ++ if (skb_write_head) { ++ skb_queue_walk_from_safe(&meta_sk->sk_write_queue, ++ skb_write_head, tmp) { ++ _mptcp_rcv_synsent_fastopen(meta_sk, skb_write_head, ++ false); ++ } ++ } ++ ++ if (skb_rtx_head) { ++ skb_rbtree_walk_from_safe(skb_rtx_head, tmp) { ++ _mptcp_rcv_synsent_fastopen(meta_sk, skb_rtx_head, ++ true); ++ } ++ } ++ ++ /* We can advance write_seq by the number of bytes unacknowledged ++ * and that were mapped in the previous loop. ++ */ ++ meta_tp->write_seq += master_tp->write_seq - master_tp->snd_una; ++ ++ /* The packets from the master_sk will be entailed to it later ++ * Until that time, its write queue is empty, and ++ * write_seq must align with snd_una ++ */ ++ master_tp->snd_nxt = master_tp->write_seq = master_tp->snd_una; ++ master_tp->packets_out = 0; ++ tcp_clear_retrans(meta_tp); ++ tcp_clear_retrans(master_tp); ++ tcp_set_ca_state(meta_tp->mpcb->master_sk, TCP_CA_Open); ++ tcp_set_ca_state(meta_sk, TCP_CA_Open); ++} ++ ++/* The skptr is needed, because if we become MPTCP-capable, we have to switch ++ * from meta-socket to master-socket. ++ * ++ * @return: 1 - we want to reset this connection ++ * 2 - we want to discard the received syn/ack ++ * 0 - everything is fine - continue ++ */ ++int mptcp_rcv_synsent_state_process(struct sock *sk, struct sock **skptr, ++ const struct sk_buff *skb, ++ const struct mptcp_options_received *mopt) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ ++ if (mptcp(tp)) { ++ u8 hash_mac_check[SHA256_DIGEST_SIZE]; ++ struct mptcp_cb *mpcb = tp->mpcb; ++ ++ mptcp_hmac(mpcb->mptcp_ver, (u8 *)&mpcb->mptcp_rem_key, ++ (u8 *)&mpcb->mptcp_loc_key, hash_mac_check, 2, ++ 4, (u8 *)&tp->mptcp->rx_opt.mptcp_recv_nonce, ++ 4, (u8 *)&tp->mptcp->mptcp_loc_nonce); ++ if (memcmp(hash_mac_check, ++ (char *)&tp->mptcp->rx_opt.mptcp_recv_tmac, 8)) { ++ MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_JOINSYNACKMAC); ++ mptcp_sub_force_close(sk); ++ return 1; ++ } ++ ++ /* Set this flag in order to postpone data sending ++ * until the 4th ack arrives. ++ */ ++ tp->mptcp->pre_established = 1; ++ tp->mptcp->rcv_low_prio = tp->mptcp->rx_opt.low_prio; ++ ++ mptcp_hmac(mpcb->mptcp_ver, (u8 *)&mpcb->mptcp_loc_key, ++ (u8 *)&mpcb->mptcp_rem_key, ++ tp->mptcp->sender_mac, 2, ++ 4, (u8 *)&tp->mptcp->mptcp_loc_nonce, ++ 4, (u8 *)&tp->mptcp->rx_opt.mptcp_recv_nonce); ++ ++ MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_JOINSYNACKRX); ++ } else if (mopt->saw_mpc) { ++ struct sock *meta_sk = sk; ++ ++ MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_MPCAPABLEACTIVEACK); ++ if (mopt->mptcp_ver > tcp_sk(sk)->mptcp_ver) ++ /* TODO Consider adding new MPTCP_INC_STATS entry */ ++ goto fallback; ++ if (tcp_sk(sk)->mptcp_ver == MPTCP_VERSION_1 && ++ mopt->mptcp_ver < MPTCP_VERSION_1) ++ /* TODO Consider adding new MPTCP_INC_STATS entry */ ++ /* TODO - record this in the cache - use v0 next time */ ++ goto fallback; ++ ++ if (mptcp_create_master_sk(sk, mopt->mptcp_sender_key, 1, ++ mopt->mptcp_ver, ++ ntohs(tcp_hdr(skb)->window))) ++ return 2; ++ ++ sk = tcp_sk(sk)->mpcb->master_sk; ++ *skptr = sk; ++ tp = tcp_sk(sk); ++ ++ /* If fastopen was used data might be in the send queue. We ++ * need to update their sequence number to MPTCP-level seqno. ++ * Note that it can happen in rare cases that fastopen_req is ++ * NULL and syn_data is 0 but fastopen indeed occurred and ++ * data has been queued in the write queue (but not sent). ++ * Example of such rare cases: connect is non-blocking and ++ * TFO is configured to work without cookies. ++ */ ++ mptcp_rcv_synsent_fastopen(meta_sk); ++ ++ /* -1, because the SYN consumed 1 byte. In case of TFO, we ++ * start the subflow-sequence number as if the data of the SYN ++ * is not part of any mapping. ++ */ ++ tp->mptcp->snt_isn = tp->snd_una - 1; ++ tp->mpcb->dss_csum = mopt->dss_csum; ++ if (tp->mpcb->dss_csum) ++ MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_CSUMENABLED); ++ ++ if (tp->mpcb->mptcp_ver >= MPTCP_VERSION_1) ++ tp->mpcb->send_mptcpv1_mpcapable = 1; ++ ++ tp->mptcp->include_mpc = 1; ++ ++ sk_set_socket(sk, meta_sk->sk_socket); ++ sk->sk_wq = meta_sk->sk_wq; ++ ++ bh_unlock_sock(sk); ++ /* hold in sk_clone_lock due to initialization to 2 */ ++ sock_put(sk); ++ } else { ++ MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_MPCAPABLEACTIVEFALLBACK); ++fallback: ++ tp->request_mptcp = 0; ++ ++ if (tp->inside_tk_table) ++ mptcp_hash_remove_bh(tp); ++ } ++ ++ if (mptcp(tp)) ++ tp->mptcp->rcv_isn = TCP_SKB_CB(skb)->seq; ++ ++ return 0; ++} ++ ++/* Similar to tcp_should_expand_sndbuf */ ++bool mptcp_should_expand_sndbuf(const struct sock *sk) ++{ ++ const struct sock *meta_sk = mptcp_meta_sk(sk); ++ const struct tcp_sock *meta_tp = tcp_sk(meta_sk); ++ const struct mptcp_tcp_sock *mptcp; ++ ++ /* We circumvent this check in tcp_check_space, because we want to ++ * always call sk_write_space. So, we reproduce the check here. ++ */ ++ if (!meta_sk->sk_socket || ++ !test_bit(SOCK_NOSPACE, &meta_sk->sk_socket->flags)) ++ return false; ++ ++ /* If the user specified a specific send buffer setting, do ++ * not modify it. ++ */ ++ if (meta_sk->sk_userlocks & SOCK_SNDBUF_LOCK) ++ return false; ++ ++ /* If we are under global TCP memory pressure, do not expand. */ ++ if (tcp_under_memory_pressure(meta_sk)) ++ return false; ++ ++ /* If we are under soft global TCP memory pressure, do not expand. */ ++ if (sk_memory_allocated(meta_sk) >= sk_prot_mem_limits(meta_sk, 0)) ++ return false; ++ ++ /* For MPTCP we look for a subsocket that could send data. ++ * If we found one, then we update the send-buffer. ++ */ ++ mptcp_for_each_sub(meta_tp->mpcb, mptcp) { ++ const struct sock *sk_it = mptcp_to_sock(mptcp); ++ const struct tcp_sock *tp_it = tcp_sk(sk_it); ++ ++ if (!mptcp_sk_can_send(sk_it)) ++ continue; ++ ++ if (tcp_packets_in_flight(tp_it) < tp_it->snd_cwnd) ++ return true; ++ } ++ ++ return false; ++} ++ ++void mptcp_tcp_set_rto(struct sock *sk) ++{ ++ tcp_set_rto(sk); ++ mptcp_set_rto(sk); ++} +diff --git a/net/mptcp/mptcp_ipv4.c b/net/mptcp/mptcp_ipv4.c +new file mode 100644 +index 000000000000..0370a7680d47 +--- /dev/null ++++ b/net/mptcp/mptcp_ipv4.c +@@ -0,0 +1,431 @@ ++/* ++ * MPTCP implementation - IPv4-specific functions ++ * ++ * Initial Design & Implementation: ++ * Sébastien Barré ++ * ++ * Current Maintainer: ++ * Christoph Paasch ++ * ++ * Additional authors: ++ * Jaakko Korkeaniemi ++ * Gregory Detal ++ * Fabien Duchêne ++ * Andreas Seelinger ++ * Lavkesh Lahngir ++ * Andreas Ripke ++ * Vlad Dogaru ++ * Octavian Purdila ++ * John Ronan ++ * Catalin Nicutar ++ * Brandon Heller ++ * ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License ++ * as published by the Free Software Foundation; either version ++ * 2 of the License, or (at your option) any later version. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++u32 mptcp_v4_get_nonce(__be32 saddr, __be32 daddr, __be16 sport, __be16 dport) ++{ ++ return siphash_4u32((__force u32)saddr, (__force u32)daddr, ++ (__force u32)sport << 16 | (__force u32)dport, ++ mptcp_seed++, &mptcp_secret); ++} ++ ++u64 mptcp_v4_get_key(__be32 saddr, __be32 daddr, __be16 sport, __be16 dport, ++ u32 seed) ++{ ++ return siphash_2u64((__force u64)saddr << 32 | (__force u64)daddr, ++ (__force u64)seed << 32 | (__force u64)sport << 16 | (__force u64)dport, ++ &mptcp_secret); ++} ++ ++ ++static void mptcp_v4_reqsk_destructor(struct request_sock *req) ++{ ++ mptcp_reqsk_destructor(req); ++ ++ tcp_v4_reqsk_destructor(req); ++} ++ ++static int mptcp_v4_init_req(struct request_sock *req, const struct sock *sk, ++ struct sk_buff *skb, bool want_cookie) ++{ ++ tcp_request_sock_ipv4_ops.init_req(req, sk, skb, want_cookie); ++ ++ mptcp_rsk(req)->hash_entry.pprev = NULL; ++ mptcp_rsk(req)->is_sub = 0; ++ inet_rsk(req)->mptcp_rqsk = 1; ++ ++ /* In case of SYN-cookies, we wait for the isn to be generated - it is ++ * input to the key-generation. ++ */ ++ if (!want_cookie) ++ mptcp_reqsk_init(req, sk, skb, false); ++ ++ return 0; ++} ++ ++#ifdef CONFIG_SYN_COOKIES ++static u32 mptcp_v4_cookie_init_seq(struct request_sock *req, const struct sock *sk, ++ const struct sk_buff *skb, __u16 *mssp) ++{ ++ __u32 isn = cookie_v4_init_sequence(req, sk, skb, mssp); ++ ++ tcp_rsk(req)->snt_isn = isn; ++ ++ mptcp_reqsk_init(req, sk, skb, true); ++ ++ return isn; ++} ++#endif ++ ++/* May be called without holding the meta-level lock */ ++static int mptcp_v4_join_init_req(struct request_sock *req, const struct sock *meta_sk, ++ struct sk_buff *skb, bool want_cookie) ++{ ++ struct mptcp_request_sock *mtreq = mptcp_rsk(req); ++ const struct mptcp_cb *mpcb = tcp_sk(meta_sk)->mpcb; ++ union inet_addr addr; ++ int loc_id; ++ bool low_prio = false; ++ ++ if (!mpcb->rem_key_set) ++ return -1; ++ ++ /* We need to do this as early as possible. Because, if we fail later ++ * (e.g., get_local_id), then reqsk_free tries to remove the ++ * request-socket from the htb in mptcp_hash_request_remove as pprev ++ * may be different from NULL. ++ */ ++ mtreq->hash_entry.pprev = NULL; ++ ++ tcp_request_sock_ipv4_ops.init_req(req, meta_sk, skb, want_cookie); ++ ++ mtreq->mptcp_loc_nonce = mptcp_v4_get_nonce(ip_hdr(skb)->saddr, ++ ip_hdr(skb)->daddr, ++ tcp_hdr(skb)->source, ++ tcp_hdr(skb)->dest); ++ addr.ip = inet_rsk(req)->ir_loc_addr; ++ loc_id = mpcb->pm_ops->get_local_id(meta_sk, AF_INET, &addr, &low_prio); ++ if (loc_id == -1) ++ return -1; ++ mtreq->loc_id = loc_id; ++ mtreq->low_prio = low_prio; ++ ++ mptcp_join_reqsk_init(mpcb, req, skb); ++ ++ return 0; ++} ++ ++/* Similar to tcp_request_sock_ops */ ++struct request_sock_ops mptcp_request_sock_ops __read_mostly = { ++ .family = PF_INET, ++ .obj_size = sizeof(struct mptcp_request_sock), ++ .rtx_syn_ack = tcp_rtx_synack, ++ .send_ack = tcp_v4_reqsk_send_ack, ++ .destructor = mptcp_v4_reqsk_destructor, ++ .send_reset = tcp_v4_send_reset, ++ .syn_ack_timeout = tcp_syn_ack_timeout, ++}; ++ ++/* Similar to: tcp_v4_conn_request ++ * May be called without holding the meta-level lock ++ */ ++static int mptcp_v4_join_request(struct sock *meta_sk, struct sk_buff *skb) ++{ ++ return tcp_conn_request(&mptcp_request_sock_ops, ++ &mptcp_join_request_sock_ipv4_ops, ++ meta_sk, skb); ++} ++ ++/* Similar to: tcp_v4_do_rcv ++ * We only process join requests here. (either the SYN or the final ACK) ++ */ ++int mptcp_v4_do_rcv(struct sock *meta_sk, struct sk_buff *skb) ++{ ++ const struct tcphdr *th = tcp_hdr(skb); ++ const struct iphdr *iph = ip_hdr(skb); ++ struct sock *child, *rsk = NULL, *sk; ++ int ret; ++ ++ sk = inet_lookup_established(sock_net(meta_sk), &tcp_hashinfo, ++ iph->saddr, th->source, iph->daddr, ++ th->dest, inet_iif(skb)); ++ ++ if (!sk) ++ goto new_subflow; ++ ++ if (is_meta_sk(sk)) { ++ WARN("%s Did not find a sub-sk - did found the meta!\n", __func__); ++ sock_put(sk); ++ goto discard; ++ } ++ ++ if (sk->sk_state == TCP_TIME_WAIT) { ++ inet_twsk_put(inet_twsk(sk)); ++ goto discard; ++ } ++ ++ if (sk->sk_state == TCP_NEW_SYN_RECV) { ++ struct request_sock *req = inet_reqsk(sk); ++ bool req_stolen; ++ ++ if (!mptcp_can_new_subflow(meta_sk)) ++ goto reset_and_discard; ++ ++ local_bh_disable(); ++ child = tcp_check_req(meta_sk, skb, req, false, &req_stolen); ++ if (!child) { ++ reqsk_put(req); ++ local_bh_enable(); ++ goto discard; ++ } ++ ++ if (child != meta_sk) { ++ ret = mptcp_finish_handshake(child, skb); ++ if (ret) { ++ rsk = child; ++ local_bh_enable(); ++ goto reset_and_discard; ++ } ++ ++ bh_unlock_sock(meta_sk); ++ local_bh_enable(); ++ return 0; ++ } ++ ++ /* tcp_check_req failed */ ++ reqsk_put(req); ++ ++ local_bh_enable(); ++ goto discard; ++ } ++ ++ ret = tcp_v4_do_rcv(sk, skb); ++ sock_put(sk); ++ ++ return ret; ++ ++new_subflow: ++ if (!mptcp_can_new_subflow(meta_sk)) ++ goto reset_and_discard; ++ ++ child = tcp_v4_cookie_check(meta_sk, skb); ++ if (!child) ++ goto discard; ++ ++ if (child != meta_sk) { ++ ret = mptcp_finish_handshake(child, skb); ++ if (ret) { ++ rsk = child; ++ goto reset_and_discard; ++ } ++ } ++ ++ if (tcp_hdr(skb)->syn) { ++ local_bh_disable(); ++ mptcp_v4_join_request(meta_sk, skb); ++ local_bh_enable(); ++ } ++ ++discard: ++ kfree_skb(skb); ++ return 0; ++ ++reset_and_discard: ++ tcp_v4_send_reset(rsk, skb); ++ goto discard; ++} ++ ++/* Create a new IPv4 subflow. ++ * ++ * We are in user-context and meta-sock-lock is hold. ++ */ ++int __mptcp_init4_subsockets(struct sock *meta_sk, const struct mptcp_loc4 *loc, ++ __be16 sport, struct mptcp_rem4 *rem, ++ struct sock **subsk) ++{ ++ struct tcp_sock *tp; ++ struct sock *sk; ++ struct sockaddr_in loc_in, rem_in; ++ struct socket_alloc sock_full; ++ struct socket *sock = (struct socket *)&sock_full; ++ int ret; ++ ++ /** First, create and prepare the new socket */ ++ memcpy(&sock_full, meta_sk->sk_socket, sizeof(sock_full)); ++ sock->state = SS_UNCONNECTED; ++ sock->ops = NULL; ++ ++ ret = inet_create(sock_net(meta_sk), sock, IPPROTO_TCP, 1); ++ if (unlikely(ret < 0)) { ++ net_err_ratelimited("%s inet_create failed ret: %d\n", ++ __func__, ret); ++ return ret; ++ } ++ ++ sk = sock->sk; ++ tp = tcp_sk(sk); ++ ++ /* All subsockets need the MPTCP-lock-class */ ++ lockdep_set_class_and_name(&(sk)->sk_lock.slock, &meta_slock_key, meta_slock_key_name); ++ lockdep_init_map(&(sk)->sk_lock.dep_map, meta_key_name, &meta_key, 0); ++ ++ ret = mptcp_add_sock(meta_sk, sk, loc->loc4_id, rem->rem4_id, GFP_KERNEL); ++ if (ret) { ++ net_err_ratelimited("%s mptcp_add_sock failed ret: %d\n", ++ __func__, ret); ++ goto error; ++ } ++ ++ tp->mptcp->slave_sk = 1; ++ tp->mptcp->low_prio = loc->low_prio; ++ ++ /* Initializing the timer for an MPTCP subflow */ ++ timer_setup(&tp->mptcp->mptcp_ack_timer, mptcp_ack_handler, 0); ++ ++ /** Then, connect the socket to the peer */ ++ loc_in.sin_family = AF_INET; ++ rem_in.sin_family = AF_INET; ++ loc_in.sin_port = sport; ++ if (rem->port) ++ rem_in.sin_port = rem->port; ++ else ++ rem_in.sin_port = inet_sk(meta_sk)->inet_dport; ++ loc_in.sin_addr = loc->addr; ++ rem_in.sin_addr = rem->addr; ++ ++ if (loc->if_idx) ++ sk->sk_bound_dev_if = loc->if_idx; ++ ++ ret = kernel_bind(sock, (struct sockaddr *)&loc_in, ++ sizeof(struct sockaddr_in)); ++ if (ret < 0) { ++ net_err_ratelimited("%s: token %#x bind() to %pI4 index %d failed, error %d\n", ++ __func__, tcp_sk(meta_sk)->mpcb->mptcp_loc_token, ++ &loc_in.sin_addr, loc->if_idx, ret); ++ goto error; ++ } ++ ++ mptcp_debug("%s: token %#x pi %d src_addr:%pI4:%d dst_addr:%pI4:%d ifidx: %d\n", ++ __func__, tcp_sk(meta_sk)->mpcb->mptcp_loc_token, ++ tp->mptcp->path_index, &loc_in.sin_addr, ++ ntohs(loc_in.sin_port), &rem_in.sin_addr, ++ ntohs(rem_in.sin_port), loc->if_idx); ++ ++ if (tcp_sk(meta_sk)->mpcb->pm_ops->init_subsocket_v4) ++ tcp_sk(meta_sk)->mpcb->pm_ops->init_subsocket_v4(sk, rem->addr); ++ ++ ret = kernel_connect(sock, (struct sockaddr *)&rem_in, ++ sizeof(struct sockaddr_in), O_NONBLOCK); ++ if (ret < 0 && ret != -EINPROGRESS) { ++ net_err_ratelimited("%s: MPTCP subsocket connect() failed, error %d\n", ++ __func__, ret); ++ goto error; ++ } ++ ++ MPTCP_INC_STATS(sock_net(meta_sk), MPTCP_MIB_JOINSYNTX); ++ ++ sk_set_socket(sk, meta_sk->sk_socket); ++ sk->sk_wq = meta_sk->sk_wq; ++ ++ if (subsk) ++ *subsk = sk; ++ ++ return 0; ++ ++error: ++ /* May happen if mptcp_add_sock fails first */ ++ if (!mptcp(tp)) { ++ tcp_close(sk, 0); ++ } else { ++ local_bh_disable(); ++ mptcp_sub_force_close(sk); ++ local_bh_enable(); ++ } ++ return ret; ++} ++EXPORT_SYMBOL(__mptcp_init4_subsockets); ++ ++const struct inet_connection_sock_af_ops mptcp_v4_specific = { ++ .queue_xmit = ip_queue_xmit, ++ .send_check = tcp_v4_send_check, ++ .rebuild_header = inet_sk_rebuild_header, ++ .sk_rx_dst_set = inet_sk_rx_dst_set, ++ .conn_request = mptcp_conn_request, ++ .syn_recv_sock = tcp_v4_syn_recv_sock, ++ .net_header_len = sizeof(struct iphdr), ++ .setsockopt = ip_setsockopt, ++ .getsockopt = ip_getsockopt, ++ .addr2sockaddr = inet_csk_addr2sockaddr, ++ .sockaddr_len = sizeof(struct sockaddr_in), ++#ifdef CONFIG_COMPAT ++ .compat_setsockopt = compat_ip_setsockopt, ++ .compat_getsockopt = compat_ip_getsockopt, ++#endif ++ .mtu_reduced = tcp_v4_mtu_reduced, ++}; ++ ++struct tcp_request_sock_ops mptcp_request_sock_ipv4_ops; ++struct tcp_request_sock_ops mptcp_join_request_sock_ipv4_ops; ++ ++/* General initialization of IPv4 for MPTCP */ ++int mptcp_pm_v4_init(void) ++{ ++ int ret = 0; ++ struct request_sock_ops *ops = &mptcp_request_sock_ops; ++ ++ mptcp_request_sock_ipv4_ops = tcp_request_sock_ipv4_ops; ++ mptcp_request_sock_ipv4_ops.init_req = mptcp_v4_init_req; ++#ifdef CONFIG_SYN_COOKIES ++ mptcp_request_sock_ipv4_ops.cookie_init_seq = mptcp_v4_cookie_init_seq; ++#endif ++ mptcp_join_request_sock_ipv4_ops = tcp_request_sock_ipv4_ops; ++ mptcp_join_request_sock_ipv4_ops.init_req = mptcp_v4_join_init_req; ++ ++ ops->slab_name = kasprintf(GFP_KERNEL, "request_sock_%s", "MPTCP"); ++ if (ops->slab_name == NULL) { ++ ret = -ENOMEM; ++ goto out; ++ } ++ ++ ops->slab = kmem_cache_create(ops->slab_name, ops->obj_size, 0, ++ SLAB_TYPESAFE_BY_RCU|SLAB_HWCACHE_ALIGN, ++ NULL); ++ ++ if (ops->slab == NULL) { ++ ret = -ENOMEM; ++ goto err_reqsk_create; ++ } ++ ++out: ++ return ret; ++ ++err_reqsk_create: ++ kfree(ops->slab_name); ++ ops->slab_name = NULL; ++ goto out; ++} ++ ++void mptcp_pm_v4_undo(void) ++{ ++ kmem_cache_destroy(mptcp_request_sock_ops.slab); ++ kfree(mptcp_request_sock_ops.slab_name); ++} +diff --git a/net/mptcp/mptcp_ipv6.c b/net/mptcp/mptcp_ipv6.c +new file mode 100644 +index 000000000000..8af32df4fd5f +--- /dev/null ++++ b/net/mptcp/mptcp_ipv6.c +@@ -0,0 +1,479 @@ ++/* ++ * MPTCP implementation - IPv6-specific functions ++ * ++ * Initial Design & Implementation: ++ * Sébastien Barré ++ * ++ * Current Maintainer: ++ * Jaakko Korkeaniemi ++ * ++ * Additional authors: ++ * Jaakko Korkeaniemi ++ * Gregory Detal ++ * Fabien Duchêne ++ * Andreas Seelinger ++ * Lavkesh Lahngir ++ * Andreas Ripke ++ * Vlad Dogaru ++ * Octavian Purdila ++ * John Ronan ++ * Catalin Nicutar ++ * Brandon Heller ++ * ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License ++ * as published by the Free Software Foundation; either version ++ * 2 of the License, or (at your option) any later version. ++ */ ++ ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++__u32 mptcp_v6_get_nonce(const __be32 *saddr, const __be32 *daddr, ++ __be16 sport, __be16 dport) ++{ ++ const struct { ++ struct in6_addr saddr; ++ struct in6_addr daddr; ++ u32 seed; ++ __be16 sport; ++ __be16 dport; ++ } __aligned(SIPHASH_ALIGNMENT) combined = { ++ .saddr = *(struct in6_addr *)saddr, ++ .daddr = *(struct in6_addr *)daddr, ++ .seed = mptcp_seed++, ++ .sport = sport, ++ .dport = dport ++ }; ++ ++ return siphash(&combined, offsetofend(typeof(combined), dport), ++ &mptcp_secret); ++} ++ ++u64 mptcp_v6_get_key(const __be32 *saddr, const __be32 *daddr, ++ __be16 sport, __be16 dport, u32 seed) ++{ ++ const struct { ++ struct in6_addr saddr; ++ struct in6_addr daddr; ++ u32 seed; ++ __be16 sport; ++ __be16 dport; ++ } __aligned(SIPHASH_ALIGNMENT) combined = { ++ .saddr = *(struct in6_addr *)saddr, ++ .daddr = *(struct in6_addr *)daddr, ++ .seed = seed, ++ .sport = sport, ++ .dport = dport ++ }; ++ ++ return siphash(&combined, offsetofend(typeof(combined), dport), ++ &mptcp_secret); ++} ++ ++static void mptcp_v6_reqsk_destructor(struct request_sock *req) ++{ ++ mptcp_reqsk_destructor(req); ++ ++ tcp_v6_reqsk_destructor(req); ++} ++ ++static int mptcp_v6_init_req(struct request_sock *req, const struct sock *sk, ++ struct sk_buff *skb, bool want_cookie) ++{ ++ tcp_request_sock_ipv6_ops.init_req(req, sk, skb, want_cookie); ++ ++ mptcp_rsk(req)->hash_entry.pprev = NULL; ++ mptcp_rsk(req)->is_sub = 0; ++ inet_rsk(req)->mptcp_rqsk = 1; ++ ++ /* In case of SYN-cookies, we wait for the isn to be generated - it is ++ * input to the key-generation. ++ */ ++ if (!want_cookie) ++ mptcp_reqsk_init(req, sk, skb, false); ++ ++ return 0; ++} ++ ++#ifdef CONFIG_SYN_COOKIES ++static u32 mptcp_v6_cookie_init_seq(struct request_sock *req, const struct sock *sk, ++ const struct sk_buff *skb, __u16 *mssp) ++{ ++ __u32 isn = cookie_v6_init_sequence(req, sk, skb, mssp); ++ ++ tcp_rsk(req)->snt_isn = isn; ++ ++ mptcp_reqsk_init(req, sk, skb, true); ++ ++ return isn; ++} ++#endif ++ ++/* May be called without holding the meta-level lock */ ++static int mptcp_v6_join_init_req(struct request_sock *req, const struct sock *meta_sk, ++ struct sk_buff *skb, bool want_cookie) ++{ ++ struct mptcp_request_sock *mtreq = mptcp_rsk(req); ++ const struct mptcp_cb *mpcb = tcp_sk(meta_sk)->mpcb; ++ union inet_addr addr; ++ int loc_id; ++ bool low_prio = false; ++ ++ if (!mpcb->rem_key_set) ++ return -1; ++ ++ /* We need to do this as early as possible. Because, if we fail later ++ * (e.g., get_local_id), then reqsk_free tries to remove the ++ * request-socket from the htb in mptcp_hash_request_remove as pprev ++ * may be different from NULL. ++ */ ++ mtreq->hash_entry.pprev = NULL; ++ ++ tcp_request_sock_ipv6_ops.init_req(req, meta_sk, skb, want_cookie); ++ ++ mtreq->mptcp_loc_nonce = mptcp_v6_get_nonce(ipv6_hdr(skb)->saddr.s6_addr32, ++ ipv6_hdr(skb)->daddr.s6_addr32, ++ tcp_hdr(skb)->source, ++ tcp_hdr(skb)->dest); ++ addr.in6 = inet_rsk(req)->ir_v6_loc_addr; ++ loc_id = mpcb->pm_ops->get_local_id(meta_sk, AF_INET6, &addr, &low_prio); ++ if (loc_id == -1) ++ return -1; ++ mtreq->loc_id = loc_id; ++ mtreq->low_prio = low_prio; ++ ++ mptcp_join_reqsk_init(mpcb, req, skb); ++ ++ return 0; ++} ++ ++/* Similar to tcp6_request_sock_ops */ ++struct request_sock_ops mptcp6_request_sock_ops __read_mostly = { ++ .family = AF_INET6, ++ .obj_size = sizeof(struct mptcp_request_sock), ++ .rtx_syn_ack = tcp_rtx_synack, ++ .send_ack = tcp_v6_reqsk_send_ack, ++ .destructor = mptcp_v6_reqsk_destructor, ++ .send_reset = tcp_v6_send_reset, ++ .syn_ack_timeout = tcp_syn_ack_timeout, ++}; ++ ++/* Similar to: tcp_v6_conn_request ++ * May be called without holding the meta-level lock ++ */ ++static int mptcp_v6_join_request(struct sock *meta_sk, struct sk_buff *skb) ++{ ++ return tcp_conn_request(&mptcp6_request_sock_ops, ++ &mptcp_join_request_sock_ipv6_ops, ++ meta_sk, skb); ++} ++ ++int mptcp_v6_do_rcv(struct sock *meta_sk, struct sk_buff *skb) ++{ ++ const struct tcphdr *th = tcp_hdr(skb); ++ const struct ipv6hdr *ip6h = ipv6_hdr(skb); ++ struct sock *child, *rsk = NULL, *sk; ++ int ret; ++ ++ sk = __inet6_lookup_established(sock_net(meta_sk), ++ &tcp_hashinfo, ++ &ip6h->saddr, th->source, ++ &ip6h->daddr, ntohs(th->dest), ++ tcp_v6_iif(skb), tcp_v6_sdif(skb)); ++ ++ if (!sk) ++ goto new_subflow; ++ ++ if (is_meta_sk(sk)) { ++ WARN("%s Did not find a sub-sk - did found the meta!\n", __func__); ++ sock_put(sk); ++ goto discard; ++ } ++ ++ if (sk->sk_state == TCP_TIME_WAIT) { ++ inet_twsk_put(inet_twsk(sk)); ++ goto discard; ++ } ++ ++ if (sk->sk_state == TCP_NEW_SYN_RECV) { ++ struct request_sock *req = inet_reqsk(sk); ++ bool req_stolen; ++ ++ if (!mptcp_can_new_subflow(meta_sk)) ++ goto reset_and_discard; ++ ++ local_bh_disable(); ++ child = tcp_check_req(meta_sk, skb, req, false, &req_stolen); ++ if (!child) { ++ reqsk_put(req); ++ local_bh_enable(); ++ goto discard; ++ } ++ ++ if (child != meta_sk) { ++ ret = mptcp_finish_handshake(child, skb); ++ if (ret) { ++ rsk = child; ++ local_bh_enable(); ++ goto reset_and_discard; ++ } ++ ++ bh_unlock_sock(meta_sk); ++ local_bh_enable(); ++ return 0; ++ } ++ ++ /* tcp_check_req failed */ ++ reqsk_put(req); ++ ++ local_bh_enable(); ++ goto discard; ++ } ++ ++ ret = tcp_v6_do_rcv(sk, skb); ++ sock_put(sk); ++ ++ return ret; ++ ++new_subflow: ++ if (!mptcp_can_new_subflow(meta_sk)) ++ goto reset_and_discard; ++ ++ child = tcp_v6_cookie_check(meta_sk, skb); ++ if (!child) ++ goto discard; ++ ++ if (child != meta_sk) { ++ ret = mptcp_finish_handshake(child, skb); ++ if (ret) { ++ rsk = child; ++ goto reset_and_discard; ++ } ++ } ++ ++ if (tcp_hdr(skb)->syn) { ++ local_bh_disable(); ++ mptcp_v6_join_request(meta_sk, skb); ++ local_bh_enable(); ++ } ++ ++discard: ++ kfree_skb(skb); ++ return 0; ++ ++reset_and_discard: ++ tcp_v6_send_reset(rsk, skb); ++ goto discard; ++} ++ ++/* Create a new IPv6 subflow. ++ * ++ * We are in user-context and meta-sock-lock is hold. ++ */ ++int __mptcp_init6_subsockets(struct sock *meta_sk, const struct mptcp_loc6 *loc, ++ __be16 sport, struct mptcp_rem6 *rem, ++ struct sock **subsk) ++{ ++ struct tcp_sock *tp; ++ struct sock *sk; ++ struct sockaddr_in6 loc_in, rem_in; ++ struct socket_alloc sock_full; ++ struct socket *sock = (struct socket *)&sock_full; ++ int ret; ++ ++ /** First, create and prepare the new socket */ ++ memcpy(&sock_full, meta_sk->sk_socket, sizeof(sock_full)); ++ sock->state = SS_UNCONNECTED; ++ sock->ops = NULL; ++ ++ ret = inet6_create(sock_net(meta_sk), sock, IPPROTO_TCP, 1); ++ if (unlikely(ret < 0)) { ++ net_err_ratelimited("%s inet6_create failed ret: %d\n", ++ __func__, ret); ++ return ret; ++ } ++ ++ sk = sock->sk; ++ tp = tcp_sk(sk); ++ ++ /* All subsockets need the MPTCP-lock-class */ ++ lockdep_set_class_and_name(&(sk)->sk_lock.slock, &meta_slock_key, meta_slock_key_name); ++ lockdep_init_map(&(sk)->sk_lock.dep_map, meta_key_name, &meta_key, 0); ++ ++ ret = mptcp_add_sock(meta_sk, sk, loc->loc6_id, rem->rem6_id, GFP_KERNEL); ++ if (ret) { ++ net_err_ratelimited("%s mptcp_add_sock failed ret: %d\n", ++ __func__, ret); ++ goto error; ++ } ++ ++ tp->mptcp->slave_sk = 1; ++ tp->mptcp->low_prio = loc->low_prio; ++ ++ /* Initializing the timer for an MPTCP subflow */ ++ timer_setup(&tp->mptcp->mptcp_ack_timer, mptcp_ack_handler, 0); ++ ++ /** Then, connect the socket to the peer */ ++ loc_in.sin6_family = AF_INET6; ++ rem_in.sin6_family = AF_INET6; ++ loc_in.sin6_port = sport; ++ if (rem->port) ++ rem_in.sin6_port = rem->port; ++ else ++ rem_in.sin6_port = inet_sk(meta_sk)->inet_dport; ++ loc_in.sin6_addr = loc->addr; ++ rem_in.sin6_addr = rem->addr; ++ ++ if (loc->if_idx) ++ sk->sk_bound_dev_if = loc->if_idx; ++ ++ ret = kernel_bind(sock, (struct sockaddr *)&loc_in, ++ sizeof(struct sockaddr_in6)); ++ if (ret < 0) { ++ net_err_ratelimited("%s: token %#x bind() to %pI6 index %d failed, error %d\n", ++ __func__, tcp_sk(meta_sk)->mpcb->mptcp_loc_token, ++ &loc_in.sin6_addr, loc->if_idx, ret); ++ goto error; ++ } ++ ++ mptcp_debug("%s: token %#x pi %d src_addr:%pI6:%d dst_addr:%pI6:%d ifidx: %u\n", ++ __func__, tcp_sk(meta_sk)->mpcb->mptcp_loc_token, ++ tp->mptcp->path_index, &loc_in.sin6_addr, ++ ntohs(loc_in.sin6_port), &rem_in.sin6_addr, ++ ntohs(rem_in.sin6_port), loc->if_idx); ++ ++ if (tcp_sk(meta_sk)->mpcb->pm_ops->init_subsocket_v6) ++ tcp_sk(meta_sk)->mpcb->pm_ops->init_subsocket_v6(sk, rem->addr); ++ ++ ret = kernel_connect(sock, (struct sockaddr *)&rem_in, ++ sizeof(struct sockaddr_in6), O_NONBLOCK); ++ if (ret < 0 && ret != -EINPROGRESS) { ++ net_err_ratelimited("%s: MPTCP subsocket connect() failed, error %d\n", ++ __func__, ret); ++ goto error; ++ } ++ ++ MPTCP_INC_STATS(sock_net(meta_sk), MPTCP_MIB_JOINSYNTX); ++ ++ sk_set_socket(sk, meta_sk->sk_socket); ++ sk->sk_wq = meta_sk->sk_wq; ++ ++ if (subsk) ++ *subsk = sk; ++ ++ return 0; ++ ++error: ++ /* May happen if mptcp_add_sock fails first */ ++ if (!mptcp(tp)) { ++ tcp_close(sk, 0); ++ } else { ++ local_bh_disable(); ++ mptcp_sub_force_close(sk); ++ local_bh_enable(); ++ } ++ return ret; ++} ++EXPORT_SYMBOL(__mptcp_init6_subsockets); ++ ++const struct inet_connection_sock_af_ops mptcp_v6_specific = { ++ .queue_xmit = inet6_csk_xmit, ++ .send_check = tcp_v6_send_check, ++ .rebuild_header = inet6_sk_rebuild_header, ++ .sk_rx_dst_set = inet6_sk_rx_dst_set, ++ .conn_request = mptcp_conn_request, ++ .syn_recv_sock = tcp_v6_syn_recv_sock, ++ .net_header_len = sizeof(struct ipv6hdr), ++ .net_frag_header_len = sizeof(struct frag_hdr), ++ .setsockopt = ipv6_setsockopt, ++ .getsockopt = ipv6_getsockopt, ++ .addr2sockaddr = inet6_csk_addr2sockaddr, ++ .sockaddr_len = sizeof(struct sockaddr_in6), ++#ifdef CONFIG_COMPAT ++ .compat_setsockopt = compat_ipv6_setsockopt, ++ .compat_getsockopt = compat_ipv6_getsockopt, ++#endif ++ .mtu_reduced = tcp_v6_mtu_reduced, ++}; ++ ++const struct inet_connection_sock_af_ops mptcp_v6_mapped = { ++ .queue_xmit = ip_queue_xmit, ++ .send_check = tcp_v4_send_check, ++ .rebuild_header = inet_sk_rebuild_header, ++ .sk_rx_dst_set = inet_sk_rx_dst_set, ++ .conn_request = mptcp_conn_request, ++ .syn_recv_sock = tcp_v6_syn_recv_sock, ++ .net_header_len = sizeof(struct iphdr), ++ .setsockopt = ipv6_setsockopt, ++ .getsockopt = ipv6_getsockopt, ++ .addr2sockaddr = inet6_csk_addr2sockaddr, ++ .sockaddr_len = sizeof(struct sockaddr_in6), ++#ifdef CONFIG_COMPAT ++ .compat_setsockopt = compat_ipv6_setsockopt, ++ .compat_getsockopt = compat_ipv6_getsockopt, ++#endif ++ .mtu_reduced = tcp_v4_mtu_reduced, ++}; ++ ++struct tcp_request_sock_ops mptcp_request_sock_ipv6_ops; ++struct tcp_request_sock_ops mptcp_join_request_sock_ipv6_ops; ++ ++int mptcp_pm_v6_init(void) ++{ ++ int ret = 0; ++ struct request_sock_ops *ops = &mptcp6_request_sock_ops; ++ ++ mptcp_request_sock_ipv6_ops = tcp_request_sock_ipv6_ops; ++ mptcp_request_sock_ipv6_ops.init_req = mptcp_v6_init_req; ++#ifdef CONFIG_SYN_COOKIES ++ mptcp_request_sock_ipv6_ops.cookie_init_seq = mptcp_v6_cookie_init_seq; ++#endif ++ ++ mptcp_join_request_sock_ipv6_ops = tcp_request_sock_ipv6_ops; ++ mptcp_join_request_sock_ipv6_ops.init_req = mptcp_v6_join_init_req; ++ ++ ops->slab_name = kasprintf(GFP_KERNEL, "request_sock_%s", "MPTCP6"); ++ if (ops->slab_name == NULL) { ++ ret = -ENOMEM; ++ goto out; ++ } ++ ++ ops->slab = kmem_cache_create(ops->slab_name, ops->obj_size, 0, ++ SLAB_TYPESAFE_BY_RCU|SLAB_HWCACHE_ALIGN, ++ NULL); ++ ++ if (ops->slab == NULL) { ++ ret = -ENOMEM; ++ goto err_reqsk_create; ++ } ++ ++out: ++ return ret; ++ ++err_reqsk_create: ++ kfree(ops->slab_name); ++ ops->slab_name = NULL; ++ goto out; ++} ++ ++void mptcp_pm_v6_undo(void) ++{ ++ kmem_cache_destroy(mptcp6_request_sock_ops.slab); ++ kfree(mptcp6_request_sock_ops.slab_name); ++} +diff --git a/net/mptcp/mptcp_ndiffports.c b/net/mptcp/mptcp_ndiffports.c +new file mode 100644 +index 000000000000..cf019990447c +--- /dev/null ++++ b/net/mptcp/mptcp_ndiffports.c +@@ -0,0 +1,174 @@ ++#include ++ ++#include ++#include ++ ++#if IS_ENABLED(CONFIG_IPV6) ++#include ++#endif ++ ++struct ndiffports_priv { ++ /* Worker struct for subflow establishment */ ++ struct work_struct subflow_work; ++ ++ struct mptcp_cb *mpcb; ++}; ++ ++static int num_subflows __read_mostly = 2; ++module_param(num_subflows, int, 0644); ++MODULE_PARM_DESC(num_subflows, "choose the number of subflows per MPTCP connection"); ++ ++/** ++ * Create all new subflows, by doing calls to mptcp_initX_subsockets ++ * ++ * This function uses a goto next_subflow, to allow releasing the lock between ++ * new subflows and giving other processes a chance to do some work on the ++ * socket and potentially finishing the communication. ++ **/ ++static void create_subflow_worker(struct work_struct *work) ++{ ++ const struct ndiffports_priv *pm_priv = container_of(work, ++ struct ndiffports_priv, ++ subflow_work); ++ struct mptcp_cb *mpcb = pm_priv->mpcb; ++ struct sock *meta_sk = mpcb->meta_sk; ++ int iter = 0; ++ ++next_subflow: ++ if (iter) { ++ release_sock(meta_sk); ++ mutex_unlock(&mpcb->mpcb_mutex); ++ ++ cond_resched(); ++ } ++ mutex_lock(&mpcb->mpcb_mutex); ++ lock_sock_nested(meta_sk, SINGLE_DEPTH_NESTING); ++ ++ if (!mptcp(tcp_sk(meta_sk))) ++ goto exit; ++ ++ iter++; ++ ++ if (sock_flag(meta_sk, SOCK_DEAD)) ++ goto exit; ++ ++ if (mpcb->master_sk && ++ !tcp_sk(mpcb->master_sk)->mptcp->fully_established) ++ goto exit; ++ ++ if (num_subflows > iter && num_subflows > mptcp_subflow_count(mpcb)) { ++ if (meta_sk->sk_family == AF_INET || ++ mptcp_v6_is_v4_mapped(meta_sk)) { ++ struct mptcp_loc4 loc; ++ struct mptcp_rem4 rem; ++ ++ loc.addr.s_addr = inet_sk(meta_sk)->inet_saddr; ++ loc.loc4_id = 0; ++ loc.low_prio = 0; ++ if (mpcb->master_sk) ++ loc.if_idx = mpcb->master_sk->sk_bound_dev_if; ++ else ++ loc.if_idx = 0; ++ ++ rem.addr.s_addr = inet_sk(meta_sk)->inet_daddr; ++ rem.port = inet_sk(meta_sk)->inet_dport; ++ rem.rem4_id = 0; /* Default 0 */ ++ ++ mptcp_init4_subsockets(meta_sk, &loc, &rem); ++ } else { ++#if IS_ENABLED(CONFIG_IPV6) ++ struct mptcp_loc6 loc; ++ struct mptcp_rem6 rem; ++ ++ loc.addr = inet6_sk(meta_sk)->saddr; ++ loc.loc6_id = 0; ++ loc.low_prio = 0; ++ if (mpcb->master_sk) ++ loc.if_idx = mpcb->master_sk->sk_bound_dev_if; ++ else ++ loc.if_idx = 0; ++ ++ rem.addr = meta_sk->sk_v6_daddr; ++ rem.port = inet_sk(meta_sk)->inet_dport; ++ rem.rem6_id = 0; /* Default 0 */ ++ ++ mptcp_init6_subsockets(meta_sk, &loc, &rem); ++#endif ++ } ++ goto next_subflow; ++ } ++ ++exit: ++ release_sock(meta_sk); ++ mutex_unlock(&mpcb->mpcb_mutex); ++ mptcp_mpcb_put(mpcb); ++ sock_put(meta_sk); ++} ++ ++static void ndiffports_new_session(const struct sock *meta_sk) ++{ ++ struct mptcp_cb *mpcb = tcp_sk(meta_sk)->mpcb; ++ struct ndiffports_priv *fmp = (struct ndiffports_priv *)&mpcb->mptcp_pm[0]; ++ ++ /* Initialize workqueue-struct */ ++ INIT_WORK(&fmp->subflow_work, create_subflow_worker); ++ fmp->mpcb = mpcb; ++} ++ ++static void ndiffports_create_subflows(struct sock *meta_sk) ++{ ++ struct mptcp_cb *mpcb = tcp_sk(meta_sk)->mpcb; ++ struct ndiffports_priv *pm_priv = (struct ndiffports_priv *)&mpcb->mptcp_pm[0]; ++ ++ if (mptcp_in_infinite_mapping_weak(mpcb) || ++ mpcb->server_side || sock_flag(meta_sk, SOCK_DEAD)) ++ return; ++ ++ if (!work_pending(&pm_priv->subflow_work)) { ++ sock_hold(meta_sk); ++ refcount_inc(&mpcb->mpcb_refcnt); ++ queue_work(mptcp_wq, &pm_priv->subflow_work); ++ } ++} ++ ++static int ndiffports_get_local_id(const struct sock *meta_sk, ++ sa_family_t family, union inet_addr *addr, ++ bool *low_prio) ++{ ++ return 0; ++} ++ ++static struct mptcp_pm_ops ndiffports __read_mostly = { ++ .new_session = ndiffports_new_session, ++ .fully_established = ndiffports_create_subflows, ++ .get_local_id = ndiffports_get_local_id, ++ .name = "ndiffports", ++ .owner = THIS_MODULE, ++}; ++ ++/* General initialization of MPTCP_PM */ ++static int __init ndiffports_register(void) ++{ ++ BUILD_BUG_ON(sizeof(struct ndiffports_priv) > MPTCP_PM_SIZE); ++ ++ if (mptcp_register_path_manager(&ndiffports)) ++ goto exit; ++ ++ return 0; ++ ++exit: ++ return -1; ++} ++ ++static void ndiffports_unregister(void) ++{ ++ mptcp_unregister_path_manager(&ndiffports); ++} ++ ++module_init(ndiffports_register); ++module_exit(ndiffports_unregister); ++ ++MODULE_AUTHOR("Christoph Paasch"); ++MODULE_LICENSE("GPL"); ++MODULE_DESCRIPTION("NDIFF-PORTS MPTCP"); ++MODULE_VERSION("0.88"); +diff --git a/net/mptcp/mptcp_netlink.c b/net/mptcp/mptcp_netlink.c +new file mode 100644 +index 000000000000..dd696841ea85 +--- /dev/null ++++ b/net/mptcp/mptcp_netlink.c +@@ -0,0 +1,1272 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* MPTCP implementation - Netlink Path Manager ++ * ++ * Analysis, Design and Implementation: ++ * - Gregory Detal ++ * - Sébastien Barré ++ * - Matthieu Baerts ++ * - Pau Espin Pedrol ++ * - Detlev Casanova ++ * - David Verbeiren ++ * - Frank Vanbever ++ * - Antoine Maes ++ * - Tim Froidcoeur ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License ++ * as published by the Free Software Foundation; either version ++ * 2 of the License, or (at your option) any later version. ++ */ ++ ++#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt ++#include ++#include ++#include ++#include ++#include ++#if IS_ENABLED(CONFIG_IPV6) ++#include ++#endif ++ ++#define MPTCP_MAX_ADDR 8 ++ ++struct mptcp_nl_priv { ++ /* Unfortunately we need to store this to generate MP_JOINs in case ++ * of the peer generating a subflow (see get_local_id). ++ */ ++ u8 loc4_bits; ++ u8 announced4; ++ struct mptcp_loc4 locaddr4[MPTCP_MAX_ADDR]; ++ ++#if IS_ENABLED(CONFIG_IPV6) ++ u8 loc6_bits; ++ u8 announced6; ++ struct mptcp_loc6 locaddr6[MPTCP_MAX_ADDR]; ++#endif ++ ++ u16 remove_addrs; ++ ++ bool is_closed; ++}; ++ ++static struct genl_family mptcp_genl_family; ++ ++#define MPTCP_GENL_EV_GRP_OFFSET 0 ++#define MPTCP_GENL_CMD_GRP_OFFSET 1 ++ ++static const struct genl_multicast_group mptcp_mcgrps[] = { ++ [MPTCP_GENL_EV_GRP_OFFSET] = { .name = MPTCP_GENL_EV_GRP_NAME, }, ++ [MPTCP_GENL_CMD_GRP_OFFSET] = { .name = MPTCP_GENL_CMD_GRP_NAME, }, ++}; ++ ++static const struct nla_policy mptcp_nl_genl_policy[MPTCP_ATTR_MAX + 1] = { ++ [MPTCP_ATTR_TOKEN] = { .type = NLA_U32, }, ++ [MPTCP_ATTR_FAMILY] = { .type = NLA_U16, }, ++ [MPTCP_ATTR_LOC_ID] = { .type = NLA_U8, }, ++ [MPTCP_ATTR_REM_ID] = { .type = NLA_U8, }, ++ [MPTCP_ATTR_SADDR4] = { .type = NLA_U32, }, ++ [MPTCP_ATTR_SADDR6] = { .type = NLA_BINARY, ++ .len = sizeof(struct in6_addr), }, ++ [MPTCP_ATTR_DADDR4] = { .type = NLA_U32, }, ++ [MPTCP_ATTR_DADDR6] = { .type = NLA_BINARY, ++ .len = sizeof(struct in6_addr), }, ++ [MPTCP_ATTR_SPORT] = { .type = NLA_U16, }, ++ [MPTCP_ATTR_DPORT] = { .type = NLA_U16, }, ++ [MPTCP_ATTR_BACKUP] = { .type = NLA_U8, }, ++ [MPTCP_ATTR_FLAGS] = { .type = NLA_U16, }, ++ [MPTCP_ATTR_TIMEOUT] = { .type = NLA_U32, }, ++ [MPTCP_ATTR_IF_IDX] = { .type = NLA_S32, }, ++}; ++ ++/* Defines the userspace PM filter on events. Set events are ignored. */ ++static u16 mptcp_nl_event_filter; ++ ++static inline struct mptcp_nl_priv * ++mptcp_nl_priv(const struct sock *meta_sk) ++{ ++ return (struct mptcp_nl_priv *)&tcp_sk(meta_sk)->mpcb->mptcp_pm[0]; ++} ++ ++static inline bool ++mptcp_nl_must_notify(u16 event, const struct sock *meta_sk) ++{ ++ struct mptcp_nl_priv *priv = mptcp_nl_priv(meta_sk); ++ ++ /* close_session() can be called before other events because it is ++ * also called when doing a fallback to TCP. We don't want to send ++ * events to the user-space after having sent the CLOSED event. ++ */ ++ if (priv->is_closed) ++ return false; ++ ++ if (event == MPTCPF_EVENT_CLOSED) ++ priv->is_closed = true; ++ ++ if (mptcp_nl_event_filter & event) ++ return false; ++ ++ if (!genl_has_listeners(&mptcp_genl_family, sock_net(meta_sk), 0)) ++ return false; ++ ++ return true; ++} ++ ++/* Find the first free index in the bitfield starting from 0 */ ++static int ++mptcp_nl_find_free_index(u8 bitfield) ++{ ++ int i; ++ ++ /* There are anyways no free bits... */ ++ if (bitfield == 0xff) ++ return -1; ++ ++ i = ffs(~bitfield) - 1; ++ if (i < 0) ++ return -1; ++ ++ return i; ++} ++ ++static inline int ++mptcp_nl_put_subsk(struct sk_buff *msg, struct sock *sk) ++{ ++ struct inet_sock *isk = inet_sk(sk); ++ struct sock *meta_sk = mptcp_meta_sk(sk); ++ u8 backup; ++ u8 sk_err; ++ ++ if (nla_put_u16(msg, MPTCP_ATTR_FAMILY, sk->sk_family)) ++ goto nla_put_failure; ++ ++ if (nla_put_u8(msg, MPTCP_ATTR_LOC_ID, tcp_sk(sk)->mptcp->loc_id)) ++ goto nla_put_failure; ++ ++ if (nla_put_u8(msg, MPTCP_ATTR_REM_ID, tcp_sk(sk)->mptcp->rem_id)) ++ goto nla_put_failure; ++ ++ switch (sk->sk_family) { ++ case AF_INET: ++ if (nla_put_u32(msg, MPTCP_ATTR_SADDR4, isk->inet_saddr)) ++ goto nla_put_failure; ++ ++ if (nla_put_u32(msg, MPTCP_ATTR_DADDR4, isk->inet_daddr)) ++ goto nla_put_failure; ++ break; ++#if IS_ENABLED(CONFIG_IPV6) ++ case AF_INET6: { ++ struct ipv6_pinfo *np = inet6_sk(sk); ++ ++ if (nla_put(msg, MPTCP_ATTR_SADDR6, sizeof(np->saddr), ++ &np->saddr)) ++ goto nla_put_failure; ++ ++ if (nla_put(msg, MPTCP_ATTR_DADDR6, sizeof(sk->sk_v6_daddr), ++ &sk->sk_v6_daddr)) ++ goto nla_put_failure; ++ break; ++ } ++#endif ++ default: ++ goto nla_put_failure; ++ } ++ ++ if (nla_put_u16(msg, MPTCP_ATTR_SPORT, ntohs(isk->inet_sport))) ++ goto nla_put_failure; ++ ++ if (nla_put_u16(msg, MPTCP_ATTR_DPORT, ntohs(isk->inet_dport))) ++ goto nla_put_failure; ++ ++ backup = !!(tcp_sk(sk)->mptcp->rcv_low_prio || ++ tcp_sk(sk)->mptcp->low_prio); ++ ++ if (nla_put_u8(msg, MPTCP_ATTR_BACKUP, backup)) ++ goto nla_put_failure; ++ ++ if (nla_put_s32(msg, MPTCP_ATTR_IF_IDX, sk->sk_bound_dev_if)) ++ goto nla_put_failure; ++ ++ sk_err = sk->sk_err ? : tcp_sk(sk)->mptcp->sk_err; ++ if (unlikely(sk_err != 0) && meta_sk->sk_state == TCP_ESTABLISHED && ++ nla_put_u8(msg, MPTCP_ATTR_ERROR, sk_err)) ++ goto nla_put_failure; ++ ++ return 0; ++ ++nla_put_failure: ++ return -1; ++} ++ ++static inline struct sk_buff * ++mptcp_nl_mcast_prepare(struct mptcp_cb *mpcb, struct sock *sk, int cmd, ++ void **hdr) ++{ ++ struct sk_buff *msg; ++ ++ /* possible optimisation: use the needed size */ ++ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_ATOMIC); ++ if (!msg) ++ return NULL; ++ ++ *hdr = genlmsg_put(msg, 0, 0, &mptcp_genl_family, 0, cmd); ++ if (!*hdr) ++ goto free_msg; ++ ++ if (nla_put_u32(msg, MPTCP_ATTR_TOKEN, mpcb->mptcp_loc_token)) ++ goto nla_put_failure; ++ ++ if (sk && mptcp_nl_put_subsk(msg, sk)) ++ goto nla_put_failure; ++ ++ return msg; ++ ++nla_put_failure: ++ genlmsg_cancel(msg, *hdr); ++free_msg: ++ nlmsg_free(msg); ++ return NULL; ++} ++ ++static inline int ++mptcp_nl_mcast_send(struct mptcp_cb *mpcb, struct sk_buff *msg, void *hdr) ++{ ++ int ret; ++ struct sock *meta_sk = mpcb->meta_sk; ++ ++ genlmsg_end(msg, hdr); ++ ++ ret = genlmsg_multicast_netns(&mptcp_genl_family, sock_net(meta_sk), ++ msg, 0, MPTCP_GENL_EV_GRP_OFFSET, ++ GFP_ATOMIC); ++ if (ret && ret != -ESRCH) ++ pr_err("%s: genlmsg_multicast failed with %d\n", __func__, ret); ++ return ret; ++} ++ ++static inline void ++mptcp_nl_mcast(struct mptcp_cb *mpcb, struct sock *sk, int cmd) ++{ ++ void *hdr; ++ struct sk_buff *msg; ++ ++ msg = mptcp_nl_mcast_prepare(mpcb, sk, cmd, &hdr); ++ if (msg) ++ mptcp_nl_mcast_send(mpcb, msg, hdr); ++ else ++ pr_warn("%s: unable to prepare multicast message\n", __func__); ++} ++ ++static inline void ++mptcp_nl_mcast_fail(struct sk_buff *msg, void *hdr) ++{ ++ genlmsg_cancel(msg, hdr); ++ nlmsg_free(msg); ++} ++ ++static void ++mptcp_nl_new(const struct sock *meta_sk, bool established) ++{ ++ struct mptcp_cb *mpcb = tcp_sk(meta_sk)->mpcb; ++ ++ mptcp_nl_mcast(mpcb, mpcb->master_sk, ++ established ? MPTCP_EVENT_ESTABLISHED ++ : MPTCP_EVENT_CREATED); ++} ++ ++static void ++mptcp_nl_pm_new_session(const struct sock *meta_sk) ++{ ++ if (!mptcp_nl_must_notify(MPTCPF_EVENT_CREATED, meta_sk)) ++ return; ++ ++ mptcp_nl_new(meta_sk, false); ++} ++ ++static inline int ++mptcp_nl_loc_id_to_index_lookup(struct sock *meta_sk, sa_family_t family, ++ u8 addr_id) ++{ ++ struct mptcp_nl_priv *priv = mptcp_nl_priv(meta_sk); ++ int i; ++ ++ switch (family) { ++ case AF_INET: ++ mptcp_for_each_bit_set(priv->loc4_bits, i) { ++ if (priv->locaddr4[i].loc4_id == addr_id) ++ return i; ++ } ++ break; ++#if IS_ENABLED(CONFIG_IPV6) ++ case AF_INET6: ++ mptcp_for_each_bit_set(priv->loc6_bits, i) { ++ if (priv->locaddr6[i].loc6_id == addr_id) ++ return i; ++ } ++ break; ++#endif ++ } ++ return -1; ++} ++ ++static inline void ++mptcp_nl_sk_setup_locaddr(struct sock *meta_sk, struct sock *sk) ++{ ++ struct mptcp_nl_priv *priv = mptcp_nl_priv(meta_sk); ++ bool backup = !!(tcp_sk(sk)->mptcp->rcv_low_prio || ++ tcp_sk(sk)->mptcp->low_prio); ++ sa_family_t family = mptcp_v6_is_v4_mapped(sk) ? AF_INET ++ : sk->sk_family; ++ u8 addr_id = tcp_sk(sk)->mptcp->loc_id; ++ int idx = mptcp_nl_loc_id_to_index_lookup(meta_sk, family, ++ addr_id); ++ ++ /* Same as in mptcp_fullmesh.c: exception for transparent sockets */ ++ int if_idx = inet_sk(sk)->transparent ? inet_sk(sk)->rx_dst_ifindex : ++ sk->sk_bound_dev_if; ++ ++ switch (family) { ++ case AF_INET: { ++ struct inet_sock *isk = inet_sk(sk); ++ ++ if (idx == -1) ++ idx = mptcp_nl_find_free_index(priv->loc4_bits); ++ if (idx == -1) { ++ pr_warn("No free index for sk loc_id v4\n"); ++ return; ++ } ++ priv->locaddr4[idx].addr.s_addr = isk->inet_saddr; ++ priv->locaddr4[idx].loc4_id = addr_id; ++ priv->locaddr4[idx].low_prio = backup; ++ priv->locaddr4[idx].if_idx = if_idx; ++ priv->loc4_bits |= 1 << idx; ++ priv->announced4 |= 1 << idx; ++ break; ++ } ++#if IS_ENABLED(CONFIG_IPV6) ++ case AF_INET6: { ++ struct ipv6_pinfo *np = inet6_sk(sk); ++ ++ if (idx == -1) ++ idx = mptcp_nl_find_free_index(priv->loc6_bits); ++ if (idx == -1) { ++ pr_warn("No free index for sk loc_id v6\n"); ++ return; ++ } ++ priv->locaddr6[idx].addr = np->saddr; ++ priv->locaddr6[idx].loc6_id = addr_id; ++ priv->locaddr6[idx].low_prio = backup; ++ priv->locaddr6[idx].if_idx = if_idx; ++ priv->loc6_bits |= 1 << idx; ++ priv->announced6 |= 1 << idx; ++ break; ++ } ++#endif ++ } ++} ++ ++static void ++mptcp_nl_pm_fully_established(struct sock *meta_sk) ++{ ++ mptcp_nl_sk_setup_locaddr(meta_sk, tcp_sk(meta_sk)->mpcb->master_sk); ++ ++ if (!mptcp_nl_must_notify(MPTCPF_EVENT_ESTABLISHED, meta_sk)) ++ return; ++ ++ mptcp_nl_new(meta_sk, true); ++} ++ ++static void ++mptcp_nl_pm_close_session(struct sock *meta_sk) ++{ ++ if (!mptcp_nl_must_notify(MPTCPF_EVENT_CLOSED, meta_sk)) ++ return; ++ ++ mptcp_nl_mcast(tcp_sk(meta_sk)->mpcb, NULL, MPTCP_EVENT_CLOSED); ++} ++ ++static void ++mptcp_nl_pm_established_subflow(struct sock *sk) ++{ ++ struct sock *meta_sk = mptcp_meta_sk(sk); ++ ++ mptcp_nl_sk_setup_locaddr(meta_sk, sk); ++ ++ if (!mptcp_nl_must_notify(MPTCPF_EVENT_SUB_ESTABLISHED, meta_sk)) ++ return; ++ ++ mptcp_nl_mcast(tcp_sk(meta_sk)->mpcb, sk, MPTCP_EVENT_SUB_ESTABLISHED); ++} ++ ++static void ++mptcp_nl_pm_delete_subflow(struct sock *sk) ++{ ++ struct sock *meta_sk = mptcp_meta_sk(sk); ++ ++ if (!mptcp_nl_must_notify(MPTCPF_EVENT_SUB_CLOSED, meta_sk)) ++ return; ++ ++ mptcp_nl_mcast(tcp_sk(meta_sk)->mpcb, sk, MPTCP_EVENT_SUB_CLOSED); ++} ++ ++static void ++mptcp_nl_pm_add_raddr(struct mptcp_cb *mpcb, const union inet_addr *addr, ++ sa_family_t family, __be16 port, u8 id) ++{ ++ struct sk_buff *msg; ++ void *hdr; ++ ++ if (!mptcp_nl_must_notify(MPTCPF_EVENT_ANNOUNCED, mpcb->meta_sk)) ++ return; ++ ++ msg = mptcp_nl_mcast_prepare(mpcb, NULL, MPTCP_EVENT_ANNOUNCED, &hdr); ++ if (!msg) ++ return; ++ ++ if (nla_put_u8(msg, MPTCP_ATTR_REM_ID, id)) ++ goto nla_put_failure; ++ ++ if (nla_put_u16(msg, MPTCP_ATTR_FAMILY, family)) ++ goto nla_put_failure; ++ ++ switch (family) { ++ case AF_INET: ++ if (nla_put_u32(msg, MPTCP_ATTR_DADDR4, addr->ip)) ++ goto nla_put_failure; ++ break; ++#if IS_ENABLED(CONFIG_IPV6) ++ case AF_INET6: ++ if (nla_put(msg, MPTCP_ATTR_DADDR6, sizeof(addr->ip6), ++ &addr->ip6)) ++ goto nla_put_failure; ++ break; ++#endif ++ default: ++ goto nla_put_failure; ++ } ++ ++ if (nla_put_u16(msg, MPTCP_ATTR_DPORT, ntohs(port))) ++ goto nla_put_failure; ++ ++ mptcp_nl_mcast_send(mpcb, msg, hdr); ++ ++ return; ++ ++nla_put_failure: ++ mptcp_nl_mcast_fail(msg, hdr); ++} ++ ++static void ++mptcp_nl_pm_rem_raddr(struct mptcp_cb *mpcb, u8 id) ++{ ++ struct sk_buff *msg; ++ void *hdr; ++ ++ if (!mptcp_nl_must_notify(MPTCPF_EVENT_REMOVED, mpcb->meta_sk)) ++ return; ++ ++ msg = mptcp_nl_mcast_prepare(mpcb, NULL, MPTCP_EVENT_REMOVED, &hdr); ++ ++ if (!msg) ++ return; ++ ++ if (nla_put_u8(msg, MPTCP_ATTR_REM_ID, id)) ++ goto nla_put_failure; ++ ++ mptcp_nl_mcast_send(mpcb, msg, hdr); ++ ++ return; ++ ++nla_put_failure: ++ mptcp_nl_mcast_fail(msg, hdr); ++} ++ ++static int ++mptcp_nl_pm_get_local_id(const struct sock *meta_sk, sa_family_t family, ++ union inet_addr *addr, bool *low_prio) ++{ ++ struct mptcp_nl_priv *priv = mptcp_nl_priv(meta_sk); ++ int i, id = 0; ++ ++ switch (family) { ++ case AF_INET: ++ mptcp_for_each_bit_set(priv->loc4_bits, i) { ++ if (addr->in.s_addr == priv->locaddr4[i].addr.s_addr) { ++ id = priv->locaddr4[i].loc4_id; ++ *low_prio = priv->locaddr4[i].low_prio; ++ goto out; ++ } ++ } ++ break; ++#if IS_ENABLED(CONFIG_IPV6) ++ case AF_INET6: ++ mptcp_for_each_bit_set(priv->loc6_bits, i) { ++ if (ipv6_addr_equal(&addr->in6, ++ &priv->locaddr6[i].addr)) { ++ id = priv->locaddr6[i].loc6_id; ++ *low_prio = priv->locaddr6[i].low_prio; ++ goto out; ++ } ++ } ++ break; ++#endif ++ } ++ return -1; ++ ++out: ++ return id; ++} ++ ++static void ++mptcp_nl_pm_addr_signal(struct sock *sk, unsigned *size, ++ struct tcp_out_options *opts, struct sk_buff *skb) ++{ ++ struct mptcp_nl_priv *priv = mptcp_nl_priv(sk); ++ struct mptcp_cb *mpcb = tcp_sk(sk)->mpcb; ++ u8 unannounced; ++ int remove_addr_len; ++ ++ unannounced = (~priv->announced4) & priv->loc4_bits; ++ if (unannounced && ++ MAX_TCP_OPTION_SPACE - *size >= MPTCP_SUB_LEN_ADD_ADDR4_ALIGN) { ++ int i = mptcp_nl_find_free_index(~unannounced); ++ ++ opts->options |= OPTION_MPTCP; ++ opts->mptcp_options |= OPTION_ADD_ADDR; ++ opts->add_addr4.addr_id = priv->locaddr4[i].loc4_id; ++ opts->add_addr4.addr = priv->locaddr4[i].addr; ++ opts->add_addr_v4 = 1; ++ ++ if (skb) ++ priv->announced4 |= (1 << i); ++ *size += MPTCP_SUB_LEN_ADD_ADDR4_ALIGN; ++ } ++ ++#if IS_ENABLED(CONFIG_IPV6) ++ unannounced = (~priv->announced6) & priv->loc6_bits; ++ if (unannounced && ++ MAX_TCP_OPTION_SPACE - *size >= MPTCP_SUB_LEN_ADD_ADDR6_ALIGN) { ++ int i = mptcp_nl_find_free_index(~unannounced); ++ ++ opts->options |= OPTION_MPTCP; ++ opts->mptcp_options |= OPTION_ADD_ADDR; ++ opts->add_addr6.addr_id = priv->locaddr6[i].loc6_id; ++ opts->add_addr6.addr = priv->locaddr6[i].addr; ++ opts->add_addr_v6 = 1; ++ ++ if (skb) ++ priv->announced6 |= (1 << i); ++ *size += MPTCP_SUB_LEN_ADD_ADDR6_ALIGN; ++ } ++#endif ++ ++ if (likely(!priv->remove_addrs)) ++ goto exit; ++ ++ remove_addr_len = mptcp_sub_len_remove_addr_align(priv->remove_addrs); ++ if (MAX_TCP_OPTION_SPACE - *size < remove_addr_len) ++ goto exit; ++ ++ opts->options |= OPTION_MPTCP; ++ opts->mptcp_options |= OPTION_REMOVE_ADDR; ++ opts->remove_addrs = priv->remove_addrs; ++ ++ if (skb) ++ priv->remove_addrs = 0; ++ *size += remove_addr_len; ++ ++exit: ++ mpcb->addr_signal = !!((~priv->announced4) & priv->loc4_bits || ++#if IS_ENABLED(CONFIG_IPV6) ++ (~priv->announced6) & priv->loc6_bits || ++#endif ++ priv->remove_addrs); ++} ++ ++static void ++mptcp_nl_pm_prio_changed(struct sock *sk, int low_prio) ++{ ++ struct sock *meta_sk = mptcp_meta_sk(sk); ++ ++ if (!mptcp_nl_must_notify(MPTCPF_EVENT_SUB_PRIORITY, meta_sk)) ++ return; ++ ++ mptcp_nl_mcast(tcp_sk(meta_sk)->mpcb, sk, MPTCP_EVENT_SUB_PRIORITY); ++} ++ ++static int ++mptcp_nl_genl_announce(struct sk_buff *skb, struct genl_info *info) ++{ ++ struct sock *meta_sk, *subsk; ++ struct mptcp_cb *mpcb; ++ struct mptcp_nl_priv *priv; ++ u32 token; ++ u8 addr_id, backup = 0; ++ u16 family; ++ int i, ret = 0; ++ union inet_addr saddr; ++ int if_idx = 0; ++ bool useless; /* unused out parameter "low_prio" */ ++ ++ if (!info->attrs[MPTCP_ATTR_TOKEN] || !info->attrs[MPTCP_ATTR_FAMILY] || ++ !info->attrs[MPTCP_ATTR_LOC_ID]) ++ return -EINVAL; ++ ++ token = nla_get_u32(info->attrs[MPTCP_ATTR_TOKEN]); ++ meta_sk = mptcp_hash_find(genl_info_net(info), token); ++ if (!meta_sk) ++ return -EINVAL; ++ ++ mpcb = tcp_sk(meta_sk)->mpcb; ++ priv = mptcp_nl_priv(meta_sk); ++ family = nla_get_u16(info->attrs[MPTCP_ATTR_FAMILY]); ++ addr_id = nla_get_u8(info->attrs[MPTCP_ATTR_LOC_ID]); ++ ++ if (info->attrs[MPTCP_ATTR_BACKUP]) ++ backup = nla_get_u8(info->attrs[MPTCP_ATTR_BACKUP]); ++ ++ if (info->attrs[MPTCP_ATTR_IF_IDX]) ++ if_idx = nla_get_s32(info->attrs[MPTCP_ATTR_IF_IDX]); ++ ++ mutex_lock(&mpcb->mpcb_mutex); ++ lock_sock_nested(meta_sk, SINGLE_DEPTH_NESTING); ++ ++ switch (family) { ++ case AF_INET: ++ if (!info->attrs[MPTCP_ATTR_SADDR4]) { ++ ret = -EINVAL; ++ goto exit; ++ } ++ ++ saddr.in.s_addr = nla_get_u32(info->attrs[MPTCP_ATTR_SADDR4]); ++ i = mptcp_nl_pm_get_local_id(meta_sk, family, ++ &saddr, &useless); ++ if (i < 0) { ++ i = mptcp_nl_find_free_index(priv->loc4_bits); ++ if (i < 0) { ++ ret = -ENOBUFS; ++ goto exit; ++ } ++ } else if (i != addr_id) { ++ ret = -EINVAL; ++ goto exit; ++ } ++ ++ priv->locaddr4[i].addr.s_addr = saddr.in.s_addr; ++ priv->locaddr4[i].loc4_id = addr_id; ++ priv->locaddr4[i].low_prio = !!backup; ++ priv->locaddr4[i].if_idx = if_idx; ++ priv->loc4_bits |= 1 << i; ++ priv->announced4 &= ~(1 << i); ++ break; ++#if IS_ENABLED(CONFIG_IPV6) ++ case AF_INET6: ++ if (!info->attrs[MPTCP_ATTR_SADDR6]) { ++ ret = -EINVAL; ++ goto exit; ++ } ++ ++ saddr.in6 = *(struct in6_addr *) ++ nla_data(info->attrs[MPTCP_ATTR_SADDR6]); ++ i = mptcp_nl_pm_get_local_id(meta_sk, family, &saddr, &useless); ++ if (i < 0) { ++ i = mptcp_nl_find_free_index(priv->loc6_bits); ++ if (i < 0) { ++ ret = -ENOBUFS; ++ goto exit; ++ } ++ } else if (i != addr_id) { ++ ret = -EINVAL; ++ goto exit; ++ } ++ ++ priv->locaddr6[i].addr = saddr.in6; ++ priv->locaddr6[i].loc6_id = addr_id; ++ priv->locaddr6[i].low_prio = !!backup; ++ priv->locaddr6[i].if_idx = if_idx; ++ priv->loc6_bits |= 1 << i; ++ priv->announced6 &= ~(1 << i); ++ break; ++#endif ++ default: ++ ret = -EINVAL; ++ goto exit; ++ } ++ ++ mpcb->addr_signal = 1; ++ ++ rcu_read_lock_bh(); ++ subsk = mptcp_select_ack_sock(meta_sk); ++ if (subsk) ++ tcp_send_ack(subsk); ++ rcu_read_unlock_bh(); ++ ++exit: ++ release_sock(meta_sk); ++ mutex_unlock(&mpcb->mpcb_mutex); ++ sock_put(meta_sk); ++ return ret; ++} ++ ++static int ++mptcp_nl_genl_remove(struct sk_buff *skb, struct genl_info *info) ++{ ++ struct sock *meta_sk, *subsk; ++ struct mptcp_cb *mpcb; ++ struct mptcp_nl_priv *priv; ++ u32 token; ++ u8 addr_id; ++ int i; ++ int retcode; ++ bool found = false; ++ ++ if (!info->attrs[MPTCP_ATTR_TOKEN] || !info->attrs[MPTCP_ATTR_LOC_ID]) ++ return -EINVAL; ++ ++ token = nla_get_u32(info->attrs[MPTCP_ATTR_TOKEN]); ++ meta_sk = mptcp_hash_find(genl_info_net(info), token); ++ if (!meta_sk) ++ return -EINVAL; ++ ++ mpcb = tcp_sk(meta_sk)->mpcb; ++ priv = mptcp_nl_priv(meta_sk); ++ addr_id = nla_get_u8(info->attrs[MPTCP_ATTR_LOC_ID]); ++ ++ mutex_lock(&mpcb->mpcb_mutex); ++ lock_sock_nested(meta_sk, SINGLE_DEPTH_NESTING); ++ ++ mptcp_for_each_bit_set(priv->loc4_bits, i) { ++ if (priv->locaddr4[i].loc4_id == addr_id) { ++ priv->loc4_bits &= ~(1 << i); ++ found = true; ++ break; ++ } ++ } ++ ++#if IS_ENABLED(CONFIG_IPV6) ++ if (!found) { ++ mptcp_for_each_bit_set(priv->loc6_bits, i) { ++ if (priv->locaddr6[i].loc6_id == addr_id) { ++ priv->loc6_bits &= ~(1 << i); ++ found = true; ++ break; ++ } ++ } ++ } ++#endif ++ ++ if (found) { ++ priv->remove_addrs |= 1 << addr_id; ++ mpcb->addr_signal = 1; ++ ++ rcu_read_lock_bh(); ++ subsk = mptcp_select_ack_sock(meta_sk); ++ if (subsk) ++ tcp_send_ack(subsk); ++ rcu_read_unlock_bh(); ++ retcode = 0; ++ } else { ++ retcode = -EINVAL; ++ } ++ ++ release_sock(meta_sk); ++ mutex_unlock(&mpcb->mpcb_mutex); ++ sock_put(meta_sk); ++ return retcode; ++} ++ ++static int ++mptcp_nl_genl_create(struct sk_buff *skb, struct genl_info *info) ++{ ++ struct sock *meta_sk, *subsk = NULL; ++ struct mptcp_cb *mpcb; ++ struct mptcp_nl_priv *priv; ++ u32 token; ++ u16 family, sport; ++ u8 loc_id, rem_id, backup = 0; ++ int i, ret = 0; ++ int if_idx; ++ ++ if (!info->attrs[MPTCP_ATTR_TOKEN] || !info->attrs[MPTCP_ATTR_FAMILY] || ++ !info->attrs[MPTCP_ATTR_LOC_ID] || !info->attrs[MPTCP_ATTR_REM_ID]) ++ return -EINVAL; ++ ++ token = nla_get_u32(info->attrs[MPTCP_ATTR_TOKEN]); ++ meta_sk = mptcp_hash_find(genl_info_net(info), token); ++ if (!meta_sk) ++ /* We use a more specific value than EINVAL here so that ++ * userspace can handle this specific case easily. This is ++ * useful to check the case in which userspace tries to create a ++ * subflow for a connection which was already destroyed recently ++ * in kernelspace, but userspace didn't have time to realize ++ * about it because there is a gap of time between kernel ++ * destroying the connection and userspace receiving the event ++ * through Netlink. It can easily happen for short life-time ++ * conns. ++ */ ++ return -EBADR; ++ ++ mpcb = tcp_sk(meta_sk)->mpcb; ++ ++ mutex_lock(&mpcb->mpcb_mutex); ++ lock_sock_nested(meta_sk, SINGLE_DEPTH_NESTING); ++ ++ if (sock_flag(meta_sk, SOCK_DEAD)) { ++ /* Same as for the EBADR case. In this case, though, we know for ++ * sure the conn owner of the subflow existed at some point (no ++ * invalid token possibility) ++ */ ++ ret = -EOWNERDEAD; ++ goto unlock; ++ } ++ ++ if (!mptcp_can_new_subflow(meta_sk)) { ++ /* Same as for the EBADR and EOWNERDEAD case but here, the MPTCP ++ * session has just been stopped, it is no longer possible to ++ * create new subflows. ++ */ ++ ret = -ENOTCONN; ++ goto unlock; ++ } ++ ++ if (mpcb->master_sk && ++ !tcp_sk(mpcb->master_sk)->mptcp->fully_established) { ++ /* First condition is not only in there for safely purposes, it ++ * can also be triggered in the same scenario as in EBADR and ++ * EOWNERDEAD ++ */ ++ ret = -EAGAIN; ++ goto unlock; ++ } ++ ++ priv = mptcp_nl_priv(meta_sk); ++ ++ family = nla_get_u16(info->attrs[MPTCP_ATTR_FAMILY]); ++ loc_id = nla_get_u8(info->attrs[MPTCP_ATTR_LOC_ID]); ++ rem_id = nla_get_u8(info->attrs[MPTCP_ATTR_REM_ID]); ++ ++ sport = info->attrs[MPTCP_ATTR_SPORT] ++ ? htons(nla_get_u16(info->attrs[MPTCP_ATTR_SPORT])) : 0; ++ backup = info->attrs[MPTCP_ATTR_BACKUP] ++ ? nla_get_u8(info->attrs[MPTCP_ATTR_BACKUP]) : 0; ++ if_idx = info->attrs[MPTCP_ATTR_IF_IDX] ++ ? nla_get_s32(info->attrs[MPTCP_ATTR_IF_IDX]) : 0; ++ ++ switch (family) { ++ case AF_INET: { ++ struct mptcp_rem4 rem = { ++ .rem4_id = rem_id, ++ }; ++ struct mptcp_loc4 loc = { ++ .loc4_id = loc_id, ++ }; ++ ++ if (!info->attrs[MPTCP_ATTR_DADDR4] || ++ !info->attrs[MPTCP_ATTR_DPORT]) { ++ goto create_failed; ++ } else { ++ rem.addr.s_addr = ++ nla_get_u32(info->attrs[MPTCP_ATTR_DADDR4]); ++ rem.port = ++ ntohs(nla_get_u16(info->attrs[MPTCP_ATTR_DPORT])); ++ } ++ ++ if (!info->attrs[MPTCP_ATTR_SADDR4]) { ++ bool found = false; ++ ++ mptcp_for_each_bit_set(priv->loc4_bits, i) { ++ if (priv->locaddr4[i].loc4_id == loc_id) { ++ loc.addr = priv->locaddr4[i].addr; ++ loc.low_prio = ++ priv->locaddr4[i].low_prio; ++ loc.if_idx = ++ priv->locaddr4[i].if_idx; ++ found = true; ++ break; ++ } ++ } ++ ++ if (!found) ++ goto create_failed; ++ } else { ++ loc.addr.s_addr = ++ nla_get_u32(info->attrs[MPTCP_ATTR_SADDR4]); ++ loc.low_prio = backup; ++ loc.if_idx = if_idx; ++ } ++ ++ ret = __mptcp_init4_subsockets(meta_sk, &loc, sport, &rem, ++ &subsk); ++ if (ret < 0) ++ goto unlock; ++ break; ++ } ++#if IS_ENABLED(CONFIG_IPV6) ++ case AF_INET6: { ++ struct mptcp_rem6 rem = { ++ .rem6_id = rem_id, ++ }; ++ struct mptcp_loc6 loc = { ++ .loc6_id = loc_id, ++ }; ++ ++ if (!info->attrs[MPTCP_ATTR_DADDR6] || ++ !info->attrs[MPTCP_ATTR_DPORT]) { ++ goto create_failed; ++ } else { ++ rem.addr = *(struct in6_addr *) ++ nla_data(info->attrs[MPTCP_ATTR_DADDR6]); ++ rem.port = ++ ntohs(nla_get_u16(info->attrs[MPTCP_ATTR_DPORT])); ++ } ++ ++ if (!info->attrs[MPTCP_ATTR_SADDR6]) { ++ bool found = false; ++ ++ mptcp_for_each_bit_set(priv->loc6_bits, i) { ++ if (priv->locaddr6[i].loc6_id == loc_id) { ++ loc.addr = priv->locaddr6[i].addr; ++ loc.low_prio = ++ priv->locaddr6[i].low_prio; ++ loc.if_idx = ++ priv->locaddr6[i].if_idx; ++ ++ found = true; ++ break; ++ } ++ } ++ ++ if (!found) ++ goto create_failed; ++ } else { ++ loc.addr = *(struct in6_addr *) ++ nla_data(info->attrs[MPTCP_ATTR_SADDR6]); ++ loc.low_prio = backup; ++ loc.if_idx = if_idx; ++ } ++ ++ ret = __mptcp_init6_subsockets(meta_sk, &loc, sport, &rem, ++ &subsk); ++ if (ret < 0) ++ goto unlock; ++ break; ++ } ++#endif ++ default: ++ goto create_failed; ++ } ++ ++unlock: ++ release_sock(meta_sk); ++ mutex_unlock(&mpcb->mpcb_mutex); ++ sock_put(meta_sk); ++ return ret; ++ ++create_failed: ++ ret = -EINVAL; ++ goto unlock; ++} ++ ++static struct sock * ++mptcp_nl_subsk_lookup(struct mptcp_cb *mpcb, struct nlattr **attrs) ++{ ++ struct sock *sk; ++ struct mptcp_tcp_sock *mptcp; ++ struct hlist_node *tmp; ++ u16 family; ++ __be16 sport, dport; ++ ++ if (!attrs[MPTCP_ATTR_FAMILY] || !attrs[MPTCP_ATTR_SPORT] || ++ !attrs[MPTCP_ATTR_DPORT]) ++ goto exit; ++ ++ family = nla_get_u16(attrs[MPTCP_ATTR_FAMILY]); ++ sport = htons(nla_get_u16(attrs[MPTCP_ATTR_SPORT])); ++ dport = htons(nla_get_u16(attrs[MPTCP_ATTR_DPORT])); ++ ++ switch (family) { ++ case AF_INET: { ++ __be32 saddr, daddr; ++ ++ if (!attrs[MPTCP_ATTR_SADDR4] || !attrs[MPTCP_ATTR_DADDR4]) ++ break; ++ ++ saddr = nla_get_u32(attrs[MPTCP_ATTR_SADDR4]); ++ daddr = nla_get_u32(attrs[MPTCP_ATTR_DADDR4]); ++ ++ mptcp_for_each_sub_safe(mpcb, mptcp, tmp) { ++ struct sock *subsk = mptcp_to_sock(mptcp); ++ struct inet_sock *isk = inet_sk(subsk); ++ ++ if (subsk->sk_family != AF_INET) ++ continue; ++ ++ if (isk->inet_saddr == saddr && ++ isk->inet_daddr == daddr && ++ isk->inet_sport == sport && ++ isk->inet_dport == dport) { ++ sk = subsk; ++ goto found; ++ } ++ } ++ break; ++ } ++#if IS_ENABLED(CONFIG_IPV6) ++ case AF_INET6: { ++ struct in6_addr saddr, daddr; ++ ++ if (!attrs[MPTCP_ATTR_SADDR6] || !attrs[MPTCP_ATTR_DADDR6]) ++ break; ++ ++ saddr = *(struct in6_addr *)nla_data(attrs[MPTCP_ATTR_SADDR6]); ++ daddr = *(struct in6_addr *)nla_data(attrs[MPTCP_ATTR_DADDR6]); ++ ++ mptcp_for_each_sub_safe(mpcb, mptcp, tmp) { ++ struct sock *subsk = mptcp_to_sock(mptcp); ++ struct inet_sock *isk = inet_sk(subsk); ++ struct ipv6_pinfo *np; ++ ++ if (subsk->sk_family != AF_INET6) ++ continue; ++ ++ np = inet6_sk(subsk); ++ if (ipv6_addr_equal(&saddr, &np->saddr) && ++ ipv6_addr_equal(&daddr, &subsk->sk_v6_daddr) && ++ isk->inet_sport == sport && ++ isk->inet_dport == dport) { ++ sk = subsk; ++ goto found; ++ } ++ } ++ break; ++ } ++#endif ++ } ++ ++exit: ++ sk = NULL; ++found: ++ return sk; ++} ++ ++static int ++mptcp_nl_genl_destroy(struct sk_buff *skb, struct genl_info *info) ++{ ++ struct sock *meta_sk, *subsk; ++ struct mptcp_cb *mpcb; ++ int ret = 0; ++ u32 token; ++ ++ if (!info->attrs[MPTCP_ATTR_TOKEN]) ++ return -EINVAL; ++ ++ token = nla_get_u32(info->attrs[MPTCP_ATTR_TOKEN]); ++ ++ meta_sk = mptcp_hash_find(genl_info_net(info), token); ++ if (!meta_sk) ++ return -EINVAL; ++ ++ mpcb = tcp_sk(meta_sk)->mpcb; ++ ++ mutex_lock(&mpcb->mpcb_mutex); ++ lock_sock_nested(meta_sk, SINGLE_DEPTH_NESTING); ++ ++ subsk = mptcp_nl_subsk_lookup(mpcb, info->attrs); ++ if (subsk) { ++ local_bh_disable(); ++ mptcp_reinject_data(subsk, 0); ++ mptcp_send_reset(subsk); ++ local_bh_enable(); ++ } else { ++ ret = -EINVAL; ++ } ++ ++ release_sock(meta_sk); ++ mutex_unlock(&mpcb->mpcb_mutex); ++ sock_put(meta_sk); ++ return ret; ++} ++ ++static int ++mptcp_nl_genl_conn_exists(struct sk_buff *skb, struct genl_info *info) ++{ ++ struct sock *meta_sk; ++ u32 token; ++ ++ if (!info->attrs[MPTCP_ATTR_TOKEN]) ++ return -EINVAL; ++ ++ token = nla_get_u32(info->attrs[MPTCP_ATTR_TOKEN]); ++ ++ meta_sk = mptcp_hash_find(genl_info_net(info), token); ++ if (!meta_sk) ++ return -ENOTCONN; ++ ++ sock_put(meta_sk); ++ return 0; ++} ++ ++static int ++mptcp_nl_genl_priority(struct sk_buff *skb, struct genl_info *info) ++{ ++ struct sock *meta_sk, *subsk; ++ struct mptcp_cb *mpcb; ++ int ret = 0; ++ u32 token; ++ u8 backup = 0; ++ ++ if (!info->attrs[MPTCP_ATTR_TOKEN]) ++ return -EINVAL; ++ ++ token = nla_get_u32(info->attrs[MPTCP_ATTR_TOKEN]); ++ if (info->attrs[MPTCP_ATTR_BACKUP]) ++ backup = nla_get_u8(info->attrs[MPTCP_ATTR_BACKUP]); ++ ++ meta_sk = mptcp_hash_find(genl_info_net(info), token); ++ if (!meta_sk) ++ return -EINVAL; ++ ++ mpcb = tcp_sk(meta_sk)->mpcb; ++ ++ mutex_lock(&mpcb->mpcb_mutex); ++ lock_sock_nested(meta_sk, SINGLE_DEPTH_NESTING); ++ ++ subsk = mptcp_nl_subsk_lookup(mpcb, info->attrs); ++ if (subsk) { ++ tcp_sk(subsk)->mptcp->send_mp_prio = 1; ++ tcp_sk(subsk)->mptcp->low_prio = !!backup; ++ ++ local_bh_disable(); ++ if (mptcp_sk_can_send_ack(subsk)) ++ tcp_send_ack(subsk); ++ else ++ ret = -ENOTCONN; ++ local_bh_enable(); ++ } else { ++ ret = -EINVAL; ++ } ++ ++ release_sock(meta_sk); ++ mutex_unlock(&mpcb->mpcb_mutex); ++ sock_put(meta_sk); ++ return ret; ++} ++ ++static int ++mptcp_nl_genl_set_filter(struct sk_buff *skb, struct genl_info *info) ++{ ++ u16 flags; ++ ++ if (!info->attrs[MPTCP_ATTR_FLAGS]) ++ return -EINVAL; ++ ++ flags = nla_get_u16(info->attrs[MPTCP_ATTR_FLAGS]); ++ ++ /* Only want to receive events that correspond to these flags */ ++ mptcp_nl_event_filter = ~flags; ++ ++ return 0; ++} ++ ++static struct genl_ops mptcp_genl_ops[] = { ++ { ++ .cmd = MPTCP_CMD_ANNOUNCE, ++ .doit = mptcp_nl_genl_announce, ++ .flags = GENL_ADMIN_PERM, ++ }, ++ { ++ .cmd = MPTCP_CMD_REMOVE, ++ .doit = mptcp_nl_genl_remove, ++ .flags = GENL_ADMIN_PERM, ++ }, ++ { ++ .cmd = MPTCP_CMD_SUB_CREATE, ++ .doit = mptcp_nl_genl_create, ++ .flags = GENL_ADMIN_PERM, ++ }, ++ { ++ .cmd = MPTCP_CMD_SUB_DESTROY, ++ .doit = mptcp_nl_genl_destroy, ++ .flags = GENL_ADMIN_PERM, ++ }, ++ { ++ .cmd = MPTCP_CMD_SUB_PRIORITY, ++ .doit = mptcp_nl_genl_priority, ++ .flags = GENL_ADMIN_PERM, ++ }, ++ { ++ .cmd = MPTCP_CMD_SET_FILTER, ++ .doit = mptcp_nl_genl_set_filter, ++ .flags = GENL_ADMIN_PERM, ++ }, ++ { ++ .cmd = MPTCP_CMD_EXIST, ++ .doit = mptcp_nl_genl_conn_exists, ++ .flags = GENL_ADMIN_PERM, ++ }, ++}; ++ ++static struct mptcp_pm_ops mptcp_nl_pm_ops = { ++ .new_session = mptcp_nl_pm_new_session, ++ .close_session = mptcp_nl_pm_close_session, ++ .fully_established = mptcp_nl_pm_fully_established, ++ .established_subflow = mptcp_nl_pm_established_subflow, ++ .delete_subflow = mptcp_nl_pm_delete_subflow, ++ .add_raddr = mptcp_nl_pm_add_raddr, ++ .rem_raddr = mptcp_nl_pm_rem_raddr, ++ .get_local_id = mptcp_nl_pm_get_local_id, ++ .addr_signal = mptcp_nl_pm_addr_signal, ++ .prio_changed = mptcp_nl_pm_prio_changed, ++ .name = "netlink", ++ .owner = THIS_MODULE, ++}; ++ ++static struct genl_family mptcp_genl_family = { ++ .hdrsize = 0, ++ .name = MPTCP_GENL_NAME, ++ .version = MPTCP_GENL_VER, ++ .maxattr = MPTCP_ATTR_MAX, ++ .policy = mptcp_nl_genl_policy, ++ .netnsok = true, ++ .module = THIS_MODULE, ++ .ops = mptcp_genl_ops, ++ .n_ops = ARRAY_SIZE(mptcp_genl_ops), ++ .mcgrps = mptcp_mcgrps, ++ .n_mcgrps = ARRAY_SIZE(mptcp_mcgrps), ++}; ++ ++static int __init ++mptcp_nl_init(void) ++{ ++ int ret; ++ ++ BUILD_BUG_ON(sizeof(struct mptcp_nl_priv) > MPTCP_PM_SIZE); ++ ++ ret = genl_register_family(&mptcp_genl_family); ++ if (ret) ++ goto out_genl; ++ ++ ret = mptcp_register_path_manager(&mptcp_nl_pm_ops); ++ if (ret) ++ goto out_pm; ++ ++ return 0; ++out_pm: ++ genl_unregister_family(&mptcp_genl_family); ++out_genl: ++ return ret; ++} ++ ++static void __exit ++mptcp_nl_exit(void) ++{ ++ mptcp_unregister_path_manager(&mptcp_nl_pm_ops); ++ genl_unregister_family(&mptcp_genl_family); ++} ++ ++module_init(mptcp_nl_init); ++module_exit(mptcp_nl_exit); ++ ++MODULE_AUTHOR("Gregory Detal "); ++MODULE_LICENSE("GPL"); ++MODULE_DESCRIPTION("MPTCP netlink-based path manager"); ++MODULE_ALIAS_GENL_FAMILY(MPTCP_GENL_NAME); +diff --git a/net/mptcp/mptcp_olia.c b/net/mptcp/mptcp_olia.c +new file mode 100644 +index 000000000000..c44eb9208581 +--- /dev/null ++++ b/net/mptcp/mptcp_olia.c +@@ -0,0 +1,318 @@ ++/* ++ * MPTCP implementation - OPPORTUNISTIC LINKED INCREASES CONGESTION CONTROL: ++ * ++ * Algorithm design: ++ * Ramin Khalili ++ * Nicolas Gast ++ * Jean-Yves Le Boudec ++ * ++ * Implementation: ++ * Ramin Khalili ++ * ++ * Ported to the official MPTCP-kernel: ++ * Christoph Paasch ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License ++ * as published by the Free Software Foundation; either version ++ * 2 of the License, or (at your option) any later version. ++ */ ++ ++ ++#include ++#include ++ ++#include ++ ++static int scale = 10; ++ ++struct mptcp_olia { ++ u32 mptcp_loss1; ++ u32 mptcp_loss2; ++ u32 mptcp_loss3; ++ int epsilon_num; ++ u32 epsilon_den; ++ int mptcp_snd_cwnd_cnt; ++}; ++ ++static inline int mptcp_olia_sk_can_send(const struct sock *sk) ++{ ++ return mptcp_sk_can_send(sk) && tcp_sk(sk)->srtt_us; ++} ++ ++static inline u64 mptcp_olia_scale(u64 val, int scale) ++{ ++ return (u64) val << scale; ++} ++ ++/* take care of artificially inflate (see RFC5681) ++ * of cwnd during fast-retransmit phase ++ */ ++static u32 mptcp_get_crt_cwnd(struct sock *sk) ++{ ++ const struct inet_connection_sock *icsk = inet_csk(sk); ++ ++ if (icsk->icsk_ca_state == TCP_CA_Recovery) ++ return tcp_sk(sk)->snd_ssthresh; ++ else ++ return tcp_sk(sk)->snd_cwnd; ++} ++ ++/* return the dominator of the first term of the increasing term */ ++static u64 mptcp_get_rate(const struct mptcp_cb *mpcb , u32 path_rtt) ++{ ++ struct mptcp_tcp_sock *mptcp; ++ u64 rate = 1; /* We have to avoid a zero-rate because it is used as a divisor */ ++ ++ mptcp_for_each_sub(mpcb, mptcp) { ++ struct sock *sk = mptcp_to_sock(mptcp); ++ struct tcp_sock *tp = tcp_sk(sk); ++ u64 scaled_num; ++ u32 tmp_cwnd; ++ ++ if (!mptcp_olia_sk_can_send(sk)) ++ continue; ++ ++ tmp_cwnd = mptcp_get_crt_cwnd(sk); ++ scaled_num = mptcp_olia_scale(tmp_cwnd, scale) * path_rtt; ++ rate += div_u64(scaled_num , tp->srtt_us); ++ } ++ rate *= rate; ++ return rate; ++} ++ ++/* find the maximum cwnd, used to find set M */ ++static u32 mptcp_get_max_cwnd(const struct mptcp_cb *mpcb) ++{ ++ struct mptcp_tcp_sock *mptcp; ++ u32 best_cwnd = 0; ++ ++ mptcp_for_each_sub(mpcb, mptcp) { ++ struct sock *sk = mptcp_to_sock(mptcp); ++ u32 tmp_cwnd; ++ ++ if (!mptcp_olia_sk_can_send(sk)) ++ continue; ++ ++ tmp_cwnd = mptcp_get_crt_cwnd(sk); ++ if (tmp_cwnd > best_cwnd) ++ best_cwnd = tmp_cwnd; ++ } ++ return best_cwnd; ++} ++ ++static void mptcp_get_epsilon(const struct mptcp_cb *mpcb) ++{ ++ struct mptcp_tcp_sock *mptcp; ++ struct mptcp_olia *ca; ++ struct tcp_sock *tp; ++ struct sock *sk; ++ u64 tmp_int, tmp_rtt, best_int = 0, best_rtt = 1; ++ u32 max_cwnd, tmp_cwnd, established_cnt = 0; ++ u8 M = 0, B_not_M = 0; ++ ++ /* TODO - integrate this in the following loop - we just want to iterate once */ ++ ++ max_cwnd = mptcp_get_max_cwnd(mpcb); ++ ++ /* find the best path */ ++ mptcp_for_each_sub(mpcb, mptcp) { ++ sk = mptcp_to_sock(mptcp); ++ tp = tcp_sk(sk); ++ ca = inet_csk_ca(sk); ++ ++ if (!mptcp_olia_sk_can_send(sk)) ++ continue; ++ ++ established_cnt++; ++ ++ tmp_rtt = (u64)tp->srtt_us * tp->srtt_us; ++ /* TODO - check here and rename variables */ ++ tmp_int = max(ca->mptcp_loss3 - ca->mptcp_loss2, ++ ca->mptcp_loss2 - ca->mptcp_loss1); ++ ++ if ((u64)tmp_int * best_rtt >= (u64)best_int * tmp_rtt) { ++ best_rtt = tmp_rtt; ++ best_int = tmp_int; ++ } ++ } ++ ++ /* TODO - integrate this here in mptcp_get_max_cwnd and in the previous loop */ ++ /* find the size of M and B_not_M */ ++ mptcp_for_each_sub(mpcb, mptcp) { ++ sk = mptcp_to_sock(mptcp); ++ tp = tcp_sk(sk); ++ ca = inet_csk_ca(sk); ++ ++ if (!mptcp_olia_sk_can_send(sk)) ++ continue; ++ ++ tmp_cwnd = mptcp_get_crt_cwnd(sk); ++ if (tmp_cwnd == max_cwnd) { ++ M++; ++ } else { ++ tmp_rtt = (u64)tp->srtt_us * tp->srtt_us; ++ tmp_int = max(ca->mptcp_loss3 - ca->mptcp_loss2, ++ ca->mptcp_loss2 - ca->mptcp_loss1); ++ ++ if ((u64)tmp_int * best_rtt == (u64)best_int * tmp_rtt) ++ B_not_M++; ++ } ++ } ++ ++ /* check if the path is in M or B_not_M and set the value of epsilon accordingly */ ++ mptcp_for_each_sub(mpcb, mptcp) { ++ sk = mptcp_to_sock(mptcp); ++ tp = tcp_sk(sk); ++ ca = inet_csk_ca(sk); ++ ++ if (!mptcp_olia_sk_can_send(sk)) ++ continue; ++ ++ if (B_not_M == 0) { ++ ca->epsilon_num = 0; ++ ca->epsilon_den = 1; ++ } else { ++ tmp_rtt = (u64)tp->srtt_us * tp->srtt_us; ++ tmp_int = max(ca->mptcp_loss3 - ca->mptcp_loss2, ++ ca->mptcp_loss2 - ca->mptcp_loss1); ++ tmp_cwnd = mptcp_get_crt_cwnd(sk); ++ ++ if (tmp_cwnd < max_cwnd && ++ (u64)tmp_int * best_rtt == (u64)best_int * tmp_rtt) { ++ ca->epsilon_num = 1; ++ ca->epsilon_den = established_cnt * B_not_M; ++ } else if (tmp_cwnd == max_cwnd) { ++ ca->epsilon_num = -1; ++ ca->epsilon_den = established_cnt * M; ++ } else { ++ ca->epsilon_num = 0; ++ ca->epsilon_den = 1; ++ } ++ } ++ } ++} ++ ++/* setting the initial values */ ++static void mptcp_olia_init(struct sock *sk) ++{ ++ const struct tcp_sock *tp = tcp_sk(sk); ++ struct mptcp_olia *ca = inet_csk_ca(sk); ++ ++ if (mptcp(tp)) { ++ ca->mptcp_loss1 = tp->snd_una; ++ ca->mptcp_loss2 = tp->snd_una; ++ ca->mptcp_loss3 = tp->snd_una; ++ ca->mptcp_snd_cwnd_cnt = 0; ++ ca->epsilon_num = 0; ++ ca->epsilon_den = 1; ++ } ++} ++ ++/* updating inter-loss distance and ssthresh */ ++static void mptcp_olia_set_state(struct sock *sk, u8 new_state) ++{ ++ if (!mptcp(tcp_sk(sk))) ++ return; ++ ++ if (new_state == TCP_CA_Loss || ++ new_state == TCP_CA_Recovery || new_state == TCP_CA_CWR) { ++ struct mptcp_olia *ca = inet_csk_ca(sk); ++ ++ if (ca->mptcp_loss3 != ca->mptcp_loss2 && ++ !inet_csk(sk)->icsk_retransmits) { ++ ca->mptcp_loss1 = ca->mptcp_loss2; ++ ca->mptcp_loss2 = ca->mptcp_loss3; ++ } ++ } ++} ++ ++/* main algorithm */ ++static void mptcp_olia_cong_avoid(struct sock *sk, u32 ack, u32 acked) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct mptcp_olia *ca = inet_csk_ca(sk); ++ const struct mptcp_cb *mpcb = tp->mpcb; ++ ++ u64 inc_num, inc_den, rate, cwnd_scaled; ++ ++ if (!mptcp(tp)) { ++ tcp_reno_cong_avoid(sk, ack, acked); ++ return; ++ } ++ ++ ca->mptcp_loss3 = tp->snd_una; ++ ++ if (!tcp_is_cwnd_limited(sk)) ++ return; ++ ++ /* slow start if it is in the safe area */ ++ if (tcp_in_slow_start(tp)) { ++ tcp_slow_start(tp, acked); ++ return; ++ } ++ ++ mptcp_get_epsilon(mpcb); ++ rate = mptcp_get_rate(mpcb, tp->srtt_us); ++ cwnd_scaled = mptcp_olia_scale(tp->snd_cwnd, scale); ++ inc_den = ca->epsilon_den * tp->snd_cwnd * rate ? : 1; ++ ++ /* calculate the increasing term, scaling is used to reduce the rounding effect */ ++ if (ca->epsilon_num == -1) { ++ if (ca->epsilon_den * cwnd_scaled * cwnd_scaled < rate) { ++ inc_num = rate - ca->epsilon_den * ++ cwnd_scaled * cwnd_scaled; ++ ca->mptcp_snd_cwnd_cnt -= div64_u64( ++ mptcp_olia_scale(inc_num , scale) , inc_den); ++ } else { ++ inc_num = ca->epsilon_den * ++ cwnd_scaled * cwnd_scaled - rate; ++ ca->mptcp_snd_cwnd_cnt += div64_u64( ++ mptcp_olia_scale(inc_num , scale) , inc_den); ++ } ++ } else { ++ inc_num = ca->epsilon_num * rate + ++ ca->epsilon_den * cwnd_scaled * cwnd_scaled; ++ ca->mptcp_snd_cwnd_cnt += div64_u64( ++ mptcp_olia_scale(inc_num , scale) , inc_den); ++ } ++ ++ ++ if (ca->mptcp_snd_cwnd_cnt >= (1 << scale) - 1) { ++ if (tp->snd_cwnd < tp->snd_cwnd_clamp) ++ tp->snd_cwnd++; ++ ca->mptcp_snd_cwnd_cnt = 0; ++ } else if (ca->mptcp_snd_cwnd_cnt <= 0 - (1 << scale) + 1) { ++ tp->snd_cwnd = max((int) 1 , (int) tp->snd_cwnd - 1); ++ ca->mptcp_snd_cwnd_cnt = 0; ++ } ++} ++ ++static struct tcp_congestion_ops mptcp_olia = { ++ .init = mptcp_olia_init, ++ .ssthresh = tcp_reno_ssthresh, ++ .cong_avoid = mptcp_olia_cong_avoid, ++ .undo_cwnd = tcp_reno_undo_cwnd, ++ .set_state = mptcp_olia_set_state, ++ .owner = THIS_MODULE, ++ .name = "olia", ++}; ++ ++static int __init mptcp_olia_register(void) ++{ ++ BUILD_BUG_ON(sizeof(struct mptcp_olia) > ICSK_CA_PRIV_SIZE); ++ return tcp_register_congestion_control(&mptcp_olia); ++} ++ ++static void __exit mptcp_olia_unregister(void) ++{ ++ tcp_unregister_congestion_control(&mptcp_olia); ++} ++ ++module_init(mptcp_olia_register); ++module_exit(mptcp_olia_unregister); ++ ++MODULE_AUTHOR("Ramin Khalili, Nicolas Gast, Jean-Yves Le Boudec"); ++MODULE_LICENSE("GPL"); ++MODULE_DESCRIPTION("MPTCP COUPLED CONGESTION CONTROL"); ++MODULE_VERSION("0.1"); +diff --git a/net/mptcp/mptcp_output.c b/net/mptcp/mptcp_output.c +new file mode 100644 +index 000000000000..39eae2199802 +--- /dev/null ++++ b/net/mptcp/mptcp_output.c +@@ -0,0 +1,2009 @@ ++/* ++ * MPTCP implementation - Sending side ++ * ++ * Initial Design & Implementation: ++ * Sébastien Barré ++ * ++ * Current Maintainer & Author: ++ * Christoph Paasch ++ * ++ * Additional authors: ++ * Jaakko Korkeaniemi ++ * Gregory Detal ++ * Fabien Duchêne ++ * Andreas Seelinger ++ * Lavkesh Lahngir ++ * Andreas Ripke ++ * Vlad Dogaru ++ * Octavian Purdila ++ * John Ronan ++ * Catalin Nicutar ++ * Brandon Heller ++ * ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License ++ * as published by the Free Software Foundation; either version ++ * 2 of the License, or (at your option) any later version. ++ */ ++ ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++ ++static const int mptcp_dss_len = MPTCP_SUB_LEN_DSS_ALIGN + ++ MPTCP_SUB_LEN_ACK_ALIGN + ++ MPTCP_SUB_LEN_SEQ_ALIGN; ++ ++static inline int mptcp_sub_len_remove_addr(u16 bitfield) ++{ ++ unsigned int c; ++ for (c = 0; bitfield; c++) ++ bitfield &= bitfield - 1; ++ return MPTCP_SUB_LEN_REMOVE_ADDR + c - 1; ++} ++ ++int mptcp_sub_len_remove_addr_align(u16 bitfield) ++{ ++ return ALIGN(mptcp_sub_len_remove_addr(bitfield), 4); ++} ++EXPORT_SYMBOL(mptcp_sub_len_remove_addr_align); ++ ++/* get the data-seq and end-data-seq and store them again in the ++ * tcp_skb_cb ++ */ ++static bool mptcp_reconstruct_mapping(struct sk_buff *skb) ++{ ++ const struct mp_dss *mpdss = (struct mp_dss *)TCP_SKB_CB(skb)->dss; ++ __be32 *p32; ++ __be16 *p16; ++ ++ if (!mptcp_is_data_seq(skb)) ++ return false; ++ ++ if (!mpdss->M) ++ return false; ++ ++ /* Move the pointer to the data-seq */ ++ p32 = (__be32 *)mpdss; ++ p32++; ++ if (mpdss->A) { ++ p32++; ++ if (mpdss->a) ++ p32++; ++ } ++ ++ TCP_SKB_CB(skb)->seq = ntohl(*p32); ++ ++ /* Get the data_len to calculate the end_data_seq */ ++ p32++; ++ p32++; ++ p16 = (__be16 *)p32; ++ TCP_SKB_CB(skb)->end_seq = ntohs(*p16) + TCP_SKB_CB(skb)->seq; ++ ++ return true; ++} ++ ++static bool mptcp_is_reinjected(const struct sk_buff *skb) ++{ ++ return TCP_SKB_CB(skb)->mptcp_flags & MPTCP_REINJECT; ++} ++ ++static void mptcp_find_and_set_pathmask(struct sock *meta_sk, struct sk_buff *skb) ++{ ++ struct rb_node **p = &meta_sk->tcp_rtx_queue.rb_node; ++ struct rb_node *parent; ++ struct sk_buff *skb_it; ++ ++ while (*p) { ++ parent = *p; ++ skb_it = rb_to_skb(parent); ++ if (before(TCP_SKB_CB(skb)->seq, TCP_SKB_CB(skb_it)->seq)) { ++ p = &parent->rb_left; ++ continue; ++ } ++ if (after(TCP_SKB_CB(skb)->seq, TCP_SKB_CB(skb_it)->seq)) { ++ p = &parent->rb_right; ++ continue; ++ } ++ ++ TCP_SKB_CB(skb)->path_mask = TCP_SKB_CB(skb_it)->path_mask; ++ break; ++ } ++} ++ ++/* Reinject data from one TCP subflow to the meta_sk. If sk == NULL, we are ++ * coming from the meta-retransmit-timer ++ */ ++static void __mptcp_reinject_data(struct sk_buff *orig_skb, struct sock *meta_sk, ++ struct sock *sk, int clone_it, ++ enum tcp_queue tcp_queue) ++{ ++ struct sk_buff *skb, *skb1; ++ const struct tcp_sock *meta_tp = tcp_sk(meta_sk); ++ struct mptcp_cb *mpcb = meta_tp->mpcb; ++ u32 seq, end_seq; ++ ++ if (clone_it) { ++ /* pskb_copy is necessary here, because the TCP/IP-headers ++ * will be changed when it's going to be reinjected on another ++ * subflow. ++ */ ++ tcp_skb_tsorted_save(orig_skb) { ++ skb = pskb_copy_for_clone(orig_skb, GFP_ATOMIC); ++ } tcp_skb_tsorted_restore(orig_skb); ++ } else { ++ if (tcp_queue == TCP_FRAG_IN_WRITE_QUEUE) { ++ __skb_unlink(orig_skb, &sk->sk_write_queue); ++ } else { ++ list_del(&orig_skb->tcp_tsorted_anchor); ++ tcp_rtx_queue_unlink(orig_skb, sk); ++ INIT_LIST_HEAD(&orig_skb->tcp_tsorted_anchor); ++ } ++ sock_set_flag(sk, SOCK_QUEUE_SHRUNK); ++ sk->sk_wmem_queued -= orig_skb->truesize; ++ sk_mem_uncharge(sk, orig_skb->truesize); ++ skb = orig_skb; ++ } ++ if (unlikely(!skb)) ++ return; ++ ++ /* Make sure that this list is clean */ ++ tcp_skb_tsorted_anchor_cleanup(skb); ++ ++ if (sk && !mptcp_reconstruct_mapping(skb)) { ++ __kfree_skb(skb); ++ return; ++ } ++ ++ skb->sk = meta_sk; ++ ++ /* Reset subflow-specific TCP control-data */ ++ TCP_SKB_CB(skb)->sacked = 0; ++ TCP_SKB_CB(skb)->tcp_flags &= (TCPHDR_ACK | TCPHDR_PSH); ++ ++ /* If it reached already the destination, we don't have to reinject it */ ++ if (!after(TCP_SKB_CB(skb)->end_seq, meta_tp->snd_una)) { ++ __kfree_skb(skb); ++ return; ++ } ++ ++ /* Only reinject segments that are fully covered by the mapping */ ++ if (skb->len + (mptcp_is_data_fin(skb) ? 1 : 0) != ++ TCP_SKB_CB(skb)->end_seq - TCP_SKB_CB(skb)->seq) { ++ struct rb_node *parent, **p = &meta_sk->tcp_rtx_queue.rb_node; ++ u32 end_seq = TCP_SKB_CB(skb)->end_seq; ++ u32 seq = TCP_SKB_CB(skb)->seq; ++ ++ __kfree_skb(skb); ++ ++ /* Ok, now we have to look for the full mapping in the meta ++ * send-queue :S ++ */ ++ ++ /* First, find the first skb that covers us */ ++ while (*p) { ++ parent = *p; ++ skb = rb_to_skb(parent); ++ ++ /* Not yet at the mapping? */ ++ if (!after(end_seq, TCP_SKB_CB(skb)->seq)) { ++ p = &parent->rb_left; ++ continue; ++ } ++ ++ if (!before(seq, TCP_SKB_CB(skb)->end_seq)) { ++ p = &parent->rb_right; ++ continue; ++ } ++ ++ break; ++ } ++ ++ if (*p) { ++ /* We found it, now let's reinject everything */ ++ skb = rb_to_skb(*p); ++ ++ skb_rbtree_walk_from(skb) { ++ if (after(TCP_SKB_CB(skb)->end_seq, end_seq)) ++ return; ++ __mptcp_reinject_data(skb, meta_sk, NULL, 1, ++ TCP_FRAG_IN_RTX_QUEUE); ++ } ++ } ++ return; ++ } ++ ++ /* Segment goes back to the MPTCP-layer. So, we need to zero the ++ * path_mask/dss. ++ */ ++ memset(TCP_SKB_CB(skb)->dss, 0 , mptcp_dss_len); ++ ++ /* We need to find out the path-mask from the meta-write-queue ++ * to properly select a subflow. ++ */ ++ mptcp_find_and_set_pathmask(meta_sk, skb); ++ ++ /* If it's empty, just add */ ++ if (skb_queue_empty(&mpcb->reinject_queue)) { ++ skb_queue_head(&mpcb->reinject_queue, skb); ++ return; ++ } ++ ++ /* Find place to insert skb - or even we can 'drop' it, as the ++ * data is already covered by other skb's in the reinject-queue. ++ * ++ * This is inspired by code from tcp_data_queue. ++ */ ++ ++ skb1 = skb_peek_tail(&mpcb->reinject_queue); ++ seq = TCP_SKB_CB(skb)->seq; ++ while (1) { ++ if (!after(TCP_SKB_CB(skb1)->seq, seq)) ++ break; ++ if (skb_queue_is_first(&mpcb->reinject_queue, skb1)) { ++ skb1 = NULL; ++ break; ++ } ++ skb1 = skb_queue_prev(&mpcb->reinject_queue, skb1); ++ } ++ ++ /* Do skb overlap to previous one? */ ++ end_seq = TCP_SKB_CB(skb)->end_seq; ++ if (skb1 && before(seq, TCP_SKB_CB(skb1)->end_seq)) { ++ if (!after(end_seq, TCP_SKB_CB(skb1)->end_seq)) { ++ /* All the bits are present. Don't reinject */ ++ __kfree_skb(skb); ++ return; ++ } ++ if (seq == TCP_SKB_CB(skb1)->seq) { ++ if (skb_queue_is_first(&mpcb->reinject_queue, skb1)) ++ skb1 = NULL; ++ else ++ skb1 = skb_queue_prev(&mpcb->reinject_queue, skb1); ++ } ++ } ++ if (!skb1) ++ __skb_queue_head(&mpcb->reinject_queue, skb); ++ else ++ __skb_queue_after(&mpcb->reinject_queue, skb1, skb); ++ ++ /* And clean segments covered by new one as whole. */ ++ while (!skb_queue_is_last(&mpcb->reinject_queue, skb)) { ++ skb1 = skb_queue_next(&mpcb->reinject_queue, skb); ++ ++ if (!after(end_seq, TCP_SKB_CB(skb1)->seq)) ++ break; ++ ++ if (before(end_seq, TCP_SKB_CB(skb1)->end_seq)) ++ break; ++ ++ __skb_unlink(skb1, &mpcb->reinject_queue); ++ __kfree_skb(skb1); ++ } ++ return; ++} ++ ++/* Inserts data into the reinject queue */ ++void mptcp_reinject_data(struct sock *sk, int clone_it) ++{ ++ struct sock *meta_sk = mptcp_meta_sk(sk); ++ struct sk_buff *skb_it, *tmp; ++ enum tcp_queue tcp_queue; ++ ++ /* It has already been closed - there is really no point in reinjecting */ ++ if (meta_sk->sk_state == TCP_CLOSE) ++ return; ++ ++ skb_queue_walk_safe(&sk->sk_write_queue, skb_it, tmp) { ++ struct tcp_skb_cb *tcb = TCP_SKB_CB(skb_it); ++ /* Subflow syn's and fin's are not reinjected. ++ * ++ * As well as empty subflow-fins with a data-fin. ++ * They are reinjected below (without the subflow-fin-flag) ++ */ ++ if (tcb->tcp_flags & TCPHDR_SYN || ++ (tcb->tcp_flags & TCPHDR_FIN && !mptcp_is_data_fin(skb_it)) || ++ (tcb->tcp_flags & TCPHDR_FIN && mptcp_is_data_fin(skb_it) && !skb_it->len)) ++ continue; ++ ++ if (mptcp_is_reinjected(skb_it)) ++ continue; ++ ++ tcb->mptcp_flags |= MPTCP_REINJECT; ++ __mptcp_reinject_data(skb_it, meta_sk, sk, clone_it, ++ TCP_FRAG_IN_WRITE_QUEUE); ++ } ++ ++ skb_it = tcp_rtx_queue_head(sk); ++ skb_rbtree_walk_from_safe(skb_it, tmp) { ++ struct tcp_skb_cb *tcb = TCP_SKB_CB(skb_it); ++ ++ /* Subflow syn's and fin's are not reinjected. ++ * ++ * As well as empty subflow-fins with a data-fin. ++ * They are reinjected below (without the subflow-fin-flag) ++ */ ++ if (tcb->tcp_flags & TCPHDR_SYN || ++ (tcb->tcp_flags & TCPHDR_FIN && !mptcp_is_data_fin(skb_it)) || ++ (tcb->tcp_flags & TCPHDR_FIN && mptcp_is_data_fin(skb_it) && !skb_it->len)) ++ continue; ++ ++ if (mptcp_is_reinjected(skb_it)) ++ continue; ++ ++ tcb->mptcp_flags |= MPTCP_REINJECT; ++ __mptcp_reinject_data(skb_it, meta_sk, sk, clone_it, ++ TCP_FRAG_IN_RTX_QUEUE); ++ } ++ ++ skb_it = tcp_write_queue_tail(meta_sk); ++ tcp_queue = TCP_FRAG_IN_WRITE_QUEUE; ++ ++ if (!skb_it) { ++ skb_it = skb_rb_last(&meta_sk->tcp_rtx_queue); ++ tcp_queue = TCP_FRAG_IN_RTX_QUEUE; ++ } ++ ++ /* If sk has sent the empty data-fin, we have to reinject it too. */ ++ if (skb_it && mptcp_is_data_fin(skb_it) && skb_it->len == 0 && ++ TCP_SKB_CB(skb_it)->path_mask & mptcp_pi_to_flag(tcp_sk(sk)->mptcp->path_index)) { ++ __mptcp_reinject_data(skb_it, meta_sk, NULL, 1, tcp_queue); ++ } ++ ++ tcp_sk(sk)->pf = 1; ++ ++ mptcp_push_pending_frames(meta_sk); ++} ++EXPORT_SYMBOL(mptcp_reinject_data); ++ ++static void mptcp_combine_dfin(const struct sk_buff *skb, ++ const struct sock *meta_sk, ++ struct sock *subsk) ++{ ++ const struct tcp_sock *meta_tp = tcp_sk(meta_sk); ++ const struct mptcp_cb *mpcb = meta_tp->mpcb; ++ ++ /* In infinite mapping we always try to combine */ ++ if (mpcb->infinite_mapping_snd) ++ goto combine; ++ ++ /* Don't combine, if they didn't combine when closing - otherwise we end ++ * up in TIME_WAIT, even if our app is smart enough to avoid it. ++ */ ++ if (!mptcp_sk_can_recv(meta_sk) && !mpcb->dfin_combined) ++ return; ++ ++ /* Don't combine if there is still outstanding data that remains to be ++ * DATA_ACKed, because otherwise we may never be able to deliver this. ++ */ ++ if (meta_tp->snd_una != TCP_SKB_CB(skb)->seq) ++ return; ++ ++combine: ++ if (tcp_close_state(subsk)) { ++ subsk->sk_shutdown |= SEND_SHUTDOWN; ++ TCP_SKB_CB(skb)->tcp_flags |= TCPHDR_FIN; ++ } ++} ++ ++static int mptcp_write_dss_mapping(const struct tcp_sock *tp, const struct sk_buff *skb, ++ __be32 *ptr) ++{ ++ const struct tcp_skb_cb *tcb = TCP_SKB_CB(skb); ++ __be32 *start = ptr; ++ __u16 data_len; ++ ++ *ptr++ = htonl(tcb->seq); /* data_seq */ ++ ++ /* If it's a non-data DATA_FIN, we set subseq to 0 (draft v7) */ ++ if (mptcp_is_data_fin(skb) && skb->len == 0) ++ *ptr++ = 0; /* subseq */ ++ else ++ *ptr++ = htonl(tp->write_seq - tp->mptcp->snt_isn); /* subseq */ ++ ++ if (tcb->mptcp_flags & MPTCPHDR_INF) ++ data_len = 0; ++ else ++ data_len = tcb->end_seq - tcb->seq; ++ ++ if (tp->mpcb->dss_csum && data_len) { ++ __sum16 *p16 = (__sum16 *)ptr; ++ __be32 hdseq = mptcp_get_highorder_sndbits(skb, tp->mpcb); ++ __wsum csum; ++ ++ *ptr = htonl(((data_len) << 16) | ++ (TCPOPT_EOL << 8) | ++ (TCPOPT_EOL)); ++ csum = csum_partial(ptr - 2, 12, skb->csum); ++ p16++; ++ *p16++ = csum_fold(csum_partial(&hdseq, sizeof(hdseq), csum)); ++ } else { ++ *ptr++ = htonl(((data_len) << 16) | ++ (TCPOPT_NOP << 8) | ++ (TCPOPT_NOP)); ++ } ++ ++ return ptr - start; ++} ++ ++static int mptcp_write_dss_data_ack(const struct tcp_sock *tp, const struct sk_buff *skb, ++ __be32 *ptr) ++{ ++ struct mp_dss *mdss = (struct mp_dss *)ptr; ++ __be32 *start = ptr; ++ ++ mdss->kind = TCPOPT_MPTCP; ++ mdss->sub = MPTCP_SUB_DSS; ++ mdss->rsv1 = 0; ++ mdss->rsv2 = 0; ++ mdss->F = mptcp_is_data_fin(skb) ? 1 : 0; ++ mdss->m = 0; ++ mdss->M = mptcp_is_data_seq(skb) ? 1 : 0; ++ mdss->a = 0; ++ mdss->A = 1; ++ mdss->len = mptcp_sub_len_dss(mdss, tp->mpcb->dss_csum); ++ ptr++; ++ ++ *ptr++ = htonl(mptcp_meta_tp(tp)->rcv_nxt); ++ ++ return ptr - start; ++} ++ ++/* RFC6824 states that once a particular subflow mapping has been sent ++ * out it must never be changed. However, packets may be split while ++ * they are in the retransmission queue (due to SACK or ACKs) and that ++ * arguably means that we would change the mapping (e.g. it splits it, ++ * our sends out a subset of the initial mapping). ++ * ++ * Furthermore, the skb checksum is not always preserved across splits ++ * (e.g. mptcp_fragment) which would mean that we need to recompute ++ * the DSS checksum in this case. ++ * ++ * To avoid this we save the initial DSS mapping which allows us to ++ * send the same DSS mapping even for fragmented retransmits. ++ */ ++static void mptcp_save_dss_data_seq(const struct tcp_sock *tp, struct sk_buff *skb) ++{ ++ struct tcp_skb_cb *tcb = TCP_SKB_CB(skb); ++ __be32 *ptr = (__be32 *)tcb->dss; ++ ++ tcb->mptcp_flags |= MPTCPHDR_SEQ; ++ ++ ptr += mptcp_write_dss_data_ack(tp, skb, ptr); ++ ptr += mptcp_write_dss_mapping(tp, skb, ptr); ++} ++ ++/* Write the MP_CAPABLE with data-option */ ++static int mptcp_write_mpcapable_data(const struct tcp_sock *tp, ++ struct sk_buff *skb, ++ __be32 *ptr) ++{ ++ struct mp_capable *mpc = (struct mp_capable *)ptr; ++ u8 length; ++ ++ if (tp->mpcb->dss_csum) ++ length = MPTCPV1_SUB_LEN_CAPABLE_DATA_CSUM; ++ else ++ length = MPTCPV1_SUB_LEN_CAPABLE_DATA; ++ ++ mpc->kind = TCPOPT_MPTCP; ++ mpc->len = length; ++ mpc->sub = MPTCP_SUB_CAPABLE; ++ mpc->ver = MPTCP_VERSION_1; ++ mpc->a = tp->mpcb->dss_csum; ++ mpc->b = 0; ++ mpc->rsv = 0; ++ mpc->h = 1; ++ ++ ptr++; ++ memcpy(ptr, TCP_SKB_CB(skb)->dss, mptcp_dss_len); ++ ++ mpc->sender_key = tp->mpcb->mptcp_loc_key; ++ mpc->receiver_key = tp->mpcb->mptcp_rem_key; ++ ++ /* dss is in a union with inet_skb_parm and ++ * the IP layer expects zeroed IPCB fields. ++ */ ++ memset(TCP_SKB_CB(skb)->dss, 0, mptcp_dss_len); ++ ++ return MPTCPV1_SUB_LEN_CAPABLE_DATA_ALIGN / sizeof(*ptr); ++} ++ ++/* Write the saved DSS mapping to the header */ ++static int mptcp_write_dss_data_seq(const struct tcp_sock *tp, struct sk_buff *skb, ++ __be32 *ptr) ++{ ++ int length; ++ __be32 *start = ptr; ++ ++ if (tp->mpcb->rem_key_set) { ++ memcpy(ptr, TCP_SKB_CB(skb)->dss, mptcp_dss_len); ++ ++ /* update the data_ack */ ++ start[1] = htonl(mptcp_meta_tp(tp)->rcv_nxt); ++ ++ length = mptcp_dss_len / sizeof(*ptr); ++ } else { ++ memcpy(ptr, TCP_SKB_CB(skb)->dss, MPTCP_SUB_LEN_DSS_ALIGN); ++ ++ ptr++; ++ memcpy(ptr, TCP_SKB_CB(skb)->dss + 2, MPTCP_SUB_LEN_SEQ_ALIGN); ++ ++ length = (MPTCP_SUB_LEN_DSS_ALIGN + MPTCP_SUB_LEN_SEQ_ALIGN) / sizeof(*ptr); ++ } ++ ++ /* dss is in a union with inet_skb_parm and ++ * the IP layer expects zeroed IPCB fields. ++ */ ++ memset(TCP_SKB_CB(skb)->dss, 0 , mptcp_dss_len); ++ ++ return length; ++} ++ ++static bool mptcp_skb_entail(struct sock *sk, struct sk_buff *skb, int reinject) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ const struct sock *meta_sk = mptcp_meta_sk(sk); ++ struct mptcp_cb *mpcb = tp->mpcb; ++ struct tcp_skb_cb *tcb; ++ struct sk_buff *subskb = NULL; ++ ++ if (!reinject) ++ TCP_SKB_CB(skb)->mptcp_flags |= (mpcb->snd_hiseq_index ? ++ MPTCPHDR_SEQ64_INDEX : 0); ++ ++ tcp_skb_tsorted_save(skb) { ++ subskb = pskb_copy_for_clone(skb, GFP_ATOMIC); ++ } tcp_skb_tsorted_restore(skb); ++ if (!subskb) ++ return false; ++ ++ /* At the subflow-level we need to call again tcp_init_tso_segs. We ++ * force this, by setting pcount to 0. It has been set to 1 prior to ++ * the call to mptcp_skb_entail. ++ */ ++ tcp_skb_pcount_set(subskb, 0); ++ ++ TCP_SKB_CB(skb)->path_mask |= mptcp_pi_to_flag(tp->mptcp->path_index); ++ ++ /* Compute checksum */ ++ if (tp->mpcb->dss_csum) ++ subskb->csum = skb->csum = skb_checksum(skb, 0, skb->len, 0); ++ ++ tcb = TCP_SKB_CB(subskb); ++ ++ if (tp->mpcb->send_infinite_mapping && ++ !tp->mpcb->infinite_mapping_snd && ++ !before(tcb->seq, mptcp_meta_tp(tp)->snd_nxt)) { ++ tp->mptcp->fully_established = 1; ++ tp->mpcb->infinite_mapping_snd = 1; ++ tp->mptcp->infinite_cutoff_seq = tp->write_seq; ++ tcb->mptcp_flags |= MPTCPHDR_INF; ++ } ++ ++ if (mptcp_is_data_fin(subskb)) ++ mptcp_combine_dfin(subskb, meta_sk, sk); ++ ++ mptcp_save_dss_data_seq(tp, subskb); ++ ++ if (mpcb->send_mptcpv1_mpcapable) { ++ TCP_SKB_CB(subskb)->mptcp_flags |= MPTCPHDR_MPC_DATA; ++ mpcb->send_mptcpv1_mpcapable = 0; ++ } ++ ++ tcb->seq = tp->write_seq; ++ ++ /* Take into account seg len */ ++ tp->write_seq += subskb->len + ((tcb->tcp_flags & TCPHDR_FIN) ? 1 : 0); ++ tcb->end_seq = tp->write_seq; ++ ++ /* txstamp_ack is handled at the meta-level */ ++ tcb->txstamp_ack = 0; ++ ++ /* If it's a non-payload DATA_FIN (also no subflow-fin), the ++ * segment is not part of the subflow but on a meta-only-level. ++ */ ++ if (!mptcp_is_data_fin(subskb) || tcb->end_seq != tcb->seq) { ++ /* Make sure that this list is clean */ ++ INIT_LIST_HEAD(&subskb->tcp_tsorted_anchor); ++ ++ tcp_add_write_queue_tail(sk, subskb); ++ sk->sk_wmem_queued += subskb->truesize; ++ sk_mem_charge(sk, subskb->truesize); ++ } else { ++ /* Necessary to initialize for tcp_transmit_skb. mss of 1, as ++ * skb->len = 0 will force tso_segs to 1. ++ */ ++ tcp_init_tso_segs(subskb, 1); ++ ++ /* Empty data-fins are sent immediatly on the subflow */ ++ if (tcp_transmit_skb(sk, subskb, 0, GFP_ATOMIC)) ++ return false; ++ } ++ ++ if (!tp->mptcp->fully_established) { ++ tp->mptcp->second_packet = 1; ++ tp->mptcp->last_end_data_seq = TCP_SKB_CB(skb)->end_seq; ++ } ++ ++ return true; ++} ++ ++/* Fragment an skb and update the mptcp meta-data. Due to reinject, we ++ * might need to undo some operations done by tcp_fragment. ++ * ++ * Be careful, the skb may come from 3 different places: ++ * - The send-queue (tcp_queue == TCP_FRAG_IN_WRITE_QUEUE) ++ * - The retransmit-queue (tcp_queue == TCP_FRAG_IN_RTX_QUEUE) ++ * - The reinject-queue (reinject == -1) ++ */ ++static int mptcp_fragment(struct sock *meta_sk, enum tcp_queue tcp_queue, ++ struct sk_buff *skb, u32 len, ++ gfp_t gfp, int reinject) ++{ ++ int ret, diff, old_factor; ++ struct sk_buff *buff; ++ u8 flags; ++ ++ if (skb_headlen(skb) < len) ++ diff = skb->len - len; ++ else ++ diff = skb->data_len; ++ old_factor = tcp_skb_pcount(skb); ++ ++ /* The mss_now in tcp_fragment is used to set the tso_segs of the skb. ++ * At the MPTCP-level we do not care about the absolute value. All we ++ * care about is that it is set to 1 for accurate packets_out ++ * accounting. ++ */ ++ ret = tcp_fragment(meta_sk, tcp_queue, skb, len, UINT_MAX, gfp); ++ if (ret) ++ return ret; ++ ++ if (tcp_queue == TCP_FRAG_IN_WRITE_QUEUE) ++ buff = skb->next; ++ else ++ buff = skb_rb_next(skb); ++ ++ flags = TCP_SKB_CB(skb)->mptcp_flags; ++ TCP_SKB_CB(skb)->mptcp_flags = flags & ~(MPTCPHDR_FIN); ++ TCP_SKB_CB(buff)->mptcp_flags = flags; ++ TCP_SKB_CB(buff)->path_mask = TCP_SKB_CB(skb)->path_mask; ++ ++ /* If reinject == 1, the buff will be added to the reinject ++ * queue, which is currently not part of memory accounting. So ++ * undo the changes done by tcp_fragment and update the ++ * reinject queue. Also, undo changes to the packet counters. ++ */ ++ if (reinject == 1) { ++ int undo = buff->truesize - diff; ++ meta_sk->sk_wmem_queued -= undo; ++ sk_mem_uncharge(meta_sk, undo); ++ ++ tcp_sk(meta_sk)->mpcb->reinject_queue.qlen++; ++ if (tcp_queue == TCP_FRAG_IN_WRITE_QUEUE) ++ meta_sk->sk_write_queue.qlen--; ++ ++ if (!before(tcp_sk(meta_sk)->snd_nxt, TCP_SKB_CB(buff)->end_seq)) { ++ undo = old_factor - tcp_skb_pcount(skb) - ++ tcp_skb_pcount(buff); ++ if (undo) ++ tcp_adjust_pcount(meta_sk, skb, -undo); ++ } ++ ++ /* tcp_fragment's call to sk_stream_alloc_skb initializes the ++ * tcp_tsorted_anchor. We need to revert this as it clashes ++ * with the refdst pointer. ++ */ ++ tcp_skb_tsorted_anchor_cleanup(buff); ++ } ++ ++ return 0; ++} ++ ++/* Inspired by tcp_write_wakeup */ ++int mptcp_write_wakeup(struct sock *meta_sk, int mib) ++{ ++ struct tcp_sock *meta_tp = tcp_sk(meta_sk); ++ struct sk_buff *skb; ++ int ans = 0; ++ ++ if (meta_sk->sk_state == TCP_CLOSE) ++ return -1; ++ ++ skb = tcp_send_head(meta_sk); ++ if (skb && ++ before(TCP_SKB_CB(skb)->seq, tcp_wnd_end(meta_tp))) { ++ unsigned int mss; ++ unsigned int seg_size = tcp_wnd_end(meta_tp) - TCP_SKB_CB(skb)->seq; ++ struct sock *subsk = meta_tp->mpcb->sched_ops->get_subflow(meta_sk, skb, true); ++ struct tcp_sock *subtp; ++ ++ WARN_ON(TCP_SKB_CB(skb)->sacked); ++ ++ if (!subsk) ++ goto window_probe; ++ subtp = tcp_sk(subsk); ++ mss = tcp_current_mss(subsk); ++ ++ seg_size = min(tcp_wnd_end(meta_tp) - TCP_SKB_CB(skb)->seq, ++ tcp_wnd_end(subtp) - subtp->write_seq); ++ ++ if (before(meta_tp->pushed_seq, TCP_SKB_CB(skb)->end_seq)) ++ meta_tp->pushed_seq = TCP_SKB_CB(skb)->end_seq; ++ ++ /* We are probing the opening of a window ++ * but the window size is != 0 ++ * must have been a result SWS avoidance ( sender ) ++ */ ++ if (seg_size < TCP_SKB_CB(skb)->end_seq - TCP_SKB_CB(skb)->seq || ++ skb->len > mss) { ++ seg_size = min(seg_size, mss); ++ TCP_SKB_CB(skb)->tcp_flags |= TCPHDR_PSH; ++ if (mptcp_fragment(meta_sk, TCP_FRAG_IN_WRITE_QUEUE, ++ skb, seg_size, GFP_ATOMIC, 0)) ++ return -1; ++ } else if (!tcp_skb_pcount(skb)) { ++ /* see mptcp_write_xmit on why we use UINT_MAX */ ++ tcp_set_skb_tso_segs(skb, UINT_MAX); ++ } ++ ++ TCP_SKB_CB(skb)->tcp_flags |= TCPHDR_PSH; ++ if (!mptcp_skb_entail(subsk, skb, 0)) ++ return -1; ++ ++ mptcp_check_sndseq_wrap(meta_tp, TCP_SKB_CB(skb)->end_seq - ++ TCP_SKB_CB(skb)->seq); ++ tcp_event_new_data_sent(meta_sk, skb); ++ ++ __tcp_push_pending_frames(subsk, mss, TCP_NAGLE_PUSH); ++ tcp_update_skb_after_send(meta_sk, skb, meta_tp->tcp_wstamp_ns); ++ meta_tp->lsndtime = tcp_jiffies32; ++ ++ return 0; ++ } else { ++ struct mptcp_tcp_sock *mptcp; ++ ++window_probe: ++ if (between(meta_tp->snd_up, meta_tp->snd_una + 1, ++ meta_tp->snd_una + 0xFFFF)) { ++ mptcp_for_each_sub(meta_tp->mpcb, mptcp) { ++ struct sock *sk_it = mptcp_to_sock(mptcp); ++ ++ if (mptcp_sk_can_send_ack(sk_it)) ++ tcp_xmit_probe_skb(sk_it, 1, mib); ++ } ++ } ++ ++ /* At least one of the tcp_xmit_probe_skb's has to succeed */ ++ mptcp_for_each_sub(meta_tp->mpcb, mptcp) { ++ struct sock *sk_it = mptcp_to_sock(mptcp); ++ int ret; ++ ++ if (!mptcp_sk_can_send_ack(sk_it)) ++ continue; ++ ++ ret = tcp_xmit_probe_skb(sk_it, 0, mib); ++ if (unlikely(ret > 0)) ++ ans = ret; ++ } ++ return ans; ++ } ++} ++ ++bool mptcp_write_xmit(struct sock *meta_sk, unsigned int mss_now, int nonagle, ++ int push_one, gfp_t gfp) ++{ ++ struct tcp_sock *meta_tp = tcp_sk(meta_sk), *subtp; ++ bool is_rwnd_limited = false; ++ struct mptcp_tcp_sock *mptcp; ++ struct sock *subsk = NULL; ++ struct mptcp_cb *mpcb = meta_tp->mpcb; ++ struct sk_buff *skb; ++ int reinject = 0; ++ unsigned int sublimit; ++ __u32 path_mask = 0; ++ ++ tcp_mstamp_refresh(meta_tp); ++ ++ if (inet_csk(meta_sk)->icsk_retransmits) { ++ /* If the timer already once fired, retransmit the head of the ++ * queue to unblock us ASAP. ++ */ ++ if (meta_tp->packets_out && !mpcb->infinite_mapping_snd) ++ mptcp_retransmit_skb(meta_sk, tcp_rtx_queue_head(meta_sk)); ++ } ++ ++ while ((skb = mpcb->sched_ops->next_segment(meta_sk, &reinject, &subsk, ++ &sublimit))) { ++ enum tcp_queue tcp_queue = TCP_FRAG_IN_WRITE_QUEUE; ++ unsigned int limit; ++ ++ WARN(TCP_SKB_CB(skb)->sacked, "sacked: %u reinject: %u", ++ TCP_SKB_CB(skb)->sacked, reinject); ++ ++ subtp = tcp_sk(subsk); ++ mss_now = tcp_current_mss(subsk); ++ ++ if (reinject == 1) { ++ if (!after(TCP_SKB_CB(skb)->end_seq, meta_tp->snd_una)) { ++ /* Segment already reached the peer, take the next one */ ++ __skb_unlink(skb, &mpcb->reinject_queue); ++ __kfree_skb(skb); ++ continue; ++ } ++ } else if (reinject == -1) { ++ tcp_queue = TCP_FRAG_IN_RTX_QUEUE; ++ } ++ ++ /* If the segment was cloned (e.g. a meta retransmission), ++ * the header must be expanded/copied so that there is no ++ * corruption of TSO information. ++ */ ++ if (skb_unclone(skb, GFP_ATOMIC)) ++ break; ++ ++ if (unlikely(!tcp_snd_wnd_test(meta_tp, skb, mss_now))) { ++ is_rwnd_limited = true; ++ break; ++ } ++ ++ /* Force tso_segs to 1 by using UINT_MAX. ++ * We actually don't care about the exact number of segments ++ * emitted on the subflow. We need just to set tso_segs, because ++ * we still need an accurate packets_out count in ++ * tcp_event_new_data_sent. ++ */ ++ tcp_set_skb_tso_segs(skb, UINT_MAX); ++ ++ /* Check for nagle, irregardless of tso_segs. If the segment is ++ * actually larger than mss_now (TSO segment), then ++ * tcp_nagle_check will have partial == false and always trigger ++ * the transmission. ++ * tcp_write_xmit has a TSO-level nagle check which is not ++ * subject to the MPTCP-level. It is based on the properties of ++ * the subflow, not the MPTCP-level. ++ * When the segment is a reinjection or redundant scheduled ++ * segment, nagle check at meta-level may prevent ++ * sending. This could hurt with certain schedulers, as they ++ * to reinjection to recover from a window-stall or reduce latency. ++ * Therefore, Nagle check should be disabled in that case. ++ */ ++ if (!reinject && ++ unlikely(!tcp_nagle_test(meta_tp, skb, mss_now, ++ (tcp_skb_is_last(meta_sk, skb) ? ++ nonagle : TCP_NAGLE_PUSH)))) ++ break; ++ ++ limit = mss_now; ++ /* skb->len > mss_now is the equivalent of tso_segs > 1 in ++ * tcp_write_xmit. Otherwise split-point would return 0. ++ */ ++ if (skb->len > mss_now && !tcp_urg_mode(meta_tp)) ++ /* We limit the size of the skb so that it fits into the ++ * window. Call tcp_mss_split_point to avoid duplicating ++ * code. ++ * We really only care about fitting the skb into the ++ * window. That's why we use UINT_MAX. If the skb does ++ * not fit into the cwnd_quota or the NIC's max-segs ++ * limitation, it will be split by the subflow's ++ * tcp_write_xmit which does the appropriate call to ++ * tcp_mss_split_point. ++ */ ++ limit = tcp_mss_split_point(meta_sk, skb, mss_now, ++ UINT_MAX / mss_now, ++ nonagle); ++ ++ if (sublimit) ++ limit = min(limit, sublimit); ++ ++ if (skb->len > limit && ++ unlikely(mptcp_fragment(meta_sk, tcp_queue, ++ skb, limit, gfp, reinject))) ++ break; ++ ++ if (!mptcp_skb_entail(subsk, skb, reinject)) ++ break; ++ ++ if (reinject <= 0) ++ tcp_update_skb_after_send(meta_sk, skb, meta_tp->tcp_wstamp_ns); ++ meta_tp->lsndtime = tcp_jiffies32; ++ ++ path_mask |= mptcp_pi_to_flag(subtp->mptcp->path_index); ++ ++ if (!reinject) { ++ mptcp_check_sndseq_wrap(meta_tp, ++ TCP_SKB_CB(skb)->end_seq - ++ TCP_SKB_CB(skb)->seq); ++ tcp_event_new_data_sent(meta_sk, skb); ++ } ++ ++ tcp_minshall_update(meta_tp, mss_now, skb); ++ ++ if (reinject > 0) { ++ __skb_unlink(skb, &mpcb->reinject_queue); ++ kfree_skb(skb); ++ } ++ ++ if (push_one) ++ break; ++ } ++ ++ if (is_rwnd_limited) ++ tcp_chrono_start(meta_sk, TCP_CHRONO_RWND_LIMITED); ++ else ++ tcp_chrono_stop(meta_sk, TCP_CHRONO_RWND_LIMITED); ++ ++ mptcp_for_each_sub(mpcb, mptcp) { ++ subsk = mptcp_to_sock(mptcp); ++ subtp = tcp_sk(subsk); ++ ++ if (!(path_mask & mptcp_pi_to_flag(subtp->mptcp->path_index))) ++ continue; ++ ++ mss_now = tcp_current_mss(subsk); ++ ++ /* Nagle is handled at the MPTCP-layer, so ++ * always push on the subflow ++ */ ++ __tcp_push_pending_frames(subsk, mss_now, TCP_NAGLE_PUSH); ++ } ++ ++ return !meta_tp->packets_out && tcp_send_head(meta_sk); ++} ++ ++void mptcp_write_space(struct sock *sk) ++{ ++ mptcp_push_pending_frames(mptcp_meta_sk(sk)); ++} ++ ++u32 __mptcp_select_window(struct sock *sk) ++{ ++ struct inet_connection_sock *icsk = inet_csk(sk); ++ struct tcp_sock *tp = tcp_sk(sk), *meta_tp = mptcp_meta_tp(tp); ++ struct sock *meta_sk = mptcp_meta_sk(sk); ++ int mss, free_space, full_space, window; ++ ++ /* MSS for the peer's data. Previous versions used mss_clamp ++ * here. I don't know if the value based on our guesses ++ * of peer's MSS is better for the performance. It's more correct ++ * but may be worse for the performance because of rcv_mss ++ * fluctuations. --SAW 1998/11/1 ++ */ ++ mss = icsk->icsk_ack.rcv_mss; ++ free_space = tcp_space(meta_sk); ++ full_space = min_t(int, meta_tp->window_clamp, ++ tcp_full_space(meta_sk)); ++ ++ if (mss > full_space) ++ mss = full_space; ++ ++ if (free_space < (full_space >> 1)) { ++ /* If free_space is decreasing due to mostly meta-level ++ * out-of-order packets, don't turn off the quick-ack mode. ++ */ ++ if (meta_tp->rcv_nxt - meta_tp->copied_seq > ((full_space - free_space) >> 1)) ++ icsk->icsk_ack.quick = 0; ++ ++ if (tcp_memory_pressure) ++ /* TODO this has to be adapted when we support different ++ * MSS's among the subflows. ++ */ ++ meta_tp->rcv_ssthresh = min(meta_tp->rcv_ssthresh, ++ 4U * meta_tp->advmss); ++ ++ if (free_space < mss) ++ return 0; ++ } ++ ++ if (free_space > meta_tp->rcv_ssthresh) ++ free_space = meta_tp->rcv_ssthresh; ++ ++ /* Don't do rounding if we are using window scaling, since the ++ * scaled window will not line up with the MSS boundary anyway. ++ */ ++ window = meta_tp->rcv_wnd; ++ if (tp->rx_opt.rcv_wscale) { ++ window = free_space; ++ ++ /* Advertise enough space so that it won't get scaled away. ++ * Import case: prevent zero window announcement if ++ * 1< mss. ++ */ ++ if (((window >> tp->rx_opt.rcv_wscale) << tp-> ++ rx_opt.rcv_wscale) != window) ++ window = (((window >> tp->rx_opt.rcv_wscale) + 1) ++ << tp->rx_opt.rcv_wscale); ++ } else { ++ /* Get the largest window that is a nice multiple of mss. ++ * Window clamp already applied above. ++ * If our current window offering is within 1 mss of the ++ * free space we just keep it. This prevents the divide ++ * and multiply from happening most of the time. ++ * We also don't do any window rounding when the free space ++ * is too small. ++ */ ++ if (window <= free_space - mss || window > free_space) ++ window = (free_space / mss) * mss; ++ else if (mss == full_space && ++ free_space > window + (full_space >> 1)) ++ window = free_space; ++ } ++ ++ return window; ++} ++ ++void mptcp_syn_options(const struct sock *sk, struct tcp_out_options *opts, ++ unsigned *remaining) ++{ ++ const struct tcp_sock *tp = tcp_sk(sk); ++ ++ opts->options |= OPTION_MPTCP; ++ if (is_master_tp(tp)) { ++ opts->mptcp_options |= OPTION_MP_CAPABLE | OPTION_TYPE_SYN; ++ opts->mptcp_ver = tp->mptcp_ver; ++ ++ if (tp->mptcp_ver >= MPTCP_VERSION_1) ++ *remaining -= MPTCPV1_SUB_LEN_CAPABLE_SYN_ALIGN; ++ else ++ *remaining -= MPTCP_SUB_LEN_CAPABLE_SYN_ALIGN; ++ ++ opts->mp_capable.sender_key = tp->mptcp_loc_key; ++ opts->dss_csum = !!sysctl_mptcp_checksum; ++ } else { ++ const struct mptcp_cb *mpcb = tp->mpcb; ++ ++ opts->mptcp_options |= OPTION_MP_JOIN | OPTION_TYPE_SYN; ++ *remaining -= MPTCP_SUB_LEN_JOIN_SYN_ALIGN; ++ opts->mp_join_syns.token = mpcb->mptcp_rem_token; ++ opts->mp_join_syns.low_prio = tp->mptcp->low_prio; ++ opts->addr_id = tp->mptcp->loc_id; ++ opts->mp_join_syns.sender_nonce = tp->mptcp->mptcp_loc_nonce; ++ } ++} ++ ++void mptcp_synack_options(struct request_sock *req, ++ struct tcp_out_options *opts, unsigned *remaining) ++{ ++ struct mptcp_request_sock *mtreq; ++ mtreq = mptcp_rsk(req); ++ ++ opts->options |= OPTION_MPTCP; ++ /* MPCB not yet set - thus it's a new MPTCP-session */ ++ if (!mtreq->is_sub) { ++ opts->mptcp_options |= OPTION_MP_CAPABLE | OPTION_TYPE_SYNACK; ++ opts->mptcp_ver = mtreq->mptcp_ver; ++ opts->mp_capable.sender_key = mtreq->mptcp_loc_key; ++ opts->dss_csum = !!sysctl_mptcp_checksum || mtreq->dss_csum; ++ if (mtreq->mptcp_ver >= MPTCP_VERSION_1) { ++ *remaining -= MPTCPV1_SUB_LEN_CAPABLE_SYNACK_ALIGN; ++ } else { ++ *remaining -= MPTCP_SUB_LEN_CAPABLE_SYN_ALIGN; ++ } ++ } else { ++ opts->mptcp_options |= OPTION_MP_JOIN | OPTION_TYPE_SYNACK; ++ opts->mp_join_syns.sender_truncated_mac = ++ mtreq->mptcp_hash_tmac; ++ opts->mp_join_syns.sender_nonce = mtreq->mptcp_loc_nonce; ++ opts->mp_join_syns.low_prio = mtreq->low_prio; ++ opts->addr_id = mtreq->loc_id; ++ *remaining -= MPTCP_SUB_LEN_JOIN_SYNACK_ALIGN; ++ } ++} ++ ++void mptcp_established_options(struct sock *sk, struct sk_buff *skb, ++ struct tcp_out_options *opts, unsigned *size) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct mptcp_cb *mpcb = tp->mpcb; ++ const struct tcp_skb_cb *tcb = skb ? TCP_SKB_CB(skb) : NULL; ++ ++ /* We are coming from tcp_current_mss with the meta_sk as an argument. ++ * It does not make sense to check for the options, because when the ++ * segment gets sent, another subflow will be chosen. ++ */ ++ if (!skb && is_meta_sk(sk)) ++ return; ++ ++ if (unlikely(tp->send_mp_fclose)) { ++ opts->options |= OPTION_MPTCP; ++ opts->mptcp_options |= OPTION_MP_FCLOSE; ++ opts->mp_capable.receiver_key = mpcb->mptcp_rem_key; ++ *size += MPTCP_SUB_LEN_FCLOSE_ALIGN; ++ return; ++ } ++ ++ /* 1. If we are the sender of the infinite-mapping, we need the ++ * MPTCPHDR_INF-flag, because a retransmission of the ++ * infinite-announcment still needs the mptcp-option. ++ * ++ * We need infinite_cutoff_seq, because retransmissions from before ++ * the infinite-cutoff-moment still need the MPTCP-signalling to stay ++ * consistent. ++ * ++ * 2. If we are the receiver of the infinite-mapping, we always skip ++ * mptcp-options, because acknowledgments from before the ++ * infinite-mapping point have already been sent out. ++ * ++ * I know, the whole infinite-mapping stuff is ugly... ++ * ++ * TODO: Handle wrapped data-sequence numbers ++ * (even if it's very unlikely) ++ */ ++ if (unlikely(mpcb->infinite_mapping_snd) && ++ ((mpcb->send_infinite_mapping && tcb && ++ mptcp_is_data_seq(skb) && ++ !(tcb->mptcp_flags & MPTCPHDR_INF) && ++ !before(tcb->seq, tp->mptcp->infinite_cutoff_seq)) || ++ !mpcb->send_infinite_mapping)) ++ return; ++ ++ if (unlikely(tp->mptcp->include_mpc)) { ++ opts->options |= OPTION_MPTCP; ++ opts->mptcp_options |= OPTION_MP_CAPABLE | ++ OPTION_TYPE_ACK; ++ ++ if (mpcb->mptcp_ver >= MPTCP_VERSION_1) ++ *size += MPTCPV1_SUB_LEN_CAPABLE_ACK_ALIGN; ++ else ++ *size += MPTCP_SUB_LEN_CAPABLE_ACK_ALIGN; ++ ++ opts->mptcp_ver = mpcb->mptcp_ver; ++ opts->mp_capable.sender_key = mpcb->mptcp_loc_key; ++ opts->mp_capable.receiver_key = mpcb->mptcp_rem_key; ++ opts->dss_csum = mpcb->dss_csum; ++ ++ if (skb) ++ tp->mptcp->include_mpc = 0; ++ } ++ if (unlikely(tp->mptcp->pre_established) && ++ (!skb || !(tcb->tcp_flags & (TCPHDR_FIN | TCPHDR_RST)))) { ++ opts->options |= OPTION_MPTCP; ++ opts->mptcp_options |= OPTION_MP_JOIN | OPTION_TYPE_ACK; ++ *size += MPTCP_SUB_LEN_JOIN_ACK_ALIGN; ++ } ++ ++ if (unlikely(mpcb->addr_signal) && mpcb->pm_ops->addr_signal && ++ mpcb->mptcp_ver >= MPTCP_VERSION_1 && skb && !mptcp_is_data_seq(skb)) { ++ mpcb->pm_ops->addr_signal(sk, size, opts, skb); ++ ++ if (opts->add_addr_v6) ++ /* Skip subsequent options */ ++ return; ++ } ++ ++ if (!tp->mptcp->include_mpc && !tp->mptcp->pre_established) { ++ opts->options |= OPTION_MPTCP; ++ opts->mptcp_options |= OPTION_DATA_ACK; ++ /* If !skb, we come from tcp_current_mss and thus we always ++ * assume that the DSS-option will be set for the data-packet. ++ */ ++ if (skb && !mptcp_is_data_seq(skb) && mpcb->rem_key_set) { ++ *size += MPTCP_SUB_LEN_ACK_ALIGN; ++ } else if ((skb && mptcp_is_data_mpcapable(skb)) || ++ (!skb && tp->mpcb->send_mptcpv1_mpcapable)) { ++ *size += MPTCPV1_SUB_LEN_CAPABLE_DATA_ALIGN; ++ } else { ++ /* Doesn't matter, if csum included or not. It will be ++ * either 10 or 12, and thus aligned = 12 ++ */ ++ if (mpcb->rem_key_set) ++ *size += MPTCP_SUB_LEN_ACK_ALIGN + ++ MPTCP_SUB_LEN_SEQ_ALIGN; ++ else ++ *size += MPTCP_SUB_LEN_SEQ_ALIGN; ++ } ++ ++ *size += MPTCP_SUB_LEN_DSS_ALIGN; ++ } ++ ++ /* In fallback mp_fail-mode, we have to repeat it until the fallback ++ * has been done by the sender ++ */ ++ if (unlikely(tp->mptcp->send_mp_fail) && skb && ++ MAX_TCP_OPTION_SPACE - *size >= MPTCP_SUB_LEN_FAIL) { ++ opts->options |= OPTION_MPTCP; ++ opts->mptcp_options |= OPTION_MP_FAIL; ++ *size += MPTCP_SUB_LEN_FAIL; ++ } ++ ++ if (unlikely(mpcb->addr_signal) && mpcb->pm_ops->addr_signal && ++ mpcb->mptcp_ver < MPTCP_VERSION_1) ++ mpcb->pm_ops->addr_signal(sk, size, opts, skb); ++ ++ if (unlikely(tp->mptcp->send_mp_prio) && ++ MAX_TCP_OPTION_SPACE - *size >= MPTCP_SUB_LEN_PRIO_ALIGN) { ++ opts->options |= OPTION_MPTCP; ++ opts->mptcp_options |= OPTION_MP_PRIO; ++ if (skb) ++ tp->mptcp->send_mp_prio = 0; ++ *size += MPTCP_SUB_LEN_PRIO_ALIGN; ++ } ++ ++ return; ++} ++ ++u16 mptcp_select_window(struct sock *sk) ++{ ++ u16 new_win = tcp_select_window(sk); ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct tcp_sock *meta_tp = mptcp_meta_tp(tp); ++ ++ meta_tp->rcv_wnd = tp->rcv_wnd; ++ meta_tp->rcv_wup = meta_tp->rcv_nxt; ++ /* no need to use tcp_update_rcv_right_edge, because at the meta level ++ * right edge cannot go back ++ */ ++ meta_tp->rcv_right_edge = meta_tp->rcv_wnd + meta_tp->rcv_wup; ++ ++ return new_win; ++} ++ ++void mptcp_options_write(__be32 *ptr, struct tcp_sock *tp, ++ const struct tcp_out_options *opts, ++ struct sk_buff *skb) ++{ ++ if (unlikely(OPTION_MP_CAPABLE & opts->mptcp_options)) { ++ struct mp_capable *mpc = (struct mp_capable *)ptr; ++ ++ mpc->kind = TCPOPT_MPTCP; ++ ++ if (OPTION_TYPE_SYN & opts->mptcp_options) { ++ mpc->ver = opts->mptcp_ver; ++ ++ if (mpc->ver >= MPTCP_VERSION_1) { ++ mpc->len = MPTCPV1_SUB_LEN_CAPABLE_SYN; ++ ptr += MPTCPV1_SUB_LEN_CAPABLE_SYN_ALIGN >> 2; ++ } else { ++ mpc->sender_key = opts->mp_capable.sender_key; ++ mpc->len = MPTCP_SUB_LEN_CAPABLE_SYN; ++ ptr += MPTCP_SUB_LEN_CAPABLE_SYN_ALIGN >> 2; ++ } ++ } else if (OPTION_TYPE_SYNACK & opts->mptcp_options) { ++ mpc->ver = opts->mptcp_ver; ++ ++ if (mpc->ver >= MPTCP_VERSION_1) { ++ mpc->len = MPTCPV1_SUB_LEN_CAPABLE_SYNACK; ++ ptr += MPTCPV1_SUB_LEN_CAPABLE_SYNACK_ALIGN >> 2; ++ } else { ++ mpc->len = MPTCP_SUB_LEN_CAPABLE_SYN; ++ ptr += MPTCP_SUB_LEN_CAPABLE_SYN_ALIGN >> 2; ++ } ++ ++ mpc->sender_key = opts->mp_capable.sender_key; ++ } else if (OPTION_TYPE_ACK & opts->mptcp_options) { ++ mpc->len = MPTCP_SUB_LEN_CAPABLE_ACK; ++ mpc->ver = opts->mptcp_ver; ++ ptr += MPTCP_SUB_LEN_CAPABLE_ACK_ALIGN >> 2; ++ ++ mpc->sender_key = opts->mp_capable.sender_key; ++ mpc->receiver_key = opts->mp_capable.receiver_key; ++ } ++ ++ mpc->sub = MPTCP_SUB_CAPABLE; ++ mpc->a = opts->dss_csum; ++ mpc->b = 0; ++ mpc->rsv = 0; ++ mpc->h = 1; ++ } ++ if (unlikely(OPTION_MP_JOIN & opts->mptcp_options)) { ++ struct mp_join *mpj = (struct mp_join *)ptr; ++ ++ mpj->kind = TCPOPT_MPTCP; ++ mpj->sub = MPTCP_SUB_JOIN; ++ mpj->rsv = 0; ++ ++ if (OPTION_TYPE_SYN & opts->mptcp_options) { ++ mpj->len = MPTCP_SUB_LEN_JOIN_SYN; ++ mpj->u.syn.token = opts->mp_join_syns.token; ++ mpj->u.syn.nonce = opts->mp_join_syns.sender_nonce; ++ mpj->b = opts->mp_join_syns.low_prio; ++ mpj->addr_id = opts->addr_id; ++ ptr += MPTCP_SUB_LEN_JOIN_SYN_ALIGN >> 2; ++ } else if (OPTION_TYPE_SYNACK & opts->mptcp_options) { ++ mpj->len = MPTCP_SUB_LEN_JOIN_SYNACK; ++ mpj->u.synack.mac = ++ opts->mp_join_syns.sender_truncated_mac; ++ mpj->u.synack.nonce = opts->mp_join_syns.sender_nonce; ++ mpj->b = opts->mp_join_syns.low_prio; ++ mpj->addr_id = opts->addr_id; ++ ptr += MPTCP_SUB_LEN_JOIN_SYNACK_ALIGN >> 2; ++ } else if (OPTION_TYPE_ACK & opts->mptcp_options) { ++ mpj->len = MPTCP_SUB_LEN_JOIN_ACK; ++ mpj->addr_id = 0; /* addr_id is rsv (RFC 6824, p. 21) */ ++ memcpy(mpj->u.ack.mac, &tp->mptcp->sender_mac[0], 20); ++ ptr += MPTCP_SUB_LEN_JOIN_ACK_ALIGN >> 2; ++ } ++ } ++ if (unlikely(OPTION_ADD_ADDR & opts->mptcp_options)) { ++ struct mp_add_addr *mpadd = (struct mp_add_addr *)ptr; ++ struct mptcp_cb *mpcb = tp->mpcb; ++ ++ mpadd->kind = TCPOPT_MPTCP; ++ if (opts->add_addr_v4) { ++ mpadd->addr_id = opts->add_addr4.addr_id; ++ mpadd->u.v4.addr = opts->add_addr4.addr; ++ if (mpcb->mptcp_ver < MPTCP_VERSION_1) { ++ mpadd->u_bit.v0.sub = MPTCP_SUB_ADD_ADDR; ++ mpadd->u_bit.v0.ipver = 4; ++ mpadd->len = MPTCP_SUB_LEN_ADD_ADDR4; ++ ptr += MPTCP_SUB_LEN_ADD_ADDR4_ALIGN >> 2; ++ } else { ++ mpadd->u_bit.v1.sub = MPTCP_SUB_ADD_ADDR; ++ mpadd->u_bit.v1.rsv = 0; ++ mpadd->u_bit.v1.echo = 0; ++ memcpy((char *)mpadd->u.v4.mac - 2, ++ (char *)&opts->add_addr4.trunc_mac, 8); ++ mpadd->len = MPTCP_SUB_LEN_ADD_ADDR4_VER1; ++ ptr += MPTCP_SUB_LEN_ADD_ADDR4_ALIGN_VER1 >> 2; ++ } ++ } else if (opts->add_addr_v6) { ++ mpadd->addr_id = opts->add_addr6.addr_id; ++ memcpy(&mpadd->u.v6.addr, &opts->add_addr6.addr, ++ sizeof(mpadd->u.v6.addr)); ++ if (mpcb->mptcp_ver < MPTCP_VERSION_1) { ++ mpadd->u_bit.v0.sub = MPTCP_SUB_ADD_ADDR; ++ mpadd->u_bit.v0.ipver = 6; ++ mpadd->len = MPTCP_SUB_LEN_ADD_ADDR6; ++ ptr += MPTCP_SUB_LEN_ADD_ADDR6_ALIGN >> 2; ++ } else { ++ mpadd->u_bit.v1.sub = MPTCP_SUB_ADD_ADDR; ++ mpadd->u_bit.v1.rsv = 0; ++ mpadd->u_bit.v1.echo = 0; ++ memcpy((char *)mpadd->u.v6.mac - 2, ++ (char *)&opts->add_addr6.trunc_mac, 8); ++ mpadd->len = MPTCP_SUB_LEN_ADD_ADDR6_VER1; ++ ptr += MPTCP_SUB_LEN_ADD_ADDR6_ALIGN_VER1 >> 2; ++ } ++ } ++ ++ MPTCP_INC_STATS(sock_net((struct sock *)tp), MPTCP_MIB_ADDADDRTX); ++ } ++ if (unlikely(OPTION_REMOVE_ADDR & opts->mptcp_options)) { ++ struct mp_remove_addr *mprem = (struct mp_remove_addr *)ptr; ++ u8 *addrs_id; ++ int id, len, len_align; ++ ++ len = mptcp_sub_len_remove_addr(opts->remove_addrs); ++ len_align = mptcp_sub_len_remove_addr_align(opts->remove_addrs); ++ ++ mprem->kind = TCPOPT_MPTCP; ++ mprem->len = len; ++ mprem->sub = MPTCP_SUB_REMOVE_ADDR; ++ mprem->rsv = 0; ++ addrs_id = &mprem->addrs_id; ++ ++ mptcp_for_each_bit_set(opts->remove_addrs, id) ++ *(addrs_id++) = id; ++ ++ /* Fill the rest with NOP's */ ++ if (len_align > len) { ++ int i; ++ for (i = 0; i < len_align - len; i++) ++ *(addrs_id++) = TCPOPT_NOP; ++ } ++ ++ ptr += len_align >> 2; ++ ++ MPTCP_INC_STATS(sock_net((struct sock *)tp), MPTCP_MIB_REMADDRTX); ++ } ++ if (unlikely(OPTION_MP_FAIL & opts->mptcp_options)) { ++ struct mp_fail *mpfail = (struct mp_fail *)ptr; ++ ++ mpfail->kind = TCPOPT_MPTCP; ++ mpfail->len = MPTCP_SUB_LEN_FAIL; ++ mpfail->sub = MPTCP_SUB_FAIL; ++ mpfail->rsv1 = 0; ++ mpfail->rsv2 = 0; ++ mpfail->data_seq = htonll(tp->mpcb->csum_cutoff_seq); ++ ++ ptr += MPTCP_SUB_LEN_FAIL_ALIGN >> 2; ++ } ++ if (unlikely(OPTION_MP_FCLOSE & opts->mptcp_options)) { ++ struct mp_fclose *mpfclose = (struct mp_fclose *)ptr; ++ ++ mpfclose->kind = TCPOPT_MPTCP; ++ mpfclose->len = MPTCP_SUB_LEN_FCLOSE; ++ mpfclose->sub = MPTCP_SUB_FCLOSE; ++ mpfclose->rsv1 = 0; ++ mpfclose->rsv2 = 0; ++ mpfclose->key = opts->mp_capable.receiver_key; ++ ++ ptr += MPTCP_SUB_LEN_FCLOSE_ALIGN >> 2; ++ } ++ ++ if (OPTION_DATA_ACK & opts->mptcp_options) { ++ if (!mptcp_is_data_seq(skb) && tp->mpcb->rem_key_set) ++ ptr += mptcp_write_dss_data_ack(tp, skb, ptr); ++ else if (mptcp_is_data_mpcapable(skb)) ++ ptr += mptcp_write_mpcapable_data(tp, skb, ptr); ++ else ++ ptr += mptcp_write_dss_data_seq(tp, skb, ptr); ++ } ++ if (unlikely(OPTION_MP_PRIO & opts->mptcp_options)) { ++ struct mp_prio *mpprio = (struct mp_prio *)ptr; ++ ++ mpprio->kind = TCPOPT_MPTCP; ++ mpprio->len = MPTCP_SUB_LEN_PRIO; ++ mpprio->sub = MPTCP_SUB_PRIO; ++ mpprio->rsv = 0; ++ mpprio->b = tp->mptcp->low_prio; ++ mpprio->addr_id = TCPOPT_NOP; ++ ++ ptr += MPTCP_SUB_LEN_PRIO_ALIGN >> 2; ++ } ++} ++ ++/* Sends the datafin */ ++void mptcp_send_fin(struct sock *meta_sk) ++{ ++ struct sk_buff *skb, *tskb = tcp_write_queue_tail(meta_sk); ++ struct tcp_sock *meta_tp = tcp_sk(meta_sk); ++ int mss_now; ++ ++ if ((1 << meta_sk->sk_state) & (TCPF_CLOSE_WAIT | TCPF_LAST_ACK)) ++ meta_tp->mpcb->passive_close = 1; ++ ++ /* Optimization, tack on the FIN if we have a queue of ++ * unsent frames. But be careful about outgoing SACKS ++ * and IP options. ++ */ ++ mss_now = mptcp_current_mss(meta_sk); ++ ++ if (tskb) { ++ TCP_SKB_CB(tskb)->mptcp_flags |= MPTCPHDR_FIN; ++ TCP_SKB_CB(tskb)->end_seq++; ++ meta_tp->write_seq++; ++ } else { ++ /* Socket is locked, keep trying until memory is available. */ ++ for (;;) { ++ skb = alloc_skb_fclone(MAX_TCP_HEADER, ++ meta_sk->sk_allocation); ++ if (skb) ++ break; ++ yield(); ++ } ++ /* Reserve space for headers and prepare control bits. */ ++ INIT_LIST_HEAD(&skb->tcp_tsorted_anchor); ++ skb_reserve(skb, MAX_TCP_HEADER); ++ ++ tcp_init_nondata_skb(skb, meta_tp->write_seq, TCPHDR_ACK); ++ TCP_SKB_CB(skb)->end_seq++; ++ TCP_SKB_CB(skb)->mptcp_flags |= MPTCPHDR_FIN; ++ tcp_queue_skb(meta_sk, skb); ++ } ++ __tcp_push_pending_frames(meta_sk, mss_now, TCP_NAGLE_OFF); ++} ++ ++void mptcp_send_active_reset(struct sock *meta_sk, gfp_t priority) ++{ ++ struct tcp_sock *meta_tp = tcp_sk(meta_sk); ++ struct mptcp_cb *mpcb = meta_tp->mpcb; ++ struct sock *sk; ++ ++ if (hlist_empty(&mpcb->conn_list)) ++ return; ++ ++ WARN_ON(meta_tp->send_mp_fclose); ++ ++ /* First - select a socket */ ++ sk = mptcp_select_ack_sock(meta_sk); ++ ++ /* May happen if no subflow is in an appropriate state, OR ++ * we are in infinite mode or about to go there - just send a reset ++ */ ++ if (!sk || mptcp_in_infinite_mapping_weak(mpcb)) { ++ /* tcp_done must be handled with bh disabled */ ++ if (!in_serving_softirq()) ++ local_bh_disable(); ++ ++ mptcp_sub_force_close_all(mpcb, NULL); ++ ++ if (!in_serving_softirq()) ++ local_bh_enable(); ++ return; ++ } ++ ++ tcp_mstamp_refresh(meta_tp); ++ ++ tcp_sk(sk)->send_mp_fclose = 1; ++ /** Reset all other subflows */ ++ ++ /* tcp_done must be handled with bh disabled */ ++ if (!in_serving_softirq()) ++ local_bh_disable(); ++ ++ mptcp_sub_force_close_all(mpcb, sk); ++ ++ tcp_set_state(sk, TCP_RST_WAIT); ++ ++ if (!in_serving_softirq()) ++ local_bh_enable(); ++ ++ tcp_send_ack(sk); ++ tcp_clear_xmit_timers(sk); ++ inet_csk_reset_keepalive_timer(sk, inet_csk(sk)->icsk_rto); ++ ++ meta_tp->send_mp_fclose = 1; ++ inet_csk(sk)->icsk_retransmits = 0; ++ ++ /* Prevent exp backoff reverting on ICMP dest unreachable */ ++ inet_csk(sk)->icsk_backoff = 0; ++ ++ MPTCP_INC_STATS(sock_net(meta_sk), MPTCP_MIB_FASTCLOSETX); ++} ++ ++static void mptcp_ack_retransmit_timer(struct sock *sk) ++{ ++ struct inet_connection_sock *icsk = inet_csk(sk); ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct net *net = sock_net(sk); ++ struct sk_buff *skb; ++ ++ if (inet_csk(sk)->icsk_af_ops->rebuild_header(sk)) ++ goto out; /* Routing failure or similar */ ++ ++ tcp_mstamp_refresh(tp); ++ ++ if (tcp_write_timeout(sk)) { ++ MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_JOINACKRTO); ++ tp->mptcp->pre_established = 0; ++ sk_stop_timer(sk, &tp->mptcp->mptcp_ack_timer); ++ tp->ops->send_active_reset(sk, GFP_ATOMIC); ++ goto out; ++ } ++ ++ skb = alloc_skb(MAX_TCP_HEADER, GFP_ATOMIC); ++ if (skb == NULL) { ++ sk_reset_timer(sk, &tp->mptcp->mptcp_ack_timer, ++ jiffies + icsk->icsk_rto); ++ return; ++ } ++ ++ /* Reserve space for headers and prepare control bits */ ++ skb_reserve(skb, MAX_TCP_HEADER); ++ tcp_init_nondata_skb(skb, tp->snd_una, TCPHDR_ACK); ++ ++ MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_JOINACKRXMIT); ++ ++ if (tcp_transmit_skb(sk, skb, 0, GFP_ATOMIC) > 0) { ++ /* Retransmission failed because of local congestion, ++ * do not backoff. ++ */ ++ if (!icsk->icsk_retransmits) ++ icsk->icsk_retransmits = 1; ++ sk_reset_timer(sk, &tp->mptcp->mptcp_ack_timer, ++ jiffies + icsk->icsk_rto); ++ return; ++ } ++ ++ if (!tp->retrans_stamp) ++ tp->retrans_stamp = tcp_time_stamp(tp) ? : 1; ++ ++ icsk->icsk_retransmits++; ++ icsk->icsk_rto = min(icsk->icsk_rto << 1, TCP_RTO_MAX); ++ sk_reset_timer(sk, &tp->mptcp->mptcp_ack_timer, ++ jiffies + icsk->icsk_rto); ++ if (retransmits_timed_out(sk, net->ipv4.sysctl_tcp_retries1 + 1, 0)) ++ __sk_dst_reset(sk); ++ ++out:; ++} ++ ++void mptcp_ack_handler(struct timer_list *t) ++{ ++ struct mptcp_tcp_sock *mptcp = from_timer(mptcp, t, mptcp_ack_timer); ++ struct sock *sk = (struct sock *)mptcp->tp; ++ struct sock *meta_sk = mptcp_meta_sk(sk); ++ ++ bh_lock_sock(meta_sk); ++ if (sock_owned_by_user(meta_sk)) { ++ /* Try again later */ ++ sk_reset_timer(sk, &tcp_sk(sk)->mptcp->mptcp_ack_timer, ++ jiffies + (HZ / 20)); ++ goto out_unlock; ++ } ++ ++ if (sk->sk_state == TCP_CLOSE) ++ goto out_unlock; ++ if (!tcp_sk(sk)->mptcp->pre_established) ++ goto out_unlock; ++ ++ mptcp_ack_retransmit_timer(sk); ++ ++ sk_mem_reclaim(sk); ++ ++out_unlock: ++ bh_unlock_sock(meta_sk); ++ sock_put(sk); ++} ++ ++/* Similar to tcp_retransmit_skb ++ * ++ * The diff is that we handle the retransmission-stats (retrans_stamp) at the ++ * meta-level. ++ */ ++int mptcp_retransmit_skb(struct sock *meta_sk, struct sk_buff *skb) ++{ ++ struct tcp_sock *meta_tp = tcp_sk(meta_sk); ++ struct sock *subsk; ++ unsigned int limit, mss_now; ++ int err = -1; ++ ++ WARN_ON(TCP_SKB_CB(skb)->sacked); ++ ++ /* Do not sent more than we queued. 1/4 is reserved for possible ++ * copying overhead: fragmentation, tunneling, mangling etc. ++ * ++ * This is a meta-retransmission thus we check on the meta-socket. ++ */ ++ if (refcount_read(&meta_sk->sk_wmem_alloc) > ++ min(meta_sk->sk_wmem_queued + (meta_sk->sk_wmem_queued >> 2), meta_sk->sk_sndbuf)) { ++ return -EAGAIN; ++ } ++ ++ /* We need to make sure that the retransmitted segment can be sent on a ++ * subflow right now. If it is too big, it needs to be fragmented. ++ */ ++ subsk = meta_tp->mpcb->sched_ops->get_subflow(meta_sk, skb, false); ++ if (!subsk) { ++ /* We want to increase icsk_retransmits, thus return 0, so that ++ * mptcp_meta_retransmit_timer enters the desired branch. ++ */ ++ err = 0; ++ goto failed; ++ } ++ mss_now = tcp_current_mss(subsk); ++ ++ /* If the segment was cloned (e.g. a meta retransmission), the header ++ * must be expanded/copied so that there is no corruption of TSO ++ * information. ++ */ ++ if (skb_unclone(skb, GFP_ATOMIC)) { ++ err = -ENOMEM; ++ goto failed; ++ } ++ ++ /* Must have been set by mptcp_write_xmit before */ ++ BUG_ON(!tcp_skb_pcount(skb)); ++ ++ limit = mss_now; ++ /* skb->len > mss_now is the equivalent of tso_segs > 1 in ++ * tcp_write_xmit. Otherwise split-point would return 0. ++ */ ++ if (skb->len > mss_now && !tcp_urg_mode(meta_tp)) ++ limit = tcp_mss_split_point(meta_sk, skb, mss_now, ++ UINT_MAX / mss_now, ++ TCP_NAGLE_OFF); ++ ++ limit = min(limit, tcp_wnd_end(meta_tp) - TCP_SKB_CB(skb)->seq); ++ ++ if (skb->len > limit && ++ unlikely(mptcp_fragment(meta_sk, TCP_FRAG_IN_RTX_QUEUE, skb, ++ limit, GFP_ATOMIC, 0))) ++ goto failed; ++ ++ if (!mptcp_skb_entail(subsk, skb, -1)) ++ goto failed; ++ ++ /* Update global TCP statistics. */ ++ MPTCP_INC_STATS(sock_net(meta_sk), MPTCP_MIB_RETRANSSEGS); ++ ++ /* Diff to tcp_retransmit_skb */ ++ ++ /* Save stamp of the first retransmit. */ ++ if (!meta_tp->retrans_stamp) { ++ tcp_mstamp_refresh(meta_tp); ++ meta_tp->retrans_stamp = tcp_time_stamp(meta_tp); ++ } ++ ++ __tcp_push_pending_frames(subsk, mss_now, TCP_NAGLE_PUSH); ++ tcp_update_skb_after_send(meta_sk, skb, meta_tp->tcp_wstamp_ns); ++ meta_tp->lsndtime = tcp_jiffies32; ++ ++ return 0; ++ ++failed: ++ NET_INC_STATS(sock_net(meta_sk), LINUX_MIB_TCPRETRANSFAIL); ++ return err; ++} ++ ++/* Similar to tcp_retransmit_timer ++ * ++ * The diff is that we have to handle retransmissions of the FAST_CLOSE-message ++ * and that we don't have an srtt estimation at the meta-level. ++ */ ++void mptcp_meta_retransmit_timer(struct sock *meta_sk) ++{ ++ struct tcp_sock *meta_tp = tcp_sk(meta_sk); ++ struct mptcp_cb *mpcb = meta_tp->mpcb; ++ struct inet_connection_sock *meta_icsk = inet_csk(meta_sk); ++ int err; ++ ++ /* In fallback, retransmission is handled at the subflow-level */ ++ if (!meta_tp->packets_out || mpcb->infinite_mapping_snd) ++ return; ++ ++ WARN_ON(tcp_rtx_queue_empty(meta_sk)); ++ ++ if (!meta_tp->snd_wnd && !sock_flag(meta_sk, SOCK_DEAD) && ++ !((1 << meta_sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV))) { ++ /* Receiver dastardly shrinks window. Our retransmits ++ * become zero probes, but we should not timeout this ++ * connection. If the socket is an orphan, time it out, ++ * we cannot allow such beasts to hang infinitely. ++ */ ++ struct inet_sock *meta_inet = inet_sk(meta_sk); ++ if (meta_sk->sk_family == AF_INET) { ++ net_dbg_ratelimited("MPTCP: Peer %pI4:%u/%u unexpectedly shrunk window %u:%u (repaired)\n", ++ &meta_inet->inet_daddr, ++ ntohs(meta_inet->inet_dport), ++ meta_inet->inet_num, meta_tp->snd_una, ++ meta_tp->snd_nxt); ++ } ++#if IS_ENABLED(CONFIG_IPV6) ++ else if (meta_sk->sk_family == AF_INET6) { ++ net_dbg_ratelimited("MPTCP: Peer %pI6:%u/%u unexpectedly shrunk window %u:%u (repaired)\n", ++ &meta_sk->sk_v6_daddr, ++ ntohs(meta_inet->inet_dport), ++ meta_inet->inet_num, meta_tp->snd_una, ++ meta_tp->snd_nxt); ++ } ++#endif ++ if (tcp_jiffies32 - meta_tp->rcv_tstamp > TCP_RTO_MAX) { ++ tcp_write_err(meta_sk); ++ return; ++ } ++ ++ mptcp_retransmit_skb(meta_sk, tcp_rtx_queue_head(meta_sk)); ++ goto out_reset_timer; ++ } ++ ++ if (tcp_write_timeout(meta_sk)) ++ return; ++ ++ if (meta_icsk->icsk_retransmits == 0) ++ NET_INC_STATS(sock_net(meta_sk), LINUX_MIB_TCPTIMEOUTS); ++ ++ meta_icsk->icsk_ca_state = TCP_CA_Loss; ++ ++ err = mptcp_retransmit_skb(meta_sk, tcp_rtx_queue_head(meta_sk)); ++ if (err > 0) { ++ /* Retransmission failed because of local congestion, ++ * do not backoff. ++ */ ++ if (!meta_icsk->icsk_retransmits) ++ meta_icsk->icsk_retransmits = 1; ++ inet_csk_reset_xmit_timer(meta_sk, ICSK_TIME_RETRANS, ++ min(meta_icsk->icsk_rto, TCP_RESOURCE_PROBE_INTERVAL), ++ TCP_RTO_MAX); ++ return; ++ } ++ ++ /* Increase the timeout each time we retransmit. Note that ++ * we do not increase the rtt estimate. rto is initialized ++ * from rtt, but increases here. Jacobson (SIGCOMM 88) suggests ++ * that doubling rto each time is the least we can get away with. ++ * In KA9Q, Karn uses this for the first few times, and then ++ * goes to quadratic. netBSD doubles, but only goes up to *64, ++ * and clamps at 1 to 64 sec afterwards. Note that 120 sec is ++ * defined in the protocol as the maximum possible RTT. I guess ++ * we'll have to use something other than TCP to talk to the ++ * University of Mars. ++ * ++ * PAWS allows us longer timeouts and large windows, so once ++ * implemented ftp to mars will work nicely. We will have to fix ++ * the 120 second clamps though! ++ */ ++ meta_icsk->icsk_backoff++; ++ meta_icsk->icsk_retransmits++; ++ ++out_reset_timer: ++ /* If stream is thin, use linear timeouts. Since 'icsk_backoff' is ++ * used to reset timer, set to 0. Recalculate 'icsk_rto' as this ++ * might be increased if the stream oscillates between thin and thick, ++ * thus the old value might already be too high compared to the value ++ * set by 'tcp_set_rto' in tcp_input.c which resets the rto without ++ * backoff. Limit to TCP_THIN_LINEAR_RETRIES before initiating ++ * exponential backoff behaviour to avoid continue hammering ++ * linear-timeout retransmissions into a black hole ++ */ ++ if (meta_sk->sk_state == TCP_ESTABLISHED && ++ (meta_tp->thin_lto || sock_net(meta_sk)->ipv4.sysctl_tcp_thin_linear_timeouts) && ++ tcp_stream_is_thin(meta_tp) && ++ meta_icsk->icsk_retransmits <= TCP_THIN_LINEAR_RETRIES) { ++ meta_icsk->icsk_backoff = 0; ++ /* We cannot do the same as in tcp_write_timer because the ++ * srtt is not set here. ++ */ ++ mptcp_set_rto(meta_sk); ++ } else { ++ /* Use normal (exponential) backoff */ ++ meta_icsk->icsk_rto = min(meta_icsk->icsk_rto << 1, TCP_RTO_MAX); ++ } ++ inet_csk_reset_xmit_timer(meta_sk, ICSK_TIME_RETRANS, meta_icsk->icsk_rto, TCP_RTO_MAX); ++ ++ return; ++} ++ ++void mptcp_sub_retransmit_timer(struct sock *sk) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ ++ tcp_retransmit_timer(sk); ++ ++ if (!tp->fastopen_rsk) { ++ mptcp_reinject_data(sk, 1); ++ mptcp_set_rto(sk); ++ } ++} ++ ++/* Modify values to an mptcp-level for the initial window of new subflows */ ++void mptcp_select_initial_window(const struct sock *sk, int __space, __u32 mss, ++ __u32 *rcv_wnd, __u32 *window_clamp, ++ int wscale_ok, __u8 *rcv_wscale, ++ __u32 init_rcv_wnd) ++{ ++ const struct mptcp_cb *mpcb = tcp_sk(sk)->mpcb; ++ ++ *window_clamp = mpcb->orig_window_clamp; ++ __space = tcp_win_from_space(sk, mpcb->orig_sk_rcvbuf); ++ ++ tcp_select_initial_window(sk, __space, mss, rcv_wnd, window_clamp, ++ wscale_ok, rcv_wscale, init_rcv_wnd); ++} ++ ++static inline u64 mptcp_calc_rate(const struct sock *meta_sk, unsigned int mss) ++{ ++ struct mptcp_tcp_sock *mptcp; ++ u64 rate = 0; ++ ++ mptcp_for_each_sub(tcp_sk(meta_sk)->mpcb, mptcp) { ++ struct sock *sk = mptcp_to_sock(mptcp); ++ struct tcp_sock *tp = tcp_sk(sk); ++ int this_mss; ++ u64 this_rate; ++ ++ if (!mptcp_sk_can_send(sk)) ++ continue; ++ ++ /* Do not consider subflows without a RTT estimation yet ++ * otherwise this_rate >>> rate. ++ */ ++ if (unlikely(!tp->srtt_us)) ++ continue; ++ ++ this_mss = tcp_current_mss(sk); ++ ++ /* If this_mss is smaller than mss, it means that a segment will ++ * be splitted in two (or more) when pushed on this subflow. If ++ * you consider that mss = 1428 and this_mss = 1420 then two ++ * segments will be generated: a 1420-byte and 8-byte segment. ++ * The latter will introduce a large overhead as for a single ++ * data segment 2 slots will be used in the congestion window. ++ * Therefore reducing by ~2 the potential throughput of this ++ * subflow. Indeed, 1428 will be send while 2840 could have been ++ * sent if mss == 1420 reducing the throughput by 2840 / 1428. ++ * ++ * The following algorithm take into account this overhead ++ * when computing the potential throughput that MPTCP can ++ * achieve when generating mss-byte segments. ++ * ++ * The formulae is the following: ++ * \sum_{\forall sub} ratio * \frac{mss * cwnd_sub}{rtt_sub} ++ * Where ratio is computed as follows: ++ * \frac{mss}{\ceil{mss / mss_sub} * mss_sub} ++ * ++ * ratio gives the reduction factor of the theoretical ++ * throughput a subflow can achieve if MPTCP uses a specific ++ * MSS value. ++ */ ++ this_rate = div64_u64((u64)mss * mss * (USEC_PER_SEC << 3) * ++ max(tp->snd_cwnd, tp->packets_out), ++ (u64)tp->srtt_us * ++ DIV_ROUND_UP(mss, this_mss) * this_mss); ++ rate += this_rate; ++ } ++ ++ return rate; ++} ++ ++static unsigned int __mptcp_current_mss(const struct sock *meta_sk) ++{ ++ struct mptcp_tcp_sock *mptcp; ++ unsigned int mss = 0; ++ u64 rate = 0; ++ ++ mptcp_for_each_sub(tcp_sk(meta_sk)->mpcb, mptcp) { ++ struct sock *sk = mptcp_to_sock(mptcp); ++ int this_mss; ++ u64 this_rate; ++ ++ if (!mptcp_sk_can_send(sk)) ++ continue; ++ ++ this_mss = tcp_current_mss(sk); ++ ++ /* Same mss values will produce the same throughput. */ ++ if (this_mss == mss) ++ continue; ++ ++ /* See whether using this mss value can theoretically improve ++ * the performances. ++ */ ++ this_rate = mptcp_calc_rate(meta_sk, this_mss); ++ if (this_rate >= rate) { ++ mss = this_mss; ++ rate = this_rate; ++ } ++ } ++ ++ return mss; ++} ++ ++unsigned int mptcp_current_mss(struct sock *meta_sk) ++{ ++ unsigned int mss = __mptcp_current_mss(meta_sk); ++ ++ /* If no subflow is available, we take a default-mss from the ++ * meta-socket. ++ */ ++ return !mss ? tcp_current_mss(meta_sk) : mss; ++} ++ ++int mptcp_check_snd_buf(const struct tcp_sock *tp) ++{ ++ const struct mptcp_tcp_sock *mptcp; ++ u32 rtt_max = tp->srtt_us; ++ u64 bw_est; ++ ++ if (!tp->srtt_us) ++ return tp->reordering + 1; ++ ++ mptcp_for_each_sub(tp->mpcb, mptcp) { ++ const struct sock *sk = mptcp_to_sock(mptcp); ++ ++ if (!mptcp_sk_can_send(sk)) ++ continue; ++ ++ if (rtt_max < tcp_sk(sk)->srtt_us) ++ rtt_max = tcp_sk(sk)->srtt_us; ++ } ++ ++ bw_est = div64_u64(((u64)tp->snd_cwnd * rtt_max) << 16, ++ (u64)tp->srtt_us); ++ ++ return max_t(unsigned int, (u32)(bw_est >> 16), ++ tp->reordering + 1); ++} ++ ++unsigned int mptcp_xmit_size_goal(const struct sock *meta_sk, u32 mss_now, ++ int large_allowed) ++{ ++ u32 xmit_size_goal = 0; ++ ++ if (large_allowed && !tcp_sk(meta_sk)->mpcb->dss_csum) { ++ struct mptcp_tcp_sock *mptcp; ++ ++ mptcp_for_each_sub(tcp_sk(meta_sk)->mpcb, mptcp) { ++ struct sock *sk = mptcp_to_sock(mptcp); ++ int this_size_goal; ++ ++ if (!mptcp_sk_can_send(sk)) ++ continue; ++ ++ this_size_goal = tcp_xmit_size_goal(sk, mss_now, 1); ++ if (this_size_goal > xmit_size_goal) ++ xmit_size_goal = this_size_goal; ++ } ++ } ++ ++ return max(xmit_size_goal, mss_now); ++} ++ +diff --git a/net/mptcp/mptcp_pm.c b/net/mptcp/mptcp_pm.c +new file mode 100644 +index 000000000000..0e24e0aaa70a +--- /dev/null ++++ b/net/mptcp/mptcp_pm.c +@@ -0,0 +1,226 @@ ++/* ++ * MPTCP implementation - MPTCP-subflow-management ++ * ++ * Initial Design & Implementation: ++ * Sébastien Barré ++ * ++ * Current Maintainer & Author: ++ * Christoph Paasch ++ * ++ * Additional authors: ++ * Jaakko Korkeaniemi ++ * Gregory Detal ++ * Fabien Duchêne ++ * Andreas Seelinger ++ * Lavkesh Lahngir ++ * Andreas Ripke ++ * Vlad Dogaru ++ * Octavian Purdila ++ * John Ronan ++ * Catalin Nicutar ++ * Brandon Heller ++ * ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License ++ * as published by the Free Software Foundation; either version ++ * 2 of the License, or (at your option) any later version. ++ */ ++ ++ ++#include ++#include ++ ++static DEFINE_SPINLOCK(mptcp_pm_list_lock); ++static LIST_HEAD(mptcp_pm_list); ++ ++static int mptcp_default_id(const struct sock *meta_sk, sa_family_t family, ++ union inet_addr *addr, bool *low_prio) ++{ ++ return 0; ++} ++ ++struct mptcp_pm_ops mptcp_pm_default = { ++ .get_local_id = mptcp_default_id, /* We do not care */ ++ .name = "default", ++ .owner = THIS_MODULE, ++}; ++ ++static struct mptcp_pm_ops *mptcp_pm_find(const char *name) ++{ ++ struct mptcp_pm_ops *e; ++ ++ list_for_each_entry_rcu(e, &mptcp_pm_list, list) { ++ if (strcmp(e->name, name) == 0) ++ return e; ++ } ++ ++ return NULL; ++} ++ ++int mptcp_register_path_manager(struct mptcp_pm_ops *pm) ++{ ++ int ret = 0; ++ ++ if (!pm->get_local_id) ++ return -EINVAL; ++ ++ spin_lock(&mptcp_pm_list_lock); ++ if (mptcp_pm_find(pm->name)) { ++ pr_notice("%s already registered\n", pm->name); ++ ret = -EEXIST; ++ } else { ++ list_add_tail_rcu(&pm->list, &mptcp_pm_list); ++ pr_info("%s registered\n", pm->name); ++ } ++ spin_unlock(&mptcp_pm_list_lock); ++ ++ return ret; ++} ++EXPORT_SYMBOL_GPL(mptcp_register_path_manager); ++ ++void mptcp_unregister_path_manager(struct mptcp_pm_ops *pm) ++{ ++ spin_lock(&mptcp_pm_list_lock); ++ list_del_rcu(&pm->list); ++ spin_unlock(&mptcp_pm_list_lock); ++ ++ /* Wait for outstanding readers to complete before the ++ * module gets removed entirely. ++ * ++ * A try_module_get() should fail by now as our module is ++ * in "going" state since no refs are held anymore and ++ * module_exit() handler being called. ++ */ ++ synchronize_rcu(); ++} ++EXPORT_SYMBOL_GPL(mptcp_unregister_path_manager); ++ ++void mptcp_get_default_path_manager(char *name) ++{ ++ struct mptcp_pm_ops *pm; ++ ++ BUG_ON(list_empty(&mptcp_pm_list)); ++ ++ rcu_read_lock(); ++ pm = list_entry(mptcp_pm_list.next, struct mptcp_pm_ops, list); ++ strncpy(name, pm->name, MPTCP_PM_NAME_MAX); ++ rcu_read_unlock(); ++} ++ ++int mptcp_set_default_path_manager(const char *name) ++{ ++ struct mptcp_pm_ops *pm; ++ int ret = -ENOENT; ++ ++ spin_lock(&mptcp_pm_list_lock); ++ pm = mptcp_pm_find(name); ++#ifdef CONFIG_MODULES ++ if (!pm && capable(CAP_NET_ADMIN)) { ++ spin_unlock(&mptcp_pm_list_lock); ++ ++ request_module("mptcp_%s", name); ++ spin_lock(&mptcp_pm_list_lock); ++ pm = mptcp_pm_find(name); ++ } ++#endif ++ ++ if (pm) { ++ list_move(&pm->list, &mptcp_pm_list); ++ ret = 0; ++ } else { ++ pr_info("%s is not available\n", name); ++ } ++ spin_unlock(&mptcp_pm_list_lock); ++ ++ return ret; ++} ++ ++static struct mptcp_pm_ops *__mptcp_pm_find_autoload(const char *name) ++{ ++ struct mptcp_pm_ops *pm = mptcp_pm_find(name); ++#ifdef CONFIG_MODULES ++ if (!pm && capable(CAP_NET_ADMIN)) { ++ rcu_read_unlock(); ++ request_module("mptcp_%s", name); ++ rcu_read_lock(); ++ pm = mptcp_pm_find(name); ++ } ++#endif ++ return pm; ++} ++ ++void mptcp_init_path_manager(struct mptcp_cb *mpcb) ++{ ++ struct mptcp_pm_ops *pm; ++ struct sock *meta_sk = mpcb->meta_sk; ++ struct tcp_sock *meta_tp = tcp_sk(meta_sk); ++ ++ rcu_read_lock(); ++ /* if path manager was set using socket option */ ++ if (meta_tp->mptcp_pm_setsockopt) { ++ pm = __mptcp_pm_find_autoload(meta_tp->mptcp_pm_name); ++ if (pm && try_module_get(pm->owner)) { ++ mpcb->pm_ops = pm; ++ goto out; ++ } ++ } ++ ++ list_for_each_entry_rcu(pm, &mptcp_pm_list, list) { ++ if (try_module_get(pm->owner)) { ++ mpcb->pm_ops = pm; ++ break; ++ } ++ } ++out: ++ rcu_read_unlock(); ++} ++ ++/* Change path manager for socket */ ++int mptcp_set_path_manager(struct sock *sk, const char *name) ++{ ++ struct mptcp_pm_ops *pm; ++ int err = 0; ++ ++ rcu_read_lock(); ++ pm = __mptcp_pm_find_autoload(name); ++ ++ if (!pm) { ++ err = -ENOENT; ++ } else if (!ns_capable(sock_net(sk)->user_ns, CAP_NET_ADMIN)) { ++ err = -EPERM; ++ } else { ++ strcpy(tcp_sk(sk)->mptcp_pm_name, name); ++ tcp_sk(sk)->mptcp_pm_setsockopt = 1; ++ } ++ rcu_read_unlock(); ++ ++ return err; ++} ++ ++/* Manage refcounts on socket close. */ ++void mptcp_cleanup_path_manager(struct mptcp_cb *mpcb) ++{ ++ module_put(mpcb->pm_ops->owner); ++} ++ ++/* Fallback to the default path-manager. */ ++void mptcp_fallback_default(struct mptcp_cb *mpcb) ++{ ++ struct mptcp_pm_ops *pm; ++ ++ mptcp_cleanup_path_manager(mpcb); ++ pm = mptcp_pm_find("default"); ++ ++ /* Cannot fail - it's the default module */ ++ try_module_get(pm->owner); ++ mpcb->pm_ops = pm; ++} ++EXPORT_SYMBOL_GPL(mptcp_fallback_default); ++ ++/* Set default value from kernel configuration at bootup */ ++static int __init mptcp_path_manager_default(void) ++{ ++ return mptcp_set_default_path_manager(CONFIG_DEFAULT_MPTCP_PM); ++} ++late_initcall(mptcp_path_manager_default); +diff --git a/net/mptcp/mptcp_redundant.c b/net/mptcp/mptcp_redundant.c +new file mode 100644 +index 000000000000..3db4e69acef2 +--- /dev/null ++++ b/net/mptcp/mptcp_redundant.c +@@ -0,0 +1,395 @@ ++/* ++ * MPTCP Scheduler to reduce latency and jitter. ++ * ++ * This scheduler sends all packets redundantly on all available subflows. ++ * ++ * Initial Design & Implementation: ++ * Tobias Erbshaeusser ++ * Alexander Froemmgen ++ * ++ * Initial corrections & modifications: ++ * Christian Pinedo ++ * Igor Lopez ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License ++ * as published by the Free Software Foundation; either version ++ * 2 of the License, or (at your option) any later version. ++ */ ++ ++#include ++#include ++ ++/* Struct to store the data of a single subflow */ ++struct redsched_priv { ++ /* The skb or NULL */ ++ struct sk_buff *skb; ++ /* Start/end sequence number of the skb. This number should be checked ++ * to be valid before the skb field is used ++ */ ++ u32 skb_start_seq; ++ u32 skb_end_seq; ++}; ++ ++/* Struct to store the data of the control block */ ++struct redsched_cb { ++ /* The next subflow where a skb should be sent or NULL */ ++ struct tcp_sock *next_subflow; ++}; ++ ++/* Returns the socket data from a given subflow socket */ ++static struct redsched_priv *redsched_get_priv(struct tcp_sock *tp) ++{ ++ return (struct redsched_priv *)&tp->mptcp->mptcp_sched[0]; ++} ++ ++/* Returns the control block data from a given meta socket */ ++static struct redsched_cb *redsched_get_cb(struct tcp_sock *tp) ++{ ++ return (struct redsched_cb *)&tp->mpcb->mptcp_sched[0]; ++} ++ ++static bool redsched_get_active_valid_sks(struct sock *meta_sk) ++{ ++ struct tcp_sock *meta_tp = tcp_sk(meta_sk); ++ struct mptcp_cb *mpcb = meta_tp->mpcb; ++ struct mptcp_tcp_sock *mptcp; ++ int active_valid_sks = 0; ++ ++ mptcp_for_each_sub(mpcb, mptcp) { ++ struct sock *sk = mptcp_to_sock(mptcp); ++ ++ if (subflow_is_active((struct tcp_sock *)sk) && ++ !mptcp_is_def_unavailable(sk)) ++ active_valid_sks++; ++ } ++ ++ return active_valid_sks; ++} ++ ++static bool redsched_use_subflow(struct sock *meta_sk, ++ int active_valid_sks, ++ struct tcp_sock *tp, ++ struct sk_buff *skb) ++{ ++ if (!skb || !mptcp_is_available((struct sock *)tp, skb, false)) ++ return false; ++ ++ if (TCP_SKB_CB(skb)->path_mask != 0) ++ return subflow_is_active(tp); ++ ++ if (TCP_SKB_CB(skb)->path_mask == 0) { ++ if (active_valid_sks == -1) ++ active_valid_sks = redsched_get_active_valid_sks(meta_sk); ++ ++ if (subflow_is_backup(tp) && active_valid_sks > 0) ++ return false; ++ else ++ return true; ++ } ++ ++ return false; ++} ++ ++#define mptcp_entry_next_rcu(__mptcp) \ ++ hlist_entry_safe(rcu_dereference_raw(hlist_next_rcu( \ ++ &(__mptcp)->node)), struct mptcp_tcp_sock, node) ++ ++static void redsched_update_next_subflow(struct tcp_sock *tp, ++ struct redsched_cb *red_cb) ++{ ++ struct mptcp_tcp_sock *mptcp = mptcp_entry_next_rcu(tp->mptcp); ++ ++ if (mptcp) ++ red_cb->next_subflow = mptcp->tp; ++ else ++ red_cb->next_subflow = NULL; ++} ++ ++static struct sock *red_get_available_subflow(struct sock *meta_sk, ++ struct sk_buff *skb, ++ bool zero_wnd_test) ++{ ++ struct tcp_sock *meta_tp = tcp_sk(meta_sk); ++ struct mptcp_cb *mpcb = meta_tp->mpcb; ++ struct redsched_cb *red_cb = redsched_get_cb(meta_tp); ++ struct tcp_sock *first_tp = red_cb->next_subflow, *tp; ++ struct mptcp_tcp_sock *mptcp; ++ int found = 0; ++ ++ /* Answer data_fin on same subflow */ ++ if (meta_sk->sk_shutdown & RCV_SHUTDOWN && ++ skb && mptcp_is_data_fin(skb)) { ++ mptcp_for_each_sub(mpcb, mptcp) { ++ struct sock *sk = mptcp_to_sock(mptcp); ++ ++ if (tcp_sk(sk)->mptcp->path_index == ++ mpcb->dfin_path_index && ++ mptcp_is_available(sk, skb, zero_wnd_test)) ++ return sk; ++ } ++ } ++ ++ if (!first_tp && !hlist_empty(&mpcb->conn_list)) { ++ first_tp = hlist_entry_safe(rcu_dereference_raw(hlist_first_rcu(&mpcb->conn_list)), ++ struct mptcp_tcp_sock, node)->tp; ++ } ++ tp = first_tp; ++ ++ /* still NULL (no subflow in conn_list?) */ ++ if (!first_tp) ++ return NULL; ++ ++ /* Search for a subflow to send it. ++ * ++ * We want to pick a subflow that is after 'first_tp' in the list of subflows. ++ * Thus, the first mptcp_for_each_sub()-loop tries to walk the list up ++ * to the subflow 'tp' and then checks whether any one of the remaining ++ * ones is eligible to send. ++ * The second mptcp_for_each-sub()-loop is then iterating from the ++ * beginning of the list up to 'first_tp'. ++ */ ++ mptcp_for_each_sub(mpcb, mptcp) { ++ /* We go up to the subflow 'tp' and start from there */ ++ if (tp == mptcp->tp) ++ found = 1; ++ ++ if (!found) ++ continue; ++ tp = mptcp->tp; ++ ++ if (mptcp_is_available((struct sock *)tp, skb, ++ zero_wnd_test)) { ++ redsched_update_next_subflow(tp, red_cb); ++ return (struct sock *)tp; ++ } ++ } ++ ++ mptcp_for_each_sub(mpcb, mptcp) { ++ tp = mptcp->tp; ++ ++ if (tp == first_tp) ++ break; ++ ++ if (mptcp_is_available((struct sock *)tp, skb, ++ zero_wnd_test)) { ++ redsched_update_next_subflow(tp, red_cb); ++ return (struct sock *)tp; ++ } ++ } ++ ++ /* No space */ ++ return NULL; ++} ++ ++/* Corrects the stored skb pointers if they are invalid */ ++static void redsched_correct_skb_pointers(struct sock *meta_sk, ++ struct redsched_priv *red_p) ++{ ++ struct tcp_sock *meta_tp = tcp_sk(meta_sk); ++ ++ if (red_p->skb && ++ (!after(red_p->skb_start_seq, meta_tp->snd_una) || ++ after(red_p->skb_end_seq, meta_tp->snd_nxt))) ++ red_p->skb = NULL; ++} ++ ++/* Returns the next skb from the queue */ ++static struct sk_buff *redsched_next_skb_from_queue(struct sk_buff_head *queue, ++ struct sk_buff *previous, ++ struct sock *meta_sk) ++{ ++ struct sk_buff *skb; ++ ++ if (!previous) ++ return tcp_rtx_queue_head(meta_sk) ? : skb_peek(queue); ++ ++ /* sk_data->skb stores the last scheduled packet for this subflow. ++ * If sk_data->skb was scheduled but not sent (e.g., due to nagle), ++ * we have to schedule it again. ++ * ++ * For the redundant scheduler, there are two cases: ++ * 1. sk_data->skb was not sent on another subflow: ++ * we have to schedule it again to ensure that we do not ++ * skip this packet. ++ * 2. sk_data->skb was already sent on another subflow: ++ * with regard to the redundant semantic, we have to ++ * schedule it again. However, we keep it simple and ignore it, ++ * as it was already sent by another subflow. ++ * This might be changed in the future. ++ * ++ * For case 1, send_head is equal previous, as only a single ++ * packet can be skipped. ++ */ ++ if (tcp_send_head(meta_sk) == previous) ++ return tcp_send_head(meta_sk); ++ ++ skb = skb_rb_next(previous); ++ if (skb) ++ return skb; ++ ++ return tcp_send_head(meta_sk); ++} ++ ++static struct sk_buff *mptcp_red_next_segment(struct sock *meta_sk, ++ int *reinject, ++ struct sock **subsk, ++ unsigned int *limit) ++{ ++ struct tcp_sock *meta_tp = tcp_sk(meta_sk); ++ struct mptcp_cb *mpcb = meta_tp->mpcb; ++ struct redsched_cb *red_cb = redsched_get_cb(meta_tp); ++ struct tcp_sock *first_tp = red_cb->next_subflow, *tp; ++ struct mptcp_tcp_sock *mptcp; ++ int active_valid_sks = -1; ++ struct sk_buff *skb; ++ int found = 0; ++ ++ /* As we set it, we have to reset it as well. */ ++ *limit = 0; ++ ++ if (skb_queue_empty(&mpcb->reinject_queue) && ++ skb_queue_empty(&meta_sk->sk_write_queue) && ++ tcp_rtx_queue_empty(meta_sk)) ++ /* Nothing to send */ ++ return NULL; ++ ++ /* First try reinjections */ ++ skb = skb_peek(&mpcb->reinject_queue); ++ if (skb) { ++ *subsk = get_available_subflow(meta_sk, skb, false); ++ if (!*subsk) ++ return NULL; ++ *reinject = 1; ++ return skb; ++ } ++ ++ /* Then try indistinctly redundant and normal skbs */ ++ ++ if (!first_tp && !hlist_empty(&mpcb->conn_list)) { ++ first_tp = hlist_entry_safe(rcu_dereference_raw(hlist_first_rcu(&mpcb->conn_list)), ++ struct mptcp_tcp_sock, node)->tp; ++ } ++ ++ /* still NULL (no subflow in conn_list?) */ ++ if (!first_tp) ++ return NULL; ++ ++ tp = first_tp; ++ ++ *reinject = 0; ++ active_valid_sks = redsched_get_active_valid_sks(meta_sk); ++ ++ /* We want to pick a subflow that is after 'first_tp' in the list of subflows. ++ * Thus, the first mptcp_for_each_sub()-loop tries to walk the list up ++ * to the subflow 'tp' and then checks whether any one of the remaining ++ * ones can send a segment. ++ * The second mptcp_for_each-sub()-loop is then iterating from the ++ * beginning of the list up to 'first_tp'. ++ */ ++ mptcp_for_each_sub(mpcb, mptcp) { ++ struct redsched_priv *red_p; ++ ++ if (tp == mptcp->tp) ++ found = 1; ++ ++ if (!found) ++ continue; ++ ++ tp = mptcp->tp; ++ ++ /* Correct the skb pointers of the current subflow */ ++ red_p = redsched_get_priv(tp); ++ redsched_correct_skb_pointers(meta_sk, red_p); ++ ++ skb = redsched_next_skb_from_queue(&meta_sk->sk_write_queue, ++ red_p->skb, meta_sk); ++ if (skb && redsched_use_subflow(meta_sk, active_valid_sks, tp, ++ skb)) { ++ red_p->skb = skb; ++ red_p->skb_start_seq = TCP_SKB_CB(skb)->seq; ++ red_p->skb_end_seq = TCP_SKB_CB(skb)->end_seq; ++ redsched_update_next_subflow(tp, red_cb); ++ *subsk = (struct sock *)tp; ++ ++ if (TCP_SKB_CB(skb)->path_mask) ++ *reinject = -1; ++ return skb; ++ } ++ } ++ ++ mptcp_for_each_sub(mpcb, mptcp) { ++ struct redsched_priv *red_p; ++ ++ tp = mptcp->tp; ++ ++ if (tp == first_tp) ++ break; ++ ++ /* Correct the skb pointers of the current subflow */ ++ red_p = redsched_get_priv(tp); ++ redsched_correct_skb_pointers(meta_sk, red_p); ++ ++ skb = redsched_next_skb_from_queue(&meta_sk->sk_write_queue, ++ red_p->skb, meta_sk); ++ if (skb && redsched_use_subflow(meta_sk, active_valid_sks, tp, ++ skb)) { ++ red_p->skb = skb; ++ red_p->skb_start_seq = TCP_SKB_CB(skb)->seq; ++ red_p->skb_end_seq = TCP_SKB_CB(skb)->end_seq; ++ redsched_update_next_subflow(tp, red_cb); ++ *subsk = (struct sock *)tp; ++ ++ if (TCP_SKB_CB(skb)->path_mask) ++ *reinject = -1; ++ return skb; ++ } ++ } ++ ++ /* Nothing to send */ ++ return NULL; ++} ++ ++static void redsched_release(struct sock *sk) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct redsched_cb *red_cb = redsched_get_cb(tp); ++ ++ /* Check if the next subflow would be the released one. If yes correct ++ * the pointer ++ */ ++ if (red_cb->next_subflow == tp) ++ redsched_update_next_subflow(tp, red_cb); ++} ++ ++static struct mptcp_sched_ops mptcp_sched_red = { ++ .get_subflow = red_get_available_subflow, ++ .next_segment = mptcp_red_next_segment, ++ .release = redsched_release, ++ .name = "redundant", ++ .owner = THIS_MODULE, ++}; ++ ++static int __init red_register(void) ++{ ++ BUILD_BUG_ON(sizeof(struct redsched_priv) > MPTCP_SCHED_SIZE); ++ BUILD_BUG_ON(sizeof(struct redsched_cb) > MPTCP_SCHED_DATA_SIZE); ++ ++ if (mptcp_register_scheduler(&mptcp_sched_red)) ++ return -1; ++ ++ return 0; ++} ++ ++static void red_unregister(void) ++{ ++ mptcp_unregister_scheduler(&mptcp_sched_red); ++} ++ ++module_init(red_register); ++module_exit(red_unregister); ++ ++MODULE_AUTHOR("Tobias Erbshaeusser, Alexander Froemmgen"); ++MODULE_LICENSE("GPL"); ++MODULE_DESCRIPTION("REDUNDANT MPTCP"); ++MODULE_VERSION("0.90"); +diff --git a/net/mptcp/mptcp_rr.c b/net/mptcp/mptcp_rr.c +new file mode 100644 +index 000000000000..396e8aaf4762 +--- /dev/null ++++ b/net/mptcp/mptcp_rr.c +@@ -0,0 +1,309 @@ ++/* MPTCP Scheduler module selector. Highly inspired by tcp_cong.c */ ++ ++#include ++#include ++ ++static unsigned char num_segments __read_mostly = 1; ++module_param(num_segments, byte, 0644); ++MODULE_PARM_DESC(num_segments, "The number of consecutive segments that are part of a burst"); ++ ++static bool cwnd_limited __read_mostly = 1; ++module_param(cwnd_limited, bool, 0644); ++MODULE_PARM_DESC(cwnd_limited, "if set to 1, the scheduler tries to fill the congestion-window on all subflows"); ++ ++struct rrsched_priv { ++ unsigned char quota; ++}; ++ ++static struct rrsched_priv *rrsched_get_priv(const struct tcp_sock *tp) ++{ ++ return (struct rrsched_priv *)&tp->mptcp->mptcp_sched[0]; ++} ++ ++/* If the sub-socket sk available to send the skb? */ ++static bool mptcp_rr_is_available(const struct sock *sk, const struct sk_buff *skb, ++ bool zero_wnd_test, bool cwnd_test) ++{ ++ const struct tcp_sock *tp = tcp_sk(sk); ++ unsigned int space, in_flight; ++ ++ /* Set of states for which we are allowed to send data */ ++ if (!mptcp_sk_can_send(sk)) ++ return false; ++ ++ /* We do not send data on this subflow unless it is ++ * fully established, i.e. the 4th ack has been received. ++ */ ++ if (tp->mptcp->pre_established) ++ return false; ++ ++ if (tp->pf) ++ return false; ++ ++ if (inet_csk(sk)->icsk_ca_state == TCP_CA_Loss) { ++ /* If SACK is disabled, and we got a loss, TCP does not exit ++ * the loss-state until something above high_seq has been acked. ++ * (see tcp_try_undo_recovery) ++ * ++ * high_seq is the snd_nxt at the moment of the RTO. As soon ++ * as we have an RTO, we won't push data on the subflow. ++ * Thus, snd_una can never go beyond high_seq. ++ */ ++ if (!tcp_is_reno(tp)) ++ return false; ++ else if (tp->snd_una != tp->high_seq) ++ return false; ++ } ++ ++ if (!tp->mptcp->fully_established) { ++ /* Make sure that we send in-order data */ ++ if (skb && tp->mptcp->second_packet && ++ tp->mptcp->last_end_data_seq != TCP_SKB_CB(skb)->seq) ++ return false; ++ } ++ ++ if (!cwnd_test) ++ goto zero_wnd_test; ++ ++ in_flight = tcp_packets_in_flight(tp); ++ /* Not even a single spot in the cwnd */ ++ if (in_flight >= tp->snd_cwnd) ++ return false; ++ ++ /* Now, check if what is queued in the subflow's send-queue ++ * already fills the cwnd. ++ */ ++ space = (tp->snd_cwnd - in_flight) * tp->mss_cache; ++ ++ if (tp->write_seq - tp->snd_nxt > space) ++ return false; ++ ++zero_wnd_test: ++ if (zero_wnd_test && !before(tp->write_seq, tcp_wnd_end(tp))) ++ return false; ++ ++ return true; ++} ++ ++/* Are we not allowed to reinject this skb on tp? */ ++static int mptcp_rr_dont_reinject_skb(const struct tcp_sock *tp, const struct sk_buff *skb) ++{ ++ /* If the skb has already been enqueued in this sk, try to find ++ * another one. ++ */ ++ return skb && ++ /* Has the skb already been enqueued into this subsocket? */ ++ mptcp_pi_to_flag(tp->mptcp->path_index) & TCP_SKB_CB(skb)->path_mask; ++} ++ ++/* We just look for any subflow that is available */ ++static struct sock *rr_get_available_subflow(struct sock *meta_sk, ++ struct sk_buff *skb, ++ bool zero_wnd_test) ++{ ++ const struct mptcp_cb *mpcb = tcp_sk(meta_sk)->mpcb; ++ struct sock *sk = NULL, *bestsk = NULL, *backupsk = NULL; ++ struct mptcp_tcp_sock *mptcp; ++ ++ /* Answer data_fin on same subflow!!! */ ++ if (meta_sk->sk_shutdown & RCV_SHUTDOWN && ++ skb && mptcp_is_data_fin(skb)) { ++ mptcp_for_each_sub(mpcb, mptcp) { ++ sk = mptcp_to_sock(mptcp); ++ if (tcp_sk(sk)->mptcp->path_index == mpcb->dfin_path_index && ++ mptcp_rr_is_available(sk, skb, zero_wnd_test, true)) ++ return sk; ++ } ++ } ++ ++ /* First, find the best subflow */ ++ mptcp_for_each_sub(mpcb, mptcp) { ++ struct tcp_sock *tp; ++ ++ sk = mptcp_to_sock(mptcp); ++ tp = tcp_sk(sk); ++ ++ if (!mptcp_rr_is_available(sk, skb, zero_wnd_test, true)) ++ continue; ++ ++ if (mptcp_rr_dont_reinject_skb(tp, skb)) { ++ backupsk = sk; ++ continue; ++ } ++ ++ bestsk = sk; ++ } ++ ++ if (bestsk) { ++ sk = bestsk; ++ } else if (backupsk) { ++ /* It has been sent on all subflows once - let's give it a ++ * chance again by restarting its pathmask. ++ */ ++ if (skb) ++ TCP_SKB_CB(skb)->path_mask = 0; ++ sk = backupsk; ++ } ++ ++ return sk; ++} ++ ++/* Returns the next segment to be sent from the mptcp meta-queue. ++ * (chooses the reinject queue if any segment is waiting in it, otherwise, ++ * chooses the normal write queue). ++ * Sets *@reinject to 1 if the returned segment comes from the ++ * reinject queue. Sets it to 0 if it is the regular send-head of the meta-sk, ++ * and sets it to -1 if it is a meta-level retransmission to optimize the ++ * receive-buffer. ++ */ ++static struct sk_buff *__mptcp_rr_next_segment(const struct sock *meta_sk, int *reinject) ++{ ++ const struct mptcp_cb *mpcb = tcp_sk(meta_sk)->mpcb; ++ struct sk_buff *skb = NULL; ++ ++ *reinject = 0; ++ ++ /* If we are in fallback-mode, just take from the meta-send-queue */ ++ if (mpcb->infinite_mapping_snd || mpcb->send_infinite_mapping) ++ return tcp_send_head(meta_sk); ++ ++ skb = skb_peek(&mpcb->reinject_queue); ++ ++ if (skb) ++ *reinject = 1; ++ else ++ skb = tcp_send_head(meta_sk); ++ return skb; ++} ++ ++static struct sk_buff *mptcp_rr_next_segment(struct sock *meta_sk, ++ int *reinject, ++ struct sock **subsk, ++ unsigned int *limit) ++{ ++ const struct mptcp_cb *mpcb = tcp_sk(meta_sk)->mpcb; ++ struct sock *choose_sk = NULL; ++ struct mptcp_tcp_sock *mptcp; ++ struct sk_buff *skb = __mptcp_rr_next_segment(meta_sk, reinject); ++ unsigned char split = num_segments; ++ unsigned char iter = 0, full_subs = 0; ++ ++ /* As we set it, we have to reset it as well. */ ++ *limit = 0; ++ ++ if (!skb) ++ return NULL; ++ ++ if (*reinject) { ++ *subsk = rr_get_available_subflow(meta_sk, skb, false); ++ if (!*subsk) ++ return NULL; ++ ++ return skb; ++ } ++ ++retry: ++ ++ /* First, we look for a subflow who is currently being used */ ++ mptcp_for_each_sub(mpcb, mptcp) { ++ struct sock *sk_it = mptcp_to_sock(mptcp); ++ struct tcp_sock *tp_it = tcp_sk(sk_it); ++ struct rrsched_priv *rr_p = rrsched_get_priv(tp_it); ++ ++ if (!mptcp_rr_is_available(sk_it, skb, false, cwnd_limited)) ++ continue; ++ ++ iter++; ++ ++ /* Is this subflow currently being used? */ ++ if (rr_p->quota > 0 && rr_p->quota < num_segments) { ++ split = num_segments - rr_p->quota; ++ choose_sk = sk_it; ++ goto found; ++ } ++ ++ /* Or, it's totally unused */ ++ if (!rr_p->quota) { ++ split = num_segments; ++ choose_sk = sk_it; ++ } ++ ++ /* Or, it must then be fully used */ ++ if (rr_p->quota >= num_segments) ++ full_subs++; ++ } ++ ++ /* All considered subflows have a full quota, and we considered at ++ * least one. ++ */ ++ if (iter && iter == full_subs) { ++ /* So, we restart this round by setting quota to 0 and retry ++ * to find a subflow. ++ */ ++ mptcp_for_each_sub(mpcb, mptcp) { ++ struct sock *sk_it = mptcp_to_sock(mptcp); ++ struct tcp_sock *tp_it = tcp_sk(sk_it); ++ struct rrsched_priv *rr_p = rrsched_get_priv(tp_it); ++ ++ if (!mptcp_rr_is_available(sk_it, skb, false, cwnd_limited)) ++ continue; ++ ++ rr_p->quota = 0; ++ } ++ ++ goto retry; ++ } ++ ++found: ++ if (choose_sk) { ++ unsigned int mss_now; ++ struct tcp_sock *choose_tp = tcp_sk(choose_sk); ++ struct rrsched_priv *rr_p = rrsched_get_priv(choose_tp); ++ ++ if (!mptcp_rr_is_available(choose_sk, skb, false, true)) ++ return NULL; ++ ++ *subsk = choose_sk; ++ mss_now = tcp_current_mss(*subsk); ++ *limit = split * mss_now; ++ ++ if (skb->len > mss_now) ++ rr_p->quota += DIV_ROUND_UP(skb->len, mss_now); ++ else ++ rr_p->quota++; ++ ++ return skb; ++ } ++ ++ return NULL; ++} ++ ++static struct mptcp_sched_ops mptcp_sched_rr = { ++ .get_subflow = rr_get_available_subflow, ++ .next_segment = mptcp_rr_next_segment, ++ .name = "roundrobin", ++ .owner = THIS_MODULE, ++}; ++ ++static int __init rr_register(void) ++{ ++ BUILD_BUG_ON(sizeof(struct rrsched_priv) > MPTCP_SCHED_SIZE); ++ ++ if (mptcp_register_scheduler(&mptcp_sched_rr)) ++ return -1; ++ ++ return 0; ++} ++ ++static void rr_unregister(void) ++{ ++ mptcp_unregister_scheduler(&mptcp_sched_rr); ++} ++ ++module_init(rr_register); ++module_exit(rr_unregister); ++ ++MODULE_AUTHOR("Christoph Paasch"); ++MODULE_LICENSE("GPL"); ++MODULE_DESCRIPTION("ROUNDROBIN MPTCP"); ++MODULE_VERSION("0.89"); +diff --git a/net/mptcp/mptcp_sched.c b/net/mptcp/mptcp_sched.c +new file mode 100644 +index 000000000000..eed9bfb44b59 +--- /dev/null ++++ b/net/mptcp/mptcp_sched.c +@@ -0,0 +1,677 @@ ++/* MPTCP Scheduler module selector. Highly inspired by tcp_cong.c */ ++ ++#include ++#include ++#include ++#include ++ ++static DEFINE_SPINLOCK(mptcp_sched_list_lock); ++static LIST_HEAD(mptcp_sched_list); ++ ++struct defsched_priv { ++ u32 last_rbuf_opti; ++}; ++ ++static struct defsched_priv *defsched_get_priv(const struct tcp_sock *tp) ++{ ++ return (struct defsched_priv *)&tp->mptcp->mptcp_sched[0]; ++} ++ ++bool mptcp_is_def_unavailable(struct sock *sk) ++{ ++ const struct tcp_sock *tp = tcp_sk(sk); ++ ++ /* Set of states for which we are allowed to send data */ ++ if (!mptcp_sk_can_send(sk)) ++ return true; ++ ++ /* We do not send data on this subflow unless it is ++ * fully established, i.e. the 4th ack has been received. ++ */ ++ if (tp->mptcp->pre_established) ++ return true; ++ ++ if (tp->pf) ++ return true; ++ ++ return false; ++} ++EXPORT_SYMBOL_GPL(mptcp_is_def_unavailable); ++ ++/* estimate number of segments currently in flight + unsent in ++ * the subflow socket. ++ */ ++static int mptcp_subflow_queued(struct sock *sk, u32 max_tso_segs) ++{ ++ const struct tcp_sock *tp = tcp_sk(sk); ++ unsigned int queued; ++ ++ /* estimate the max number of segments in the write queue ++ * this is an overestimation, avoiding to iterate over the queue ++ * to make a better estimation. ++ * Having only one skb in the queue however might trigger tso deferral, ++ * delaying the sending of a tso segment in the hope that skb_entail ++ * will append more data to the skb soon. ++ * Therefore, in the case only one skb is in the queue, we choose to ++ * potentially underestimate, risking to schedule one skb too many onto ++ * the subflow rather than not enough. ++ */ ++ if (sk->sk_write_queue.qlen > 1) ++ queued = sk->sk_write_queue.qlen * max_tso_segs; ++ else ++ queued = sk->sk_write_queue.qlen; ++ ++ return queued + tcp_packets_in_flight(tp); ++} ++ ++static bool mptcp_is_temp_unavailable(struct sock *sk, ++ const struct sk_buff *skb, ++ bool zero_wnd_test) ++{ ++ const struct tcp_sock *tp = tcp_sk(sk); ++ unsigned int mss_now; ++ ++ if (inet_csk(sk)->icsk_ca_state == TCP_CA_Loss) { ++ /* If SACK is disabled, and we got a loss, TCP does not exit ++ * the loss-state until something above high_seq has been ++ * acked. (see tcp_try_undo_recovery) ++ * ++ * high_seq is the snd_nxt at the moment of the RTO. As soon ++ * as we have an RTO, we won't push data on the subflow. ++ * Thus, snd_una can never go beyond high_seq. ++ */ ++ if (!tcp_is_reno(tp)) ++ return true; ++ else if (tp->snd_una != tp->high_seq) ++ return true; ++ } ++ ++ if (!tp->mptcp->fully_established) { ++ /* Make sure that we send in-order data */ ++ if (skb && tp->mptcp->second_packet && ++ tp->mptcp->last_end_data_seq != TCP_SKB_CB(skb)->seq) ++ return true; ++ } ++ ++ mss_now = tcp_current_mss(sk); ++ ++ /* Not even a single spot in the cwnd */ ++ if (mptcp_subflow_queued(sk, tcp_tso_segs(sk, mss_now)) >= tp->snd_cwnd) ++ return true; ++ ++ if (zero_wnd_test && !before(tp->write_seq, tcp_wnd_end(tp))) ++ return true; ++ ++ /* Don't send on this subflow if we bypass the allowed send-window at ++ * the per-subflow level. Similar to tcp_snd_wnd_test, but manually ++ * calculated end_seq (because here at this point end_seq is still at ++ * the meta-level). ++ */ ++ if (skb && zero_wnd_test && ++ after(tp->write_seq + min(skb->len, mss_now), tcp_wnd_end(tp))) ++ return true; ++ ++ return false; ++} ++ ++/* Is the sub-socket sk available to send the skb? */ ++bool mptcp_is_available(struct sock *sk, const struct sk_buff *skb, ++ bool zero_wnd_test) ++{ ++ return !mptcp_is_def_unavailable(sk) && ++ !mptcp_is_temp_unavailable(sk, skb, zero_wnd_test); ++} ++EXPORT_SYMBOL_GPL(mptcp_is_available); ++ ++/* Are we not allowed to reinject this skb on tp? */ ++static int mptcp_dont_reinject_skb(const struct tcp_sock *tp, const struct sk_buff *skb) ++{ ++ /* If the skb has already been enqueued in this sk, try to find ++ * another one. ++ */ ++ return skb && ++ /* Has the skb already been enqueued into this subsocket? */ ++ mptcp_pi_to_flag(tp->mptcp->path_index) & TCP_SKB_CB(skb)->path_mask; ++} ++ ++bool subflow_is_backup(const struct tcp_sock *tp) ++{ ++ return tp->mptcp->rcv_low_prio || tp->mptcp->low_prio; ++} ++EXPORT_SYMBOL_GPL(subflow_is_backup); ++ ++bool subflow_is_active(const struct tcp_sock *tp) ++{ ++ return !tp->mptcp->rcv_low_prio && !tp->mptcp->low_prio; ++} ++EXPORT_SYMBOL_GPL(subflow_is_active); ++ ++/* Generic function to iterate over used and unused subflows and to select the ++ * best one ++ */ ++static struct sock ++*get_subflow_from_selectors(struct mptcp_cb *mpcb, struct sk_buff *skb, ++ bool (*selector)(const struct tcp_sock *), ++ bool zero_wnd_test, bool *force) ++{ ++ struct sock *bestsk = NULL; ++ u32 min_srtt = 0xffffffff; ++ bool found_unused = false; ++ bool found_unused_una = false; ++ struct mptcp_tcp_sock *mptcp; ++ ++ mptcp_for_each_sub(mpcb, mptcp) { ++ struct sock *sk = mptcp_to_sock(mptcp); ++ struct tcp_sock *tp = tcp_sk(sk); ++ bool unused = false; ++ ++ /* First, we choose only the wanted sks */ ++ if (!(*selector)(tp)) ++ continue; ++ ++ if (!mptcp_dont_reinject_skb(tp, skb)) ++ unused = true; ++ else if (found_unused) ++ /* If a unused sk was found previously, we continue - ++ * no need to check used sks anymore. ++ */ ++ continue; ++ ++ if (mptcp_is_def_unavailable(sk)) ++ continue; ++ ++ if (mptcp_is_temp_unavailable(sk, skb, zero_wnd_test)) { ++ if (unused) ++ found_unused_una = true; ++ continue; ++ } ++ ++ if (unused) { ++ if (!found_unused) { ++ /* It's the first time we encounter an unused ++ * sk - thus we reset the bestsk (which might ++ * have been set to a used sk). ++ */ ++ min_srtt = 0xffffffff; ++ bestsk = NULL; ++ } ++ found_unused = true; ++ } ++ ++ if (tp->srtt_us < min_srtt) { ++ min_srtt = tp->srtt_us; ++ bestsk = sk; ++ } ++ } ++ ++ if (bestsk) { ++ /* The force variable is used to mark the returned sk as ++ * previously used or not-used. ++ */ ++ if (found_unused) ++ *force = true; ++ else ++ *force = false; ++ } else { ++ /* The force variable is used to mark if there are temporally ++ * unavailable not-used sks. ++ */ ++ if (found_unused_una) ++ *force = true; ++ else ++ *force = false; ++ } ++ ++ return bestsk; ++} ++ ++/* This is the scheduler. This function decides on which flow to send ++ * a given MSS. If all subflows are found to be busy, NULL is returned ++ * The flow is selected based on the shortest RTT. ++ * If all paths have full cong windows, we simply return NULL. ++ * ++ * Additionally, this function is aware of the backup-subflows. ++ */ ++struct sock *get_available_subflow(struct sock *meta_sk, struct sk_buff *skb, ++ bool zero_wnd_test) ++{ ++ struct mptcp_cb *mpcb = tcp_sk(meta_sk)->mpcb; ++ struct sock *sk; ++ bool looping = false, force; ++ ++ /* Answer data_fin on same subflow!!! */ ++ if (meta_sk->sk_shutdown & RCV_SHUTDOWN && ++ skb && mptcp_is_data_fin(skb)) { ++ struct mptcp_tcp_sock *mptcp; ++ ++ mptcp_for_each_sub(mpcb, mptcp) { ++ sk = mptcp_to_sock(mptcp); ++ ++ if (tcp_sk(sk)->mptcp->path_index == mpcb->dfin_path_index && ++ mptcp_is_available(sk, skb, zero_wnd_test)) ++ return sk; ++ } ++ } ++ ++ /* Find the best subflow */ ++restart: ++ sk = get_subflow_from_selectors(mpcb, skb, &subflow_is_active, ++ zero_wnd_test, &force); ++ if (force) ++ /* one unused active sk or one NULL sk when there is at least ++ * one temporally unavailable unused active sk ++ */ ++ return sk; ++ ++ sk = get_subflow_from_selectors(mpcb, skb, &subflow_is_backup, ++ zero_wnd_test, &force); ++ if (!force && skb) { ++ /* one used backup sk or one NULL sk where there is no one ++ * temporally unavailable unused backup sk ++ * ++ * the skb passed through all the available active and backups ++ * sks, so clean the path mask ++ */ ++ TCP_SKB_CB(skb)->path_mask = 0; ++ ++ if (!looping) { ++ looping = true; ++ goto restart; ++ } ++ } ++ return sk; ++} ++EXPORT_SYMBOL_GPL(get_available_subflow); ++ ++static struct sk_buff *mptcp_rcv_buf_optimization(struct sock *sk, int penal) ++{ ++ struct sock *meta_sk; ++ const struct tcp_sock *tp = tcp_sk(sk); ++ struct mptcp_tcp_sock *mptcp; ++ struct sk_buff *skb_head; ++ struct defsched_priv *def_p = defsched_get_priv(tp); ++ ++ meta_sk = mptcp_meta_sk(sk); ++ skb_head = tcp_rtx_queue_head(meta_sk); ++ ++ if (!skb_head) ++ return NULL; ++ ++ /* If penalization is optional (coming from mptcp_next_segment() and ++ * We are not send-buffer-limited we do not penalize. The retransmission ++ * is just an optimization to fix the idle-time due to the delay before ++ * we wake up the application. ++ */ ++ if (!penal && sk_stream_memory_free(meta_sk)) ++ goto retrans; ++ ++ /* Only penalize again after an RTT has elapsed */ ++ if (tcp_jiffies32 - def_p->last_rbuf_opti < usecs_to_jiffies(tp->srtt_us >> 3)) ++ goto retrans; ++ ++ /* Half the cwnd of the slow flows */ ++ mptcp_for_each_sub(tp->mpcb, mptcp) { ++ struct tcp_sock *tp_it = mptcp->tp; ++ ++ if (tp_it != tp && ++ TCP_SKB_CB(skb_head)->path_mask & mptcp_pi_to_flag(tp_it->mptcp->path_index)) { ++ if (tp->srtt_us < tp_it->srtt_us && inet_csk((struct sock *)tp_it)->icsk_ca_state == TCP_CA_Open) { ++ u32 prior_cwnd = tp_it->snd_cwnd; ++ ++ tp_it->snd_cwnd = max(tp_it->snd_cwnd >> 1U, 1U); ++ ++ /* If in slow start, do not reduce the ssthresh */ ++ if (prior_cwnd >= tp_it->snd_ssthresh) ++ tp_it->snd_ssthresh = max(tp_it->snd_ssthresh >> 1U, 2U); ++ ++ def_p->last_rbuf_opti = tcp_jiffies32; ++ } ++ } ++ } ++ ++retrans: ++ ++ /* Segment not yet injected into this path? Take it!!! */ ++ if (!(TCP_SKB_CB(skb_head)->path_mask & mptcp_pi_to_flag(tp->mptcp->path_index))) { ++ bool do_retrans = false; ++ mptcp_for_each_sub(tp->mpcb, mptcp) { ++ struct tcp_sock *tp_it = mptcp->tp; ++ ++ if (tp_it != tp && ++ TCP_SKB_CB(skb_head)->path_mask & mptcp_pi_to_flag(tp_it->mptcp->path_index)) { ++ if (tp_it->snd_cwnd <= 4) { ++ do_retrans = true; ++ break; ++ } ++ ++ if (4 * tp->srtt_us >= tp_it->srtt_us) { ++ do_retrans = false; ++ break; ++ } else { ++ do_retrans = true; ++ } ++ } ++ } ++ ++ if (do_retrans && mptcp_is_available(sk, skb_head, false)) { ++ trace_mptcp_retransmit(sk, skb_head); ++ return skb_head; ++ } ++ } ++ return NULL; ++} ++ ++/* Returns the next segment to be sent from the mptcp meta-queue. ++ * (chooses the reinject queue if any segment is waiting in it, otherwise, ++ * chooses the normal write queue). ++ * Sets *@reinject to 1 if the returned segment comes from the ++ * reinject queue. Sets it to 0 if it is the regular send-head of the meta-sk, ++ * and sets it to -1 if it is a meta-level retransmission to optimize the ++ * receive-buffer. ++ */ ++static struct sk_buff *__mptcp_next_segment(struct sock *meta_sk, int *reinject) ++{ ++ const struct mptcp_cb *mpcb = tcp_sk(meta_sk)->mpcb; ++ struct sk_buff *skb = NULL; ++ ++ *reinject = 0; ++ ++ /* If we are in fallback-mode, just take from the meta-send-queue */ ++ if (mpcb->infinite_mapping_snd || mpcb->send_infinite_mapping) ++ return tcp_send_head(meta_sk); ++ ++ skb = skb_peek(&mpcb->reinject_queue); ++ ++ if (skb) { ++ *reinject = 1; ++ } else { ++ skb = tcp_send_head(meta_sk); ++ ++ if (!skb && meta_sk->sk_socket && ++ test_bit(SOCK_NOSPACE, &meta_sk->sk_socket->flags) && ++ sk_stream_wspace(meta_sk) < sk_stream_min_wspace(meta_sk)) { ++ struct sock *subsk; ++ ++ /* meta is send buffer limited */ ++ tcp_chrono_start(meta_sk, TCP_CHRONO_SNDBUF_LIMITED); ++ ++ subsk = mpcb->sched_ops->get_subflow(meta_sk, ++ NULL, false); ++ if (!subsk) ++ return NULL; ++ ++ skb = mptcp_rcv_buf_optimization(subsk, 0); ++ if (skb) ++ *reinject = -1; ++ else ++ tcp_chrono_start(subsk, ++ TCP_CHRONO_SNDBUF_LIMITED); ++ } ++ } ++ return skb; ++} ++ ++struct sk_buff *mptcp_next_segment(struct sock *meta_sk, ++ int *reinject, ++ struct sock **subsk, ++ unsigned int *limit) ++{ ++ struct sk_buff *skb = __mptcp_next_segment(meta_sk, reinject); ++ unsigned int mss_now; ++ u32 max_len, gso_max_segs, max_segs, max_tso_segs, window; ++ struct tcp_sock *subtp; ++ int queued; ++ ++ /* As we set it, we have to reset it as well. */ ++ *limit = 0; ++ ++ if (!skb) ++ return NULL; ++ ++ *subsk = tcp_sk(meta_sk)->mpcb->sched_ops->get_subflow(meta_sk, skb, false); ++ if (!*subsk) ++ return NULL; ++ ++ subtp = tcp_sk(*subsk); ++ mss_now = tcp_current_mss(*subsk); ++ ++ if (!*reinject && unlikely(!tcp_snd_wnd_test(tcp_sk(meta_sk), skb, mss_now))) { ++ /* an active flow is selected, but segment will not be sent due ++ * to no more space in send window ++ * this means the meta is receive window limited ++ * the subflow might also be, if we have nothing to reinject ++ */ ++ tcp_chrono_start(meta_sk, TCP_CHRONO_RWND_LIMITED); ++ skb = mptcp_rcv_buf_optimization(*subsk, 1); ++ if (skb) ++ *reinject = -1; ++ else ++ return NULL; ++ } ++ ++ if (!*reinject) { ++ /* this will stop any other chronos on the meta */ ++ tcp_chrono_start(meta_sk, TCP_CHRONO_BUSY); ++ } ++ ++ /* No splitting required, as we will only send one single segment */ ++ if (skb->len <= mss_now) ++ return skb; ++ ++ max_tso_segs = tcp_tso_segs(*subsk, tcp_current_mss(*subsk)); ++ queued = mptcp_subflow_queued(*subsk, max_tso_segs); ++ ++ /* this condition should already have been established in ++ * mptcp_is_temp_unavailable when selecting available flows ++ */ ++ WARN_ONCE(subtp->snd_cwnd <= queued, "Selected subflow no cwnd room"); ++ ++ gso_max_segs = (*subsk)->sk_gso_max_segs; ++ if (!gso_max_segs) /* No gso supported on the subflow's NIC */ ++ gso_max_segs = 1; ++ ++ max_segs = min_t(unsigned int, subtp->snd_cwnd - queued, gso_max_segs); ++ if (!max_segs) ++ return NULL; ++ ++ /* if there is room for a segment, schedule up to a complete TSO ++ * segment to avoid TSO splitting. Even if it is more than allowed by ++ * the congestion window. ++ */ ++ max_segs = max_t(unsigned int, max_tso_segs, max_segs); ++ ++ max_len = min(mss_now * max_segs, skb->len); ++ ++ window = tcp_wnd_end(subtp) - subtp->write_seq; ++ ++ /* max_len now also respects the announced receive-window */ ++ max_len = min(max_len, window); ++ ++ *limit = max_len; ++ ++ return skb; ++} ++EXPORT_SYMBOL_GPL(mptcp_next_segment); ++ ++static void defsched_init(struct sock *sk) ++{ ++ struct defsched_priv *def_p = defsched_get_priv(tcp_sk(sk)); ++ ++ def_p->last_rbuf_opti = tcp_jiffies32; ++} ++ ++struct mptcp_sched_ops mptcp_sched_default = { ++ .get_subflow = get_available_subflow, ++ .next_segment = mptcp_next_segment, ++ .init = defsched_init, ++ .name = "default", ++ .owner = THIS_MODULE, ++}; ++ ++static struct mptcp_sched_ops *mptcp_sched_find(const char *name) ++{ ++ struct mptcp_sched_ops *e; ++ ++ list_for_each_entry_rcu(e, &mptcp_sched_list, list) { ++ if (strcmp(e->name, name) == 0) ++ return e; ++ } ++ ++ return NULL; ++} ++ ++int mptcp_register_scheduler(struct mptcp_sched_ops *sched) ++{ ++ int ret = 0; ++ ++ if (!sched->get_subflow || !sched->next_segment) ++ return -EINVAL; ++ ++ spin_lock(&mptcp_sched_list_lock); ++ if (mptcp_sched_find(sched->name)) { ++ pr_notice("%s already registered\n", sched->name); ++ ret = -EEXIST; ++ } else { ++ list_add_tail_rcu(&sched->list, &mptcp_sched_list); ++ pr_info("%s registered\n", sched->name); ++ } ++ spin_unlock(&mptcp_sched_list_lock); ++ ++ return ret; ++} ++EXPORT_SYMBOL_GPL(mptcp_register_scheduler); ++ ++void mptcp_unregister_scheduler(struct mptcp_sched_ops *sched) ++{ ++ spin_lock(&mptcp_sched_list_lock); ++ list_del_rcu(&sched->list); ++ spin_unlock(&mptcp_sched_list_lock); ++ ++ /* Wait for outstanding readers to complete before the ++ * module gets removed entirely. ++ * ++ * A try_module_get() should fail by now as our module is ++ * in "going" state since no refs are held anymore and ++ * module_exit() handler being called. ++ */ ++ synchronize_rcu(); ++} ++EXPORT_SYMBOL_GPL(mptcp_unregister_scheduler); ++ ++void mptcp_get_default_scheduler(char *name) ++{ ++ struct mptcp_sched_ops *sched; ++ ++ BUG_ON(list_empty(&mptcp_sched_list)); ++ ++ rcu_read_lock(); ++ sched = list_entry(mptcp_sched_list.next, struct mptcp_sched_ops, list); ++ strncpy(name, sched->name, MPTCP_SCHED_NAME_MAX); ++ rcu_read_unlock(); ++} ++ ++int mptcp_set_default_scheduler(const char *name) ++{ ++ struct mptcp_sched_ops *sched; ++ int ret = -ENOENT; ++ ++ spin_lock(&mptcp_sched_list_lock); ++ sched = mptcp_sched_find(name); ++#ifdef CONFIG_MODULES ++ if (!sched && capable(CAP_NET_ADMIN)) { ++ spin_unlock(&mptcp_sched_list_lock); ++ ++ request_module("mptcp_%s", name); ++ spin_lock(&mptcp_sched_list_lock); ++ sched = mptcp_sched_find(name); ++ } ++#endif ++ ++ if (sched) { ++ list_move(&sched->list, &mptcp_sched_list); ++ ret = 0; ++ } else { ++ pr_info("%s is not available\n", name); ++ } ++ spin_unlock(&mptcp_sched_list_lock); ++ ++ return ret; ++} ++ ++/* Must be called with rcu lock held */ ++static struct mptcp_sched_ops *__mptcp_sched_find_autoload(const char *name) ++{ ++ struct mptcp_sched_ops *sched = mptcp_sched_find(name); ++#ifdef CONFIG_MODULES ++ if (!sched && capable(CAP_NET_ADMIN)) { ++ rcu_read_unlock(); ++ request_module("mptcp_%s", name); ++ rcu_read_lock(); ++ sched = mptcp_sched_find(name); ++ } ++#endif ++ return sched; ++} ++ ++void mptcp_init_scheduler(struct mptcp_cb *mpcb) ++{ ++ struct mptcp_sched_ops *sched; ++ struct sock *meta_sk = mpcb->meta_sk; ++ struct tcp_sock *meta_tp = tcp_sk(meta_sk); ++ ++ rcu_read_lock(); ++ /* if scheduler was set using socket option */ ++ if (meta_tp->mptcp_sched_setsockopt) { ++ sched = __mptcp_sched_find_autoload(meta_tp->mptcp_sched_name); ++ if (sched && try_module_get(sched->owner)) { ++ mpcb->sched_ops = sched; ++ goto out; ++ } ++ } ++ ++ list_for_each_entry_rcu(sched, &mptcp_sched_list, list) { ++ if (try_module_get(sched->owner)) { ++ mpcb->sched_ops = sched; ++ break; ++ } ++ } ++out: ++ rcu_read_unlock(); ++} ++ ++/* Change scheduler for socket */ ++int mptcp_set_scheduler(struct sock *sk, const char *name) ++{ ++ struct mptcp_sched_ops *sched; ++ int err = 0; ++ ++ rcu_read_lock(); ++ sched = __mptcp_sched_find_autoload(name); ++ ++ if (!sched) { ++ err = -ENOENT; ++ } else if (!ns_capable(sock_net(sk)->user_ns, CAP_NET_ADMIN)) { ++ err = -EPERM; ++ } else { ++ strcpy(tcp_sk(sk)->mptcp_sched_name, name); ++ tcp_sk(sk)->mptcp_sched_setsockopt = 1; ++ } ++ rcu_read_unlock(); ++ ++ return err; ++} ++ ++/* Manage refcounts on socket close. */ ++void mptcp_cleanup_scheduler(struct mptcp_cb *mpcb) ++{ ++ module_put(mpcb->sched_ops->owner); ++} ++ ++/* Set default value from kernel configuration at bootup */ ++static int __init mptcp_scheduler_default(void) ++{ ++ BUILD_BUG_ON(sizeof(struct defsched_priv) > MPTCP_SCHED_SIZE); ++ ++ return mptcp_set_default_scheduler(CONFIG_DEFAULT_MPTCP_SCHED); ++} ++late_initcall(mptcp_scheduler_default); +diff --git a/net/mptcp/mptcp_wvegas.c b/net/mptcp/mptcp_wvegas.c +new file mode 100644 +index 000000000000..787ddaab98a2 +--- /dev/null ++++ b/net/mptcp/mptcp_wvegas.c +@@ -0,0 +1,271 @@ ++/* ++ * MPTCP implementation - WEIGHTED VEGAS ++ * ++ * Algorithm design: ++ * Yu Cao ++ * Mingwei Xu ++ * Xiaoming Fu ++ * ++ * Implementation: ++ * Yu Cao ++ * Enhuan Dong ++ * ++ * Ported to the official MPTCP-kernel: ++ * Christoph Paasch ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License ++ * as published by the Free Software Foundation; either version ++ * 2 of the License, or (at your option) any later version. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++static int initial_alpha = 2; ++static int total_alpha = 10; ++static int gamma = 1; ++ ++module_param(initial_alpha, int, 0644); ++MODULE_PARM_DESC(initial_alpha, "initial alpha for all subflows"); ++module_param(total_alpha, int, 0644); ++MODULE_PARM_DESC(total_alpha, "total alpha for all subflows"); ++module_param(gamma, int, 0644); ++MODULE_PARM_DESC(gamma, "limit on increase (scale by 2)"); ++ ++#define MPTCP_WVEGAS_SCALE 16 ++ ++/* wVegas variables */ ++struct wvegas { ++ u32 beg_snd_nxt; /* right edge during last RTT */ ++ u8 doing_wvegas_now;/* if true, do wvegas for this RTT */ ++ ++ u16 cnt_rtt; /* # of RTTs measured within last RTT */ ++ u32 sampled_rtt; /* cumulative RTTs measured within last RTT (in usec) */ ++ u32 base_rtt; /* the min of all wVegas RTT measurements seen (in usec) */ ++ ++ u64 instant_rate; /* cwnd / srtt_us, unit: pkts/us * 2^16 */ ++ u64 weight; /* the ratio of subflow's rate to the total rate, * 2^16 */ ++ int alpha; /* alpha for each subflows */ ++ ++ u32 queue_delay; /* queue delay*/ ++}; ++ ++ ++static inline u64 mptcp_wvegas_scale(u32 val, int scale) ++{ ++ return (u64) val << scale; ++} ++ ++static void wvegas_enable(const struct sock *sk) ++{ ++ const struct tcp_sock *tp = tcp_sk(sk); ++ struct wvegas *wvegas = inet_csk_ca(sk); ++ ++ wvegas->doing_wvegas_now = 1; ++ ++ wvegas->beg_snd_nxt = tp->snd_nxt; ++ ++ wvegas->cnt_rtt = 0; ++ wvegas->sampled_rtt = 0; ++ ++ wvegas->instant_rate = 0; ++ wvegas->alpha = initial_alpha; ++ wvegas->weight = mptcp_wvegas_scale(1, MPTCP_WVEGAS_SCALE); ++ ++ wvegas->queue_delay = 0; ++} ++ ++static inline void wvegas_disable(const struct sock *sk) ++{ ++ struct wvegas *wvegas = inet_csk_ca(sk); ++ ++ wvegas->doing_wvegas_now = 0; ++} ++ ++static void mptcp_wvegas_init(struct sock *sk) ++{ ++ struct wvegas *wvegas = inet_csk_ca(sk); ++ ++ wvegas->base_rtt = 0x7fffffff; ++ wvegas_enable(sk); ++} ++ ++static inline u64 mptcp_wvegas_rate(u32 cwnd, u32 rtt_us) ++{ ++ return div_u64(mptcp_wvegas_scale(cwnd, MPTCP_WVEGAS_SCALE), rtt_us); ++} ++ ++static void mptcp_wvegas_pkts_acked(struct sock *sk, ++ const struct ack_sample *sample) ++{ ++ struct wvegas *wvegas = inet_csk_ca(sk); ++ u32 vrtt; ++ ++ if (sample->rtt_us < 0) ++ return; ++ ++ vrtt = sample->rtt_us + 1; ++ ++ if (vrtt < wvegas->base_rtt) ++ wvegas->base_rtt = vrtt; ++ ++ wvegas->sampled_rtt += vrtt; ++ wvegas->cnt_rtt++; ++} ++ ++static void mptcp_wvegas_state(struct sock *sk, u8 ca_state) ++{ ++ if (ca_state == TCP_CA_Open) ++ wvegas_enable(sk); ++ else ++ wvegas_disable(sk); ++} ++ ++static void mptcp_wvegas_cwnd_event(struct sock *sk, enum tcp_ca_event event) ++{ ++ if (event == CA_EVENT_CWND_RESTART) { ++ mptcp_wvegas_init(sk); ++ } else if (event == CA_EVENT_LOSS) { ++ struct wvegas *wvegas = inet_csk_ca(sk); ++ wvegas->instant_rate = 0; ++ } ++} ++ ++static inline u32 mptcp_wvegas_ssthresh(const struct tcp_sock *tp) ++{ ++ return min(tp->snd_ssthresh, tp->snd_cwnd); ++} ++ ++static u64 mptcp_wvegas_weight(const struct mptcp_cb *mpcb, const struct sock *sk) ++{ ++ u64 total_rate = 0; ++ const struct wvegas *wvegas = inet_csk_ca(sk); ++ struct mptcp_tcp_sock *mptcp; ++ ++ if (!mpcb) ++ return wvegas->weight; ++ ++ ++ mptcp_for_each_sub(mpcb, mptcp) { ++ struct sock *sub_sk = mptcp_to_sock(mptcp); ++ struct wvegas *sub_wvegas = inet_csk_ca(sub_sk); ++ ++ /* sampled_rtt is initialized by 0 */ ++ if (mptcp_sk_can_send(sub_sk) && (sub_wvegas->sampled_rtt > 0)) ++ total_rate += sub_wvegas->instant_rate; ++ } ++ ++ if (total_rate && wvegas->instant_rate) ++ return div64_u64(mptcp_wvegas_scale(wvegas->instant_rate, MPTCP_WVEGAS_SCALE), total_rate); ++ else ++ return wvegas->weight; ++} ++ ++static void mptcp_wvegas_cong_avoid(struct sock *sk, u32 ack, u32 acked) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct wvegas *wvegas = inet_csk_ca(sk); ++ ++ if (!wvegas->doing_wvegas_now) { ++ tcp_reno_cong_avoid(sk, ack, acked); ++ return; ++ } ++ ++ if (after(ack, wvegas->beg_snd_nxt)) { ++ wvegas->beg_snd_nxt = tp->snd_nxt; ++ ++ if (wvegas->cnt_rtt <= 2) { ++ tcp_reno_cong_avoid(sk, ack, acked); ++ } else { ++ u32 rtt, diff, q_delay; ++ u64 target_cwnd; ++ ++ rtt = wvegas->sampled_rtt / wvegas->cnt_rtt; ++ target_cwnd = div_u64(((u64)tp->snd_cwnd * wvegas->base_rtt), rtt); ++ ++ diff = div_u64((u64)tp->snd_cwnd * (rtt - wvegas->base_rtt), rtt); ++ ++ if (diff > gamma && tcp_in_slow_start(tp)) { ++ tp->snd_cwnd = min(tp->snd_cwnd, (u32)target_cwnd+1); ++ tp->snd_ssthresh = mptcp_wvegas_ssthresh(tp); ++ ++ } else if (tcp_in_slow_start(tp)) { ++ tcp_slow_start(tp, acked); ++ } else { ++ if (diff >= wvegas->alpha) { ++ wvegas->instant_rate = mptcp_wvegas_rate(tp->snd_cwnd, rtt); ++ wvegas->weight = mptcp_wvegas_weight(tp->mpcb, sk); ++ wvegas->alpha = max(2U, (u32)((wvegas->weight * total_alpha) >> MPTCP_WVEGAS_SCALE)); ++ } ++ if (diff > wvegas->alpha) { ++ tp->snd_cwnd--; ++ tp->snd_ssthresh = mptcp_wvegas_ssthresh(tp); ++ } else if (diff < wvegas->alpha) { ++ tp->snd_cwnd++; ++ } ++ ++ /* Try to drain link queue if needed*/ ++ q_delay = rtt - wvegas->base_rtt; ++ if ((wvegas->queue_delay == 0) || (wvegas->queue_delay > q_delay)) ++ wvegas->queue_delay = q_delay; ++ ++ if (q_delay >= 2 * wvegas->queue_delay) { ++ u32 backoff_factor = div_u64(mptcp_wvegas_scale(wvegas->base_rtt, MPTCP_WVEGAS_SCALE), 2 * rtt); ++ tp->snd_cwnd = ((u64)tp->snd_cwnd * backoff_factor) >> MPTCP_WVEGAS_SCALE; ++ wvegas->queue_delay = 0; ++ } ++ } ++ ++ if (tp->snd_cwnd < 2) ++ tp->snd_cwnd = 2; ++ else if (tp->snd_cwnd > tp->snd_cwnd_clamp) ++ tp->snd_cwnd = tp->snd_cwnd_clamp; ++ ++ tp->snd_ssthresh = tcp_current_ssthresh(sk); ++ } ++ ++ wvegas->cnt_rtt = 0; ++ wvegas->sampled_rtt = 0; ++ } ++ /* Use normal slow start */ ++ else if (tcp_in_slow_start(tp)) ++ tcp_slow_start(tp, acked); ++} ++ ++ ++static struct tcp_congestion_ops mptcp_wvegas __read_mostly = { ++ .init = mptcp_wvegas_init, ++ .ssthresh = tcp_reno_ssthresh, ++ .cong_avoid = mptcp_wvegas_cong_avoid, ++ .undo_cwnd = tcp_reno_undo_cwnd, ++ .pkts_acked = mptcp_wvegas_pkts_acked, ++ .set_state = mptcp_wvegas_state, ++ .cwnd_event = mptcp_wvegas_cwnd_event, ++ ++ .owner = THIS_MODULE, ++ .name = "wvegas", ++}; ++ ++static int __init mptcp_wvegas_register(void) ++{ ++ BUILD_BUG_ON(sizeof(struct wvegas) > ICSK_CA_PRIV_SIZE); ++ tcp_register_congestion_control(&mptcp_wvegas); ++ return 0; ++} ++ ++static void __exit mptcp_wvegas_unregister(void) ++{ ++ tcp_unregister_congestion_control(&mptcp_wvegas); ++} ++ ++module_init(mptcp_wvegas_register); ++module_exit(mptcp_wvegas_unregister); ++ ++MODULE_AUTHOR("Yu Cao, Enhuan Dong"); ++MODULE_LICENSE("GPL"); ++MODULE_DESCRIPTION("MPTCP wVegas"); ++MODULE_VERSION("0.1"); +diff --git a/net/socket.c b/net/socket.c +index 94358566c9d1..a26eeeda2b4d 100644 +--- a/net/socket.c ++++ b/net/socket.c +@@ -91,6 +91,7 @@ + #include + + #include ++#include + #include + #include + +@@ -1339,6 +1340,7 @@ int __sock_create(struct net *net, int family, int type, int protocol, + int err; + struct socket *sock; + const struct net_proto_family *pf; ++ int old_protocol = protocol; + + /* + * Check protocol is in range +@@ -1359,6 +1361,9 @@ int __sock_create(struct net *net, int family, int type, int protocol, + family = PF_PACKET; + } + ++ if (old_protocol == IPPROTO_MPTCP) ++ protocol = IPPROTO_TCP; ++ + err = security_socket_create(family, type, protocol, kern); + if (err) + return err; +@@ -1408,6 +1413,10 @@ int __sock_create(struct net *net, int family, int type, int protocol, + if (err < 0) + goto out_module_put; + ++ if (sysctl_mptcp_enabled && old_protocol == IPPROTO_MPTCP && ++ type == SOCK_STREAM && (family == AF_INET || family == AF_INET6)) ++ mptcp_enable_sock(sock->sk); ++ + /* + * Now to bump the refcnt of the [loadable] module that owns this + * socket at sock_release time we decrement its refcnt. +diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h +index 63038eb23560..7150eb62db86 100644 +--- a/tools/include/uapi/linux/bpf.h ++++ b/tools/include/uapi/linux/bpf.h +@@ -3438,6 +3438,7 @@ enum { + BPF_TCP_LISTEN, + BPF_TCP_CLOSING, /* Now a valid state */ + BPF_TCP_NEW_SYN_RECV, ++ BPF_TCP_RST_WAIT, + + BPF_TCP_MAX_STATES /* Leave at the end! */ + }; diff --git a/root/target/linux/ipq60xx/patches-5.4/692-tcp_nanqinlang.patch b/root/target/linux/ipq60xx/patches-5.4/692-tcp_nanqinlang.patch new file mode 100644 index 00000000..56051ae7 --- /dev/null +++ b/root/target/linux/ipq60xx/patches-5.4/692-tcp_nanqinlang.patch @@ -0,0 +1,1037 @@ +--- a/net/ipv4/Makefile.anc 2019-11-23 23:01:47.069966970 +0100 ++++ b/net/ipv4/Makefile 2019-11-23 23:03:01.416428035 +0100 +@@ -48,6 +48,7 @@ + obj-$(CONFIG_INET_UDP_DIAG) += udp_diag.o + obj-$(CONFIG_INET_RAW_DIAG) += raw_diag.o + obj-$(CONFIG_TCP_CONG_BBR) += tcp_bbr.o ++obj-$(CONFIG_TCP_CONG_NANQINLANG) += tcp_nanqinlang.o + obj-$(CONFIG_TCP_CONG_BIC) += tcp_bic.o + obj-$(CONFIG_TCP_CONG_CDG) += tcp_cdg.o + obj-$(CONFIG_TCP_CONG_CUBIC) += tcp_cubic.o +--- a/net/ipv4/Kconfig.anc 2019-11-23 23:01:52.649851417 +0100 ++++ b/net/ipv4/Kconfig 2019-11-23 23:04:21.974762180 +0100 +@@ -681,6 +681,21 @@ + bufferbloat, policers, or AQM schemes that do not provide a delay + signal. It requires the fq ("Fair Queue") pacing packet scheduler. + ++config TCP_CONG_NANQINLANG ++ tristate "NANGINLANG TCP" ++ default n ++ ---help--- ++ ++ BBR (Bottleneck Bandwidth and RTT) TCP congestion control aims to ++ maximize network utilization and minimize queues. It builds an explicit ++ model of the the bottleneck delivery rate and path round-trip ++ propagation delay. It tolerates packet loss and delay unrelated to ++ congestion. It can operate over LAN, WAN, cellular, wifi, or cable ++ modem links. It can coexist with flows that use loss-based congestion ++ control, and can operate with shallow buffers, deep buffers, ++ bufferbloat, policers, or AQM schemes that do not provide a delay ++ signal. It requires the fq ("Fair Queue") pacing packet scheduler. ++ + config TCP_CONG_LIA + tristate "MPTCP Linked Increase" + depends on MPTCP +@@ -763,6 +778,9 @@ + config DEFAULT_BBR + bool "BBR" if TCP_CONG_BBR=y + ++ config DEFAULT_NANQINLANG ++ bool "BBR" if TCP_CONG_NANQINLANG=y ++ + config DEFAULT_LIA + bool "Lia" if TCP_CONG_LIA=y + +@@ -806,6 +824,7 @@ + default "dctcp" if DEFAULT_DCTCP + default "cdg" if DEFAULT_CDG + default "bbr" if DEFAULT_BBR ++ default "nanqinlang" if DEFAULT_NANQINLANG + default "cubic" + + config TCP_MD5SIG +--- /dev/null 2019-11-25 21:13:36.728349757 +0100 ++++ b/net/ipv4/tcp_nanqinlang.c 2019-11-25 21:10:00.392068414 +0100 +@@ -0,0 +1,982 @@ ++/* Bottleneck Bandwidth and RTT (BBR) congestion control ++ * ++ * BBR congestion control computes the sending rate based on the delivery ++ * rate (throughput) estimated from ACKs. In a nutshell: ++ * ++ * On each ACK, update our model of the network path: ++ * bottleneck_bandwidth = windowed_max(delivered / elapsed, 10 round trips) ++ * min_rtt = windowed_min(rtt, 10 seconds) ++ * pacing_rate = pacing_gain * bottleneck_bandwidth ++ * cwnd = max(cwnd_gain * bottleneck_bandwidth * min_rtt, 4) ++ * ++ * The core algorithm does not react directly to packet losses or delays, ++ * although BBR may adjust the size of next send per ACK when loss is ++ * observed, or adjust the sending rate if it estimates there is a ++ * traffic policer, in order to keep the drop rate reasonable. ++ * ++ * Here is a state transition diagram for BBR: ++ * ++ * | ++ * V ++ * +---> STARTUP ----+ ++ * | | | ++ * | V | ++ * | DRAIN ----+ ++ * | | | ++ * | V | ++ * +---> PROBE_BW ----+ ++ * | ^ | | ++ * | | | | ++ * | +----+ | ++ * | | ++ * +---- PROBE_RTT <--+ ++ * ++ * A BBR flow starts in STARTUP, and ramps up its sending rate quickly. ++ * When it estimates the pipe is full, it enters DRAIN to drain the queue. ++ * In steady state a BBR flow only uses PROBE_BW and PROBE_RTT. ++ * A long-lived BBR flow spends the vast majority of its time remaining ++ * (repeatedly) in PROBE_BW, fully probing and utilizing the pipe's bandwidth ++ * in a fair manner, with a small, bounded queue. *If* a flow has been ++ * continuously sending for the entire min_rtt window, and hasn't seen an RTT ++ * sample that matches or decreases its min_rtt estimate for 10 seconds, then ++ * it briefly enters PROBE_RTT to cut inflight to a minimum value to re-probe ++ * the path's two-way propagation delay (min_rtt). When exiting PROBE_RTT, if ++ * we estimated that we reached the full bw of the pipe then we enter PROBE_BW; ++ * otherwise we enter STARTUP to try to fill the pipe. ++ * ++ * BBR is described in detail in: ++ * "BBR: Congestion-Based Congestion Control", ++ * Neal Cardwell, Yuchung Cheng, C. Stephen Gunn, Soheil Hassas Yeganeh, ++ * Van Jacobson. ACM Queue, Vol. 14 No. 5, September-October 2016. ++ * ++ * There is a public e-mail list for discussing BBR development and testing: ++ * https://groups.google.com/forum/#!forum/bbr-dev ++ * ++ * NOTE: BBR might be used with the fq qdisc ("man tc-fq") with pacing enabled, ++ * otherwise TCP stack falls back to an internal pacing using one high ++ * resolution timer per TCP socket and may use more resources. ++ */ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++/* Scale factor for rate in pkt/uSec unit to avoid truncation in bandwidth ++ * estimation. The rate unit ~= (1500 bytes / 1 usec / 2^24) ~= 715 bps. ++ * This handles bandwidths from 0.06pps (715bps) to 256Mpps (3Tbps) in a u32. ++ * Since the minimum window is >=4 packets, the lower bound isn't ++ * an issue. The upper bound isn't an issue with existing technologies. ++ */ ++#define BW_SCALE 24 ++#define BW_UNIT (1 << BW_SCALE) ++ ++#define BBR_SCALE 8 /* scaling factor for fractions in BBR (e.g. gains) */ ++#define BBR_UNIT (1 << BBR_SCALE) ++ ++/* BBR has the following modes for deciding how fast to send: */ ++enum bbr_mode { ++ BBR_STARTUP, /* ramp up sending rate rapidly to fill pipe */ ++ BBR_DRAIN, /* drain any queue created during startup */ ++ BBR_PROBE_BW, /* discover, share bw: pace around estimated bw */ ++ BBR_PROBE_RTT, /* cut inflight to min to probe min_rtt */ ++}; ++ ++/* BBR congestion control block */ ++struct bbr { ++ u32 min_rtt_us; /* min RTT in min_rtt_win_sec window */ ++ u32 min_rtt_stamp; /* timestamp of min_rtt_us */ ++ u32 probe_rtt_done_stamp; /* end time for BBR_PROBE_RTT mode */ ++ struct minmax bw; /* Max recent delivery rate in pkts/uS << 24 */ ++ u32 rtt_cnt; /* count of packet-timed rounds elapsed */ ++ u32 next_rtt_delivered; /* scb->tx.delivered at end of round */ ++ u64 cycle_mstamp; /* time of this cycle phase start */ ++ u32 mode:3, /* current bbr_mode in state machine */ ++ prev_ca_state:3, /* CA state on previous ACK */ ++ packet_conservation:1, /* use packet conservation? */ ++ round_start:1, /* start of packet-timed tx->ack round? */ ++ idle_restart:1, /* restarting after idle? */ ++ probe_rtt_round_done:1, /* a BBR_PROBE_RTT round at 4 pkts? */ ++ unused:13, ++ lt_is_sampling:1, /* taking long-term ("LT") samples now? */ ++ lt_rtt_cnt:7, /* round trips in long-term interval */ ++ lt_use_bw:1; /* use lt_bw as our bw estimate? */ ++ u32 lt_bw; /* LT est delivery rate in pkts/uS << 24 */ ++ u32 lt_last_delivered; /* LT intvl start: tp->delivered */ ++ u32 lt_last_stamp; /* LT intvl start: tp->delivered_mstamp */ ++ u32 lt_last_lost; /* LT intvl start: tp->lost */ ++ u32 pacing_gain:10, /* current gain for setting pacing rate */ ++ cwnd_gain:10, /* current gain for setting cwnd */ ++ full_bw_reached:1, /* reached full bw in Startup? */ ++ full_bw_cnt:2, /* number of rounds without large bw gains */ ++ cycle_idx:3, /* current index in pacing_gain cycle array */ ++ has_seen_rtt:1, /* have we seen an RTT sample yet? */ ++ unused_b:5; ++ u32 prior_cwnd; /* prior cwnd upon entering loss recovery */ ++ u32 full_bw; /* recent bw, to estimate if pipe is full */ ++}; ++ ++#define CYCLE_LEN 8 /* number of phases in a pacing gain cycle */ ++ ++/* Window length of bw filter (in rounds): */ ++static const int bbr_bw_rtts = CYCLE_LEN + 2; ++/* Window length of min_rtt filter (in sec): */ ++static const u32 bbr_min_rtt_win_sec = 10; ++/* Minimum time (in ms) spent at bbr_cwnd_min_target in BBR_PROBE_RTT mode: */ ++static const u32 bbr_probe_rtt_mode_ms = 100; ++/* Skip TSO below the following bandwidth (bits/sec): */ ++static const int bbr_min_tso_rate = 1200000; ++ ++/* We use a high_gain value of 2/ln(2) because it's the smallest pacing gain ++ * that will allow a smoothly increasing pacing rate that will double each RTT ++ * and send the same number of packets per RTT that an un-paced, slow-starting ++ * Reno or CUBIC flow would: ++ */ ++static const int bbr_high_gain = BBR_UNIT * 3000 / 1000 + 1; ++/* The pacing gain of 1/high_gain in BBR_DRAIN is calculated to typically drain ++ * the queue created in BBR_STARTUP in a single round: ++ */ ++static const int bbr_drain_gain = BBR_UNIT * 1000 / 3000; ++/* The gain for deriving steady-state cwnd tolerates delayed/stretched ACKs: */ ++static const int bbr_cwnd_gain = BBR_UNIT * 2; ++/* The pacing_gain values for the PROBE_BW gain cycle, to discover/share bw: */ ++static const int bbr_pacing_gain[] = { ++ BBR_UNIT * 6 / 4, /* probe for more available bw */ ++ BBR_UNIT * 3 / 4, /* drain queue and/or yield bw to other flows */ ++ BBR_UNIT * 5 / 4, BBR_UNIT * 5 / 4, BBR_UNIT * 5 / 4, /* cruise at 1.0*bw to utilize pipe, */ ++ BBR_UNIT * 6 / 4, BBR_UNIT * 6 / 4, BBR_UNIT * 6 / 4 /* without creating excess queue... */ ++}; ++/* Randomize the starting gain cycling phase over N phases: */ ++static const u32 bbr_cycle_rand = 7; ++ ++/* Try to keep at least this many packets in flight, if things go smoothly. For ++ * smooth functioning, a sliding window protocol ACKing every other packet ++ * needs at least 4 packets in flight: ++ */ ++static const u32 bbr_cwnd_min_target = 4; ++ ++/* To estimate if BBR_STARTUP mode (i.e. high_gain) has filled pipe... */ ++/* If bw has increased significantly (1.25x), there may be more bw available: */ ++static const u32 bbr_full_bw_thresh = BBR_UNIT * 5 / 4; ++/* But after 3 rounds w/o significant bw growth, estimate pipe is full: */ ++static const u32 bbr_full_bw_cnt = 3; ++ ++/* "long-term" ("LT") bandwidth estimator parameters... */ ++/* The minimum number of rounds in an LT bw sampling interval: */ ++static const u32 bbr_lt_intvl_min_rtts = 4; ++/* If lost/delivered ratio > 20%, interval is "lossy" and we may be policed: */ ++static const u32 bbr_lt_loss_thresh = 50; ++/* If 2 intervals have a bw ratio <= 1/8, their bw is "consistent": */ ++static const u32 bbr_lt_bw_ratio = BBR_UNIT / 4; ++/* If 2 intervals have a bw diff <= 4 Kbit/sec their bw is "consistent": */ ++static const u32 bbr_lt_bw_diff = 4000 / 8; ++/* If we estimate we're policed, use lt_bw for this many round trips: */ ++static const u32 bbr_lt_bw_max_rtts = 48; ++ ++static void bbr_check_probe_rtt_done(struct sock *sk); ++ ++/* Do we estimate that STARTUP filled the pipe? */ ++static bool bbr_full_bw_reached(const struct sock *sk) ++{ ++ const struct bbr *bbr = inet_csk_ca(sk); ++ ++ return bbr->full_bw_reached; ++} ++ ++/* Return the windowed max recent bandwidth sample, in pkts/uS << BW_SCALE. */ ++static u32 bbr_max_bw(const struct sock *sk) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ return minmax_get(&bbr->bw); ++} ++ ++/* Return the estimated bandwidth of the path, in pkts/uS << BW_SCALE. */ ++static u32 bbr_bw(const struct sock *sk) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ return bbr->lt_use_bw ? bbr->lt_bw : bbr_max_bw(sk); ++} ++ ++/* Return rate in bytes per second, optionally with a gain. ++ * The order here is chosen carefully to avoid overflow of u64. This should ++ * work for input rates of up to 2.9Tbit/sec and gain of 2.89x. ++ */ ++static u64 bbr_rate_bytes_per_sec(struct sock *sk, u64 rate, int gain) ++{ ++ unsigned int mss = tcp_sk(sk)->mss_cache; ++ ++ if (!tcp_needs_internal_pacing(sk)) ++ mss = tcp_mss_to_mtu(sk, mss); ++ rate *= mss; ++ rate *= gain; ++ rate >>= BBR_SCALE; ++ rate *= USEC_PER_SEC; ++ return rate >> BW_SCALE; ++} ++ ++/* Convert a BBR bw and gain factor to a pacing rate in bytes per second. */ ++static u32 bbr_bw_to_pacing_rate(struct sock *sk, u32 bw, int gain) ++{ ++ u64 rate = bw; ++ ++ rate = bbr_rate_bytes_per_sec(sk, rate, gain); ++ rate = min_t(u64, rate, sk->sk_max_pacing_rate); ++ return rate; ++} ++ ++/* Initialize pacing rate to: high_gain * init_cwnd / RTT. */ ++static void bbr_init_pacing_rate_from_rtt(struct sock *sk) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct bbr *bbr = inet_csk_ca(sk); ++ u64 bw; ++ u32 rtt_us; ++ ++ if (tp->srtt_us) { /* any RTT sample yet? */ ++ rtt_us = max(tp->srtt_us >> 3, 1U); ++ bbr->has_seen_rtt = 1; ++ } else { /* no RTT sample yet */ ++ rtt_us = USEC_PER_MSEC; /* use nominal default RTT */ ++ } ++ bw = (u64)tp->snd_cwnd * BW_UNIT; ++ do_div(bw, rtt_us); ++ sk->sk_pacing_rate = bbr_bw_to_pacing_rate(sk, bw, bbr_high_gain); ++} ++ ++/* Pace using current bw estimate and a gain factor. In order to help drive the ++ * network toward lower queues while maintaining high utilization and low ++ * latency, the average pacing rate aims to be slightly (~1%) lower than the ++ * estimated bandwidth. This is an important aspect of the design. In this ++ * implementation this slightly lower pacing rate is achieved implicitly by not ++ * including link-layer headers in the packet size used for the pacing rate. ++ */ ++static void bbr_set_pacing_rate(struct sock *sk, u32 bw, int gain) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct bbr *bbr = inet_csk_ca(sk); ++ u32 rate = bbr_bw_to_pacing_rate(sk, bw, gain); ++ ++ if (unlikely(!bbr->has_seen_rtt && tp->srtt_us)) ++ bbr_init_pacing_rate_from_rtt(sk); ++ if (bbr_full_bw_reached(sk) || rate > sk->sk_pacing_rate) ++ sk->sk_pacing_rate = rate; ++} ++ ++/* override sysctl_tcp_min_tso_segs */ ++static u32 bbr_min_tso_segs(struct sock *sk) ++{ ++ return sk->sk_pacing_rate < (bbr_min_tso_rate >> 3) ? 1 : 2; ++} ++ ++static u32 bbr_tso_segs_goal(struct sock *sk) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ u32 segs, bytes; ++ ++ /* Sort of tcp_tso_autosize() but ignoring ++ * driver provided sk_gso_max_size. ++ */ ++ bytes = min_t(u32, sk->sk_pacing_rate >> sk->sk_pacing_shift, ++ GSO_MAX_SIZE - 1 - MAX_TCP_HEADER); ++ segs = max_t(u32, bytes / tp->mss_cache, bbr_min_tso_segs(sk)); ++ ++ return min(segs, 0x7FU); ++} ++ ++/* Save "last known good" cwnd so we can restore it after losses or PROBE_RTT */ ++static void bbr_save_cwnd(struct sock *sk) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ if (bbr->prev_ca_state < TCP_CA_Recovery && bbr->mode != BBR_PROBE_RTT) ++ bbr->prior_cwnd = tp->snd_cwnd; /* this cwnd is good enough */ ++ else /* loss recovery or BBR_PROBE_RTT have temporarily cut cwnd */ ++ bbr->prior_cwnd = max(bbr->prior_cwnd, tp->snd_cwnd); ++} ++ ++static void bbr_cwnd_event(struct sock *sk, enum tcp_ca_event event) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ if (event == CA_EVENT_TX_START && tp->app_limited) { ++ bbr->idle_restart = 1; ++ /* Avoid pointless buffer overflows: pace at est. bw if we don't ++ * need more speed (we're restarting from idle and app-limited). ++ */ ++ if (bbr->mode == BBR_PROBE_BW) ++ bbr_set_pacing_rate(sk, bbr_bw(sk), BBR_UNIT); ++ else if (bbr->mode == BBR_PROBE_RTT) ++ bbr_check_probe_rtt_done(sk); ++ } ++} ++ ++/* Find target cwnd. Right-size the cwnd based on min RTT and the ++ * estimated bottleneck bandwidth: ++ * ++ * cwnd = bw * min_rtt * gain = BDP * gain ++ * ++ * The key factor, gain, controls the amount of queue. While a small gain ++ * builds a smaller queue, it becomes more vulnerable to noise in RTT ++ * measurements (e.g., delayed ACKs or other ACK compression effects). This ++ * noise may cause BBR to under-estimate the rate. ++ * ++ * To achieve full performance in high-speed paths, we budget enough cwnd to ++ * fit full-sized skbs in-flight on both end hosts to fully utilize the path: ++ * - one skb in sending host Qdisc, ++ * - one skb in sending host TSO/GSO engine ++ * - one skb being received by receiver host LRO/GRO/delayed-ACK engine ++ * Don't worry, at low rates (bbr_min_tso_rate) this won't bloat cwnd because ++ * in such cases tso_segs_goal is 1. The minimum cwnd is 4 packets, ++ * which allows 2 outstanding 2-packet sequences, to try to keep pipe ++ * full even with ACK-every-other-packet delayed ACKs. ++ */ ++static u32 bbr_target_cwnd(struct sock *sk, u32 bw, int gain) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ u32 cwnd; ++ u64 w; ++ ++ /* If we've never had a valid RTT sample, cap cwnd at the initial ++ * default. This should only happen when the connection is not using TCP ++ * timestamps and has retransmitted all of the SYN/SYNACK/data packets ++ * ACKed so far. In this case, an RTO can cut cwnd to 1, in which ++ * case we need to slow-start up toward something safe: TCP_INIT_CWND. ++ */ ++ if (unlikely(bbr->min_rtt_us == ~0U)) /* no valid RTT samples yet? */ ++ return TCP_INIT_CWND; /* be safe: cap at default initial cwnd*/ ++ ++ w = (u64)bw * bbr->min_rtt_us; ++ ++ /* Apply a gain to the given value, then remove the BW_SCALE shift. */ ++ cwnd = (((w * gain) >> BBR_SCALE) + BW_UNIT - 1) / BW_UNIT; ++ ++ /* Allow enough full-sized skbs in flight to utilize end systems. */ ++ cwnd += 3 * bbr_tso_segs_goal(sk); ++ ++ /* Reduce delayed ACKs by rounding up cwnd to the next even number. */ ++ cwnd = (cwnd + 1) & ~1U; ++ ++ /* Ensure gain cycling gets inflight above BDP even for small BDPs. */ ++ if (bbr->mode == BBR_PROBE_BW && gain > BBR_UNIT) ++ cwnd += 2; ++ ++ return cwnd; ++} ++ ++/* An optimization in BBR to reduce losses: On the first round of recovery, we ++ * follow the packet conservation principle: send P packets per P packets acked. ++ * After that, we slow-start and send at most 2*P packets per P packets acked. ++ * After recovery finishes, or upon undo, we restore the cwnd we had when ++ * recovery started (capped by the target cwnd based on estimated BDP). ++ * ++ * TODO(ycheng/ncardwell): implement a rate-based approach. ++ */ ++static bool bbr_set_cwnd_to_recover_or_restore( ++ struct sock *sk, const struct rate_sample *rs, u32 acked, u32 *new_cwnd) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct bbr *bbr = inet_csk_ca(sk); ++ u8 prev_state = bbr->prev_ca_state, state = inet_csk(sk)->icsk_ca_state; ++ u32 cwnd = tp->snd_cwnd; ++ ++ /* An ACK for P pkts should release at most 2*P packets. We do this ++ * in two steps. First, here we deduct the number of lost packets. ++ * Then, in bbr_set_cwnd() we slow start up toward the target cwnd. ++ */ ++ if (rs->losses > 0) ++ cwnd = max_t(s32, cwnd - rs->losses, 1); ++ ++ if (state == TCP_CA_Recovery && prev_state != TCP_CA_Recovery) { ++ /* Starting 1st round of Recovery, so do packet conservation. */ ++ bbr->packet_conservation = 1; ++ bbr->next_rtt_delivered = tp->delivered; /* start round now */ ++ /* Cut unused cwnd from app behavior, TSQ, or TSO deferral: */ ++ cwnd = tcp_packets_in_flight(tp) + acked; ++ } else if (prev_state >= TCP_CA_Recovery && state < TCP_CA_Recovery) { ++ /* Exiting loss recovery; restore cwnd saved before recovery. */ ++ cwnd = max(cwnd, bbr->prior_cwnd); ++ bbr->packet_conservation = 0; ++ } ++ bbr->prev_ca_state = state; ++ ++ if (bbr->packet_conservation) { ++ *new_cwnd = max(cwnd, tcp_packets_in_flight(tp) + acked); ++ return true; /* yes, using packet conservation */ ++ } ++ *new_cwnd = cwnd; ++ return false; ++} ++ ++/* Slow-start up toward target cwnd (if bw estimate is growing, or packet loss ++ * has drawn us down below target), or snap down to target if we're above it. ++ */ ++static void bbr_set_cwnd(struct sock *sk, const struct rate_sample *rs, ++ u32 acked, u32 bw, int gain) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct bbr *bbr = inet_csk_ca(sk); ++ u32 cwnd = tp->snd_cwnd, target_cwnd = 0; ++ ++ if (!acked) ++ goto done; /* no packet fully ACKed; just apply caps */ ++ ++ if (bbr_set_cwnd_to_recover_or_restore(sk, rs, acked, &cwnd)) ++ goto done; ++ ++ /* If we're below target cwnd, slow start cwnd toward target cwnd. */ ++ target_cwnd = bbr_target_cwnd(sk, bw, gain); ++ if (bbr_full_bw_reached(sk)) /* only cut cwnd if we filled the pipe */ ++ cwnd = min(cwnd + acked, target_cwnd); ++ else if (cwnd < target_cwnd || tp->delivered < TCP_INIT_CWND) ++ cwnd = cwnd + acked; ++ cwnd = max(cwnd, bbr_cwnd_min_target); ++ ++done: ++ tp->snd_cwnd = min(cwnd, tp->snd_cwnd_clamp); /* apply global cap */ ++ if (bbr->mode == BBR_PROBE_RTT) /* drain queue, refresh min_rtt */ ++ tp->snd_cwnd = min(tp->snd_cwnd, bbr_cwnd_min_target); ++} ++ ++/* End cycle phase if it's time and/or we hit the phase's in-flight target. */ ++static bool bbr_is_next_cycle_phase(struct sock *sk, ++ const struct rate_sample *rs) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct bbr *bbr = inet_csk_ca(sk); ++ bool is_full_length = ++ tcp_stamp_us_delta(tp->delivered_mstamp, bbr->cycle_mstamp) > ++ bbr->min_rtt_us; ++ u32 inflight, bw; ++ ++ /* The pacing_gain of 1.0 paces at the estimated bw to try to fully ++ * use the pipe without increasing the queue. ++ */ ++ if (bbr->pacing_gain == BBR_UNIT) ++ return is_full_length; /* just use wall clock time */ ++ ++ inflight = rs->prior_in_flight; /* what was in-flight before ACK? */ ++ bw = bbr_max_bw(sk); ++ ++ /* A pacing_gain > 1.0 probes for bw by trying to raise inflight to at ++ * least pacing_gain*BDP; this may take more than min_rtt if min_rtt is ++ * small (e.g. on a LAN). We do not persist if packets are lost, since ++ * a path with small buffers may not hold that much. ++ */ ++ if (bbr->pacing_gain > BBR_UNIT) ++ return is_full_length && ++ (rs->losses || /* perhaps pacing_gain*BDP won't fit */ ++ inflight >= bbr_target_cwnd(sk, bw, bbr->pacing_gain)); ++ ++ /* A pacing_gain < 1.0 tries to drain extra queue we added if bw ++ * probing didn't find more bw. If inflight falls to match BDP then we ++ * estimate queue is drained; persisting would underutilize the pipe. ++ */ ++ return is_full_length || ++ inflight <= bbr_target_cwnd(sk, bw, BBR_UNIT); ++} ++ ++static void bbr_advance_cycle_phase(struct sock *sk) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ bbr->cycle_idx = (bbr->cycle_idx + 1) & (CYCLE_LEN - 1); ++ bbr->cycle_mstamp = tp->delivered_mstamp; ++ bbr->pacing_gain = bbr->lt_use_bw ? BBR_UNIT : ++ bbr_pacing_gain[bbr->cycle_idx]; ++} ++ ++/* Gain cycling: cycle pacing gain to converge to fair share of available bw. */ ++static void bbr_update_cycle_phase(struct sock *sk, ++ const struct rate_sample *rs) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ if (bbr->mode == BBR_PROBE_BW && bbr_is_next_cycle_phase(sk, rs)) ++ bbr_advance_cycle_phase(sk); ++} ++ ++static void bbr_reset_startup_mode(struct sock *sk) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ bbr->mode = BBR_STARTUP; ++ bbr->pacing_gain = bbr_high_gain; ++ bbr->cwnd_gain = bbr_high_gain; ++} ++ ++static void bbr_reset_probe_bw_mode(struct sock *sk) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ bbr->mode = BBR_PROBE_BW; ++ bbr->pacing_gain = BBR_UNIT; ++ bbr->cwnd_gain = bbr_cwnd_gain; ++ bbr->cycle_idx = CYCLE_LEN - 1 - prandom_u32_max(bbr_cycle_rand); ++ bbr_advance_cycle_phase(sk); /* flip to next phase of gain cycle */ ++} ++ ++static void bbr_reset_mode(struct sock *sk) ++{ ++ if (!bbr_full_bw_reached(sk)) ++ bbr_reset_startup_mode(sk); ++ else ++ bbr_reset_probe_bw_mode(sk); ++} ++ ++/* Start a new long-term sampling interval. */ ++static void bbr_reset_lt_bw_sampling_interval(struct sock *sk) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ bbr->lt_last_stamp = div_u64(tp->delivered_mstamp, USEC_PER_MSEC); ++ bbr->lt_last_delivered = tp->delivered; ++ bbr->lt_last_lost = tp->lost; ++ bbr->lt_rtt_cnt = 0; ++} ++ ++/* Completely reset long-term bandwidth sampling. */ ++static void bbr_reset_lt_bw_sampling(struct sock *sk) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ bbr->lt_bw = 0; ++ bbr->lt_use_bw = 0; ++ bbr->lt_is_sampling = false; ++ bbr_reset_lt_bw_sampling_interval(sk); ++} ++ ++/* Long-term bw sampling interval is done. Estimate whether we're policed. */ ++static void bbr_lt_bw_interval_done(struct sock *sk, u32 bw) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ u32 diff; ++ ++ if (bbr->lt_bw) { /* do we have bw from a previous interval? */ ++ /* Is new bw close to the lt_bw from the previous interval? */ ++ diff = abs(bw - bbr->lt_bw); ++ if ((diff * BBR_UNIT <= bbr_lt_bw_ratio * bbr->lt_bw) || ++ (bbr_rate_bytes_per_sec(sk, diff, BBR_UNIT) <= ++ bbr_lt_bw_diff)) { ++ /* All criteria are met; estimate we're policed. */ ++ bbr->lt_bw = (bw + bbr->lt_bw) >> 1; /* avg 2 intvls */ ++ bbr->lt_use_bw = 1; ++ bbr->pacing_gain = BBR_UNIT; /* try to avoid drops */ ++ bbr->lt_rtt_cnt = 0; ++ return; ++ } ++ } ++ bbr->lt_bw = bw; ++ bbr_reset_lt_bw_sampling_interval(sk); ++} ++ ++/* Token-bucket traffic policers are common (see "An Internet-Wide Analysis of ++ * Traffic Policing", SIGCOMM 2016). BBR detects token-bucket policers and ++ * explicitly models their policed rate, to reduce unnecessary losses. We ++ * estimate that we're policed if we see 2 consecutive sampling intervals with ++ * consistent throughput and high packet loss. If we think we're being policed, ++ * set lt_bw to the "long-term" average delivery rate from those 2 intervals. ++ */ ++static void bbr_lt_bw_sampling(struct sock *sk, const struct rate_sample *rs) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct bbr *bbr = inet_csk_ca(sk); ++ u32 lost, delivered; ++ u64 bw; ++ u32 t; ++ ++ if (bbr->lt_use_bw) { /* already using long-term rate, lt_bw? */ ++ if (bbr->mode == BBR_PROBE_BW && bbr->round_start && ++ ++bbr->lt_rtt_cnt >= bbr_lt_bw_max_rtts) { ++ bbr_reset_lt_bw_sampling(sk); /* stop using lt_bw */ ++ bbr_reset_probe_bw_mode(sk); /* restart gain cycling */ ++ } ++ return; ++ } ++ ++ /* Wait for the first loss before sampling, to let the policer exhaust ++ * its tokens and estimate the steady-state rate allowed by the policer. ++ * Starting samples earlier includes bursts that over-estimate the bw. ++ */ ++ if (!bbr->lt_is_sampling) { ++ if (!rs->losses) ++ return; ++ bbr_reset_lt_bw_sampling_interval(sk); ++ bbr->lt_is_sampling = true; ++ } ++ ++ /* To avoid underestimates, reset sampling if we run out of data. */ ++ if (rs->is_app_limited) { ++ bbr_reset_lt_bw_sampling(sk); ++ return; ++ } ++ ++ if (bbr->round_start) ++ bbr->lt_rtt_cnt++; /* count round trips in this interval */ ++ if (bbr->lt_rtt_cnt < bbr_lt_intvl_min_rtts) ++ return; /* sampling interval needs to be longer */ ++ if (bbr->lt_rtt_cnt > 4 * bbr_lt_intvl_min_rtts) { ++ bbr_reset_lt_bw_sampling(sk); /* interval is too long */ ++ return; ++ } ++ ++ /* End sampling interval when a packet is lost, so we estimate the ++ * policer tokens were exhausted. Stopping the sampling before the ++ * tokens are exhausted under-estimates the policed rate. ++ */ ++ if (!rs->losses) ++ return; ++ ++ /* Calculate packets lost and delivered in sampling interval. */ ++ lost = tp->lost - bbr->lt_last_lost; ++ delivered = tp->delivered - bbr->lt_last_delivered; ++ /* Is loss rate (lost/delivered) >= lt_loss_thresh? If not, wait. */ ++ if (!delivered || (lost << BBR_SCALE) < bbr_lt_loss_thresh * delivered) ++ return; ++ ++ /* Find average delivery rate in this sampling interval. */ ++ t = div_u64(tp->delivered_mstamp, USEC_PER_MSEC) - bbr->lt_last_stamp; ++ if ((s32)t < 1) ++ return; /* interval is less than one ms, so wait */ ++ /* Check if can multiply without overflow */ ++ if (t >= ~0U / USEC_PER_MSEC) { ++ bbr_reset_lt_bw_sampling(sk); /* interval too long; reset */ ++ return; ++ } ++ t *= USEC_PER_MSEC; ++ bw = (u64)delivered * BW_UNIT; ++ do_div(bw, t); ++ bbr_lt_bw_interval_done(sk, bw); ++} ++ ++/* Estimate the bandwidth based on how fast packets are delivered */ ++static void bbr_update_bw(struct sock *sk, const struct rate_sample *rs) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct bbr *bbr = inet_csk_ca(sk); ++ u64 bw; ++ ++ bbr->round_start = 0; ++ if (rs->delivered < 0 || rs->interval_us <= 0) ++ return; /* Not a valid observation */ ++ ++ /* See if we've reached the next RTT */ ++ if (!before(rs->prior_delivered, bbr->next_rtt_delivered)) { ++ bbr->next_rtt_delivered = tp->delivered; ++ bbr->rtt_cnt++; ++ bbr->round_start = 1; ++ bbr->packet_conservation = 0; ++ } ++ ++ bbr_lt_bw_sampling(sk, rs); ++ ++ /* Divide delivered by the interval to find a (lower bound) bottleneck ++ * bandwidth sample. Delivered is in packets and interval_us in uS and ++ * ratio will be <<1 for most connections. So delivered is first scaled. ++ */ ++ bw = (u64)rs->delivered * BW_UNIT; ++ do_div(bw, rs->interval_us); ++ ++ /* If this sample is application-limited, it is likely to have a very ++ * low delivered count that represents application behavior rather than ++ * the available network rate. Such a sample could drag down estimated ++ * bw, causing needless slow-down. Thus, to continue to send at the ++ * last measured network rate, we filter out app-limited samples unless ++ * they describe the path bw at least as well as our bw model. ++ * ++ * So the goal during app-limited phase is to proceed with the best ++ * network rate no matter how long. We automatically leave this ++ * phase when app writes faster than the network can deliver :) ++ */ ++ if (!rs->is_app_limited || bw >= bbr_max_bw(sk)) { ++ /* Incorporate new sample into our max bw filter. */ ++ minmax_running_max(&bbr->bw, bbr_bw_rtts, bbr->rtt_cnt, bw); ++ } ++} ++ ++/* Estimate when the pipe is full, using the change in delivery rate: BBR ++ * estimates that STARTUP filled the pipe if the estimated bw hasn't changed by ++ * at least bbr_full_bw_thresh (25%) after bbr_full_bw_cnt (3) non-app-limited ++ * rounds. Why 3 rounds: 1: rwin autotuning grows the rwin, 2: we fill the ++ * higher rwin, 3: we get higher delivery rate samples. Or transient ++ * cross-traffic or radio noise can go away. CUBIC Hystart shares a similar ++ * design goal, but uses delay and inter-ACK spacing instead of bandwidth. ++ */ ++static void bbr_check_full_bw_reached(struct sock *sk, ++ const struct rate_sample *rs) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ u32 bw_thresh; ++ ++ if (bbr_full_bw_reached(sk) || !bbr->round_start || rs->is_app_limited) ++ return; ++ ++ bw_thresh = (u64)bbr->full_bw * bbr_full_bw_thresh >> BBR_SCALE; ++ if (bbr_max_bw(sk) >= bw_thresh) { ++ bbr->full_bw = bbr_max_bw(sk); ++ bbr->full_bw_cnt = 0; ++ return; ++ } ++ ++bbr->full_bw_cnt; ++ bbr->full_bw_reached = bbr->full_bw_cnt >= bbr_full_bw_cnt; ++} ++ ++/* If pipe is probably full, drain the queue and then enter steady-state. */ ++static void bbr_check_drain(struct sock *sk, const struct rate_sample *rs) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ if (bbr->mode == BBR_STARTUP && bbr_full_bw_reached(sk)) { ++ bbr->mode = BBR_DRAIN; /* drain queue we created */ ++ bbr->pacing_gain = bbr_drain_gain; /* pace slow to drain */ ++ bbr->cwnd_gain = bbr_high_gain; /* maintain cwnd */ ++ tcp_sk(sk)->snd_ssthresh = ++ bbr_target_cwnd(sk, bbr_max_bw(sk), BBR_UNIT); ++ } /* fall through to check if in-flight is already small: */ ++ if (bbr->mode == BBR_DRAIN && ++ tcp_packets_in_flight(tcp_sk(sk)) <= ++ bbr_target_cwnd(sk, bbr_max_bw(sk), BBR_UNIT)) ++ bbr_reset_probe_bw_mode(sk); /* we estimate queue is drained */ ++} ++ ++static void bbr_check_probe_rtt_done(struct sock *sk) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ if (!(bbr->probe_rtt_done_stamp && ++ after(tcp_jiffies32, bbr->probe_rtt_done_stamp))) ++ return; ++ ++ bbr->min_rtt_stamp = tcp_jiffies32; /* wait a while until PROBE_RTT */ ++ tp->snd_cwnd = max(tp->snd_cwnd, bbr->prior_cwnd); ++ bbr_reset_mode(sk); ++} ++ ++/* The goal of PROBE_RTT mode is to have BBR flows cooperatively and ++ * periodically drain the bottleneck queue, to converge to measure the true ++ * min_rtt (unloaded propagation delay). This allows the flows to keep queues ++ * small (reducing queuing delay and packet loss) and achieve fairness among ++ * BBR flows. ++ * ++ * The min_rtt filter window is 10 seconds. When the min_rtt estimate expires, ++ * we enter PROBE_RTT mode and cap the cwnd at bbr_cwnd_min_target=4 packets. ++ * After at least bbr_probe_rtt_mode_ms=200ms and at least one packet-timed ++ * round trip elapsed with that flight size <= 4, we leave PROBE_RTT mode and ++ * re-enter the previous mode. BBR uses 200ms to approximately bound the ++ * performance penalty of PROBE_RTT's cwnd capping to roughly 2% (200ms/10s). ++ * ++ * Note that flows need only pay 2% if they are busy sending over the last 10 ++ * seconds. Interactive applications (e.g., Web, RPCs, video chunks) often have ++ * natural silences or low-rate periods within 10 seconds where the rate is low ++ * enough for long enough to drain its queue in the bottleneck. We pick up ++ * these min RTT measurements opportunistically with our min_rtt filter. :-) ++ */ ++static void bbr_update_min_rtt(struct sock *sk, const struct rate_sample *rs) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct bbr *bbr = inet_csk_ca(sk); ++ bool filter_expired; ++ ++ /* Track min RTT seen in the min_rtt_win_sec filter window: */ ++ filter_expired = after(tcp_jiffies32, ++ bbr->min_rtt_stamp + bbr_min_rtt_win_sec * HZ); ++ if (rs->rtt_us >= 0 && ++ (rs->rtt_us <= bbr->min_rtt_us || ++ (filter_expired && !rs->is_ack_delayed))) { ++ bbr->min_rtt_us = rs->rtt_us; ++ bbr->min_rtt_stamp = tcp_jiffies32; ++ } ++ ++ if (bbr_probe_rtt_mode_ms > 0 && filter_expired && ++ !bbr->idle_restart && bbr->mode != BBR_PROBE_RTT) { ++ bbr->mode = BBR_PROBE_RTT; /* dip, drain queue */ ++ bbr->pacing_gain = BBR_UNIT; ++ bbr->cwnd_gain = BBR_UNIT; ++ bbr_save_cwnd(sk); /* note cwnd so we can restore it */ ++ bbr->probe_rtt_done_stamp = 0; ++ } ++ ++ if (bbr->mode == BBR_PROBE_RTT) { ++ /* Ignore low rate samples during this mode. */ ++ tp->app_limited = ++ (tp->delivered + tcp_packets_in_flight(tp)) ? : 1; ++ /* Maintain min packets in flight for max(200 ms, 1 round). */ ++ if (!bbr->probe_rtt_done_stamp && ++ tcp_packets_in_flight(tp) <= bbr_cwnd_min_target) { ++ bbr->probe_rtt_done_stamp = tcp_jiffies32 + ++ msecs_to_jiffies(bbr_probe_rtt_mode_ms); ++ bbr->probe_rtt_round_done = 0; ++ bbr->next_rtt_delivered = tp->delivered; ++ } else if (bbr->probe_rtt_done_stamp) { ++ if (bbr->round_start) ++ bbr->probe_rtt_round_done = 1; ++ if (bbr->probe_rtt_round_done) ++ bbr_check_probe_rtt_done(sk); ++ } ++ } ++ /* Restart after idle ends only once we process a new S/ACK for data */ ++ if (rs->delivered > 0) ++ bbr->idle_restart = 0; ++} ++ ++static void bbr_update_model(struct sock *sk, const struct rate_sample *rs) ++{ ++ bbr_update_bw(sk, rs); ++ bbr_update_cycle_phase(sk, rs); ++ bbr_check_full_bw_reached(sk, rs); ++ bbr_check_drain(sk, rs); ++ bbr_update_min_rtt(sk, rs); ++} ++ ++static void bbr_main(struct sock *sk, const struct rate_sample *rs) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ u32 bw; ++ ++ bbr_update_model(sk, rs); ++ ++ bw = bbr_bw(sk); ++ bbr_set_pacing_rate(sk, bw, bbr->pacing_gain); ++ bbr_set_cwnd(sk, rs, rs->acked_sacked, bw, bbr->cwnd_gain); ++} ++ ++static void bbr_init(struct sock *sk) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ bbr->prior_cwnd = 0; ++ tp->snd_ssthresh = TCP_INFINITE_SSTHRESH; ++ bbr->rtt_cnt = 0; ++ bbr->next_rtt_delivered = 0; ++ bbr->prev_ca_state = TCP_CA_Open; ++ bbr->packet_conservation = 0; ++ ++ bbr->probe_rtt_done_stamp = 0; ++ bbr->probe_rtt_round_done = 0; ++ bbr->min_rtt_us = tcp_min_rtt(tp); ++ bbr->min_rtt_stamp = tcp_jiffies32; ++ ++ minmax_reset(&bbr->bw, bbr->rtt_cnt, 0); /* init max bw to 0 */ ++ ++ bbr->has_seen_rtt = 0; ++ bbr_init_pacing_rate_from_rtt(sk); ++ ++ bbr->round_start = 0; ++ bbr->idle_restart = 0; ++ bbr->full_bw_reached = 0; ++ bbr->full_bw = 0; ++ bbr->full_bw_cnt = 0; ++ bbr->cycle_mstamp = 0; ++ bbr->cycle_idx = 0; ++ bbr_reset_lt_bw_sampling(sk); ++ bbr_reset_startup_mode(sk); ++ ++ cmpxchg(&sk->sk_pacing_status, SK_PACING_NONE, SK_PACING_NEEDED); ++} ++ ++static u32 bbr_sndbuf_expand(struct sock *sk) ++{ ++ /* Provision 3 * cwnd since BBR may slow-start even during recovery. */ ++ return 3; ++} ++ ++/* In theory BBR does not need to undo the cwnd since it does not ++ * always reduce cwnd on losses (see bbr_main()). Keep it for now. ++ */ ++static u32 bbr_undo_cwnd(struct sock *sk) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ bbr->full_bw = 0; /* spurious slow-down; reset full pipe detection */ ++ bbr->full_bw_cnt = 0; ++ bbr_reset_lt_bw_sampling(sk); ++ return tcp_sk(sk)->snd_cwnd; ++} ++ ++/* Entering loss recovery, so save cwnd for when we exit or undo recovery. */ ++static u32 bbr_ssthresh(struct sock *sk) ++{ ++ bbr_save_cwnd(sk); ++ return tcp_sk(sk)->snd_ssthresh; ++} ++ ++static size_t bbr_get_info(struct sock *sk, u32 ext, int *attr, ++ union tcp_cc_info *info) ++{ ++ if (ext & (1 << (INET_DIAG_BBRINFO - 1)) || ++ ext & (1 << (INET_DIAG_VEGASINFO - 1))) { ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct bbr *bbr = inet_csk_ca(sk); ++ u64 bw = bbr_bw(sk); ++ ++ bw = bw * tp->mss_cache * USEC_PER_SEC >> BW_SCALE; ++ memset(&info->bbr, 0, sizeof(info->bbr)); ++ info->bbr.bbr_bw_lo = (u32)bw; ++ info->bbr.bbr_bw_hi = (u32)(bw >> 32); ++ info->bbr.bbr_min_rtt = bbr->min_rtt_us; ++ info->bbr.bbr_pacing_gain = bbr->pacing_gain; ++ info->bbr.bbr_cwnd_gain = bbr->cwnd_gain; ++ *attr = INET_DIAG_BBRINFO; ++ return sizeof(info->bbr); ++ } ++ return 0; ++} ++ ++static void bbr_set_state(struct sock *sk, u8 new_state) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ if (new_state == TCP_CA_Loss) { ++ struct rate_sample rs = { .losses = 1 }; ++ ++ bbr->prev_ca_state = TCP_CA_Loss; ++ bbr->full_bw = 0; ++ bbr->round_start = 1; /* treat RTO like end of a round */ ++ bbr_lt_bw_sampling(sk, &rs); ++ } ++} ++ ++static struct tcp_congestion_ops tcp_bbr_cong_ops __read_mostly = { ++ .flags = TCP_CONG_NON_RESTRICTED, ++ .name = "nanqinlang", ++ .owner = THIS_MODULE, ++ .init = bbr_init, ++ .cong_control = bbr_main, ++ .sndbuf_expand = bbr_sndbuf_expand, ++ .undo_cwnd = bbr_undo_cwnd, ++ .cwnd_event = bbr_cwnd_event, ++ .ssthresh = bbr_ssthresh, ++ .min_tso_segs = bbr_min_tso_segs, ++ .get_info = bbr_get_info, ++ .set_state = bbr_set_state, ++}; ++ ++static int __init bbr_register(void) ++{ ++ BUILD_BUG_ON(sizeof(struct bbr) > ICSK_CA_PRIV_SIZE); ++ return tcp_register_congestion_control(&tcp_bbr_cong_ops); ++} ++ ++static void __exit bbr_unregister(void) ++{ ++ tcp_unregister_congestion_control(&tcp_bbr_cong_ops); ++} ++ ++module_init(bbr_register); ++module_exit(bbr_unregister); ++ ++MODULE_AUTHOR("Van Jacobson "); ++MODULE_AUTHOR("Neal Cardwell "); ++MODULE_AUTHOR("Yuchung Cheng "); ++MODULE_AUTHOR("Soheil Hassas Yeganeh "); ++MODULE_LICENSE("Dual BSD/GPL"); ++MODULE_DESCRIPTION("TCP BBR (Bottleneck Bandwidth and RTT)"); ++MODULE_AUTHOR("Nanqinlang "); diff --git a/root/target/linux/ipq60xx/patches-5.4/693-tcp_bbr2.patch b/root/target/linux/ipq60xx/patches-5.4/693-tcp_bbr2.patch new file mode 100644 index 00000000..44055724 --- /dev/null +++ b/root/target/linux/ipq60xx/patches-5.4/693-tcp_bbr2.patch @@ -0,0 +1,3368 @@ +diff --git a/include/linux/tcp.h b/include/linux/tcp.h +index ca6f01531e64..145b15f5d07e 100644 +--- a/include/linux/tcp.h ++++ b/include/linux/tcp.h +@@ -216,8 +216,9 @@ struct tcp_sock { + } rack; + u16 advmss; /* Advertised MSS */ + u8 compressed_ack; + u8 tlp_retrans:1, /* TLP is a retransmission */ + unused_1:7; ++ u8 fast_ack_mode:2; /* which fast ack mode ? */ + u32 chrono_start; /* Start time in jiffies of a TCP chrono */ + u32 chrono_stat[3]; /* Time in jiffies for chrono_stat stats */ + u8 chrono_type:2, /* current chronograph type */ +diff --git a/include/net/inet_connection_sock.h b/include/net/inet_connection_sock.h +index 895546058a20..1038f192cd09 100644 +--- a/include/net/inet_connection_sock.h ++++ b/include/net/inet_connection_sock.h +@@ -135,8 +135,9 @@ struct inet_connection_sock { + } icsk_mtup; + u32 icsk_user_timeout; + +- u64 icsk_ca_priv[104 / sizeof(u64)]; +-#define ICSK_CA_PRIV_SIZE (13 * sizeof(u64)) ++/* XXX inflated by temporary internal debugging info */ ++#define ICSK_CA_PRIV_SIZE (216) ++ u64 icsk_ca_priv[ICSK_CA_PRIV_SIZE / sizeof(u64)]; + }; + + #define ICSK_TIME_RETRANS 1 /* Retransmit timer */ +diff --git a/include/net/tcp.h b/include/net/tcp.h +index 36f195fb576a..c28000be002a 100644 +--- a/include/net/tcp.h ++++ b/include/net/tcp.h +@@ -776,6 +776,11 @@ static inline u32 tcp_stamp_us_delta(u64 t1, u64 t0) + return max_t(s64, t1 - t0, 0); + } + ++static inline u32 tcp_stamp32_us_delta(u32 t1, u32 t0) ++{ ++ return max_t(s32, t1 - t0, 0); ++} ++ + static inline u32 tcp_skb_timestamp(const struct sk_buff *skb) + { + return tcp_ns_to_ts(skb->skb_mstamp_ns); +@@ -843,16 +848,22 @@ struct tcp_skb_cb { + __u32 ack_seq; /* Sequence number ACK'd */ + union { + struct { ++#define TCPCB_DELIVERED_CE_MASK ((1U<<20) - 1) + /* There is space for up to 24 bytes */ +- __u32 in_flight:30,/* Bytes in flight at transmit */ +- is_app_limited:1, /* cwnd not fully used? */ +- unused:1; ++ __u32 is_app_limited:1, /* cwnd not fully used? */ ++ delivered_ce:20, ++ unused:11; + /* pkts S/ACKed so far upon tx of skb, incl retrans: */ + __u32 delivered; + /* start of send pipeline phase */ +- u64 first_tx_mstamp; ++ u32 first_tx_mstamp; + /* when we reached the "delivered" count */ +- u64 delivered_mstamp; ++ u32 delivered_mstamp; ++#define TCPCB_IN_FLIGHT_BITS 20 ++#define TCPCB_IN_FLIGHT_MAX ((1U << TCPCB_IN_FLIGHT_BITS) - 1) ++ u32 in_flight:20, /* packets in flight at transmit */ ++ unused2:12; ++ u32 lost; /* packets lost so far upon tx of skb */ + } tx; /* only used for outgoing skbs */ + union { + struct inet_skb_parm h4; +@@ -996,6 +1007,8 @@ enum tcp_ca_ack_event_flags { + #define TCP_CONG_NON_RESTRICTED 0x1 + /* Requires ECN/ECT set on all packets */ + #define TCP_CONG_NEEDS_ECN 0x2 ++/* Wants notification of CE events (CA_EVENT_ECN_IS_CE, CA_EVENT_ECN_NO_CE). */ ++#define TCP_CONG_WANTS_CE_EVENTS 0x100000 + + union tcp_cc_info; + +@@ -1015,8 +1028,13 @@ struct ack_sample { + */ + struct rate_sample { + u64 prior_mstamp; /* starting timestamp for interval */ ++ u32 prior_lost; /* tp->lost at "prior_mstamp" */ + u32 prior_delivered; /* tp->delivered at "prior_mstamp" */ ++ u32 prior_delivered_ce;/* tp->delivered_ce at "prior_mstamp" */ ++ u32 tx_in_flight; /* packets in flight at starting timestamp */ ++ s32 lost; /* number of packets lost over interval */ + s32 delivered; /* number of packets delivered over interval */ ++ s32 delivered_ce; /* packets delivered w/ CE mark over interval */ + long interval_us; /* time for tp->delivered to incr "delivered" */ + u32 snd_interval_us; /* snd interval for delivered packets */ + u32 rcv_interval_us; /* rcv interval for delivered packets */ +@@ -1027,6 +1045,7 @@ struct rate_sample { + bool is_app_limited; /* is sample from packet with bubble in pipe? */ + bool is_retrans; /* is sample from retransmission? */ + bool is_ack_delayed; /* is this (likely) a delayed ACK? */ ++ bool is_ece; /* did this ACK have ECN marked? */ + }; + + struct tcp_congestion_ops { +@@ -1053,10 +1072,12 @@ struct tcp_congestion_ops { + u32 (*undo_cwnd)(struct sock *sk); + /* hook for packet ack accounting (optional) */ + void (*pkts_acked)(struct sock *sk, const struct ack_sample *sample); +- /* override sysctl_tcp_min_tso_segs */ +- u32 (*min_tso_segs)(struct sock *sk); ++ /* pick target number of segments per TSO/GSO skb (optional): */ ++ u32 (*tso_segs)(struct sock *sk, unsigned int mss_now); + /* returns the multiplier used in tcp_sndbuf_expand (optional) */ + u32 (*sndbuf_expand)(struct sock *sk); ++ /* react to a specific lost skb (optional) */ ++ void (*skb_marked_lost)(struct sock *sk, const struct sk_buff *skb); + /* call when packets are delivered to update cwnd and pacing rate, + * after all the ca_state processing. (optional) + */ +@@ -1101,6 +1122,14 @@ static inline char *tcp_ca_get_name_by_key(u32 key, char *buffer) + } + #endif + ++static inline bool tcp_ca_wants_ce_events(const struct sock *sk) ++{ ++ const struct inet_connection_sock *icsk = inet_csk(sk); ++ ++ return icsk->icsk_ca_ops->flags & (TCP_CONG_NEEDS_ECN | ++ TCP_CONG_WANTS_CE_EVENTS); ++} ++ + static inline bool tcp_ca_needs_ecn(const struct sock *sk) + { + const struct inet_connection_sock *icsk = inet_csk(sk); +@@ -1126,6 +1155,7 @@ static inline void tcp_ca_event(struct sock *sk, const enum tcp_ca_event event) + } + + /* From tcp_rate.c */ ++void tcp_set_tx_in_flight(struct sock *sk, struct sk_buff *skb); + void tcp_rate_skb_sent(struct sock *sk, struct sk_buff *skb); + void tcp_rate_skb_delivered(struct sock *sk, struct sk_buff *skb, + struct rate_sample *rs); +diff --git a/include/uapi/linux/inet_diag.h b/include/uapi/linux/inet_diag.h +index a1ff345b3f33..ffc88b01868d 100644 +--- a/include/uapi/linux/inet_diag.h ++++ b/include/uapi/linux/inet_diag.h +@@ -206,9 +206,42 @@ struct tcp_bbr_info { + __u32 bbr_cwnd_gain; /* cwnd gain shifted left 8 bits */ + }; + ++/* Phase as reported in netlink/ss stats. */ ++enum tcp_bbr2_phase { ++ BBR2_PHASE_INVALID = 0, ++ BBR2_PHASE_STARTUP = 1, ++ BBR2_PHASE_DRAIN = 2, ++ BBR2_PHASE_PROBE_RTT = 3, ++ BBR2_PHASE_PROBE_BW_UP = 4, ++ BBR2_PHASE_PROBE_BW_DOWN = 5, ++ BBR2_PHASE_PROBE_BW_CRUISE = 6, ++ BBR2_PHASE_PROBE_BW_REFILL = 7 ++}; ++ ++struct tcp_bbr2_info { ++ /* u64 bw: bandwidth (app throughput) estimate in Byte per sec: */ ++ __u32 bbr_bw_lsb; /* lower 32 bits of bw */ ++ __u32 bbr_bw_msb; /* upper 32 bits of bw */ ++ __u32 bbr_min_rtt; /* min-filtered RTT in uSec */ ++ __u32 bbr_pacing_gain; /* pacing gain shifted left 8 bits */ ++ __u32 bbr_cwnd_gain; /* cwnd gain shifted left 8 bits */ ++ __u32 bbr_bw_hi_lsb; /* lower 32 bits of bw_hi */ ++ __u32 bbr_bw_hi_msb; /* upper 32 bits of bw_hi */ ++ __u32 bbr_bw_lo_lsb; /* lower 32 bits of bw_lo */ ++ __u32 bbr_bw_lo_msb; /* upper 32 bits of bw_lo */ ++ __u8 bbr_mode; /* current bbr_mode in state machine */ ++ __u8 bbr_phase; /* current state machine phase */ ++ __u8 unused1; /* alignment padding; not used yet */ ++ __u8 bbr_version; /* MUST be at this offset in struct */ ++ __u32 bbr_inflight_lo; /* lower/short-term data volume bound */ ++ __u32 bbr_inflight_hi; /* higher/long-term data volume bound */ ++ __u32 bbr_extra_acked; /* max excess packets ACKed in epoch */ ++}; ++ + union tcp_cc_info { + struct tcpvegas_info vegas; + struct tcp_dctcp_info dctcp; + struct tcp_bbr_info bbr; ++ struct tcp_bbr2_info bbr2; + }; + #endif /* _UAPI_INET_DIAG_H_ */ +diff --git a/net/ipv4/Kconfig b/net/ipv4/Kconfig +index 03381f3e12ba..a638dee76e8b 100644 +--- a/net/ipv4/Kconfig ++++ b/net/ipv4/Kconfig +@@ -654,6 +654,24 @@ config TCP_CONG_NANQUINLANG + bufferbloat, policers, or AQM schemes that do not provide a delay + signal. It requires the fq ("Fair Queue") pacing packet scheduler. + ++config TCP_CONG_BBR2 ++ tristate "BBR2 TCP" ++ default n ++ ---help--- ++ ++ BBR2 TCP congestion control is a model-based congestion control ++ algorithm that aims to maximize network utilization, keep queues and ++ retransmit rates low, and to be able to coexist with Reno/CUBIC in ++ common scenarios. It builds an explicit model of the network path. It ++ tolerates a targeted degree of random packet loss and delay that are ++ unrelated to congestion. It can operate over LAN, WAN, cellular, wifi, ++ or cable modem links, and can use DCTCP-L4S-style ECN signals. It can ++ coexist with flows that use loss-based congestion control, and can ++ operate with shallow buffers, deep buffers, bufferbloat, policers, or ++ AQM schemes that do not provide a delay signal. It requires pacing, ++ using either TCP internal pacing or the fq ("Fair Queue") pacing packet ++ scheduler. ++ + config TCP_CONG_LIA + tristate "MPTCP Linked Increase" + depends on MPTCP +@@ -691,6 +709,9 @@ choice + config DEFAULT_BBR + bool "BBR" if TCP_CONG_BBR=y + ++ config DEFAULT_BBR2 ++ bool "BBR2" if TCP_CONG_BBR2=y ++ + config DEFAULT_RENO + bool "Reno" + endchoice +@@ -715,6 +736,7 @@ config DEFAULT_TCP_CONG + default "dctcp" if DEFAULT_DCTCP + default "cdg" if DEFAULT_CDG + default "bbr" if DEFAULT_BBR ++ default "bbr2" if DEFAULT_BBR2 + default "nanqinlang" if DEFAULT_NANQINLANG + default "cubic" + + config TCP_MD5SIG +diff --git a/net/ipv4/Makefile b/net/ipv4/Makefile +index d57ecfaf89d4..7f187a52156c 100644 +--- a/net/ipv4/Makefile ++++ b/net/ipv4/Makefile +@@ -45,7 +45,8 @@ obj-$(CONFIG_INET_TCP_DIAG) += tcp_diag.o + obj-$(CONFIG_INET_UDP_DIAG) += udp_diag.o + obj-$(CONFIG_INET_RAW_DIAG) += raw_diag.o + obj-$(CONFIG_TCP_CONG_BBR) += tcp_bbr.o ++obj-$(CONFIG_TCP_CONG_BBR2) += tcp_bbr2.o + obj-$(CONFIG_TCP_CONG_NANQINLANG) += tcp_nanqinlang.o + obj-$(CONFIG_TCP_CONG_BIC) += tcp_bic.o + obj-$(CONFIG_TCP_CONG_CDG) += tcp_cdg.o + obj-$(CONFIG_TCP_CONG_CUBIC) += tcp_cubic.o +diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c +index 9b48aec29aca..5d41c97d9c5e 100644 +--- a/net/ipv4/tcp.c ++++ b/net/ipv4/tcp.c +@@ -2659,6 +2659,7 @@ int tcp_disconnect(struct sock *sk, int flags) + tp->rx_opt.dsack = 0; + tp->rx_opt.num_sacks = 0; + tp->rcv_ooopack = 0; ++ tp->fast_ack_mode = 0; + + + /* Clean up fastopen related fields */ +diff --git a/net/ipv4/tcp_bbr.c b/net/ipv4/tcp_bbr.c +index 32772d6ded4e..2d6677521575 100644 +--- a/net/ipv4/tcp_bbr.c ++++ b/net/ipv4/tcp_bbr.c +@@ -292,26 +292,40 @@ static void bbr_set_pacing_rate(struct sock *sk, u32 bw, int gain) + sk->sk_pacing_rate = rate; + } + +-/* override sysctl_tcp_min_tso_segs */ + static u32 bbr_min_tso_segs(struct sock *sk) + { + return sk->sk_pacing_rate < (bbr_min_tso_rate >> 3) ? 1 : 2; + } + ++/* Return the number of segments BBR would like in a TSO/GSO skb, given ++ * a particular max gso size as a constraint. ++ */ ++static u32 bbr_tso_segs_generic(struct sock *sk, unsigned int mss_now, ++ u32 gso_max_size) ++{ ++ u32 segs; ++ u32 bytes; ++ ++ /* Budget a TSO/GSO burst size allowance based on bw (pacing_rate). */ ++ bytes = sk->sk_pacing_rate >> sk->sk_pacing_shift; ++ ++ bytes = min_t(u32, bytes, gso_max_size - 1 - MAX_TCP_HEADER); ++ segs = max_t(u32, bytes / mss_now, bbr_min_tso_segs(sk)); ++ return segs; ++} ++ ++/* Custom tcp_tso_autosize() for BBR, used at transmit time to cap skb size. */ ++static u32 bbr_tso_segs(struct sock *sk, unsigned int mss_now) ++{ ++ return bbr_tso_segs_generic(sk, mss_now, sk->sk_gso_max_size); ++} ++ ++/* Like bbr_tso_segs(), using mss_cache, ignoring driver's sk_gso_max_size. */ + static u32 bbr_tso_segs_goal(struct sock *sk) + { + struct tcp_sock *tp = tcp_sk(sk); +- u32 segs, bytes; +- +- /* Sort of tcp_tso_autosize() but ignoring +- * driver provided sk_gso_max_size. +- */ +- bytes = min_t(unsigned long, +- sk->sk_pacing_rate >> READ_ONCE(sk->sk_pacing_shift), +- GSO_MAX_SIZE - 1 - MAX_TCP_HEADER); +- segs = max_t(u32, bytes / tp->mss_cache, bbr_min_tso_segs(sk)); + +- return min(segs, 0x7FU); ++ return bbr_tso_segs_generic(sk, tp->mss_cache, GSO_MAX_SIZE); + } + + /* Save "last known good" cwnd so we can restore it after losses or PROBE_RTT */ +@@ -1147,7 +1162,7 @@ static struct tcp_congestion_ops tcp_bbr_cong_ops __read_mostly = { + .undo_cwnd = bbr_undo_cwnd, + .cwnd_event = bbr_cwnd_event, + .ssthresh = bbr_ssthresh, +- .min_tso_segs = bbr_min_tso_segs, ++ .tso_segs = bbr_tso_segs, + .get_info = bbr_get_info, + .set_state = bbr_set_state, + }; +--- a/net/ipv4/tcp_nanqinlang.c ++++ b/net/ipv4/tcp_nanqinlang.c +@@ -292,25 +292,40 @@ static void bbr_set_pacing_rate(struct sock *sk, u32 bw, int gain) + sk->sk_pacing_rate = rate; + } + +-/* override sysctl_tcp_min_tso_segs */ + static u32 bbr_min_tso_segs(struct sock *sk) + { + return sk->sk_pacing_rate < (bbr_min_tso_rate >> 3) ? 1 : 2; + } + ++/* Return the number of segments BBR would like in a TSO/GSO skb, given ++ * a particular max gso size as a constraint. ++ */ ++static u32 bbr_tso_segs_generic(struct sock *sk, unsigned int mss_now, ++ u32 gso_max_size) ++{ ++ u32 segs; ++ u32 bytes; ++ ++ /* Budget a TSO/GSO burst size allowance based on bw (pacing_rate). */ ++ bytes = sk->sk_pacing_rate >> sk->sk_pacing_shift; ++ ++ bytes = min_t(u32, bytes, gso_max_size - 1 - MAX_TCP_HEADER); ++ segs = max_t(u32, bytes / mss_now, bbr_min_tso_segs(sk)); ++ return segs; ++} ++ ++/* Custom tcp_tso_autosize() for BBR, used at transmit time to cap skb size. */ ++static u32 bbr_tso_segs(struct sock *sk, unsigned int mss_now) ++{ ++ return bbr_tso_segs_generic(sk, mss_now, sk->sk_gso_max_size); ++} ++ ++/* Like bbr_tso_segs(), using mss_cache, ignoring driver's sk_gso_max_size. */ + static u32 bbr_tso_segs_goal(struct sock *sk) + { + struct tcp_sock *tp = tcp_sk(sk); +- u32 segs, bytes; +- +- /* Sort of tcp_tso_autosize() but ignoring +- * driver provided sk_gso_max_size. +- */ +- bytes = min_t(u32, sk->sk_pacing_rate >> sk->sk_pacing_shift, +- GSO_MAX_SIZE - 1 - MAX_TCP_HEADER); +- segs = max_t(u32, bytes / tp->mss_cache, bbr_min_tso_segs(sk)); + +- return min(segs, 0x7FU); ++ return bbr_tso_segs_generic(sk, tp->mss_cache, GSO_MAX_SIZE); + } + + /* Save "last known good" cwnd so we can restore it after losses or PROBE_RTT */ +@@ -1147,7 +1162,7 @@ static struct tcp_congestion_ops tcp_bbr_cong_ops __read_mostly = { + .undo_cwnd = bbr_undo_cwnd, + .cwnd_event = bbr_cwnd_event, + .ssthresh = bbr_ssthresh, +- .min_tso_segs = bbr_min_tso_segs, ++ .tso_segs = bbr_tso_segs, + .get_info = bbr_get_info, + .set_state = bbr_set_state, + }; +diff --git a/net/ipv4/tcp_bbr2.c b/net/ipv4/tcp_bbr2.c +new file mode 100644 +index 000000000000..875ebfef5a03 +--- /dev/null ++++ b/net/ipv4/tcp_bbr2.c +@@ -0,0 +1,2671 @@ ++/* BBR (Bottleneck Bandwidth and RTT) congestion control, v2 ++ * ++ * BBRv2 is a model-based congestion control algorithm that aims for low ++ * queues, low loss, and (bounded) Reno/CUBIC coexistence. To maintain a model ++ * of the network path, it uses measurements of bandwidth and RTT, as well as ++ * (if they occur) packet loss and/or DCTCP/L4S-style ECN signals. Note that ++ * although it can use ECN or loss signals explicitly, it does not require ++ * either; it can bound its in-flight data based on its estimate of the BDP. ++ * ++ * The model has both higher and lower bounds for the operating range: ++ * lo: bw_lo, inflight_lo: conservative short-term lower bound ++ * hi: bw_hi, inflight_hi: robust long-term upper bound ++ * The bandwidth-probing time scale is (a) extended dynamically based on ++ * estimated BDP to improve coexistence with Reno/CUBIC; (b) bounded by ++ * an interactive wall-clock time-scale to be more scalable and responsive ++ * than Reno and CUBIC. ++ * ++ * Here is a state transition diagram for BBR: ++ * ++ * | ++ * V ++ * +---> STARTUP ----+ ++ * | | | ++ * | V | ++ * | DRAIN ----+ ++ * | | | ++ * | V | ++ * +---> PROBE_BW ----+ ++ * | ^ | | ++ * | | | | ++ * | +----+ | ++ * | | ++ * +---- PROBE_RTT <--+ ++ * ++ * A BBR flow starts in STARTUP, and ramps up its sending rate quickly. ++ * When it estimates the pipe is full, it enters DRAIN to drain the queue. ++ * In steady state a BBR flow only uses PROBE_BW and PROBE_RTT. ++ * A long-lived BBR flow spends the vast majority of its time remaining ++ * (repeatedly) in PROBE_BW, fully probing and utilizing the pipe's bandwidth ++ * in a fair manner, with a small, bounded queue. *If* a flow has been ++ * continuously sending for the entire min_rtt window, and hasn't seen an RTT ++ * sample that matches or decreases its min_rtt estimate for 10 seconds, then ++ * it briefly enters PROBE_RTT to cut inflight to a minimum value to re-probe ++ * the path's two-way propagation delay (min_rtt). When exiting PROBE_RTT, if ++ * we estimated that we reached the full bw of the pipe then we enter PROBE_BW; ++ * otherwise we enter STARTUP to try to fill the pipe. ++ * ++ * BBR is described in detail in: ++ * "BBR: Congestion-Based Congestion Control", ++ * Neal Cardwell, Yuchung Cheng, C. Stephen Gunn, Soheil Hassas Yeganeh, ++ * Van Jacobson. ACM Queue, Vol. 14 No. 5, September-October 2016. ++ * ++ * There is a public e-mail list for discussing BBR development and testing: ++ * https://groups.google.com/forum/#!forum/bbr-dev ++ * ++ * NOTE: BBR might be used with the fq qdisc ("man tc-fq") with pacing enabled, ++ * otherwise TCP stack falls back to an internal pacing using one high ++ * resolution timer per TCP socket and may use more resources. ++ */ ++#include ++#include ++#include ++#include ++#include ++ ++#include "tcp_dctcp.h" ++ ++/* Scale factor for rate in pkt/uSec unit to avoid truncation in bandwidth ++ * estimation. The rate unit ~= (1500 bytes / 1 usec / 2^24) ~= 715 bps. ++ * This handles bandwidths from 0.06pps (715bps) to 256Mpps (3Tbps) in a u32. ++ * Since the minimum window is >=4 packets, the lower bound isn't ++ * an issue. The upper bound isn't an issue with existing technologies. ++ */ ++#define BW_SCALE 24 ++#define BW_UNIT (1 << BW_SCALE) ++ ++#define BBR_SCALE 8 /* scaling factor for fractions in BBR (e.g. gains) */ ++#define BBR_UNIT (1 << BBR_SCALE) ++ ++#define FLAG_DEBUG_VERBOSE 0x1 /* Verbose debugging messages */ ++#define FLAG_DEBUG_LOOPBACK 0x2 /* Do NOT skip loopback addr */ ++ ++#define CYCLE_LEN 8 /* number of phases in a pacing gain cycle */ ++ ++/* BBR has the following modes for deciding how fast to send: */ ++enum bbr_mode { ++ BBR_STARTUP, /* ramp up sending rate rapidly to fill pipe */ ++ BBR_DRAIN, /* drain any queue created during startup */ ++ BBR_PROBE_BW, /* discover, share bw: pace around estimated bw */ ++ BBR_PROBE_RTT, /* cut inflight to min to probe min_rtt */ ++}; ++ ++/* How does the incoming ACK stream relate to our bandwidth probing? */ ++enum bbr_ack_phase { ++ BBR_ACKS_INIT, /* not probing; not getting probe feedback */ ++ BBR_ACKS_REFILLING, /* sending at est. bw to fill pipe */ ++ BBR_ACKS_PROBE_STARTING, /* inflight rising to probe bw */ ++ BBR_ACKS_PROBE_FEEDBACK, /* getting feedback from bw probing */ ++ BBR_ACKS_PROBE_STOPPING, /* stopped probing; still getting feedback */ ++}; ++ ++/* BBR congestion control block */ ++struct bbr { ++ u32 min_rtt_us; /* min RTT in min_rtt_win_sec window */ ++ u32 min_rtt_stamp; /* timestamp of min_rtt_us */ ++ u32 probe_rtt_done_stamp; /* end time for BBR_PROBE_RTT mode */ ++ u32 probe_rtt_min_us; /* min RTT in bbr_probe_rtt_win_ms window */ ++ u32 probe_rtt_min_stamp; /* timestamp of probe_rtt_min_us*/ ++ u32 next_rtt_delivered; /* scb->tx.delivered at end of round */ ++ u32 prior_rcv_nxt; /* tp->rcv_nxt when CE state last changed */ ++ u64 cycle_mstamp; /* time of this cycle phase start */ ++ u32 mode:3, /* current bbr_mode in state machine */ ++ prev_ca_state:3, /* CA state on previous ACK */ ++ packet_conservation:1, /* use packet conservation? */ ++ round_start:1, /* start of packet-timed tx->ack round? */ ++ ce_state:1, /* If most recent data has CE bit set */ ++ bw_probe_up_rounds:5, /* cwnd-limited rounds in PROBE_UP */ ++ try_fast_path:1, /* can we take fast path? */ ++ unused2:11, ++ idle_restart:1, /* restarting after idle? */ ++ probe_rtt_round_done:1, /* a BBR_PROBE_RTT round at 4 pkts? */ ++ cycle_idx:3, /* current index in pacing_gain cycle array */ ++ has_seen_rtt:1; /* have we seen an RTT sample yet? */ ++ u32 pacing_gain:11, /* current gain for setting pacing rate */ ++ cwnd_gain:11, /* current gain for setting cwnd */ ++ full_bw_reached:1, /* reached full bw in Startup? */ ++ full_bw_cnt:2, /* number of rounds without large bw gains */ ++ init_cwnd:7; /* initial cwnd */ ++ u32 prior_cwnd; /* prior cwnd upon entering loss recovery */ ++ u32 full_bw; /* recent bw, to estimate if pipe is full */ ++ ++ /* For tracking ACK aggregation: */ ++ u64 ack_epoch_mstamp; /* start of ACK sampling epoch */ ++ u16 extra_acked[2]; /* max excess data ACKed in epoch */ ++ u32 ack_epoch_acked:20, /* packets (S)ACKed in sampling epoch */ ++ extra_acked_win_rtts:5, /* age of extra_acked, in round trips */ ++ extra_acked_win_idx:1, /* current index in extra_acked array */ ++ /* BBR v2 state: */ ++ unused1:2, ++ startup_ecn_rounds:2, /* consecutive hi ECN STARTUP rounds */ ++ loss_in_cycle:1, /* packet loss in this cycle? */ ++ ecn_in_cycle:1; /* ECN in this cycle? */ ++ u32 loss_round_delivered; /* scb->tx.delivered ending loss round */ ++ u32 undo_bw_lo; /* bw_lo before latest losses */ ++ u32 undo_inflight_lo; /* inflight_lo before latest losses */ ++ u32 undo_inflight_hi; /* inflight_hi before latest losses */ ++ u32 bw_latest; /* max delivered bw in last round trip */ ++ u32 bw_lo; /* lower bound on sending bandwidth */ ++ u32 bw_hi[2]; /* upper bound of sending bandwidth range*/ ++ u32 inflight_latest; /* max delivered data in last round trip */ ++ u32 inflight_lo; /* lower bound of inflight data range */ ++ u32 inflight_hi; /* upper bound of inflight data range */ ++ u32 bw_probe_up_cnt; /* packets delivered per inflight_hi incr */ ++ u32 bw_probe_up_acks; /* packets (S)ACKed since inflight_hi incr */ ++ u32 probe_wait_us; /* PROBE_DOWN until next clock-driven probe */ ++ u32 ecn_eligible:1, /* sender can use ECN (RTT, handshake)? */ ++ ecn_alpha:9, /* EWMA delivered_ce/delivered; 0..256 */ ++ bw_probe_samples:1, /* rate samples reflect bw probing? */ ++ prev_probe_too_high:1, /* did last PROBE_UP go too high? */ ++ stopped_risky_probe:1, /* last PROBE_UP stopped due to risk? */ ++ rounds_since_probe:8, /* packet-timed rounds since probed bw */ ++ loss_round_start:1, /* loss_round_delivered round trip? */ ++ loss_in_round:1, /* loss marked in this round trip? */ ++ ecn_in_round:1, /* ECN marked in this round trip? */ ++ ack_phase:3, /* bbr_ack_phase: meaning of ACKs */ ++ loss_events_in_round:4,/* losses in STARTUP round */ ++ initialized:1; /* has bbr_init() been called? */ ++ u32 alpha_last_delivered; /* tp->delivered at alpha update */ ++ u32 alpha_last_delivered_ce; /* tp->delivered_ce at alpha update */ ++ ++ /* Params configurable using setsockopt. Refer to correspoding ++ * module param for detailed description of params. ++ */ ++ struct bbr_params { ++ u32 high_gain:11, /* max allowed value: 2047 */ ++ drain_gain:10, /* max allowed value: 1023 */ ++ cwnd_gain:11; /* max allowed value: 2047 */ ++ u32 cwnd_min_target:4, /* max allowed value: 15 */ ++ min_rtt_win_sec:5, /* max allowed value: 31 */ ++ probe_rtt_mode_ms:9, /* max allowed value: 511 */ ++ full_bw_cnt:3, /* max allowed value: 7 */ ++ cwnd_tso_budget:1, /* allowed values: {0, 1} */ ++ unused3:6, ++ drain_to_target:1, /* boolean */ ++ precise_ece_ack:1, /* boolean */ ++ extra_acked_in_startup:1, /* allowed values: {0, 1} */ ++ fast_path:1; /* boolean */ ++ u32 full_bw_thresh:10, /* max allowed value: 1023 */ ++ startup_cwnd_gain:11, /* max allowed value: 2047 */ ++ bw_probe_pif_gain:9, /* max allowed value: 511 */ ++ usage_based_cwnd:1, /* boolean */ ++ unused2:1; ++ u16 probe_rtt_win_ms:14, /* max allowed value: 16383 */ ++ refill_add_inc:2; /* max allowed value: 3 */ ++ u16 extra_acked_gain:11, /* max allowed value: 2047 */ ++ extra_acked_win_rtts:5; /* max allowed value: 31*/ ++ u16 pacing_gain[CYCLE_LEN]; /* max allowed value: 1023 */ ++ /* Mostly BBR v2 parameters below here: */ ++ u32 ecn_alpha_gain:8, /* max allowed value: 255 */ ++ ecn_factor:8, /* max allowed value: 255 */ ++ ecn_thresh:8, /* max allowed value: 255 */ ++ beta:8; /* max allowed value: 255 */ ++ u32 ecn_max_rtt_us:19, /* max allowed value: 524287 */ ++ bw_probe_reno_gain:9, /* max allowed value: 511 */ ++ full_loss_cnt:4; /* max allowed value: 15 */ ++ u32 probe_rtt_cwnd_gain:8, /* max allowed value: 255 */ ++ inflight_headroom:8, /* max allowed value: 255 */ ++ loss_thresh:8, /* max allowed value: 255 */ ++ bw_probe_max_rounds:8; /* max allowed value: 255 */ ++ u32 bw_probe_rand_rounds:4, /* max allowed value: 15 */ ++ bw_probe_base_us:26, /* usecs: 0..2^26-1 (67 secs) */ ++ full_ecn_cnt:2; /* max allowed value: 3 */ ++ u32 bw_probe_rand_us:26, /* usecs: 0..2^26-1 (67 secs) */ ++ undo:1, /* boolean */ ++ tso_rtt_shift:4, /* max allowed value: 15 */ ++ unused5:1; ++ u32 ecn_reprobe_gain:9, /* max allowed value: 511 */ ++ unused1:14, ++ ecn_alpha_init:9; /* max allowed value: 256 */ ++ } params; ++ ++ struct { ++ u32 snd_isn; /* Initial sequence number */ ++ u32 rs_bw; /* last valid rate sample bw */ ++ u32 target_cwnd; /* target cwnd, based on BDP */ ++ u8 undo:1, /* Undo even happened but not yet logged */ ++ unused:7; ++ char event; /* single-letter event debug codes */ ++ u16 unused2; ++ } debug; ++}; ++ ++struct bbr_context { ++ u32 sample_bw; ++ u32 target_cwnd; ++ u32 log:1; ++}; ++ ++/* Window length of min_rtt filter (in sec). Max allowed value is 31 (0x1F) */ ++static u32 bbr_min_rtt_win_sec = 10; ++/* Minimum time (in ms) spent at bbr_cwnd_min_target in BBR_PROBE_RTT mode. ++ * Max allowed value is 511 (0x1FF). ++ */ ++static u32 bbr_probe_rtt_mode_ms = 200; ++/* Window length of probe_rtt_min_us filter (in ms), and consequently the ++ * typical interval between PROBE_RTT mode entries. ++ * Note that bbr_probe_rtt_win_ms must be <= bbr_min_rtt_win_sec * MSEC_PER_SEC ++ */ ++static u32 bbr_probe_rtt_win_ms = 5000; ++/* Skip TSO below the following bandwidth (bits/sec): */ ++static int bbr_min_tso_rate = 1200000; ++ ++/* Use min_rtt to help adapt TSO burst size, with smaller min_rtt resulting ++ * in bigger TSO bursts. By default we cut the RTT-based allowance in half ++ * for every 2^9 usec (aka 512 us) of RTT, so that the RTT-based allowance ++ * is below 1500 bytes after 6 * ~500 usec = 3ms. ++ */ ++static u32 bbr_tso_rtt_shift = 9; /* halve allowance per 2^9 usecs, 512us */ ++ ++/* Select cwnd TSO budget approach: ++ * 0: padding ++ * 1: flooring ++ */ ++static uint bbr_cwnd_tso_budget = 1; ++ ++/* Pace at ~1% below estimated bw, on average, to reduce queue at bottleneck. ++ * In order to help drive the network toward lower queues and low latency while ++ * maintaining high utilization, the average pacing rate aims to be slightly ++ * lower than the estimated bandwidth. This is an important aspect of the ++ * design. ++ */ ++static const int bbr_pacing_margin_percent = 1; ++ ++/* We use a high_gain value of 2/ln(2) because it's the smallest pacing gain ++ * that will allow a smoothly increasing pacing rate that will double each RTT ++ * and send the same number of packets per RTT that an un-paced, slow-starting ++ * Reno or CUBIC flow would. Max allowed value is 2047 (0x7FF). ++ */ ++static int bbr_high_gain = BBR_UNIT * 2885 / 1000 + 1; ++/* The gain for deriving startup cwnd. Max allowed value is 2047 (0x7FF). */ ++static int bbr_startup_cwnd_gain = BBR_UNIT * 2885 / 1000 + 1; ++/* The pacing gain of 1/high_gain in BBR_DRAIN is calculated to typically drain ++ * the queue created in BBR_STARTUP in a single round. Max allowed value ++ * is 1023 (0x3FF). ++ */ ++static int bbr_drain_gain = BBR_UNIT * 1000 / 2885; ++/* The gain for deriving steady-state cwnd tolerates delayed/stretched ACKs. ++ * Max allowed value is 2047 (0x7FF). ++ */ ++static int bbr_cwnd_gain = BBR_UNIT * 2; ++/* The pacing_gain values for the PROBE_BW gain cycle, to discover/share bw. ++ * Max allowed value for each element is 1023 (0x3FF). ++ */ ++enum bbr_pacing_gain_phase { ++ BBR_BW_PROBE_UP = 0, /* push up inflight to probe for bw/vol */ ++ BBR_BW_PROBE_DOWN = 1, /* drain excess inflight from the queue */ ++ BBR_BW_PROBE_CRUISE = 2, /* use pipe, w/ headroom in queue/pipe */ ++ BBR_BW_PROBE_REFILL = 3, /* v2: refill the pipe again to 100% */ ++}; ++static int bbr_pacing_gain[] = { ++ BBR_UNIT * 5 / 4, /* probe for more available bw */ ++ BBR_UNIT * 3 / 4, /* drain queue and/or yield bw to other flows */ ++ BBR_UNIT, BBR_UNIT, BBR_UNIT, /* cruise at 1.0*bw to utilize pipe, */ ++ BBR_UNIT, BBR_UNIT, BBR_UNIT /* without creating excess queue... */ ++}; ++ ++/* Try to keep at least this many packets in flight, if things go smoothly. For ++ * smooth functioning, a sliding window protocol ACKing every other packet ++ * needs at least 4 packets in flight. Max allowed value is 15 (0xF). ++ */ ++static u32 bbr_cwnd_min_target = 4; ++ ++/* Cwnd to BDP proportion in PROBE_RTT mode scaled by BBR_UNIT. Default: 50%. ++ * Use 0 to disable. Max allowed value is 255. ++ */ ++static u32 bbr_probe_rtt_cwnd_gain = BBR_UNIT * 1 / 2; ++ ++/* To estimate if BBR_STARTUP mode (i.e. high_gain) has filled pipe... */ ++/* If bw has increased significantly (1.25x), there may be more bw available. ++ * Max allowed value is 1023 (0x3FF). ++ */ ++static u32 bbr_full_bw_thresh = BBR_UNIT * 5 / 4; ++/* But after 3 rounds w/o significant bw growth, estimate pipe is full. ++ * Max allowed value is 7 (0x7). ++ */ ++static u32 bbr_full_bw_cnt = 3; ++ ++static u32 bbr_flags; /* Debugging related stuff */ ++ ++/* Whether to debug using printk. ++ */ ++static bool bbr_debug_with_printk; ++ ++/* Whether to debug using ftrace event tcp:tcp_bbr_event. ++ * Ignored when bbr_debug_with_printk is set. ++ */ ++static bool bbr_debug_ftrace; ++ ++/* Experiment: each cycle, try to hold sub-unity gain until inflight <= BDP. */ ++static bool bbr_drain_to_target = true; /* default: enabled */ ++ ++/* Experiment: Flags to control BBR with ECN behavior. ++ */ ++static bool bbr_precise_ece_ack = true; /* default: enabled */ ++ ++/* The max rwin scaling shift factor is 14 (RFC 1323), so the max sane rwin is ++ * (2^(16+14) B)/(1024 B/packet) = 1M packets. ++ */ ++static u32 bbr_cwnd_warn_val = 1U << 20; ++ ++static u16 bbr_debug_port_mask; ++ ++/* BBR module parameters. These are module parameters only in Google prod. ++ * Upstream these are intentionally not module parameters. ++ */ ++static int bbr_pacing_gain_size = CYCLE_LEN; ++ ++/* Gain factor for adding extra_acked to target cwnd: */ ++static int bbr_extra_acked_gain = 256; ++ ++/* Window length of extra_acked window. Max allowed val is 31. */ ++static u32 bbr_extra_acked_win_rtts = 5; ++ ++/* Max allowed val for ack_epoch_acked, after which sampling epoch is reset */ ++static u32 bbr_ack_epoch_acked_reset_thresh = 1U << 20; ++ ++/* Time period for clamping cwnd increment due to ack aggregation */ ++static u32 bbr_extra_acked_max_us = 100 * 1000; ++ ++/* Use extra acked in startup ? ++ * 0: disabled ++ * 1: use latest extra_acked value from 1-2 rtt in startup ++ */ ++static int bbr_extra_acked_in_startup = 1; /* default: enabled */ ++ ++/* Experiment: don't grow cwnd beyond twice of what we just probed. */ ++static bool bbr_usage_based_cwnd; /* default: disabled */ ++ ++/* For lab testing, researchers can enable BBRv2 ECN support with this flag, ++ * when they know that any ECN marks that the connections experience will be ++ * DCTCP/L4S-style ECN marks, rather than RFC3168 ECN marks. ++ * TODO(ncardwell): Production use of the BBRv2 ECN functionality depends on ++ * negotiation or configuration that is outside the scope of the BBRv2 ++ * alpha release. ++ */ ++static bool bbr_ecn_enable = false; ++ ++module_param_named(min_tso_rate, bbr_min_tso_rate, int, 0644); ++module_param_named(tso_rtt_shift, bbr_tso_rtt_shift, int, 0644); ++module_param_named(high_gain, bbr_high_gain, int, 0644); ++module_param_named(drain_gain, bbr_drain_gain, int, 0644); ++module_param_named(startup_cwnd_gain, bbr_startup_cwnd_gain, int, 0644); ++module_param_named(cwnd_gain, bbr_cwnd_gain, int, 0644); ++module_param_array_named(pacing_gain, bbr_pacing_gain, int, ++ &bbr_pacing_gain_size, 0644); ++module_param_named(cwnd_min_target, bbr_cwnd_min_target, uint, 0644); ++module_param_named(probe_rtt_cwnd_gain, ++ bbr_probe_rtt_cwnd_gain, uint, 0664); ++module_param_named(cwnd_warn_val, bbr_cwnd_warn_val, uint, 0664); ++module_param_named(debug_port_mask, bbr_debug_port_mask, ushort, 0644); ++module_param_named(flags, bbr_flags, uint, 0644); ++module_param_named(debug_ftrace, bbr_debug_ftrace, bool, 0644); ++module_param_named(debug_with_printk, bbr_debug_with_printk, bool, 0644); ++module_param_named(min_rtt_win_sec, bbr_min_rtt_win_sec, uint, 0644); ++module_param_named(probe_rtt_mode_ms, bbr_probe_rtt_mode_ms, uint, 0644); ++module_param_named(probe_rtt_win_ms, bbr_probe_rtt_win_ms, uint, 0644); ++module_param_named(full_bw_thresh, bbr_full_bw_thresh, uint, 0644); ++module_param_named(full_bw_cnt, bbr_full_bw_cnt, uint, 0644); ++module_param_named(cwnd_tso_bduget, bbr_cwnd_tso_budget, uint, 0664); ++module_param_named(extra_acked_gain, bbr_extra_acked_gain, int, 0664); ++module_param_named(extra_acked_win_rtts, ++ bbr_extra_acked_win_rtts, uint, 0664); ++module_param_named(extra_acked_max_us, ++ bbr_extra_acked_max_us, uint, 0664); ++module_param_named(ack_epoch_acked_reset_thresh, ++ bbr_ack_epoch_acked_reset_thresh, uint, 0664); ++module_param_named(drain_to_target, bbr_drain_to_target, bool, 0664); ++module_param_named(precise_ece_ack, bbr_precise_ece_ack, bool, 0664); ++module_param_named(extra_acked_in_startup, ++ bbr_extra_acked_in_startup, int, 0664); ++module_param_named(usage_based_cwnd, bbr_usage_based_cwnd, bool, 0664); ++module_param_named(ecn_enable, bbr_ecn_enable, bool, 0664); ++ ++static void bbr2_exit_probe_rtt(struct sock *sk); ++static void bbr2_reset_congestion_signals(struct sock *sk); ++ ++static void bbr_check_probe_rtt_done(struct sock *sk); ++ ++/* Do we estimate that STARTUP filled the pipe? */ ++static bool bbr_full_bw_reached(const struct sock *sk) ++{ ++ const struct bbr *bbr = inet_csk_ca(sk); ++ ++ return bbr->full_bw_reached; ++} ++ ++/* Return the windowed max recent bandwidth sample, in pkts/uS << BW_SCALE. */ ++static u32 bbr_max_bw(const struct sock *sk) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ return max(bbr->bw_hi[0], bbr->bw_hi[1]); ++} ++ ++/* Return the estimated bandwidth of the path, in pkts/uS << BW_SCALE. */ ++static u32 bbr_bw(const struct sock *sk) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ return min(bbr_max_bw(sk), bbr->bw_lo); ++} ++ ++/* Return maximum extra acked in past k-2k round trips, ++ * where k = bbr_extra_acked_win_rtts. ++ */ ++static u16 bbr_extra_acked(const struct sock *sk) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ return max(bbr->extra_acked[0], bbr->extra_acked[1]); ++} ++ ++/* Return rate in bytes per second, optionally with a gain. ++ * The order here is chosen carefully to avoid overflow of u64. This should ++ * work for input rates of up to 2.9Tbit/sec and gain of 2.89x. ++ */ ++static u64 bbr_rate_bytes_per_sec(struct sock *sk, u64 rate, int gain, ++ int margin) ++{ ++ unsigned int mss = tcp_sk(sk)->mss_cache; ++ ++ rate *= mss; ++ rate *= gain; ++ rate >>= BBR_SCALE; ++ rate *= USEC_PER_SEC / 100 * (100 - margin); ++ rate >>= BW_SCALE; ++ rate = max(rate, 1ULL); ++ return rate; ++} ++ ++static u64 bbr_bw_bytes_per_sec(struct sock *sk, u64 rate) ++{ ++ return bbr_rate_bytes_per_sec(sk, rate, BBR_UNIT, 0); ++} ++ ++static u64 bbr_rate_kbps(struct sock *sk, u64 rate) ++{ ++ rate = bbr_bw_bytes_per_sec(sk, rate); ++ rate *= 8; ++ do_div(rate, 1000); ++ return rate; ++} ++ ++static u32 bbr_tso_segs_goal(struct sock *sk); ++static void bbr_debug(struct sock *sk, u32 acked, ++ const struct rate_sample *rs, struct bbr_context *ctx) ++{ ++ static const char ca_states[] = { ++ [TCP_CA_Open] = 'O', ++ [TCP_CA_Disorder] = 'D', ++ [TCP_CA_CWR] = 'C', ++ [TCP_CA_Recovery] = 'R', ++ [TCP_CA_Loss] = 'L', ++ }; ++ static const char mode[] = { ++ 'G', /* Growing - BBR_STARTUP */ ++ 'D', /* Drain - BBR_DRAIN */ ++ 'W', /* Window - BBR_PROBE_BW */ ++ 'M', /* Min RTT - BBR_PROBE_RTT */ ++ }; ++ static const char ack_phase[] = { /* bbr_ack_phase strings */ ++ 'I', /* BBR_ACKS_INIT - 'Init' */ ++ 'R', /* BBR_ACKS_REFILLING - 'Refilling' */ ++ 'B', /* BBR_ACKS_PROBE_STARTING - 'Before' */ ++ 'F', /* BBR_ACKS_PROBE_FEEDBACK - 'Feedback' */ ++ 'A', /* BBR_ACKS_PROBE_STOPPING - 'After' */ ++ }; ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct bbr *bbr = inet_csk_ca(sk); ++ const u32 una = tp->snd_una - bbr->debug.snd_isn; ++ const u32 fack = tcp_highest_sack_seq(tp); ++ const u16 dport = ntohs(inet_sk(sk)->inet_dport); ++ bool is_port_match = (bbr_debug_port_mask && ++ ((dport & bbr_debug_port_mask) == 0)); ++ char debugmsg[320]; ++ ++ if (sk->sk_state == TCP_SYN_SENT) ++ return; /* no bbr_init() yet if SYN retransmit -> CA_Loss */ ++ ++ if (!tp->snd_cwnd || tp->snd_cwnd > bbr_cwnd_warn_val) { ++ char addr[INET6_ADDRSTRLEN + 10] = { 0 }; ++ ++ if (sk->sk_family == AF_INET) ++ snprintf(addr, sizeof(addr), "%pI4:%u", ++ &inet_sk(sk)->inet_daddr, dport); ++ else if (sk->sk_family == AF_INET6) ++ snprintf(addr, sizeof(addr), "%pI6:%u", ++ &sk->sk_v6_daddr, dport); ++ ++ WARN_ONCE(1, ++ "BBR %s cwnd alert: %u " ++ "snd_una: %u ca: %d pacing_gain: %u cwnd_gain: %u " ++ "bw: %u rtt: %u min_rtt: %u " ++ "acked: %u tso_segs: %u " ++ "bw: %d %ld %d pif: %u\n", ++ addr, tp->snd_cwnd, ++ una, inet_csk(sk)->icsk_ca_state, ++ bbr->pacing_gain, bbr->cwnd_gain, ++ bbr_max_bw(sk), (tp->srtt_us >> 3), bbr->min_rtt_us, ++ acked, bbr_tso_segs_goal(sk), ++ rs->delivered, rs->interval_us, rs->is_retrans, ++ tcp_packets_in_flight(tp)); ++ } ++ ++ if (likely(!bbr_debug_with_printk && !bbr_debug_ftrace)) ++ return; ++ ++ if (!sock_flag(sk, SOCK_DBG) && !is_port_match) ++ return; ++ ++ if (!ctx->log && !tp->app_limited && !(bbr_flags & FLAG_DEBUG_VERBOSE)) ++ return; ++ ++ if (ipv4_is_loopback(inet_sk(sk)->inet_daddr) && ++ !(bbr_flags & FLAG_DEBUG_LOOPBACK)) ++ return; ++ ++ snprintf(debugmsg, sizeof(debugmsg) - 1, ++ "BBR %pI4:%-5u %5u,%03u:%-7u %c " ++ "%c %2u br %2u cr %2d rtt %5ld d %2d i %5ld mrtt %d %cbw %llu " ++ "bw %llu lb %llu ib %llu qb %llu " ++ "a %u if %2u %c %c dl %u l %u al %u # %u t %u %c %c " ++ "lr %d er %d ea %d bwl %lld il %d ih %d c %d " ++ "v %d %c %u %c %s\n", ++ &inet_sk(sk)->inet_daddr, dport, ++ una / 1000, una % 1000, fack - tp->snd_una, ++ ca_states[inet_csk(sk)->icsk_ca_state], ++ bbr->debug.undo ? '@' : mode[bbr->mode], ++ tp->snd_cwnd, ++ bbr_extra_acked(sk), /* br (legacy): extra_acked */ ++ rs->tx_in_flight, /* cr (legacy): tx_inflight */ ++ rs->rtt_us, ++ rs->delivered, ++ rs->interval_us, ++ bbr->min_rtt_us, ++ rs->is_app_limited ? '_' : 'l', ++ bbr_rate_kbps(sk, ctx->sample_bw), /* lbw: latest sample bw */ ++ bbr_rate_kbps(sk, bbr_max_bw(sk)), /* bw: max bw */ ++ 0ULL, /* lb: [obsolete] */ ++ 0ULL, /* ib: [obsolete] */ ++ (u64)sk->sk_pacing_rate * 8 / 1000, ++ acked, ++ tcp_packets_in_flight(tp), ++ rs->is_ack_delayed ? 'd' : '.', ++ bbr->round_start ? '*' : '.', ++ tp->delivered, tp->lost, ++ tp->app_limited, ++ 0, /* #: [obsolete] */ ++ ctx->target_cwnd, ++ tp->reord_seen ? 'r' : '.', /* r: reordering seen? */ ++ ca_states[bbr->prev_ca_state], ++ (rs->lost + rs->delivered) > 0 ? ++ (1000 * rs->lost / ++ (rs->lost + rs->delivered)) : 0, /* lr: loss rate x1000 */ ++ (rs->delivered) > 0 ? ++ (1000 * rs->delivered_ce / ++ (rs->delivered)) : 0, /* er: ECN rate x1000 */ ++ 1000 * bbr->ecn_alpha >> BBR_SCALE, /* ea: ECN alpha x1000 */ ++ bbr->bw_lo == ~0U ? ++ -1 : (s64)bbr_rate_kbps(sk, bbr->bw_lo), /* bwl */ ++ bbr->inflight_lo, /* il */ ++ bbr->inflight_hi, /* ih */ ++ bbr->bw_probe_up_cnt, /* c */ ++ 2, /* v: version */ ++ bbr->debug.event, ++ bbr->cycle_idx, ++ ack_phase[bbr->ack_phase], ++ bbr->bw_probe_samples ? "Y" : "N"); ++ debugmsg[sizeof(debugmsg) - 1] = 0; ++ ++ /* printk takes a higher precedence. */ ++ if (bbr_debug_with_printk) ++ printk(KERN_DEBUG "%s", debugmsg); ++ ++ if (unlikely(bbr->debug.undo)) ++ bbr->debug.undo = 0; ++} ++ ++/* Convert a BBR bw and gain factor to a pacing rate in bytes per second. */ ++static unsigned long bbr_bw_to_pacing_rate(struct sock *sk, u32 bw, int gain) ++{ ++ u64 rate = bw; ++ ++ rate = bbr_rate_bytes_per_sec(sk, rate, gain, ++ bbr_pacing_margin_percent); ++ rate = min_t(u64, rate, sk->sk_max_pacing_rate); ++ return rate; ++} ++ ++/* Initialize pacing rate to: high_gain * init_cwnd / RTT. */ ++static void bbr_init_pacing_rate_from_rtt(struct sock *sk) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct bbr *bbr = inet_csk_ca(sk); ++ u64 bw; ++ u32 rtt_us; ++ ++ if (tp->srtt_us) { /* any RTT sample yet? */ ++ rtt_us = max(tp->srtt_us >> 3, 1U); ++ bbr->has_seen_rtt = 1; ++ } else { /* no RTT sample yet */ ++ rtt_us = USEC_PER_MSEC; /* use nominal default RTT */ ++ } ++ bw = (u64)tp->snd_cwnd * BW_UNIT; ++ do_div(bw, rtt_us); ++ sk->sk_pacing_rate = bbr_bw_to_pacing_rate(sk, bw, bbr->params.high_gain); ++} ++ ++/* Pace using current bw estimate and a gain factor. */ ++static void bbr_set_pacing_rate(struct sock *sk, u32 bw, int gain) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct bbr *bbr = inet_csk_ca(sk); ++ unsigned long rate = bbr_bw_to_pacing_rate(sk, bw, gain); ++ ++ if (unlikely(!bbr->has_seen_rtt && tp->srtt_us)) ++ bbr_init_pacing_rate_from_rtt(sk); ++ if (bbr_full_bw_reached(sk) || rate > sk->sk_pacing_rate) ++ sk->sk_pacing_rate = rate; ++} ++ ++static u32 bbr_min_tso_segs(struct sock *sk) ++{ ++ return sk->sk_pacing_rate < (bbr_min_tso_rate >> 3) ? 1 : 2; ++} ++ ++/* Return the number of segments BBR would like in a TSO/GSO skb, given ++ * a particular max gso size as a constraint. ++ */ ++static u32 bbr_tso_segs_generic(struct sock *sk, unsigned int mss_now, ++ u32 gso_max_size) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ u32 segs, r; ++ u64 bytes; ++ ++ /* Budget a TSO/GSO burst size allowance based on bw (pacing_rate). */ ++ bytes = sk->sk_pacing_rate >> sk->sk_pacing_shift; ++ ++ /* Budget a TSO/GSO burst size allowance based on min_rtt. For every ++ * K = 2^tso_rtt_shift microseconds of min_rtt, halve the burst. ++ * The min_rtt-based burst allowance is: 64 KBytes / 2^(min_rtt/K) ++ */ ++ if (bbr->params.tso_rtt_shift) { ++ r = bbr->min_rtt_us >> bbr->params.tso_rtt_shift; ++ if (r < BITS_PER_TYPE(u32)) /* prevent undefined behavior */ ++ bytes += GSO_MAX_SIZE >> r; ++ } ++ ++ bytes = min_t(u32, bytes, gso_max_size - 1 - MAX_TCP_HEADER); ++ segs = max_t(u32, bytes / mss_now, bbr_min_tso_segs(sk)); ++ return segs; ++} ++ ++/* Custom tcp_tso_autosize() for BBR, used at transmit time to cap skb size. */ ++static u32 bbr_tso_segs(struct sock *sk, unsigned int mss_now) ++{ ++ return bbr_tso_segs_generic(sk, mss_now, sk->sk_gso_max_size); ++} ++ ++/* Like bbr_tso_segs(), using mss_cache, ignoring driver's sk_gso_max_size. */ ++static u32 bbr_tso_segs_goal(struct sock *sk) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ ++ return bbr_tso_segs_generic(sk, tp->mss_cache, GSO_MAX_SIZE); ++} ++ ++/* Save "last known good" cwnd so we can restore it after losses or PROBE_RTT */ ++static void bbr_save_cwnd(struct sock *sk) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ if (bbr->prev_ca_state < TCP_CA_Recovery && bbr->mode != BBR_PROBE_RTT) ++ bbr->prior_cwnd = tp->snd_cwnd; /* this cwnd is good enough */ ++ else /* loss recovery or BBR_PROBE_RTT have temporarily cut cwnd */ ++ bbr->prior_cwnd = max(bbr->prior_cwnd, tp->snd_cwnd); ++} ++ ++static void bbr_cwnd_event(struct sock *sk, enum tcp_ca_event event) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ if (event == CA_EVENT_TX_START && tp->app_limited) { ++ bbr->idle_restart = 1; ++ bbr->ack_epoch_mstamp = tp->tcp_mstamp; ++ bbr->ack_epoch_acked = 0; ++ /* Avoid pointless buffer overflows: pace at est. bw if we don't ++ * need more speed (we're restarting from idle and app-limited). ++ */ ++ if (bbr->mode == BBR_PROBE_BW) ++ bbr_set_pacing_rate(sk, bbr_bw(sk), BBR_UNIT); ++ else if (bbr->mode == BBR_PROBE_RTT) ++ bbr_check_probe_rtt_done(sk); ++ } else if ((event == CA_EVENT_ECN_IS_CE || ++ event == CA_EVENT_ECN_NO_CE) && ++ bbr_ecn_enable && ++ bbr->params.precise_ece_ack) { ++ u32 state = bbr->ce_state; ++ dctcp_ece_ack_update(sk, event, &bbr->prior_rcv_nxt, &state); ++ bbr->ce_state = state; ++ if (tp->fast_ack_mode == 2 && event == CA_EVENT_ECN_IS_CE) ++ tcp_enter_quickack_mode(sk, TCP_MAX_QUICKACKS); ++ } ++} ++ ++/* Calculate bdp based on min RTT and the estimated bottleneck bandwidth: ++ * ++ * bdp = ceil(bw * min_rtt * gain) ++ * ++ * The key factor, gain, controls the amount of queue. While a small gain ++ * builds a smaller queue, it becomes more vulnerable to noise in RTT ++ * measurements (e.g., delayed ACKs or other ACK compression effects). This ++ * noise may cause BBR to under-estimate the rate. ++ */ ++static u32 bbr_bdp(struct sock *sk, u32 bw, int gain) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ u32 bdp; ++ u64 w; ++ ++ /* If we've never had a valid RTT sample, cap cwnd at the initial ++ * default. This should only happen when the connection is not using TCP ++ * timestamps and has retransmitted all of the SYN/SYNACK/data packets ++ * ACKed so far. In this case, an RTO can cut cwnd to 1, in which ++ * case we need to slow-start up toward something safe: initial cwnd. ++ */ ++ if (unlikely(bbr->min_rtt_us == ~0U)) /* no valid RTT samples yet? */ ++ return bbr->init_cwnd; /* be safe: cap at initial cwnd */ ++ ++ w = (u64)bw * bbr->min_rtt_us; ++ ++ /* Apply a gain to the given value, remove the BW_SCALE shift, and ++ * round the value up to avoid a negative feedback loop. ++ */ ++ bdp = (((w * gain) >> BBR_SCALE) + BW_UNIT - 1) / BW_UNIT; ++ ++ return bdp; ++} ++ ++/* To achieve full performance in high-speed paths, we budget enough cwnd to ++ * fit full-sized skbs in-flight on both end hosts to fully utilize the path: ++ * - one skb in sending host Qdisc, ++ * - one skb in sending host TSO/GSO engine ++ * - one skb being received by receiver host LRO/GRO/delayed-ACK engine ++ * Don't worry, at low rates (bbr_min_tso_rate) this won't bloat cwnd because ++ * in such cases tso_segs_goal is 1. The minimum cwnd is 4 packets, ++ * which allows 2 outstanding 2-packet sequences, to try to keep pipe ++ * full even with ACK-every-other-packet delayed ACKs. ++ */ ++static u32 bbr_quantization_budget(struct sock *sk, u32 cwnd) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ u32 tso_segs_goal; ++ ++ tso_segs_goal = 3 * bbr_tso_segs_goal(sk); ++ ++ /* Allow enough full-sized skbs in flight to utilize end systems. */ ++ if (bbr->params.cwnd_tso_budget == 1) { ++ cwnd = max_t(u32, cwnd, tso_segs_goal); ++ cwnd = max_t(u32, cwnd, bbr->params.cwnd_min_target); ++ } else { ++ cwnd += tso_segs_goal; ++ cwnd = (cwnd + 1) & ~1U; ++ } ++ /* Ensure gain cycling gets inflight above BDP even for small BDPs. */ ++ if (bbr->mode == BBR_PROBE_BW && bbr->cycle_idx == BBR_BW_PROBE_UP) ++ cwnd += 2; ++ ++ return cwnd; ++} ++ ++/* Find inflight based on min RTT and the estimated bottleneck bandwidth. */ ++static u32 bbr_inflight(struct sock *sk, u32 bw, int gain) ++{ ++ u32 inflight; ++ ++ inflight = bbr_bdp(sk, bw, gain); ++ inflight = bbr_quantization_budget(sk, inflight); ++ ++ return inflight; ++} ++ ++/* With pacing at lower layers, there's often less data "in the network" than ++ * "in flight". With TSQ and departure time pacing at lower layers (e.g. fq), ++ * we often have several skbs queued in the pacing layer with a pre-scheduled ++ * earliest departure time (EDT). BBR adapts its pacing rate based on the ++ * inflight level that it estimates has already been "baked in" by previous ++ * departure time decisions. We calculate a rough estimate of the number of our ++ * packets that might be in the network at the earliest departure time for the ++ * next skb scheduled: ++ * in_network_at_edt = inflight_at_edt - (EDT - now) * bw ++ * If we're increasing inflight, then we want to know if the transmit of the ++ * EDT skb will push inflight above the target, so inflight_at_edt includes ++ * bbr_tso_segs_goal() from the skb departing at EDT. If decreasing inflight, ++ * then estimate if inflight will sink too low just before the EDT transmit. ++ */ ++static u32 bbr_packets_in_net_at_edt(struct sock *sk, u32 inflight_now) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct bbr *bbr = inet_csk_ca(sk); ++ u64 now_ns, edt_ns, interval_us; ++ u32 interval_delivered, inflight_at_edt; ++ ++ now_ns = tp->tcp_clock_cache; ++ edt_ns = max(tp->tcp_wstamp_ns, now_ns); ++ interval_us = div_u64(edt_ns - now_ns, NSEC_PER_USEC); ++ interval_delivered = (u64)bbr_bw(sk) * interval_us >> BW_SCALE; ++ inflight_at_edt = inflight_now; ++ if (bbr->pacing_gain > BBR_UNIT) /* increasing inflight */ ++ inflight_at_edt += bbr_tso_segs_goal(sk); /* include EDT skb */ ++ if (interval_delivered >= inflight_at_edt) ++ return 0; ++ return inflight_at_edt - interval_delivered; ++} ++ ++/* Find the cwnd increment based on estimate of ack aggregation */ ++static u32 bbr_ack_aggregation_cwnd(struct sock *sk) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ u32 max_aggr_cwnd, aggr_cwnd = 0; ++ ++ if (bbr->params.extra_acked_gain && ++ (bbr_full_bw_reached(sk) || bbr->params.extra_acked_in_startup)) { ++ max_aggr_cwnd = ((u64)bbr_bw(sk) * bbr_extra_acked_max_us) ++ / BW_UNIT; ++ aggr_cwnd = (bbr->params.extra_acked_gain * bbr_extra_acked(sk)) ++ >> BBR_SCALE; ++ aggr_cwnd = min(aggr_cwnd, max_aggr_cwnd); ++ } ++ ++ return aggr_cwnd; ++} ++ ++/* Returns the cwnd for PROBE_RTT mode. */ ++static u32 bbr_probe_rtt_cwnd(struct sock *sk) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ if (bbr->params.probe_rtt_cwnd_gain == 0) ++ return bbr->params.cwnd_min_target; ++ return max_t(u32, bbr->params.cwnd_min_target, ++ bbr_bdp(sk, bbr_bw(sk), bbr->params.probe_rtt_cwnd_gain)); ++} ++ ++/* Slow-start up toward target cwnd (if bw estimate is growing, or packet loss ++ * has drawn us down below target), or snap down to target if we're above it. ++ */ ++static void bbr_set_cwnd(struct sock *sk, const struct rate_sample *rs, ++ u32 acked, u32 bw, int gain, u32 cwnd, ++ struct bbr_context *ctx) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct bbr *bbr = inet_csk_ca(sk); ++ u32 target_cwnd = 0, prev_cwnd = tp->snd_cwnd, max_probe; ++ ++ if (!acked) ++ goto done; /* no packet fully ACKed; just apply caps */ ++ ++ target_cwnd = bbr_bdp(sk, bw, gain); ++ ++ /* Increment the cwnd to account for excess ACKed data that seems ++ * due to aggregation (of data and/or ACKs) visible in the ACK stream. ++ */ ++ target_cwnd += bbr_ack_aggregation_cwnd(sk); ++ target_cwnd = bbr_quantization_budget(sk, target_cwnd); ++ ++ /* If we're below target cwnd, slow start cwnd toward target cwnd. */ ++ bbr->debug.target_cwnd = target_cwnd; ++ ++ /* Update cwnd and enable fast path if cwnd reaches target_cwnd. */ ++ bbr->try_fast_path = 0; ++ if (bbr_full_bw_reached(sk)) { /* only cut cwnd if we filled the pipe */ ++ cwnd += acked; ++ if (cwnd >= target_cwnd) { ++ cwnd = target_cwnd; ++ bbr->try_fast_path = 1; ++ } ++ } else if (cwnd < target_cwnd || cwnd < 2 * bbr->init_cwnd) { ++ cwnd += acked; ++ } else { ++ bbr->try_fast_path = 1; ++ } ++ ++ /* When growing cwnd, don't grow beyond twice what we just probed. */ ++ if (bbr->params.usage_based_cwnd) { ++ max_probe = max(2 * tp->max_packets_out, tp->snd_cwnd); ++ cwnd = min(cwnd, max_probe); ++ } ++ ++ cwnd = max_t(u32, cwnd, bbr->params.cwnd_min_target); ++done: ++ tp->snd_cwnd = min(cwnd, tp->snd_cwnd_clamp); /* apply global cap */ ++ if (bbr->mode == BBR_PROBE_RTT) /* drain queue, refresh min_rtt */ ++ tp->snd_cwnd = min_t(u32, tp->snd_cwnd, bbr_probe_rtt_cwnd(sk)); ++ ++ ctx->target_cwnd = target_cwnd; ++ ctx->log = (tp->snd_cwnd != prev_cwnd); ++} ++ ++/* See if we have reached next round trip */ ++static void bbr_update_round_start(struct sock *sk, ++ const struct rate_sample *rs, struct bbr_context *ctx) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ bbr->round_start = 0; ++ ++ /* See if we've reached the next RTT */ ++ if (rs->interval_us > 0 && ++ !before(rs->prior_delivered, bbr->next_rtt_delivered)) { ++ bbr->next_rtt_delivered = tp->delivered; ++ bbr->round_start = 1; ++ } ++} ++ ++/* Calculate the bandwidth based on how fast packets are delivered */ ++static void bbr_calculate_bw_sample(struct sock *sk, ++ const struct rate_sample *rs, struct bbr_context *ctx) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ u64 bw = 0; ++ ++ /* Divide delivered by the interval to find a (lower bound) bottleneck ++ * bandwidth sample. Delivered is in packets and interval_us in uS and ++ * ratio will be <<1 for most connections. So delivered is first scaled. ++ * Round up to allow growth at low rates, even with integer division. ++ */ ++ if (rs->interval_us > 0) { ++ if (WARN_ONCE(rs->delivered < 0, ++ "negative delivered: %d interval_us: %ld\n", ++ rs->delivered, rs->interval_us)) ++ return; ++ ++ bw = DIV_ROUND_UP_ULL((u64)rs->delivered * BW_UNIT, rs->interval_us); ++ } ++ ++ ctx->sample_bw = bw; ++ bbr->debug.rs_bw = bw; ++} ++ ++/* Estimates the windowed max degree of ack aggregation. ++ * This is used to provision extra in-flight data to keep sending during ++ * inter-ACK silences. ++ * ++ * Degree of ack aggregation is estimated as extra data acked beyond expected. ++ * ++ * max_extra_acked = "maximum recent excess data ACKed beyond max_bw * interval" ++ * cwnd += max_extra_acked ++ * ++ * Max extra_acked is clamped by cwnd and bw * bbr_extra_acked_max_us (100 ms). ++ * Max filter is an approximate sliding window of 5-10 (packet timed) round ++ * trips for non-startup phase, and 1-2 round trips for startup. ++ */ ++static void bbr_update_ack_aggregation(struct sock *sk, ++ const struct rate_sample *rs) ++{ ++ u32 epoch_us, expected_acked, extra_acked; ++ struct bbr *bbr = inet_csk_ca(sk); ++ struct tcp_sock *tp = tcp_sk(sk); ++ u32 extra_acked_win_rtts_thresh = bbr->params.extra_acked_win_rtts; ++ ++ if (!bbr->params.extra_acked_gain || rs->acked_sacked <= 0 || ++ rs->delivered < 0 || rs->interval_us <= 0) ++ return; ++ ++ if (bbr->round_start) { ++ bbr->extra_acked_win_rtts = min(0x1F, ++ bbr->extra_acked_win_rtts + 1); ++ if (bbr->params.extra_acked_in_startup && ++ !bbr_full_bw_reached(sk)) ++ extra_acked_win_rtts_thresh = 1; ++ if (bbr->extra_acked_win_rtts >= ++ extra_acked_win_rtts_thresh) { ++ bbr->extra_acked_win_rtts = 0; ++ bbr->extra_acked_win_idx = bbr->extra_acked_win_idx ? ++ 0 : 1; ++ bbr->extra_acked[bbr->extra_acked_win_idx] = 0; ++ } ++ } ++ ++ /* Compute how many packets we expected to be delivered over epoch. */ ++ epoch_us = tcp_stamp_us_delta(tp->delivered_mstamp, ++ bbr->ack_epoch_mstamp); ++ expected_acked = ((u64)bbr_bw(sk) * epoch_us) / BW_UNIT; ++ ++ /* Reset the aggregation epoch if ACK rate is below expected rate or ++ * significantly large no. of ack received since epoch (potentially ++ * quite old epoch). ++ */ ++ if (bbr->ack_epoch_acked <= expected_acked || ++ (bbr->ack_epoch_acked + rs->acked_sacked >= ++ bbr_ack_epoch_acked_reset_thresh)) { ++ bbr->ack_epoch_acked = 0; ++ bbr->ack_epoch_mstamp = tp->delivered_mstamp; ++ expected_acked = 0; ++ } ++ ++ /* Compute excess data delivered, beyond what was expected. */ ++ bbr->ack_epoch_acked = min_t(u32, 0xFFFFF, ++ bbr->ack_epoch_acked + rs->acked_sacked); ++ extra_acked = bbr->ack_epoch_acked - expected_acked; ++ extra_acked = min(extra_acked, tp->snd_cwnd); ++ if (extra_acked > bbr->extra_acked[bbr->extra_acked_win_idx]) ++ bbr->extra_acked[bbr->extra_acked_win_idx] = extra_acked; ++} ++ ++/* Estimate when the pipe is full, using the change in delivery rate: BBR ++ * estimates that STARTUP filled the pipe if the estimated bw hasn't changed by ++ * at least bbr_full_bw_thresh (25%) after bbr_full_bw_cnt (3) non-app-limited ++ * rounds. Why 3 rounds: 1: rwin autotuning grows the rwin, 2: we fill the ++ * higher rwin, 3: we get higher delivery rate samples. Or transient ++ * cross-traffic or radio noise can go away. CUBIC Hystart shares a similar ++ * design goal, but uses delay and inter-ACK spacing instead of bandwidth. ++ */ ++static void bbr_check_full_bw_reached(struct sock *sk, ++ const struct rate_sample *rs) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ u32 bw_thresh; ++ ++ if (bbr_full_bw_reached(sk) || !bbr->round_start || rs->is_app_limited) ++ return; ++ ++ bw_thresh = (u64)bbr->full_bw * bbr->params.full_bw_thresh >> BBR_SCALE; ++ if (bbr_max_bw(sk) >= bw_thresh) { ++ bbr->full_bw = bbr_max_bw(sk); ++ bbr->full_bw_cnt = 0; ++ return; ++ } ++ ++bbr->full_bw_cnt; ++ bbr->full_bw_reached = bbr->full_bw_cnt >= bbr->params.full_bw_cnt; ++} ++ ++/* If pipe is probably full, drain the queue and then enter steady-state. */ ++static bool bbr_check_drain(struct sock *sk, const struct rate_sample *rs, ++ struct bbr_context *ctx) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ if (bbr->mode == BBR_STARTUP && bbr_full_bw_reached(sk)) { ++ bbr->mode = BBR_DRAIN; /* drain queue we created */ ++ tcp_sk(sk)->snd_ssthresh = ++ bbr_inflight(sk, bbr_max_bw(sk), BBR_UNIT); ++ bbr2_reset_congestion_signals(sk); ++ } /* fall through to check if in-flight is already small: */ ++ if (bbr->mode == BBR_DRAIN && ++ bbr_packets_in_net_at_edt(sk, tcp_packets_in_flight(tcp_sk(sk))) <= ++ bbr_inflight(sk, bbr_max_bw(sk), BBR_UNIT)) ++ return true; /* exiting DRAIN now */ ++ return false; ++} ++ ++static void bbr_check_probe_rtt_done(struct sock *sk) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ if (!(bbr->probe_rtt_done_stamp && ++ after(tcp_jiffies32, bbr->probe_rtt_done_stamp))) ++ return; ++ ++ bbr->probe_rtt_min_stamp = tcp_jiffies32; /* schedule next PROBE_RTT */ ++ tp->snd_cwnd = max(tp->snd_cwnd, bbr->prior_cwnd); ++ bbr2_exit_probe_rtt(sk); ++} ++ ++/* The goal of PROBE_RTT mode is to have BBR flows cooperatively and ++ * periodically drain the bottleneck queue, to converge to measure the true ++ * min_rtt (unloaded propagation delay). This allows the flows to keep queues ++ * small (reducing queuing delay and packet loss) and achieve fairness among ++ * BBR flows. ++ * ++ * The min_rtt filter window is 10 seconds. When the min_rtt estimate expires, ++ * we enter PROBE_RTT mode and cap the cwnd at bbr_cwnd_min_target=4 packets. ++ * After at least bbr_probe_rtt_mode_ms=200ms and at least one packet-timed ++ * round trip elapsed with that flight size <= 4, we leave PROBE_RTT mode and ++ * re-enter the previous mode. BBR uses 200ms to approximately bound the ++ * performance penalty of PROBE_RTT's cwnd capping to roughly 2% (200ms/10s). ++ * ++ * Note that flows need only pay 2% if they are busy sending over the last 10 ++ * seconds. Interactive applications (e.g., Web, RPCs, video chunks) often have ++ * natural silences or low-rate periods within 10 seconds where the rate is low ++ * enough for long enough to drain its queue in the bottleneck. We pick up ++ * these min RTT measurements opportunistically with our min_rtt filter. :-) ++ */ ++static void bbr_update_min_rtt(struct sock *sk, const struct rate_sample *rs) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct bbr *bbr = inet_csk_ca(sk); ++ bool probe_rtt_expired, min_rtt_expired; ++ u32 expire; ++ ++ /* Track min RTT in probe_rtt_win_ms to time next PROBE_RTT state. */ ++ expire = bbr->probe_rtt_min_stamp + ++ msecs_to_jiffies(bbr->params.probe_rtt_win_ms); ++ probe_rtt_expired = after(tcp_jiffies32, expire); ++ if (rs->rtt_us >= 0 && ++ (rs->rtt_us <= bbr->probe_rtt_min_us || ++ (probe_rtt_expired && !rs->is_ack_delayed))) { ++ bbr->probe_rtt_min_us = rs->rtt_us; ++ bbr->probe_rtt_min_stamp = tcp_jiffies32; ++ } ++ /* Track min RTT seen in the min_rtt_win_sec filter window: */ ++ expire = bbr->min_rtt_stamp + bbr->params.min_rtt_win_sec * HZ; ++ min_rtt_expired = after(tcp_jiffies32, expire); ++ if (bbr->probe_rtt_min_us <= bbr->min_rtt_us || ++ min_rtt_expired) { ++ bbr->min_rtt_us = bbr->probe_rtt_min_us; ++ bbr->min_rtt_stamp = bbr->probe_rtt_min_stamp; ++ } ++ ++ if (bbr->params.probe_rtt_mode_ms > 0 && probe_rtt_expired && ++ !bbr->idle_restart && bbr->mode != BBR_PROBE_RTT) { ++ bbr->mode = BBR_PROBE_RTT; /* dip, drain queue */ ++ bbr_save_cwnd(sk); /* note cwnd so we can restore it */ ++ bbr->probe_rtt_done_stamp = 0; ++ bbr->ack_phase = BBR_ACKS_PROBE_STOPPING; ++ bbr->next_rtt_delivered = tp->delivered; ++ } ++ ++ if (bbr->mode == BBR_PROBE_RTT) { ++ /* Ignore low rate samples during this mode. */ ++ tp->app_limited = ++ (tp->delivered + tcp_packets_in_flight(tp)) ? : 1; ++ /* Maintain min packets in flight for max(200 ms, 1 round). */ ++ if (!bbr->probe_rtt_done_stamp && ++ tcp_packets_in_flight(tp) <= bbr_probe_rtt_cwnd(sk)) { ++ bbr->probe_rtt_done_stamp = tcp_jiffies32 + ++ msecs_to_jiffies(bbr->params.probe_rtt_mode_ms); ++ bbr->probe_rtt_round_done = 0; ++ bbr->next_rtt_delivered = tp->delivered; ++ } else if (bbr->probe_rtt_done_stamp) { ++ if (bbr->round_start) ++ bbr->probe_rtt_round_done = 1; ++ if (bbr->probe_rtt_round_done) ++ bbr_check_probe_rtt_done(sk); ++ } ++ } ++ /* Restart after idle ends only once we process a new S/ACK for data */ ++ if (rs->delivered > 0) ++ bbr->idle_restart = 0; ++} ++ ++static void bbr_update_gains(struct sock *sk) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ switch (bbr->mode) { ++ case BBR_STARTUP: ++ bbr->pacing_gain = bbr->params.high_gain; ++ bbr->cwnd_gain = bbr->params.startup_cwnd_gain; ++ break; ++ case BBR_DRAIN: ++ bbr->pacing_gain = bbr->params.drain_gain; /* slow, to drain */ ++ bbr->cwnd_gain = bbr->params.startup_cwnd_gain; /* keep cwnd */ ++ break; ++ case BBR_PROBE_BW: ++ bbr->pacing_gain = bbr->params.pacing_gain[bbr->cycle_idx]; ++ bbr->cwnd_gain = bbr->params.cwnd_gain; ++ break; ++ case BBR_PROBE_RTT: ++ bbr->pacing_gain = BBR_UNIT; ++ bbr->cwnd_gain = BBR_UNIT; ++ break; ++ default: ++ WARN_ONCE(1, "BBR bad mode: %u\n", bbr->mode); ++ break; ++ } ++} ++ ++static void bbr_init(struct sock *sk) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct bbr *bbr = inet_csk_ca(sk); ++ int i; ++ ++ WARN_ON_ONCE(tp->snd_cwnd >= bbr_cwnd_warn_val); ++ ++ bbr->initialized = 1; ++ bbr->params.high_gain = min(0x7FF, bbr_high_gain); ++ bbr->params.drain_gain = min(0x3FF, bbr_drain_gain); ++ bbr->params.startup_cwnd_gain = min(0x7FF, bbr_startup_cwnd_gain); ++ bbr->params.cwnd_gain = min(0x7FF, bbr_cwnd_gain); ++ bbr->params.cwnd_tso_budget = min(0x1U, bbr_cwnd_tso_budget); ++ bbr->params.cwnd_min_target = min(0xFU, bbr_cwnd_min_target); ++ bbr->params.min_rtt_win_sec = min(0x1FU, bbr_min_rtt_win_sec); ++ bbr->params.probe_rtt_mode_ms = min(0x1FFU, bbr_probe_rtt_mode_ms); ++ bbr->params.full_bw_cnt = min(0x7U, bbr_full_bw_cnt); ++ bbr->params.full_bw_thresh = min(0x3FFU, bbr_full_bw_thresh); ++ bbr->params.extra_acked_gain = min(0x7FF, bbr_extra_acked_gain); ++ bbr->params.extra_acked_win_rtts = min(0x1FU, bbr_extra_acked_win_rtts); ++ bbr->params.drain_to_target = bbr_drain_to_target ? 1 : 0; ++ bbr->params.precise_ece_ack = bbr_precise_ece_ack ? 1 : 0; ++ bbr->params.extra_acked_in_startup = bbr_extra_acked_in_startup ? 1 : 0; ++ bbr->params.probe_rtt_cwnd_gain = min(0xFFU, bbr_probe_rtt_cwnd_gain); ++ bbr->params.probe_rtt_win_ms = ++ min(0x3FFFU, ++ min_t(u32, bbr_probe_rtt_win_ms, ++ bbr->params.min_rtt_win_sec * MSEC_PER_SEC)); ++ for (i = 0; i < CYCLE_LEN; i++) ++ bbr->params.pacing_gain[i] = min(0x3FF, bbr_pacing_gain[i]); ++ bbr->params.usage_based_cwnd = bbr_usage_based_cwnd ? 1 : 0; ++ bbr->params.tso_rtt_shift = min(0xFU, bbr_tso_rtt_shift); ++ ++ bbr->debug.snd_isn = tp->snd_una; ++ bbr->debug.target_cwnd = 0; ++ bbr->debug.undo = 0; ++ ++ bbr->init_cwnd = min(0x7FU, tp->snd_cwnd); ++ bbr->prior_cwnd = tp->prior_cwnd; ++ tp->snd_ssthresh = TCP_INFINITE_SSTHRESH; ++ bbr->next_rtt_delivered = 0; ++ bbr->prev_ca_state = TCP_CA_Open; ++ bbr->packet_conservation = 0; ++ ++ bbr->probe_rtt_done_stamp = 0; ++ bbr->probe_rtt_round_done = 0; ++ bbr->probe_rtt_min_us = tcp_min_rtt(tp); ++ bbr->probe_rtt_min_stamp = tcp_jiffies32; ++ bbr->min_rtt_us = tcp_min_rtt(tp); ++ bbr->min_rtt_stamp = tcp_jiffies32; ++ ++ bbr->has_seen_rtt = 0; ++ bbr_init_pacing_rate_from_rtt(sk); ++ ++ bbr->round_start = 0; ++ bbr->idle_restart = 0; ++ bbr->full_bw_reached = 0; ++ bbr->full_bw = 0; ++ bbr->full_bw_cnt = 0; ++ bbr->cycle_mstamp = 0; ++ bbr->cycle_idx = 0; ++ bbr->mode = BBR_STARTUP; ++ bbr->debug.rs_bw = 0; ++ ++ bbr->ack_epoch_mstamp = tp->tcp_mstamp; ++ bbr->ack_epoch_acked = 0; ++ bbr->extra_acked_win_rtts = 0; ++ bbr->extra_acked_win_idx = 0; ++ bbr->extra_acked[0] = 0; ++ bbr->extra_acked[1] = 0; ++ ++ bbr->ce_state = 0; ++ bbr->prior_rcv_nxt = tp->rcv_nxt; ++ bbr->try_fast_path = 0; ++ ++ cmpxchg(&sk->sk_pacing_status, SK_PACING_NONE, SK_PACING_NEEDED); ++} ++ ++static u32 bbr_sndbuf_expand(struct sock *sk) ++{ ++ /* Provision 3 * cwnd since BBR may slow-start even during recovery. */ ++ return 3; ++} ++ ++/* __________________________________________________________________________ ++ * ++ * Functions new to BBR v2 ("bbr") congestion control are below here. ++ * __________________________________________________________________________ ++ */ ++ ++/* Incorporate a new bw sample into the current window of our max filter. */ ++static void bbr2_take_bw_hi_sample(struct sock *sk, u32 bw) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ bbr->bw_hi[1] = max(bw, bbr->bw_hi[1]); ++} ++ ++/* Keep max of last 1-2 cycles. Each PROBE_BW cycle, flip filter window. */ ++static void bbr2_advance_bw_hi_filter(struct sock *sk) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ if (!bbr->bw_hi[1]) ++ return; /* no samples in this window; remember old window */ ++ bbr->bw_hi[0] = bbr->bw_hi[1]; ++ bbr->bw_hi[1] = 0; ++} ++ ++/* How much do we want in flight? Our BDP, unless congestion cut cwnd. */ ++static u32 bbr2_target_inflight(struct sock *sk) ++{ ++ u32 bdp = bbr_inflight(sk, bbr_bw(sk), BBR_UNIT); ++ ++ return min(bdp, tcp_sk(sk)->snd_cwnd); ++} ++ ++static bool bbr2_is_probing_bandwidth(struct sock *sk) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ return (bbr->mode == BBR_STARTUP) || ++ (bbr->mode == BBR_PROBE_BW && ++ (bbr->cycle_idx == BBR_BW_PROBE_REFILL || ++ bbr->cycle_idx == BBR_BW_PROBE_UP)); ++} ++ ++/* Has the given amount of time elapsed since we marked the phase start? */ ++static bool bbr2_has_elapsed_in_phase(const struct sock *sk, u32 interval_us) ++{ ++ const struct tcp_sock *tp = tcp_sk(sk); ++ const struct bbr *bbr = inet_csk_ca(sk); ++ ++ return tcp_stamp_us_delta(tp->tcp_mstamp, ++ bbr->cycle_mstamp + interval_us) > 0; ++} ++ ++static void bbr2_handle_queue_too_high_in_startup(struct sock *sk) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ bbr->full_bw_reached = 1; ++ bbr->inflight_hi = bbr_inflight(sk, bbr_max_bw(sk), BBR_UNIT); ++} ++ ++/* Exit STARTUP upon N consecutive rounds with ECN mark rate > ecn_thresh. */ ++static void bbr2_check_ecn_too_high_in_startup(struct sock *sk, u32 ce_ratio) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ if (bbr_full_bw_reached(sk) || !bbr->ecn_eligible || ++ !bbr->params.full_ecn_cnt || !bbr->params.ecn_thresh) ++ return; ++ ++ if (ce_ratio >= bbr->params.ecn_thresh) ++ bbr->startup_ecn_rounds++; ++ else ++ bbr->startup_ecn_rounds = 0; ++ ++ if (bbr->startup_ecn_rounds >= bbr->params.full_ecn_cnt) { ++ bbr->debug.event = 'E'; /* ECN caused STARTUP exit */ ++ bbr2_handle_queue_too_high_in_startup(sk); ++ return; ++ } ++} ++ ++static void bbr2_update_ecn_alpha(struct sock *sk) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct bbr *bbr = inet_csk_ca(sk); ++ s32 delivered, delivered_ce; ++ u64 alpha, ce_ratio; ++ u32 gain; ++ ++ if (bbr->params.ecn_factor == 0) ++ return; ++ ++ delivered = tp->delivered - bbr->alpha_last_delivered; ++ delivered_ce = tp->delivered_ce - bbr->alpha_last_delivered_ce; ++ ++ if (delivered == 0 || /* avoid divide by zero */ ++ WARN_ON_ONCE(delivered < 0 || delivered_ce < 0)) /* backwards? */ ++ return; ++ ++ /* See if we should use ECN sender logic for this connection. */ ++ if (!bbr->ecn_eligible && bbr_ecn_enable && ++ (bbr->min_rtt_us <= bbr->params.ecn_max_rtt_us || ++ !bbr->params.ecn_max_rtt_us)) ++ bbr->ecn_eligible = 1; ++ ++ ce_ratio = (u64)delivered_ce << BBR_SCALE; ++ do_div(ce_ratio, delivered); ++ gain = bbr->params.ecn_alpha_gain; ++ alpha = ((BBR_UNIT - gain) * bbr->ecn_alpha) >> BBR_SCALE; ++ alpha += (gain * ce_ratio) >> BBR_SCALE; ++ bbr->ecn_alpha = min_t(u32, alpha, BBR_UNIT); ++ ++ bbr->alpha_last_delivered = tp->delivered; ++ bbr->alpha_last_delivered_ce = tp->delivered_ce; ++ ++ bbr2_check_ecn_too_high_in_startup(sk, ce_ratio); ++} ++ ++/* Each round trip of BBR_BW_PROBE_UP, double volume of probing data. */ ++static void bbr2_raise_inflight_hi_slope(struct sock *sk) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct bbr *bbr = inet_csk_ca(sk); ++ u32 growth_this_round, cnt; ++ ++ /* Calculate "slope": packets S/Acked per inflight_hi increment. */ ++ growth_this_round = 1 << bbr->bw_probe_up_rounds; ++ bbr->bw_probe_up_rounds = min(bbr->bw_probe_up_rounds + 1, 30); ++ cnt = tp->snd_cwnd / growth_this_round; ++ cnt = max(cnt, 1U); ++ bbr->bw_probe_up_cnt = cnt; ++ bbr->debug.event = 'G'; /* Grow inflight_hi slope */ ++} ++ ++/* In BBR_BW_PROBE_UP, not seeing high loss/ECN/queue, so raise inflight_hi. */ ++static void bbr2_probe_inflight_hi_upward(struct sock *sk, ++ const struct rate_sample *rs) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct bbr *bbr = inet_csk_ca(sk); ++ u32 delta; ++ ++ if (!tp->is_cwnd_limited || tp->snd_cwnd < bbr->inflight_hi) { ++ bbr->bw_probe_up_acks = 0; /* don't accmulate unused credits */ ++ return; /* not fully using inflight_hi, so don't grow it */ ++ } ++ ++ /* For each bw_probe_up_cnt packets ACKed, increase inflight_hi by 1. */ ++ bbr->bw_probe_up_acks += rs->acked_sacked; ++ if (bbr->bw_probe_up_acks >= bbr->bw_probe_up_cnt) { ++ delta = bbr->bw_probe_up_acks / bbr->bw_probe_up_cnt; ++ bbr->bw_probe_up_acks -= delta * bbr->bw_probe_up_cnt; ++ bbr->inflight_hi += delta; ++ bbr->debug.event = 'I'; /* Increment inflight_hi */ ++ } ++ ++ if (bbr->round_start) ++ bbr2_raise_inflight_hi_slope(sk); ++} ++ ++/* Does loss/ECN rate for this sample say inflight is "too high"? ++ * This is used by both the bbr_check_loss_too_high_in_startup() function, ++ * which can be used in either v1 or v2, and the PROBE_UP phase of v2, which ++ * uses it to notice when loss/ECN rates suggest inflight is too high. ++ */ ++static bool bbr2_is_inflight_too_high(const struct sock *sk, ++ const struct rate_sample *rs) ++{ ++ const struct bbr *bbr = inet_csk_ca(sk); ++ u32 loss_thresh, ecn_thresh; ++ ++ if (rs->lost > 0 && rs->tx_in_flight) { ++ loss_thresh = (u64)rs->tx_in_flight * bbr->params.loss_thresh >> ++ BBR_SCALE; ++ if (rs->lost > loss_thresh) ++ return true; ++ } ++ ++ if (rs->delivered_ce > 0 && rs->delivered > 0 && ++ bbr->ecn_eligible && bbr->params.ecn_thresh) { ++ ecn_thresh = (u64)rs->delivered * bbr->params.ecn_thresh >> ++ BBR_SCALE; ++ if (rs->delivered_ce >= ecn_thresh) ++ return true; ++ } ++ ++ return false; ++} ++ ++/* Calculate the tx_in_flight level that corresponded to excessive loss. ++ * We find "lost_prefix" segs of the skb where loss rate went too high, ++ * by solving for "lost_prefix" in the following equation: ++ * lost / inflight >= loss_thresh ++ * (lost_prev + lost_prefix) / (inflight_prev + lost_prefix) >= loss_thresh ++ * Then we take that equation, convert it to fixed point, and ++ * round up to the nearest packet. ++ */ ++static u32 bbr2_inflight_hi_from_lost_skb(const struct sock *sk, ++ const struct rate_sample *rs, ++ const struct sk_buff *skb) ++{ ++ const struct bbr *bbr = inet_csk_ca(sk); ++ u32 loss_thresh = bbr->params.loss_thresh; ++ u32 pcount, divisor, inflight_hi; ++ s32 inflight_prev, lost_prev; ++ u64 loss_budget, lost_prefix; ++ ++ pcount = tcp_skb_pcount(skb); ++ ++ /* How much data was in flight before this skb? */ ++ inflight_prev = rs->tx_in_flight - pcount; ++ if (WARN_ONCE(inflight_prev < 0, ++ "tx_in_flight: %u pcount: %u reneg: %u", ++ rs->tx_in_flight, pcount, tcp_sk(sk)->is_sack_reneg)) ++ return ~0U; ++ ++ /* How much inflight data was marked lost before this skb? */ ++ lost_prev = rs->lost - pcount; ++ if (WARN_ON_ONCE(lost_prev < 0)) ++ return ~0U; ++ ++ /* At what prefix of this lost skb did losss rate exceed loss_thresh? */ ++ loss_budget = (u64)inflight_prev * loss_thresh + BBR_UNIT - 1; ++ loss_budget >>= BBR_SCALE; ++ if (lost_prev >= loss_budget) { ++ lost_prefix = 0; /* previous losses crossed loss_thresh */ ++ } else { ++ lost_prefix = loss_budget - lost_prev; ++ lost_prefix <<= BBR_SCALE; ++ divisor = BBR_UNIT - loss_thresh; ++ if (WARN_ON_ONCE(!divisor)) /* loss_thresh is 8 bits */ ++ return ~0U; ++ do_div(lost_prefix, divisor); ++ } ++ ++ inflight_hi = inflight_prev + lost_prefix; ++ return inflight_hi; ++} ++ ++/* If loss/ECN rates during probing indicated we may have overfilled a ++ * buffer, return an operating point that tries to leave unutilized headroom in ++ * the path for other flows, for fairness convergence and lower RTTs and loss. ++ */ ++static u32 bbr2_inflight_with_headroom(const struct sock *sk) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ u32 headroom, headroom_fraction; ++ ++ if (bbr->inflight_hi == ~0U) ++ return ~0U; ++ ++ headroom_fraction = bbr->params.inflight_headroom; ++ headroom = ((u64)bbr->inflight_hi * headroom_fraction) >> BBR_SCALE; ++ headroom = max(headroom, 1U); ++ return max_t(s32, bbr->inflight_hi - headroom, ++ bbr->params.cwnd_min_target); ++} ++ ++/* Bound cwnd to a sensible level, based on our current probing state ++ * machine phase and model of a good inflight level (inflight_lo, inflight_hi). ++ */ ++static void bbr2_bound_cwnd_for_inflight_model(struct sock *sk) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct bbr *bbr = inet_csk_ca(sk); ++ u32 cap; ++ ++ /* tcp_rcv_synsent_state_process() currently calls tcp_ack() ++ * and thus cong_control() without first initializing us(!). ++ */ ++ if (!bbr->initialized) ++ return; ++ ++ cap = ~0U; ++ if (bbr->mode == BBR_PROBE_BW && ++ bbr->cycle_idx != BBR_BW_PROBE_CRUISE) { ++ /* Probe to see if more packets fit in the path. */ ++ cap = bbr->inflight_hi; ++ } else { ++ if (bbr->mode == BBR_PROBE_RTT || ++ (bbr->mode == BBR_PROBE_BW && ++ bbr->cycle_idx == BBR_BW_PROBE_CRUISE)) ++ cap = bbr2_inflight_with_headroom(sk); ++ } ++ /* Adapt to any loss/ECN since our last bw probe. */ ++ cap = min(cap, bbr->inflight_lo); ++ ++ cap = max_t(u32, cap, bbr->params.cwnd_min_target); ++ tp->snd_cwnd = min(cap, tp->snd_cwnd); ++} ++ ++/* Estimate a short-term lower bound on the capacity available now, based ++ * on measurements of the current delivery process and recent history. When we ++ * are seeing loss/ECN at times when we are not probing bw, then conservatively ++ * move toward flow balance by multiplicatively cutting our short-term ++ * estimated safe rate and volume of data (bw_lo and inflight_lo). We use a ++ * multiplicative decrease in order to converge to a lower capacity in time ++ * logarithmic in the magnitude of the decrease. ++ * ++ * However, we do not cut our short-term estimates lower than the current rate ++ * and volume of delivered data from this round trip, since from the current ++ * delivery process we can estimate the measured capacity available now. ++ * ++ * Anything faster than that approach would knowingly risk high loss, which can ++ * cause low bw for Reno/CUBIC and high loss recovery latency for ++ * request/response flows using any congestion control. ++ */ ++static void bbr2_adapt_lower_bounds(struct sock *sk) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct bbr *bbr = inet_csk_ca(sk); ++ u32 ecn_cut, ecn_inflight_lo, beta; ++ ++ /* We only use lower-bound estimates when not probing bw. ++ * When probing we need to push inflight higher to probe bw. ++ */ ++ if (bbr2_is_probing_bandwidth(sk)) ++ return; ++ ++ /* ECN response. */ ++ if (bbr->ecn_in_round && bbr->ecn_eligible && bbr->params.ecn_factor) { ++ /* Reduce inflight to (1 - alpha*ecn_factor). */ ++ ecn_cut = (BBR_UNIT - ++ ((bbr->ecn_alpha * bbr->params.ecn_factor) >> ++ BBR_SCALE)); ++ if (bbr->inflight_lo == ~0U) ++ bbr->inflight_lo = tp->snd_cwnd; ++ ecn_inflight_lo = (u64)bbr->inflight_lo * ecn_cut >> BBR_SCALE; ++ } else { ++ ecn_inflight_lo = ~0U; ++ } ++ ++ /* Loss response. */ ++ if (bbr->loss_in_round) { ++ /* Reduce bw and inflight to (1 - beta). */ ++ if (bbr->bw_lo == ~0U) ++ bbr->bw_lo = bbr_max_bw(sk); ++ if (bbr->inflight_lo == ~0U) ++ bbr->inflight_lo = tp->snd_cwnd; ++ beta = bbr->params.beta; ++ bbr->bw_lo = ++ max_t(u32, bbr->bw_latest, ++ (u64)bbr->bw_lo * ++ (BBR_UNIT - beta) >> BBR_SCALE); ++ bbr->inflight_lo = ++ max_t(u32, bbr->inflight_latest, ++ (u64)bbr->inflight_lo * ++ (BBR_UNIT - beta) >> BBR_SCALE); ++ } ++ ++ /* Adjust to the lower of the levels implied by loss or ECN. */ ++ bbr->inflight_lo = min(bbr->inflight_lo, ecn_inflight_lo); ++} ++ ++/* Reset any short-term lower-bound adaptation to congestion, so that we can ++ * push our inflight up. ++ */ ++static void bbr2_reset_lower_bounds(struct sock *sk) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ bbr->bw_lo = ~0U; ++ bbr->inflight_lo = ~0U; ++} ++ ++/* After bw probing (STARTUP/PROBE_UP), reset signals before entering a state ++ * machine phase where we adapt our lower bound based on congestion signals. ++ */ ++static void bbr2_reset_congestion_signals(struct sock *sk) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ bbr->loss_in_round = 0; ++ bbr->ecn_in_round = 0; ++ bbr->loss_in_cycle = 0; ++ bbr->ecn_in_cycle = 0; ++ bbr->bw_latest = 0; ++ bbr->inflight_latest = 0; ++} ++ ++/* Update (most of) our congestion signals: track the recent rate and volume of ++ * delivered data, presence of loss, and EWMA degree of ECN marking. ++ */ ++static void bbr2_update_congestion_signals( ++ struct sock *sk, const struct rate_sample *rs, struct bbr_context *ctx) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct bbr *bbr = inet_csk_ca(sk); ++ u64 bw; ++ ++ bbr->loss_round_start = 0; ++ if (rs->interval_us <= 0 || !rs->acked_sacked) ++ return; /* Not a valid observation */ ++ bw = ctx->sample_bw; ++ ++ if (!rs->is_app_limited || bw >= bbr_max_bw(sk)) ++ bbr2_take_bw_hi_sample(sk, bw); ++ ++ bbr->loss_in_round |= (rs->losses > 0); ++ ++ /* Update rate and volume of delivered data from latest round trip: */ ++ bbr->bw_latest = max_t(u32, bbr->bw_latest, ctx->sample_bw); ++ bbr->inflight_latest = max_t(u32, bbr->inflight_latest, rs->delivered); ++ ++ if (before(rs->prior_delivered, bbr->loss_round_delivered)) ++ return; /* skip the per-round-trip updates */ ++ /* Now do per-round-trip updates. */ ++ bbr->loss_round_delivered = tp->delivered; /* mark round trip */ ++ bbr->loss_round_start = 1; ++ bbr2_adapt_lower_bounds(sk); ++ ++ /* Update windowed "latest" (single-round-trip) filters. */ ++ bbr->loss_in_round = 0; ++ bbr->ecn_in_round = 0; ++ bbr->bw_latest = ctx->sample_bw; ++ bbr->inflight_latest = rs->delivered; ++} ++ ++/* Bandwidth probing can cause loss. To help coexistence with loss-based ++ * congestion control we spread out our probing in a Reno-conscious way. Due to ++ * the shape of the Reno sawtooth, the time required between loss epochs for an ++ * idealized Reno flow is a number of round trips that is the BDP of that ++ * flow. We count packet-timed round trips directly, since measured RTT can ++ * vary widely, and Reno is driven by packet-timed round trips. ++ */ ++static bool bbr2_is_reno_coexistence_probe_time(struct sock *sk) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ u32 inflight, rounds, reno_gain, reno_rounds; ++ ++ /* Random loss can shave some small percentage off of our inflight ++ * in each round. To survive this, flows need robust periodic probes. ++ */ ++ rounds = bbr->params.bw_probe_max_rounds; ++ ++ reno_gain = bbr->params.bw_probe_reno_gain; ++ if (reno_gain) { ++ inflight = bbr2_target_inflight(sk); ++ reno_rounds = ((u64)inflight * reno_gain) >> BBR_SCALE; ++ rounds = min(rounds, reno_rounds); ++ } ++ return bbr->rounds_since_probe >= rounds; ++} ++ ++/* How long do we want to wait before probing for bandwidth (and risking ++ * loss)? We randomize the wait, for better mixing and fairness convergence. ++ * ++ * We bound the Reno-coexistence inter-bw-probe time to be 62-63 round trips. ++ * This is calculated to allow fairness with a 25Mbps, 30ms Reno flow, ++ * (eg 4K video to a broadband user): ++ * BDP = 25Mbps * .030sec /(1514bytes) = 61.9 packets ++ * ++ * We bound the BBR-native inter-bw-probe wall clock time to be: ++ * (a) higher than 2 sec: to try to avoid causing loss for a long enough time ++ * to allow Reno at 30ms to get 4K video bw, the inter-bw-probe time must ++ * be at least: 25Mbps * .030sec / (1514bytes) * 0.030sec = 1.9secs ++ * (b) lower than 3 sec: to ensure flows can start probing in a reasonable ++ * amount of time to discover unutilized bw on human-scale interactive ++ * time-scales (e.g. perhaps traffic from a web page download that we ++ * were competing with is now complete). ++ */ ++static void bbr2_pick_probe_wait(struct sock *sk) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ /* Decide the random round-trip bound for wait until probe: */ ++ bbr->rounds_since_probe = ++ prandom_u32_max(bbr->params.bw_probe_rand_rounds); ++ /* Decide the random wall clock bound for wait until probe: */ ++ bbr->probe_wait_us = bbr->params.bw_probe_base_us + ++ prandom_u32_max(bbr->params.bw_probe_rand_us); ++} ++ ++static void bbr2_set_cycle_idx(struct sock *sk, int cycle_idx) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ bbr->cycle_idx = cycle_idx; ++ /* New phase, so need to update cwnd and pacing rate. */ ++ bbr->try_fast_path = 0; ++} ++ ++/* Send at estimated bw to fill the pipe, but not queue. We need this phase ++ * before PROBE_UP, because as soon as we send faster than the available bw ++ * we will start building a queue, and if the buffer is shallow we can cause ++ * loss. If we do not fill the pipe before we cause this loss, our bw_hi and ++ * inflight_hi estimates will underestimate. ++ */ ++static void bbr2_start_bw_probe_refill(struct sock *sk, u32 bw_probe_up_rounds) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ bbr2_reset_lower_bounds(sk); ++ if (bbr->inflight_hi != ~0U) ++ bbr->inflight_hi += bbr->params.refill_add_inc; ++ bbr->bw_probe_up_rounds = bw_probe_up_rounds; ++ bbr->bw_probe_up_acks = 0; ++ bbr->stopped_risky_probe = 0; ++ bbr->ack_phase = BBR_ACKS_REFILLING; ++ bbr->next_rtt_delivered = tp->delivered; ++ bbr2_set_cycle_idx(sk, BBR_BW_PROBE_REFILL); ++} ++ ++/* Now probe max deliverable data rate and volume. */ ++static void bbr2_start_bw_probe_up(struct sock *sk) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ bbr->ack_phase = BBR_ACKS_PROBE_STARTING; ++ bbr->next_rtt_delivered = tp->delivered; ++ bbr->cycle_mstamp = tp->tcp_mstamp; ++ bbr2_set_cycle_idx(sk, BBR_BW_PROBE_UP); ++ bbr2_raise_inflight_hi_slope(sk); ++} ++ ++/* Start a new PROBE_BW probing cycle of some wall clock length. Pick a wall ++ * clock time at which to probe beyond an inflight that we think to be ++ * safe. This will knowingly risk packet loss, so we want to do this rarely, to ++ * keep packet loss rates low. Also start a round-trip counter, to probe faster ++ * if we estimate a Reno flow at our BDP would probe faster. ++ */ ++static void bbr2_start_bw_probe_down(struct sock *sk) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ bbr2_reset_congestion_signals(sk); ++ bbr->bw_probe_up_cnt = ~0U; /* not growing inflight_hi any more */ ++ bbr2_pick_probe_wait(sk); ++ bbr->cycle_mstamp = tp->tcp_mstamp; /* start wall clock */ ++ bbr->ack_phase = BBR_ACKS_PROBE_STOPPING; ++ bbr->next_rtt_delivered = tp->delivered; ++ bbr2_set_cycle_idx(sk, BBR_BW_PROBE_DOWN); ++} ++ ++/* Cruise: maintain what we estimate to be a neutral, conservative ++ * operating point, without attempting to probe up for bandwidth or down for ++ * RTT, and only reducing inflight in response to loss/ECN signals. ++ */ ++static void bbr2_start_bw_probe_cruise(struct sock *sk) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ if (bbr->inflight_lo != ~0U) ++ bbr->inflight_lo = min(bbr->inflight_lo, bbr->inflight_hi); ++ ++ bbr2_set_cycle_idx(sk, BBR_BW_PROBE_CRUISE); ++} ++ ++/* Loss and/or ECN rate is too high while probing. ++ * Adapt (once per bw probe) by cutting inflight_hi and then restarting cycle. ++ */ ++static void bbr2_handle_inflight_too_high(struct sock *sk, ++ const struct rate_sample *rs) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ const u32 beta = bbr->params.beta; ++ ++ bbr->prev_probe_too_high = 1; ++ bbr->bw_probe_samples = 0; /* only react once per probe */ ++ bbr->debug.event = 'L'; /* Loss/ECN too high */ ++ /* If we are app-limited then we are not robustly ++ * probing the max volume of inflight data we think ++ * might be safe (analogous to how app-limited bw ++ * samples are not known to be robustly probing bw). ++ */ ++ if (!rs->is_app_limited) ++ bbr->inflight_hi = max_t(u32, rs->tx_in_flight, ++ (u64)bbr2_target_inflight(sk) * ++ (BBR_UNIT - beta) >> BBR_SCALE); ++ if (bbr->mode == BBR_PROBE_BW && bbr->cycle_idx == BBR_BW_PROBE_UP) ++ bbr2_start_bw_probe_down(sk); ++} ++ ++/* If we're seeing bw and loss samples reflecting our bw probing, adapt ++ * using the signals we see. If loss or ECN mark rate gets too high, then adapt ++ * inflight_hi downward. If we're able to push inflight higher without such ++ * signals, push higher: adapt inflight_hi upward. ++ */ ++static bool bbr2_adapt_upper_bounds(struct sock *sk, ++ const struct rate_sample *rs) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ /* Track when we'll see bw/loss samples resulting from our bw probes. */ ++ if (bbr->ack_phase == BBR_ACKS_PROBE_STARTING && bbr->round_start) ++ bbr->ack_phase = BBR_ACKS_PROBE_FEEDBACK; ++ if (bbr->ack_phase == BBR_ACKS_PROBE_STOPPING && bbr->round_start) { ++ /* End of samples from bw probing phase. */ ++ bbr->bw_probe_samples = 0; ++ bbr->ack_phase = BBR_ACKS_INIT; ++ /* At this point in the cycle, our current bw sample is also ++ * our best recent chance at finding the highest available bw ++ * for this flow. So now is the best time to forget the bw ++ * samples from the previous cycle, by advancing the window. ++ */ ++ if (bbr->mode == BBR_PROBE_BW && !rs->is_app_limited) ++ bbr2_advance_bw_hi_filter(sk); ++ /* If we had an inflight_hi, then probed and pushed inflight all ++ * the way up to hit that inflight_hi without seeing any ++ * high loss/ECN in all the resulting ACKs from that probing, ++ * then probe up again, this time letting inflight persist at ++ * inflight_hi for a round trip, then accelerating beyond. ++ */ ++ if (bbr->mode == BBR_PROBE_BW && ++ bbr->stopped_risky_probe && !bbr->prev_probe_too_high) { ++ bbr->debug.event = 'R'; /* reprobe */ ++ bbr2_start_bw_probe_refill(sk, 0); ++ return true; /* yes, decided state transition */ ++ } ++ } ++ ++ if (bbr2_is_inflight_too_high(sk, rs)) { ++ if (bbr->bw_probe_samples) /* sample is from bw probing? */ ++ bbr2_handle_inflight_too_high(sk, rs); ++ } else { ++ /* Loss/ECN rate is declared safe. Adjust upper bound upward. */ ++ if (bbr->inflight_hi == ~0U) /* no excess queue signals yet? */ ++ return false; ++ ++ /* To be resilient to random loss, we must raise inflight_hi ++ * if we observe in any phase that a higher level is safe. ++ */ ++ if (rs->tx_in_flight > bbr->inflight_hi) { ++ bbr->inflight_hi = rs->tx_in_flight; ++ bbr->debug.event = 'U'; /* raise up inflight_hi */ ++ } ++ ++ if (bbr->mode == BBR_PROBE_BW && ++ bbr->cycle_idx == BBR_BW_PROBE_UP) ++ bbr2_probe_inflight_hi_upward(sk, rs); ++ } ++ ++ return false; ++} ++ ++/* Check if it's time to probe for bandwidth now, and if so, kick it off. */ ++static bool bbr2_check_time_to_probe_bw(struct sock *sk) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ u32 n; ++ ++ /* If we seem to be at an operating point where we are not seeing loss ++ * but we are seeing ECN marks, then when the ECN marks cease we reprobe ++ * quickly (in case a burst of cross-traffic has ceased and freed up bw, ++ * or in case we are sharing with multiplicatively probing traffic). ++ */ ++ if (bbr->params.ecn_reprobe_gain && bbr->ecn_eligible && ++ bbr->ecn_in_cycle && !bbr->loss_in_cycle && ++ inet_csk(sk)->icsk_ca_state == TCP_CA_Open) { ++ bbr->debug.event = 'A'; /* *A*ll clear to probe *A*gain */ ++ /* Calculate n so that when bbr2_raise_inflight_hi_slope() ++ * computes growth_this_round as 2^n it will be roughly the ++ * desired volume of data (inflight_hi*ecn_reprobe_gain). ++ */ ++ n = ilog2((((u64)bbr->inflight_hi * ++ bbr->params.ecn_reprobe_gain) >> BBR_SCALE)); ++ bbr2_start_bw_probe_refill(sk, n); ++ return true; ++ } ++ ++ if (bbr2_has_elapsed_in_phase(sk, bbr->probe_wait_us) || ++ bbr2_is_reno_coexistence_probe_time(sk)) { ++ bbr2_start_bw_probe_refill(sk, 0); ++ return true; ++ } ++ return false; ++} ++ ++/* Is it time to transition from PROBE_DOWN to PROBE_CRUISE? */ ++static bool bbr2_check_time_to_cruise(struct sock *sk, u32 inflight, u32 bw) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ bool is_under_bdp, is_long_enough; ++ ++ /* Always need to pull inflight down to leave headroom in queue. */ ++ if (inflight > bbr2_inflight_with_headroom(sk)) ++ return false; ++ ++ is_under_bdp = inflight <= bbr_inflight(sk, bw, BBR_UNIT); ++ if (bbr->params.drain_to_target) ++ return is_under_bdp; ++ ++ is_long_enough = bbr2_has_elapsed_in_phase(sk, bbr->min_rtt_us); ++ return is_under_bdp || is_long_enough; ++} ++ ++/* PROBE_BW state machine: cruise, refill, probe for bw, or drain? */ ++static void bbr2_update_cycle_phase(struct sock *sk, ++ const struct rate_sample *rs) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ bool is_risky = false, is_queuing = false; ++ u32 inflight, bw; ++ ++ if (!bbr_full_bw_reached(sk)) ++ return; ++ ++ /* In DRAIN, PROBE_BW, or PROBE_RTT, adjust upper bounds. */ ++ if (bbr2_adapt_upper_bounds(sk, rs)) ++ return; /* already decided state transition */ ++ ++ if (bbr->mode != BBR_PROBE_BW) ++ return; ++ ++ inflight = bbr_packets_in_net_at_edt(sk, rs->prior_in_flight); ++ bw = bbr_max_bw(sk); ++ ++ switch (bbr->cycle_idx) { ++ /* First we spend most of our time cruising with a pacing_gain of 1.0, ++ * which paces at the estimated bw, to try to fully use the pipe ++ * without building queue. If we encounter loss/ECN marks, we adapt ++ * by slowing down. ++ */ ++ case BBR_BW_PROBE_CRUISE: ++ if (bbr2_check_time_to_probe_bw(sk)) ++ return; /* already decided state transition */ ++ break; ++ ++ /* After cruising, when it's time to probe, we first "refill": we send ++ * at the estimated bw to fill the pipe, before probing higher and ++ * knowingly risking overflowing the bottleneck buffer (causing loss). ++ */ ++ case BBR_BW_PROBE_REFILL: ++ if (bbr->round_start) { ++ /* After one full round trip of sending in REFILL, we ++ * start to see bw samples reflecting our REFILL, which ++ * may be putting too much data in flight. ++ */ ++ bbr->bw_probe_samples = 1; ++ bbr2_start_bw_probe_up(sk); ++ } ++ break; ++ ++ /* After we refill the pipe, we probe by using a pacing_gain > 1.0, to ++ * probe for bw. If we have not seen loss/ECN, we try to raise inflight ++ * to at least pacing_gain*BDP; note that this may take more than ++ * min_rtt if min_rtt is small (e.g. on a LAN). ++ * ++ * We terminate PROBE_UP bandwidth probing upon any of the following: ++ * ++ * (1) We've pushed inflight up to hit the inflight_hi target set in the ++ * most recent previous bw probe phase. Thus we want to start ++ * draining the queue immediately because it's very likely the most ++ * recently sent packets will fill the queue and cause drops. ++ * (checked here) ++ * (2) We have probed for at least 1*min_rtt_us, and the ++ * estimated queue is high enough (inflight > 1.25 * estimated_bdp). ++ * (checked here) ++ * (3) Loss filter says loss rate is "too high". ++ * (checked in bbr_is_inflight_too_high()) ++ * (4) ECN filter says ECN mark rate is "too high". ++ * (checked in bbr_is_inflight_too_high()) ++ */ ++ case BBR_BW_PROBE_UP: ++ if (bbr->prev_probe_too_high && ++ inflight >= bbr->inflight_hi) { ++ bbr->stopped_risky_probe = 1; ++ is_risky = true; ++ bbr->debug.event = 'D'; /* D for danger */ ++ } else if (bbr2_has_elapsed_in_phase(sk, bbr->min_rtt_us) && ++ inflight >= ++ bbr_inflight(sk, bw, ++ bbr->params.bw_probe_pif_gain)) { ++ is_queuing = true; ++ bbr->debug.event = 'Q'; /* building Queue */ ++ } ++ if (is_risky || is_queuing) { ++ bbr->prev_probe_too_high = 0; /* no loss/ECN (yet) */ ++ bbr2_start_bw_probe_down(sk); /* restart w/ down */ ++ } ++ break; ++ ++ /* After probing in PROBE_UP, we have usually accumulated some data in ++ * the bottleneck buffer (if bw probing didn't find more bw). We next ++ * enter PROBE_DOWN to try to drain any excess data from the queue. To ++ * do this, we use a pacing_gain < 1.0. We hold this pacing gain until ++ * our inflight is less then that target cruising point, which is the ++ * minimum of (a) the amount needed to leave headroom, and (b) the ++ * estimated BDP. Once inflight falls to match the target, we estimate ++ * the queue is drained; persisting would underutilize the pipe. ++ */ ++ case BBR_BW_PROBE_DOWN: ++ if (bbr2_check_time_to_probe_bw(sk)) ++ return; /* already decided state transition */ ++ if (bbr2_check_time_to_cruise(sk, inflight, bw)) ++ bbr2_start_bw_probe_cruise(sk); ++ break; ++ ++ default: ++ WARN_ONCE(1, "BBR invalid cycle index %u\n", bbr->cycle_idx); ++ } ++} ++ ++/* Exiting PROBE_RTT, so return to bandwidth probing in STARTUP or PROBE_BW. */ ++static void bbr2_exit_probe_rtt(struct sock *sk) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ bbr2_reset_lower_bounds(sk); ++ if (bbr_full_bw_reached(sk)) { ++ bbr->mode = BBR_PROBE_BW; ++ /* Raising inflight after PROBE_RTT may cause loss, so reset ++ * the PROBE_BW clock and schedule the next bandwidth probe for ++ * a friendly and randomized future point in time. ++ */ ++ bbr2_start_bw_probe_down(sk); ++ /* Since we are exiting PROBE_RTT, we know inflight is ++ * below our estimated BDP, so it is reasonable to cruise. ++ */ ++ bbr2_start_bw_probe_cruise(sk); ++ } else { ++ bbr->mode = BBR_STARTUP; ++ } ++} ++ ++/* Exit STARTUP based on loss rate > 1% and loss gaps in round >= N. Wait until ++ * the end of the round in recovery to get a good estimate of how many packets ++ * have been lost, and how many we need to drain with a low pacing rate. ++ */ ++static void bbr2_check_loss_too_high_in_startup(struct sock *sk, ++ const struct rate_sample *rs) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ if (bbr_full_bw_reached(sk)) ++ return; ++ ++ /* For STARTUP exit, check the loss rate at the end of each round trip ++ * of Recovery episodes in STARTUP. We check the loss rate at the end ++ * of the round trip to filter out noisy/low loss and have a better ++ * sense of inflight (extent of loss), so we can drain more accurately. ++ */ ++ if (rs->losses && bbr->loss_events_in_round < 0xf) ++ bbr->loss_events_in_round++; /* update saturating counter */ ++ if (bbr->params.full_loss_cnt && bbr->loss_round_start && ++ inet_csk(sk)->icsk_ca_state == TCP_CA_Recovery && ++ bbr->loss_events_in_round >= bbr->params.full_loss_cnt && ++ bbr2_is_inflight_too_high(sk, rs)) { ++ bbr->debug.event = 'P'; /* Packet loss caused STARTUP exit */ ++ bbr2_handle_queue_too_high_in_startup(sk); ++ return; ++ } ++ if (bbr->loss_round_start) ++ bbr->loss_events_in_round = 0; ++} ++ ++/* If we are done draining, advance into steady state operation in PROBE_BW. */ ++static void bbr2_check_drain(struct sock *sk, const struct rate_sample *rs, ++ struct bbr_context *ctx) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ if (bbr_check_drain(sk, rs, ctx)) { ++ bbr->mode = BBR_PROBE_BW; ++ bbr2_start_bw_probe_down(sk); ++ } ++} ++ ++static void bbr2_update_model(struct sock *sk, const struct rate_sample *rs, ++ struct bbr_context *ctx) ++{ ++ bbr2_update_congestion_signals(sk, rs, ctx); ++ bbr_update_ack_aggregation(sk, rs); ++ bbr2_check_loss_too_high_in_startup(sk, rs); ++ bbr_check_full_bw_reached(sk, rs); ++ bbr2_check_drain(sk, rs, ctx); ++ bbr2_update_cycle_phase(sk, rs); ++ bbr_update_min_rtt(sk, rs); ++} ++ ++/* Fast path for app-limited case. ++ * ++ * On each ack, we execute bbr state machine, which primarily consists of: ++ * 1) update model based on new rate sample, and ++ * 2) update control based on updated model or state change. ++ * ++ * There are certain workload/scenarios, e.g. app-limited case, where ++ * either we can skip updating model or we can skip update of both model ++ * as well as control. This provides signifcant softirq cpu savings for ++ * processing incoming acks. ++ * ++ * In case of app-limited, if there is no congestion (loss/ecn) and ++ * if observed bw sample is less than current estimated bw, then we can ++ * skip some of the computation in bbr state processing: ++ * ++ * - if there is no rtt/mode/phase change: In this case, since all the ++ * parameters of the network model are constant, we can skip model ++ * as well control update. ++ * ++ * - else we can skip rest of the model update. But we still need to ++ * update the control to account for the new rtt/mode/phase. ++ * ++ * Returns whether we can take fast path or not. ++ */ ++static bool bbr2_fast_path(struct sock *sk, bool *update_model, ++ const struct rate_sample *rs, struct bbr_context *ctx) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ u32 prev_min_rtt_us, prev_mode; ++ ++ if (bbr->params.fast_path && bbr->try_fast_path && ++ rs->is_app_limited && ctx->sample_bw < bbr_max_bw(sk) && ++ !bbr->loss_in_round && !bbr->ecn_in_round) { ++ prev_mode = bbr->mode; ++ prev_min_rtt_us = bbr->min_rtt_us; ++ bbr2_check_drain(sk, rs, ctx); ++ bbr2_update_cycle_phase(sk, rs); ++ bbr_update_min_rtt(sk, rs); ++ ++ if (bbr->mode == prev_mode && ++ bbr->min_rtt_us == prev_min_rtt_us && ++ bbr->try_fast_path) ++ return true; ++ ++ /* Skip model update, but control still needs to be updated */ ++ *update_model = false; ++ } ++ return false; ++} ++ ++void bbr2_main(struct sock *sk, const struct rate_sample *rs) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct bbr *bbr = inet_csk_ca(sk); ++ struct bbr_context ctx = { 0 }; ++ bool update_model = true; ++ u32 bw; ++ ++ bbr->debug.event = '.'; /* init to default NOP (no event yet) */ ++ ++ bbr_update_round_start(sk, rs, &ctx); ++ if (bbr->round_start) { ++ bbr->rounds_since_probe = ++ min_t(s32, bbr->rounds_since_probe + 1, 0xFF); ++ bbr2_update_ecn_alpha(sk); ++ } ++ ++ bbr->ecn_in_round |= rs->is_ece; ++ bbr_calculate_bw_sample(sk, rs, &ctx); ++ ++ if (bbr2_fast_path(sk, &update_model, rs, &ctx)) ++ goto out; ++ ++ if (update_model) ++ bbr2_update_model(sk, rs, &ctx); ++ ++ bbr_update_gains(sk); ++ bw = bbr_bw(sk); ++ bbr_set_pacing_rate(sk, bw, bbr->pacing_gain); ++ bbr_set_cwnd(sk, rs, rs->acked_sacked, bw, bbr->cwnd_gain, ++ tp->snd_cwnd, &ctx); ++ bbr2_bound_cwnd_for_inflight_model(sk); ++ ++out: ++ bbr->prev_ca_state = inet_csk(sk)->icsk_ca_state; ++ bbr->loss_in_cycle |= rs->lost > 0; ++ bbr->ecn_in_cycle |= rs->delivered_ce > 0; ++ ++ bbr_debug(sk, rs->acked_sacked, rs, &ctx); ++} ++ ++/* Module parameters that are settable by TCP_CONGESTION_PARAMS are declared ++ * down here, so that the algorithm functions that use the parameters must use ++ * the per-socket parameters; if they accidentally use the global version ++ * then there will be a compile error. ++ * TODO(ncardwell): move all per-socket parameters down to this section. ++ */ ++ ++/* On losses, scale down inflight and pacing rate by beta scaled by BBR_SCALE. ++ * No loss response when 0. Max allwed value is 255. ++ */ ++static u32 bbr_beta = BBR_UNIT * 30 / 100; ++ ++/* Gain factor for ECN mark ratio samples, scaled by BBR_SCALE. ++ * Max allowed value is 255. ++ */ ++static u32 bbr_ecn_alpha_gain = BBR_UNIT * 1 / 16; /* 1/16 = 6.25% */ ++ ++/* The initial value for the ecn_alpha state variable. Default and max ++ * BBR_UNIT (256), representing 1.0. This allows a flow to respond quickly ++ * to congestion if the bottleneck is congested when the flow starts up. ++ */ ++static u32 bbr_ecn_alpha_init = BBR_UNIT; /* 1.0, to respond quickly */ ++ ++/* On ECN, cut inflight_lo to (1 - ecn_factor * ecn_alpha) scaled by BBR_SCALE. ++ * No ECN based bounding when 0. Max allwed value is 255. ++ */ ++static u32 bbr_ecn_factor = BBR_UNIT * 1 / 3; /* 1/3 = 33% */ ++ ++/* Estimate bw probing has gone too far if CE ratio exceeds this threshold. ++ * Scaled by BBR_SCALE. Disabled when 0. Max allowed is 255. ++ */ ++static u32 bbr_ecn_thresh = BBR_UNIT * 1 / 2; /* 1/2 = 50% */ ++ ++/* Max RTT (in usec) at which to use sender-side ECN logic. ++ * Disabled when 0 (ECN allowed at any RTT). ++ * Max allowed for the parameter is 524287 (0x7ffff) us, ~524 ms. ++ */ ++static u32 bbr_ecn_max_rtt_us = 5000; ++ ++/* If non-zero, if in a cycle with no losses but some ECN marks, after ECN ++ * clears then use a multiplicative increase to quickly reprobe bw by ++ * starting inflight probing at the given multiple of inflight_hi. ++ * Default for this experimental knob is 0 (disabled). ++ * Planned value for experiments: BBR_UNIT * 1 / 2 = 128, representing 0.5. ++ */ ++static u32 bbr_ecn_reprobe_gain; ++ ++/* Estimate bw probing has gone too far if loss rate exceeds this level. */ ++static u32 bbr_loss_thresh = BBR_UNIT * 2 / 100; /* 2% loss */ ++ ++/* Exit STARTUP if number of loss marking events in a Recovery round is >= N, ++ * and loss rate is higher than bbr_loss_thresh. ++ * Disabled if 0. Max allowed value is 15 (0xF). ++ */ ++static u32 bbr_full_loss_cnt = 8; ++ ++/* Exit STARTUP if number of round trips with ECN mark rate above ecn_thresh ++ * meets this count. Max allowed value is 3. ++ */ ++static u32 bbr_full_ecn_cnt = 2; ++ ++/* Fraction of unutilized headroom to try to leave in path upon high loss. */ ++static u32 bbr_inflight_headroom = BBR_UNIT * 15 / 100; ++ ++/* Multiplier to get target inflight (as multiple of BDP) for PROBE_UP phase. ++ * Default is 1.25x, as in BBR v1. Max allowed is 511. ++ */ ++static u32 bbr_bw_probe_pif_gain = BBR_UNIT * 5 / 4; ++ ++/* Multiplier to get Reno-style probe epoch duration as: k * BDP round trips. ++ * If zero, disables this BBR v2 Reno-style BDP-scaled coexistence mechanism. ++ * Max allowed is 511. ++ */ ++static u32 bbr_bw_probe_reno_gain = BBR_UNIT; ++ ++/* Max number of packet-timed rounds to wait before probing for bandwidth. If ++ * we want to tolerate 1% random loss per round, and not have this cut our ++ * inflight too much, we must probe for bw periodically on roughly this scale. ++ * If low, limits Reno/CUBIC coexistence; if high, limits loss tolerance. ++ * We aim to be fair with Reno/CUBIC up to a BDP of at least: ++ * BDP = 25Mbps * .030sec /(1514bytes) = 61.9 packets ++ */ ++static u32 bbr_bw_probe_max_rounds = 63; ++ ++/* Max amount of randomness to inject in round counting for Reno-coexistence. ++ * Max value is 15. ++ */ ++static u32 bbr_bw_probe_rand_rounds = 2; ++ ++/* Use BBR-native probe time scale starting at this many usec. ++ * We aim to be fair with Reno/CUBIC up to an inter-loss time epoch of at least: ++ * BDP*RTT = 25Mbps * .030sec /(1514bytes) * 0.030sec = 1.9 secs ++ */ ++static u32 bbr_bw_probe_base_us = 2 * USEC_PER_SEC; /* 2 secs */ ++ ++/* Use BBR-native probes spread over this many usec: */ ++static u32 bbr_bw_probe_rand_us = 1 * USEC_PER_SEC; /* 1 secs */ ++ ++/* Undo the model changes made in loss recovery if recovery was spurious? */ ++static bool bbr_undo = true; ++ ++/* Use fast path if app-limited, no loss/ECN, and target cwnd was reached? */ ++static bool bbr_fast_path = true; /* default: enabled */ ++ ++/* Use fast ack mode ? */ ++static int bbr_fast_ack_mode = 1; /* default: rwnd check off */ ++ ++/* How much to additively increase inflight_hi when entering REFILL? */ ++static u32 bbr_refill_add_inc; /* default: disabled */ ++ ++module_param_named(beta, bbr_beta, uint, 0644); ++module_param_named(ecn_alpha_gain, bbr_ecn_alpha_gain, uint, 0644); ++module_param_named(ecn_alpha_init, bbr_ecn_alpha_init, uint, 0644); ++module_param_named(ecn_factor, bbr_ecn_factor, uint, 0644); ++module_param_named(ecn_thresh, bbr_ecn_thresh, uint, 0644); ++module_param_named(ecn_max_rtt_us, bbr_ecn_max_rtt_us, uint, 0644); ++module_param_named(ecn_reprobe_gain, bbr_ecn_reprobe_gain, uint, 0644); ++module_param_named(loss_thresh, bbr_loss_thresh, uint, 0664); ++module_param_named(full_loss_cnt, bbr_full_loss_cnt, uint, 0664); ++module_param_named(full_ecn_cnt, bbr_full_ecn_cnt, uint, 0664); ++module_param_named(inflight_headroom, bbr_inflight_headroom, uint, 0664); ++module_param_named(bw_probe_pif_gain, bbr_bw_probe_pif_gain, uint, 0664); ++module_param_named(bw_probe_reno_gain, bbr_bw_probe_reno_gain, uint, 0664); ++module_param_named(bw_probe_max_rounds, bbr_bw_probe_max_rounds, uint, 0664); ++module_param_named(bw_probe_rand_rounds, bbr_bw_probe_rand_rounds, uint, 0664); ++module_param_named(bw_probe_base_us, bbr_bw_probe_base_us, uint, 0664); ++module_param_named(bw_probe_rand_us, bbr_bw_probe_rand_us, uint, 0664); ++module_param_named(undo, bbr_undo, bool, 0664); ++module_param_named(fast_path, bbr_fast_path, bool, 0664); ++module_param_named(fast_ack_mode, bbr_fast_ack_mode, uint, 0664); ++module_param_named(refill_add_inc, bbr_refill_add_inc, uint, 0664); ++ ++static void bbr2_init(struct sock *sk) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ bbr_init(sk); /* run shared init code for v1 and v2 */ ++ ++ /* BBR v2 parameters: */ ++ bbr->params.beta = min_t(u32, 0xFFU, bbr_beta); ++ bbr->params.ecn_alpha_gain = min_t(u32, 0xFFU, bbr_ecn_alpha_gain); ++ bbr->params.ecn_alpha_init = min_t(u32, BBR_UNIT, bbr_ecn_alpha_init); ++ bbr->params.ecn_factor = min_t(u32, 0xFFU, bbr_ecn_factor); ++ bbr->params.ecn_thresh = min_t(u32, 0xFFU, bbr_ecn_thresh); ++ bbr->params.ecn_max_rtt_us = min_t(u32, 0x7ffffU, bbr_ecn_max_rtt_us); ++ bbr->params.ecn_reprobe_gain = min_t(u32, 0x1FF, bbr_ecn_reprobe_gain); ++ bbr->params.loss_thresh = min_t(u32, 0xFFU, bbr_loss_thresh); ++ bbr->params.full_loss_cnt = min_t(u32, 0xFU, bbr_full_loss_cnt); ++ bbr->params.full_ecn_cnt = min_t(u32, 0x3U, bbr_full_ecn_cnt); ++ bbr->params.inflight_headroom = ++ min_t(u32, 0xFFU, bbr_inflight_headroom); ++ bbr->params.bw_probe_pif_gain = ++ min_t(u32, 0x1FFU, bbr_bw_probe_pif_gain); ++ bbr->params.bw_probe_reno_gain = ++ min_t(u32, 0x1FFU, bbr_bw_probe_reno_gain); ++ bbr->params.bw_probe_max_rounds = ++ min_t(u32, 0xFFU, bbr_bw_probe_max_rounds); ++ bbr->params.bw_probe_rand_rounds = ++ min_t(u32, 0xFU, bbr_bw_probe_rand_rounds); ++ bbr->params.bw_probe_base_us = ++ min_t(u32, (1 << 26) - 1, bbr_bw_probe_base_us); ++ bbr->params.bw_probe_rand_us = ++ min_t(u32, (1 << 26) - 1, bbr_bw_probe_rand_us); ++ bbr->params.undo = bbr_undo; ++ bbr->params.fast_path = bbr_fast_path ? 1 : 0; ++ bbr->params.refill_add_inc = min_t(u32, 0x3U, bbr_refill_add_inc); ++ ++ /* BBR v2 state: */ ++ bbr->initialized = 1; ++ /* Start sampling ECN mark rate after first full flight is ACKed: */ ++ bbr->loss_round_delivered = tp->delivered + 1; ++ bbr->loss_round_start = 0; ++ bbr->undo_bw_lo = 0; ++ bbr->undo_inflight_lo = 0; ++ bbr->undo_inflight_hi = 0; ++ bbr->loss_events_in_round = 0; ++ bbr->startup_ecn_rounds = 0; ++ bbr2_reset_congestion_signals(sk); ++ bbr->bw_lo = ~0U; ++ bbr->bw_hi[0] = 0; ++ bbr->bw_hi[1] = 0; ++ bbr->inflight_lo = ~0U; ++ bbr->inflight_hi = ~0U; ++ bbr->bw_probe_up_cnt = ~0U; ++ bbr->bw_probe_up_acks = 0; ++ bbr->bw_probe_up_rounds = 0; ++ bbr->probe_wait_us = 0; ++ bbr->stopped_risky_probe = 0; ++ bbr->ack_phase = BBR_ACKS_INIT; ++ bbr->rounds_since_probe = 0; ++ bbr->bw_probe_samples = 0; ++ bbr->prev_probe_too_high = 0; ++ bbr->ecn_eligible = 0; ++ bbr->ecn_alpha = bbr->params.ecn_alpha_init; ++ bbr->alpha_last_delivered = 0; ++ bbr->alpha_last_delivered_ce = 0; ++ ++ tp->fast_ack_mode = min_t(u32, 0x2U, bbr_fast_ack_mode); ++} ++ ++/* Core TCP stack informs us that the given skb was just marked lost. */ ++static void bbr2_skb_marked_lost(struct sock *sk, const struct sk_buff *skb) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct bbr *bbr = inet_csk_ca(sk); ++ struct tcp_skb_cb *scb = TCP_SKB_CB(skb); ++ struct rate_sample rs; ++ ++ /* Capture "current" data over the full round trip of loss, ++ * to have a better chance to see the full capacity of the path. ++ */ ++ if (!bbr->loss_in_round) /* first loss in this round trip? */ ++ bbr->loss_round_delivered = tp->delivered; /* set round trip */ ++ bbr->loss_in_round = 1; ++ bbr->loss_in_cycle = 1; ++ ++ if (!bbr->bw_probe_samples) ++ return; /* not an skb sent while probing for bandwidth */ ++ if (unlikely(!scb->tx.delivered_mstamp)) ++ return; /* skb was SACKed, reneged, marked lost; ignore it */ ++ /* We are probing for bandwidth. Construct a rate sample that ++ * estimates what happened in the flight leading up to this lost skb, ++ * then see if the loss rate went too high, and if so at which packet. ++ */ ++ memset(&rs, 0, sizeof(rs)); ++ rs.tx_in_flight = scb->tx.in_flight; ++ rs.lost = tp->lost - scb->tx.lost; ++ rs.is_app_limited = scb->tx.is_app_limited; ++ if (bbr2_is_inflight_too_high(sk, &rs)) { ++ rs.tx_in_flight = bbr2_inflight_hi_from_lost_skb(sk, &rs, skb); ++ bbr2_handle_inflight_too_high(sk, &rs); ++ } ++} ++ ++/* Revert short-term model if current loss recovery event was spurious. */ ++static u32 bbr2_undo_cwnd(struct sock *sk) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ bbr->debug.undo = 1; ++ bbr->full_bw = 0; /* spurious slow-down; reset full pipe detection */ ++ bbr->full_bw_cnt = 0; ++ bbr->loss_in_round = 0; ++ ++ if (!bbr->params.undo) ++ return tp->snd_cwnd; ++ ++ /* Revert to cwnd and other state saved before loss episode. */ ++ bbr->bw_lo = max(bbr->bw_lo, bbr->undo_bw_lo); ++ bbr->inflight_lo = max(bbr->inflight_lo, bbr->undo_inflight_lo); ++ bbr->inflight_hi = max(bbr->inflight_hi, bbr->undo_inflight_hi); ++ return bbr->prior_cwnd; ++} ++ ++/* Entering loss recovery, so save state for when we undo recovery. */ ++static u32 bbr2_ssthresh(struct sock *sk) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ bbr_save_cwnd(sk); ++ /* For undo, save state that adapts based on loss signal. */ ++ bbr->undo_bw_lo = bbr->bw_lo; ++ bbr->undo_inflight_lo = bbr->inflight_lo; ++ bbr->undo_inflight_hi = bbr->inflight_hi; ++ return tcp_sk(sk)->snd_ssthresh; ++} ++ ++static enum tcp_bbr2_phase bbr2_get_phase(struct bbr *bbr) ++{ ++ switch (bbr->mode) { ++ case BBR_STARTUP: ++ return BBR2_PHASE_STARTUP; ++ case BBR_DRAIN: ++ return BBR2_PHASE_DRAIN; ++ case BBR_PROBE_BW: ++ break; ++ case BBR_PROBE_RTT: ++ return BBR2_PHASE_PROBE_RTT; ++ default: ++ return BBR2_PHASE_INVALID; ++ } ++ switch (bbr->cycle_idx) { ++ case BBR_BW_PROBE_UP: ++ return BBR2_PHASE_PROBE_BW_UP; ++ case BBR_BW_PROBE_DOWN: ++ return BBR2_PHASE_PROBE_BW_DOWN; ++ case BBR_BW_PROBE_CRUISE: ++ return BBR2_PHASE_PROBE_BW_CRUISE; ++ case BBR_BW_PROBE_REFILL: ++ return BBR2_PHASE_PROBE_BW_REFILL; ++ default: ++ return BBR2_PHASE_INVALID; ++ } ++} ++ ++static size_t bbr2_get_info(struct sock *sk, u32 ext, int *attr, ++ union tcp_cc_info *info) ++{ ++ if (ext & (1 << (INET_DIAG_BBRINFO - 1)) || ++ ext & (1 << (INET_DIAG_VEGASINFO - 1))) { ++ struct bbr *bbr = inet_csk_ca(sk); ++ u64 bw = bbr_bw_bytes_per_sec(sk, bbr_bw(sk)); ++ u64 bw_hi = bbr_bw_bytes_per_sec(sk, bbr_max_bw(sk)); ++ u64 bw_lo = bbr->bw_lo == ~0U ? ++ ~0ULL : bbr_bw_bytes_per_sec(sk, bbr->bw_lo); ++ ++ memset(&info->bbr2, 0, sizeof(info->bbr2)); ++ info->bbr2.bbr_bw_lsb = (u32)bw; ++ info->bbr2.bbr_bw_msb = (u32)(bw >> 32); ++ info->bbr2.bbr_min_rtt = bbr->min_rtt_us; ++ info->bbr2.bbr_pacing_gain = bbr->pacing_gain; ++ info->bbr2.bbr_cwnd_gain = bbr->cwnd_gain; ++ info->bbr2.bbr_bw_hi_lsb = (u32)bw_hi; ++ info->bbr2.bbr_bw_hi_msb = (u32)(bw_hi >> 32); ++ info->bbr2.bbr_bw_lo_lsb = (u32)bw_lo; ++ info->bbr2.bbr_bw_lo_msb = (u32)(bw_lo >> 32); ++ info->bbr2.bbr_mode = bbr->mode; ++ info->bbr2.bbr_phase = (__u8)bbr2_get_phase(bbr); ++ info->bbr2.bbr_version = (__u8)2; ++ info->bbr2.bbr_inflight_lo = bbr->inflight_lo; ++ info->bbr2.bbr_inflight_hi = bbr->inflight_hi; ++ info->bbr2.bbr_extra_acked = bbr_extra_acked(sk); ++ *attr = INET_DIAG_BBRINFO; ++ return sizeof(info->bbr2); ++ } ++ return 0; ++} ++ ++static void bbr2_set_state(struct sock *sk, u8 new_state) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ if (new_state == TCP_CA_Loss) { ++ struct rate_sample rs = { .losses = 1 }; ++ struct bbr_context ctx = { 0 }; ++ ++ bbr->prev_ca_state = TCP_CA_Loss; ++ bbr->full_bw = 0; ++ if (!bbr2_is_probing_bandwidth(sk) && bbr->inflight_lo == ~0U) { ++ /* bbr_adapt_lower_bounds() needs cwnd before ++ * we suffered an RTO, to update inflight_lo: ++ */ ++ bbr->inflight_lo = ++ max(tp->snd_cwnd, bbr->prior_cwnd); ++ } ++ bbr_debug(sk, 0, &rs, &ctx); ++ } else if (bbr->prev_ca_state == TCP_CA_Loss && ++ new_state != TCP_CA_Loss) { ++ tp->snd_cwnd = max(tp->snd_cwnd, bbr->prior_cwnd); ++ bbr->try_fast_path = 0; /* bound cwnd using latest model */ ++ } ++} ++ ++static struct tcp_congestion_ops tcp_bbr2_cong_ops __read_mostly = { ++ .flags = TCP_CONG_NON_RESTRICTED | TCP_CONG_WANTS_CE_EVENTS, ++ .name = "bbr2", ++ .owner = THIS_MODULE, ++ .init = bbr2_init, ++ .cong_control = bbr2_main, ++ .sndbuf_expand = bbr_sndbuf_expand, ++ .skb_marked_lost = bbr2_skb_marked_lost, ++ .undo_cwnd = bbr2_undo_cwnd, ++ .cwnd_event = bbr_cwnd_event, ++ .ssthresh = bbr2_ssthresh, ++ .tso_segs = bbr_tso_segs, ++ .get_info = bbr2_get_info, ++ .set_state = bbr2_set_state, ++}; ++ ++static int __init bbr_register(void) ++{ ++ BUILD_BUG_ON(sizeof(struct bbr) > ICSK_CA_PRIV_SIZE); ++ return tcp_register_congestion_control(&tcp_bbr2_cong_ops); ++} ++ ++static void __exit bbr_unregister(void) ++{ ++ tcp_unregister_congestion_control(&tcp_bbr2_cong_ops); ++} ++ ++module_init(bbr_register); ++module_exit(bbr_unregister); ++ ++MODULE_AUTHOR("Van Jacobson "); ++MODULE_AUTHOR("Neal Cardwell "); ++MODULE_AUTHOR("Yuchung Cheng "); ++MODULE_AUTHOR("Soheil Hassas Yeganeh "); ++MODULE_AUTHOR("Priyaranjan Jha "); ++MODULE_AUTHOR("Yousuk Seung "); ++MODULE_AUTHOR("Kevin Yang "); ++MODULE_AUTHOR("Arjun Roy "); ++ ++MODULE_LICENSE("Dual BSD/GPL"); ++MODULE_DESCRIPTION("TCP BBR (Bottleneck Bandwidth and RTT)"); +diff --git a/net/ipv4/tcp_cong.c b/net/ipv4/tcp_cong.c +index c445a81d144e..4a9a6b20d1e7 100644 +--- a/net/ipv4/tcp_cong.c ++++ b/net/ipv4/tcp_cong.c +@@ -179,6 +179,7 @@ void tcp_init_congestion_control(struct sock *sk) + const struct inet_connection_sock *icsk = inet_csk(sk); + + tcp_sk(sk)->prior_ssthresh = 0; ++ tcp_sk(sk)->fast_ack_mode = 0; + if (icsk->icsk_ca_ops->init) + icsk->icsk_ca_ops->init(sk); + if (tcp_ca_needs_ecn(sk)) +diff --git a/net/ipv4/tcp_input.c b/net/ipv4/tcp_input.c +index 88b987ca9ebb..c3538bd0d008 100644 +--- a/net/ipv4/tcp_input.c ++++ b/net/ipv4/tcp_input.c +@@ -283,7 +283,7 @@ static void __tcp_ecn_check_ce(struct sock *sk, const struct sk_buff *skb) + tcp_enter_quickack_mode(sk, 2); + break; + case INET_ECN_CE: +- if (tcp_ca_needs_ecn(sk)) ++ if (tcp_ca_wants_ce_events(sk)) + tcp_ca_event(sk, CA_EVENT_ECN_IS_CE); + + if (!(tp->ecn_flags & TCP_ECN_DEMAND_CWR)) { +@@ -294,7 +294,7 @@ static void __tcp_ecn_check_ce(struct sock *sk, const struct sk_buff *skb) + tp->ecn_flags |= TCP_ECN_SEEN; + break; + default: +- if (tcp_ca_needs_ecn(sk)) ++ if (tcp_ca_wants_ce_events(sk)) + tcp_ca_event(sk, CA_EVENT_ECN_NO_CE); + tp->ecn_flags |= TCP_ECN_SEEN; + break; +@@ -954,8 +954,14 @@ void tcp_skb_mark_lost_uncond_verify(struct tcp_sock *tp, struct sk_buff *skb) + + tcp_sum_lost(tp, skb); + if (!(TCP_SKB_CB(skb)->sacked & (TCPCB_LOST|TCPCB_SACKED_ACKED))) { ++ struct sock *sk = (struct sock *)tp; ++ const struct tcp_congestion_ops *ca_ops; ++ + tp->lost_out += tcp_skb_pcount(skb); + TCP_SKB_CB(skb)->sacked |= TCPCB_LOST; ++ ca_ops = inet_csk(sk)->icsk_ca_ops; ++ if (ca_ops->skb_marked_lost) ++ ca_ops->skb_marked_lost(sk, skb); + } + } + +@@ -1311,6 +1317,17 @@ static bool tcp_shifted_skb(struct sock *sk, struct sk_buff *prev, + WARN_ON_ONCE(tcp_skb_pcount(skb) < pcount); + tcp_skb_pcount_add(skb, -pcount); + ++ /* Adjust tx.in_flight as pcount is shifted from skb to prev. */ ++ if (WARN_ONCE(TCP_SKB_CB(skb)->tx.in_flight < pcount, ++ "prev in_flight: %u skb in_flight: %u pcount: %u", ++ TCP_SKB_CB(prev)->tx.in_flight, ++ TCP_SKB_CB(skb)->tx.in_flight, ++ pcount)) ++ TCP_SKB_CB(skb)->tx.in_flight = 0; ++ else ++ TCP_SKB_CB(skb)->tx.in_flight -= pcount; ++ TCP_SKB_CB(prev)->tx.in_flight += pcount; ++ + /* When we're adding to gso_segs == 1, gso_size will be zero, + * in theory this shouldn't be necessary but as long as DSACK + * code can come after this skb later on it's better to keep +@@ -3078,7 +3095,6 @@ static int tcp_clean_rtx_queue(struct sock *sk, u32 prior_fack, + long seq_rtt_us = -1L; + long ca_rtt_us = -1L; + u32 pkts_acked = 0; +- u32 last_in_flight = 0; + bool rtt_update; + int flag = 0; + +@@ -3116,7 +3132,6 @@ static int tcp_clean_rtx_queue(struct sock *sk, u32 prior_fack, + if (!first_ackt) + first_ackt = last_ackt; + +- last_in_flight = TCP_SKB_CB(skb)->tx.in_flight; + if (before(start_seq, reord)) + reord = start_seq; + if (!after(scb->end_seq, tp->high_seq)) +@@ -3176,8 +3191,8 @@ static int tcp_clean_rtx_queue(struct sock *sk, u32 prior_fack, + seq_rtt_us = tcp_stamp_us_delta(tp->tcp_mstamp, first_ackt); + ca_rtt_us = tcp_stamp_us_delta(tp->tcp_mstamp, last_ackt); + +- if (pkts_acked == 1 && last_in_flight < tp->mss_cache && +- last_in_flight && !prior_sacked && fully_acked && ++ if (pkts_acked == 1 && fully_acked && !prior_sacked && ++ (tp->snd_una - prior_snd_una) < tp->mss_cache && + sack->rate->prior_delivered + 1 == tp->delivered && + !(flag & (FLAG_CA_ALERT | FLAG_SYN_ACKED))) { + /* Conservatively mark a delayed ACK. It's typically +@@ -3234,9 +3249,10 @@ static int tcp_clean_rtx_queue(struct sock *sk, u32 prior_fack, + + if (icsk->icsk_ca_ops->pkts_acked) { + struct ack_sample sample = { .pkts_acked = pkts_acked, +- .rtt_us = sack->rate->rtt_us, +- .in_flight = last_in_flight }; ++ .rtt_us = sack->rate->rtt_us }; + ++ sample.in_flight = tp->mss_cache * ++ (tp->delivered - sack->rate->prior_delivered); + icsk->icsk_ca_ops->pkts_acked(sk, &sample); + } + +@@ -3632,6 +3648,7 @@ static int tcp_ack(struct sock *sk, const struct sk_buff *skb, int flag) + + prior_fack = tcp_is_sack(tp) ? tcp_highest_sack_seq(tp) : tp->snd_una; + rs.prior_in_flight = tcp_packets_in_flight(tp); ++ tcp_rate_check_app_limited(sk); + + /* ts_recent update must be made after we are sure that the packet + * is in window. +@@ -3714,6 +3731,7 @@ static int tcp_ack(struct sock *sk, const struct sk_buff *skb, int flag) + delivered = tcp_newly_delivered(sk, delivered, flag); + lost = tp->lost - lost; /* freshly marked lost */ + rs.is_ack_delayed = !!(flag & FLAG_ACK_MAYBE_DELAYED); ++ rs.is_ece = !!(flag & FLAG_ECE); + tcp_rate_gen(sk, delivered, lost, is_sack_reneg, sack_state.rate); + tcp_cong_control(sk, ack, delivered, flag, sack_state.rate); + tcp_xmit_recovery(sk, rexmit); +@@ -5228,13 +5246,14 @@ static void __tcp_ack_snd_check(struct sock *sk, int ofo_possible) + + /* More than one full frame received... */ + if (((tp->rcv_nxt - tp->rcv_wup) > inet_csk(sk)->icsk_ack.rcv_mss && ++ (tp->fast_ack_mode == 1 || + /* ... and right edge of window advances far enough. + * (tcp_recvmsg() will send ACK otherwise). + * If application uses SO_RCVLOWAT, we want send ack now if + * we have not received enough bytes to satisfy the condition. + */ +- (meta_tp->rcv_nxt - meta_tp->copied_seq < meta_sk->sk_rcvlowat || +- tp->ops->__select_window(sk) >= tp->rcv_wnd)) || ++ (meta_tp->rcv_nxt - meta_tp->copied_seq < meta_sk->sk_rcvlowat || ++ tp->ops->__select_window(sk) >= tp->rcv_wnd))) || + /* We ACK each frame or... */ + tcp_in_quickack_mode(sk) || + /* Protocol state mandates a one-time immediate ACK */ +diff --git a/net/ipv4/tcp_output.c b/net/ipv4/tcp_output.c +index be6d22b8190f..4943f96aade8 100644 +--- a/net/ipv4/tcp_output.c ++++ b/net/ipv4/tcp_output.c +@@ -1031,8 +1031,6 @@ static int __tcp_transmit_skb(struct sock *sk, struct sk_buff *skb, + tp->tcp_wstamp_ns = max(tp->tcp_wstamp_ns, tp->tcp_clock_cache); + skb->skb_mstamp_ns = tp->tcp_wstamp_ns; + if (clone_it) { +- TCP_SKB_CB(skb)->tx.in_flight = TCP_SKB_CB(skb)->end_seq +- - tp->snd_una; + oskb = skb; + + tcp_skb_tsorted_save(oskb) { +@@ -1298,7 +1296,7 @@ int tcp_fragment(struct sock *sk, enum tcp_queue tcp_queue, + { + struct tcp_sock *tp = tcp_sk(sk); + struct sk_buff *buff; +- int nsize, old_factor; ++ int nsize, old_factor, inflight_prev; + long limit; + int nlen; + u8 flags; +@@ -1376,6 +1374,15 @@ int tcp_fragment(struct sock *sk, enum tcp_queue tcp_queue, + + if (diff) + tcp_adjust_pcount(sk, skb, diff); ++ ++ /* Set buff tx.in_flight as if buff were sent by itself. */ ++ inflight_prev = TCP_SKB_CB(skb)->tx.in_flight - old_factor; ++ /* if (WARN_ONCE(inflight_prev < 0, ++ "inconsistent: tx.in_flight: %u old_factor: %d", ++ TCP_SKB_CB(skb)->tx.in_flight, old_factor)) */ ++ if (inflight_prev < 0) inflight_prev = 0; ++ TCP_SKB_CB(buff)->tx.in_flight = inflight_prev + ++ tcp_skb_pcount(buff); + } + + /* Link BUFF into the send queue. */ +@@ -1743,13 +1750,12 @@ static u32 tcp_tso_autosize(const struct sock *sk, unsigned int mss_now, + static u32 tcp_tso_segs(struct sock *sk, unsigned int mss_now) + { + const struct tcp_congestion_ops *ca_ops = inet_csk(sk)->icsk_ca_ops; +- u32 min_tso, tso_segs; +- +- min_tso = ca_ops->min_tso_segs ? +- ca_ops->min_tso_segs(sk) : +- sock_net(sk)->ipv4.sysctl_tcp_min_tso_segs; ++ u32 tso_segs; + +- tso_segs = tcp_tso_autosize(sk, mss_now, min_tso); ++ tso_segs = ca_ops->tso_segs ? ++ ca_ops->tso_segs(sk, mss_now) : ++ tcp_tso_autosize(sk, mss_now, ++ sock_net(sk)->ipv4.sysctl_tcp_min_tso_segs); + return min_t(u32, tso_segs, sk->sk_gso_max_segs); + } + +@@ -2387,6 +2393,7 @@ static bool tcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle, + skb->skb_mstamp_ns = tp->tcp_wstamp_ns = tp->tcp_clock_cache; + list_move_tail(&skb->tcp_tsorted_anchor, &tp->tsorted_sent_queue); + tcp_init_tso_segs(skb, mss_now); ++ tcp_set_tx_in_flight(sk, skb); + goto repair; /* Skip network transmission */ + } + +diff --git a/net/ipv4/tcp_rate.c b/net/ipv4/tcp_rate.c +index 0de693565963..796fa6e5310c 100644 +--- a/net/ipv4/tcp_rate.c ++++ b/net/ipv4/tcp_rate.c +@@ -34,6 +34,24 @@ + * ready to send in the write queue. + */ + ++void tcp_set_tx_in_flight(struct sock *sk, struct sk_buff *skb) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ u32 in_flight; ++ ++ /* Check, sanitize, and record packets in flight after skb was sent. */ ++ in_flight = tcp_packets_in_flight(tp) + tcp_skb_pcount(skb); ++ if (WARN_ONCE(in_flight > TCPCB_IN_FLIGHT_MAX, ++ "insane in_flight %u cc %s mss %u " ++ "cwnd %u pif %u %u %u %u\n", ++ in_flight, inet_csk(sk)->icsk_ca_ops->name, ++ tp->mss_cache, tp->snd_cwnd, ++ tp->packets_out, tp->retrans_out, ++ tp->sacked_out, tp->lost_out)) ++ in_flight = TCPCB_IN_FLIGHT_MAX; ++ TCP_SKB_CB(skb)->tx.in_flight = in_flight; ++} ++ + /* Snapshot the current delivery information in the skb, to generate + * a rate sample later when the skb is (s)acked in tcp_rate_skb_delivered(). + */ +@@ -65,7 +83,10 @@ void tcp_rate_skb_sent(struct sock *sk, struct sk_buff *skb) + TCP_SKB_CB(skb)->tx.first_tx_mstamp = tp->first_tx_mstamp; + TCP_SKB_CB(skb)->tx.delivered_mstamp = tp->delivered_mstamp; + TCP_SKB_CB(skb)->tx.delivered = tp->delivered; ++ TCP_SKB_CB(skb)->tx.delivered_ce = tp->delivered_ce; ++ TCP_SKB_CB(skb)->tx.lost = tp->lost; + TCP_SKB_CB(skb)->tx.is_app_limited = tp->app_limited ? 1 : 0; ++ tcp_set_tx_in_flight(sk, skb); + } + + /* When an skb is sacked or acked, we fill in the rate sample with the (prior) +@@ -86,16 +107,20 @@ void tcp_rate_skb_delivered(struct sock *sk, struct sk_buff *skb, + + if (!rs->prior_delivered || + after(scb->tx.delivered, rs->prior_delivered)) { ++ rs->prior_lost = scb->tx.lost; ++ rs->prior_delivered_ce = scb->tx.delivered_ce; + rs->prior_delivered = scb->tx.delivered; + rs->prior_mstamp = scb->tx.delivered_mstamp; + rs->is_app_limited = scb->tx.is_app_limited; + rs->is_retrans = scb->sacked & TCPCB_RETRANS; ++ rs->tx_in_flight = scb->tx.in_flight; + + /* Record send time of most recently ACKed packet: */ + tp->first_tx_mstamp = tcp_skb_timestamp_us(skb); + /* Find the duration of the "send phase" of this window: */ +- rs->interval_us = tcp_stamp_us_delta(tp->first_tx_mstamp, +- scb->tx.first_tx_mstamp); ++ rs->interval_us = tcp_stamp32_us_delta( ++ tp->first_tx_mstamp, ++ scb->tx.first_tx_mstamp); + + } + /* Mark off the skb delivered once it's sacked to avoid being +@@ -137,6 +162,11 @@ void tcp_rate_gen(struct sock *sk, u32 delivered, u32 lost, + return; + } + rs->delivered = tp->delivered - rs->prior_delivered; ++ rs->lost = tp->lost - rs->prior_lost; ++ ++ rs->delivered_ce = tp->delivered_ce - rs->prior_delivered_ce; ++ /* delivered_ce occupies less than 32 bits in the skb control block */ ++ rs->delivered_ce &= TCPCB_DELIVERED_CE_MASK; + + /* Model sending data and receiving ACKs as separate pipeline phases + * for a window. Usually the ACK phase is longer, but with ACK +@@ -144,7 +174,7 @@ void tcp_rate_gen(struct sock *sk, u32 delivered, u32 lost, + * longer phase. + */ + snd_us = rs->interval_us; /* send phase */ +- ack_us = tcp_stamp_us_delta(tp->tcp_mstamp, ++ ack_us = tcp_stamp32_us_delta(tp->tcp_mstamp, + rs->prior_mstamp); /* ack phase */ + rs->interval_us = max(snd_us, ack_us); + +diff --git a/net/ipv4/tcp_timer.c b/net/ipv4/tcp_timer.c +index dd5a6317a801..1da66d4bbb08 100644 +--- a/net/ipv4/tcp_timer.c ++++ b/net/ipv4/tcp_timer.c +@@ -584,6 +584,7 @@ void tcp_write_timer_handler(struct sock *sk) + goto out; + } + ++ tcp_rate_check_app_limited(sk); + tcp_mstamp_refresh(tcp_sk(sk)); + event = icsk->icsk_pending; + diff --git a/root/target/linux/ipq60xx/patches-5.4/901-arm64-boot-add-dts-files.patch b/root/target/linux/ipq60xx/patches-5.4/901-arm64-boot-add-dts-files.patch new file mode 100644 index 00000000..7e7c995d --- /dev/null +++ b/root/target/linux/ipq60xx/patches-5.4/901-arm64-boot-add-dts-files.patch @@ -0,0 +1,13 @@ +--- a/arch/arm64/boot/dts/qcom/Makefile ++++ b/arch/arm64/boot/dts/qcom/Makefile +@@ -7,6 +7,10 @@ dtb-$(CONFIG_ARCH_QCOM) += ipq6018-cp01-c4.dtb + dtb-$(CONFIG_ARCH_QCOM) += ipq6018-cp01-c5.dtb + dtb-$(CONFIG_ARCH_QCOM) += ipq6018-cp02-c1.dtb + dtb-$(CONFIG_ARCH_QCOM) += ipq6018-cp03-c1.dtb ++dtb-$(CONFIG_ARCH_QCOM) += ipq6018-q60.dtb ++dtb-$(CONFIG_ARCH_QCOM) += ipq6018-x5.dtb ++dtb-$(CONFIG_ARCH_QCOM) += ipq6018-x8.dtb ++dtb-$(CONFIG_ARCH_QCOM) += ipq6018-x511.dtb + dtb-$(CONFIG_ARCH_QCOM) += ipq8074-hk01.dtb + dtb-$(CONFIG_ARCH_QCOM) += ipq8074-hk01.c2.dtb + dtb-$(CONFIG_ARCH_QCOM) += ipq8074-hk01.c3.dtb diff --git a/root/target/linux/ipq60xx/patches-5.4/902-add-gpio-export.patch b/root/target/linux/ipq60xx/patches-5.4/902-add-gpio-export.patch new file mode 100644 index 00000000..abe066ba --- /dev/null +++ b/root/target/linux/ipq60xx/patches-5.4/902-add-gpio-export.patch @@ -0,0 +1,162 @@ +Index: linux-5.4.164/drivers/gpio/gpiolib-of.c +=================================================================== +--- linux-5.4.164.orig/drivers/gpio/gpiolib-of.c ++++ linux-5.4.164/drivers/gpio/gpiolib-of.c +@@ -19,6 +19,8 @@ + #include + #include + #include ++#include ++#include + + #include "gpiolib.h" + #include "gpiolib-of.h" +@@ -915,3 +917,69 @@ void of_gpiochip_remove(struct gpio_chip + { + of_node_put(chip->of_node); + } ++ ++ ++static struct of_device_id gpio_export_ids[] = { ++ { .compatible = "gpio-export" }, ++ { /* sentinel */ } ++}; ++ ++static int of_gpio_export_probe(struct platform_device *pdev) ++{ ++ struct device_node *np = pdev->dev.of_node; ++ struct device_node *cnp; ++ u32 val; ++ int nb = 0; ++ ++ for_each_child_of_node(np, cnp) { ++ const char *name = NULL; ++ int gpio; ++ bool dmc; ++ int max_gpio = 1; ++ int i; ++ ++ of_property_read_string(cnp, "gpio-export,name", &name); ++ ++ if (!name) ++ max_gpio = of_gpio_count(cnp); ++ ++ for (i = 0; i < max_gpio; i++) { ++ unsigned flags = 0; ++ enum of_gpio_flags of_flags; ++ ++ gpio = of_get_gpio_flags(cnp, i, &of_flags); ++ if (!gpio_is_valid(gpio)) ++ return gpio; ++ ++ if (of_flags == OF_GPIO_ACTIVE_LOW) ++ flags |= GPIOF_ACTIVE_LOW; ++ ++ if (!of_property_read_u32(cnp, "gpio-export,output", &val)) ++ flags |= val ? GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW; ++ else ++ flags |= GPIOF_IN; ++ ++ if (devm_gpio_request_one(&pdev->dev, gpio, flags, name ? name : of_node_full_name(np))) ++ continue; ++ ++ dmc = of_property_read_bool(cnp, "gpio-export,direction_may_change"); ++ gpio_export_with_name(gpio, dmc, name); ++ nb++; ++ } ++ } ++ ++ dev_info(&pdev->dev, "%d gpio(s) exported\n", nb); ++ ++ return 0; ++} ++ ++static struct platform_driver gpio_export_driver = { ++ .driver = { ++ .name = "gpio-export", ++ .owner = THIS_MODULE, ++ .of_match_table = of_match_ptr(gpio_export_ids), ++ }, ++ .probe = of_gpio_export_probe, ++}; ++ ++module_platform_driver(gpio_export_driver); +Index: linux-5.4.164/drivers/gpio/gpiolib-sysfs.c +=================================================================== +--- linux-5.4.164.orig/drivers/gpio/gpiolib-sysfs.c ++++ linux-5.4.164/drivers/gpio/gpiolib-sysfs.c +@@ -571,7 +571,7 @@ static struct class gpio_class = { + * + * Returns zero on success, else an error. + */ +-int gpiod_export(struct gpio_desc *desc, bool direction_may_change) ++int __gpiod_export(struct gpio_desc *desc, bool direction_may_change, const char *name) + { + struct gpio_chip *chip; + struct gpio_device *gdev; +@@ -633,6 +633,8 @@ int gpiod_export(struct gpio_desc *desc, + offset = gpio_chip_hwgpio(desc); + if (chip->names && chip->names[offset]) + ioname = chip->names[offset]; ++ if (name) ++ ioname = name; + + dev = device_create_with_groups(&gpio_class, &gdev->dev, + MKDEV(0, 0), data, gpio_groups, +@@ -654,6 +656,13 @@ err_unlock: + gpiod_dbg(desc, "%s: status %d\n", __func__, status); + return status; + } ++ ++EXPORT_SYMBOL_GPL(__gpiod_export); ++ ++int gpiod_export(struct gpio_desc *desc, bool direction_may_change) ++{ ++ return __gpiod_export(desc, direction_may_change, NULL); ++} + EXPORT_SYMBOL_GPL(gpiod_export); + + static int match_export(struct device *dev, const void *desc) +Index: linux-5.4.164/include/asm-generic/gpio.h +=================================================================== +--- linux-5.4.164.orig/include/asm-generic/gpio.h ++++ linux-5.4.164/include/asm-generic/gpio.h +@@ -127,6 +127,12 @@ static inline int gpio_export(unsigned g + return gpiod_export(gpio_to_desc(gpio), direction_may_change); + } + ++int __gpiod_export(struct gpio_desc *desc, bool direction_may_change, const char *name); ++static inline int gpio_export_with_name(unsigned gpio, bool direction_may_change, const char *name) ++{ ++ return __gpiod_export(gpio_to_desc(gpio), direction_may_change, name); ++} ++ + static inline int gpio_export_link(struct device *dev, const char *name, + unsigned gpio) + { +Index: linux-5.4.164/include/linux/gpio/consumer.h +=================================================================== +--- linux-5.4.164.orig/include/linux/gpio/consumer.h ++++ linux-5.4.164/include/linux/gpio/consumer.h +@@ -668,6 +668,7 @@ static inline void devm_acpi_dev_remove_ + + #if IS_ENABLED(CONFIG_GPIOLIB) && IS_ENABLED(CONFIG_GPIO_SYSFS) + ++int _gpiod_export(struct gpio_desc *desc, bool direction_may_change, const char *name); + int gpiod_export(struct gpio_desc *desc, bool direction_may_change); + int gpiod_export_link(struct device *dev, const char *name, + struct gpio_desc *desc); +@@ -675,6 +676,13 @@ void gpiod_unexport(struct gpio_desc *de + + #else /* CONFIG_GPIOLIB && CONFIG_GPIO_SYSFS */ + ++static inline int _gpiod_export(struct gpio_desc *desc, ++ bool direction_may_change, ++ const char *name) ++{ ++ return -ENOSYS; ++} ++ + static inline int gpiod_export(struct gpio_desc *desc, + bool direction_may_change) + { diff --git a/root/target/linux/ipq60xx/patches-5.4/999-display-model-name-in-proc-cpuinfo.patch b/root/target/linux/ipq60xx/patches-5.4/999-display-model-name-in-proc-cpuinfo.patch new file mode 100644 index 00000000..c2d9d890 --- /dev/null +++ b/root/target/linux/ipq60xx/patches-5.4/999-display-model-name-in-proc-cpuinfo.patch @@ -0,0 +1,11 @@ +--- a/arch/arm64/kernel/cpuinfo.c ++++ b/arch/arm64/kernel/cpuinfo.c +@@ -139,7 +139,7 @@ static int c_show(struct seq_file *m, void *v) + * "processor". Give glibc what it expects. + */ + seq_printf(m, "processor\t: %d\n", i); +- if (compat) ++ /* if (compat) */ + seq_printf(m, "model name\t: ARMv8 Processor rev %d (%s)\n", + MIDR_REVISION(midr), COMPAT_ELF_PLATFORM); + diff --git a/root/target/linux/ipq60xx/profiles/default.mk b/root/target/linux/ipq60xx/profiles/default.mk new file mode 100644 index 00000000..f6ded854 --- /dev/null +++ b/root/target/linux/ipq60xx/profiles/default.mk @@ -0,0 +1,9 @@ +define Profile/Default + NAME:=Default Profile + PRIORITY:=1 +endef + +define Profile/Default/Description + Default package set compatible with most boards. +endef +$(eval $(call Profile,Default))

    Ib8Vgg>aJ!`uLyc%;Z4*VJRh{k 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