From 798c0b86e6af211cfc7e07266c4f5994c28e25fd Mon Sep 17 00:00:00 2001 From: liuzf Date: Thu, 17 Oct 2024 12:00:30 +0800 Subject: [PATCH] feat: Add wwan dialing for ec20 and ec200 --- .../etc/init.d/S52_4G-Daemon.sh | 16 - .../etc/init.d/S97rpdzkj-mobilenet | 23 + .../fs-overlay-facial/usr/bin/4G_dialing.sh | 109 + .../fs-overlay-facial/usr/bin/quectel-CM | Bin 0 -> 191552 bytes .../usr/bin/rpdzkj-mobilenet.sh | 7 + .../arm/configs/rv1126-facial-gate.config | 4 + kernel/drivers/net/usb/Makefile | 3 +- kernel/drivers/net/usb/qmi_wwan_q.c | 2429 +++++++++++++++++ kernel/drivers/net/usb/rmnet_nss.c | 424 +++ kernel/drivers/usb/serial/opticon.c | 0 kernel/drivers/usb/serial/qcserial.c | 4 +- kernel/drivers/usb/serial/usb_wwan.c | 27 +- 12 files changed, 3022 insertions(+), 24 deletions(-) delete mode 100755 buildroot/board/rockchip/rv1126_rv1109/fs-overlay-facial/etc/init.d/S52_4G-Daemon.sh create mode 100755 buildroot/board/rockchip/rv1126_rv1109/fs-overlay-facial/etc/init.d/S97rpdzkj-mobilenet create mode 100755 buildroot/board/rockchip/rv1126_rv1109/fs-overlay-facial/usr/bin/4G_dialing.sh create mode 100755 buildroot/board/rockchip/rv1126_rv1109/fs-overlay-facial/usr/bin/quectel-CM create mode 100755 buildroot/board/rockchip/rv1126_rv1109/fs-overlay-facial/usr/bin/rpdzkj-mobilenet.sh mode change 100644 => 100755 kernel/drivers/net/usb/Makefile create mode 100755 kernel/drivers/net/usb/qmi_wwan_q.c create mode 100755 kernel/drivers/net/usb/rmnet_nss.c mode change 100644 => 100755 kernel/drivers/usb/serial/opticon.c mode change 100644 => 100755 kernel/drivers/usb/serial/qcserial.c mode change 100644 => 100755 kernel/drivers/usb/serial/usb_wwan.c diff --git a/buildroot/board/rockchip/rv1126_rv1109/fs-overlay-facial/etc/init.d/S52_4G-Daemon.sh b/buildroot/board/rockchip/rv1126_rv1109/fs-overlay-facial/etc/init.d/S52_4G-Daemon.sh deleted file mode 100755 index e99cac688..000000000 --- a/buildroot/board/rockchip/rv1126_rv1109/fs-overlay-facial/etc/init.d/S52_4G-Daemon.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/sh -# - -case "$1" in - start) - #export DEBUG_4G=0 - 4G-demon & - ;; - stop) - killall 4G-demon - ;; - *) - echo "no function!" - ;; -esac - diff --git a/buildroot/board/rockchip/rv1126_rv1109/fs-overlay-facial/etc/init.d/S97rpdzkj-mobilenet b/buildroot/board/rockchip/rv1126_rv1109/fs-overlay-facial/etc/init.d/S97rpdzkj-mobilenet new file mode 100755 index 000000000..b98c7317f --- /dev/null +++ b/buildroot/board/rockchip/rv1126_rv1109/fs-overlay-facial/etc/init.d/S97rpdzkj-mobilenet @@ -0,0 +1,23 @@ +#!/bin/bash +case "$1" in + start) + printf "Starting mobilenet:" + rpdzkj-mobilenet.sh >> /tmp/moblienet 2>&1 & + [ $? = 0 ] && echo "OK" || echo "FAIL" + ;; + stop) + printf "Starting mobilenet:" + killall rpdzkj-mobilenet.sh + [ $? = 0 ] && echo "OK" || echo "FAIL" + ;; + restart|reload) + "$0" stop + "$0" start + ;; + *) + echo "Usage: $0 {start|stop|restart}" + exit 1 +esac + +exit $? + diff --git a/buildroot/board/rockchip/rv1126_rv1109/fs-overlay-facial/usr/bin/4G_dialing.sh b/buildroot/board/rockchip/rv1126_rv1109/fs-overlay-facial/usr/bin/4G_dialing.sh new file mode 100755 index 000000000..4503d2464 --- /dev/null +++ b/buildroot/board/rockchip/rv1126_rv1109/fs-overlay-facial/usr/bin/4G_dialing.sh @@ -0,0 +1,109 @@ +#!/bin/bash +DIRECTORY="/dev/serial/by-id" +Serial_port="" +counter=0 + +Switching_mode() +{ + PRODUCT_NAME=$(cat "$device_dir/product") + MANUFACTURER=$(cat "$device_dir/manufacturer") + SERIAL_NUMBER=$(cat "$device_dir/serial") + + if [ "$SERIAL_NUMBER" == "" ];then + Serial="usb-${MANUFACTURER}_${PRODUCT_NAME}" + else + Serial="usb-${MANUFACTURER}_${PRODUCT_NAME}_$SERIAL_NUMBER" + fi + + for file in $(ls "$DIRECTORY" | grep "$Serial" | sort);do + if [ "$counter" -eq 2 ];then + Serial_port=$file + echo "Serial_port:$Serial_port" + break + fi + counter=$((counter+1)) + done + +} + +for device_dir in /sys/bus/usb/devices/*; do + if [ -e "$device_dir/idProduct" ];then + product_id=$(cat "$device_dir/idProduct") + + if [ "$product_id" = "6002" ] || [ "$product_id" = "6001" ] || [ "$product_id" = "6005" ]; then + echo "This is a EC200 module!" + for interface in ${device_dir}/*/net/*; do + name=$(basename $interface) + echo "name:$name" + + done + if [ ! -e "/sys/class/net/$name" ];then + echo "$name not found!!!" + Switching_mode "$device_dir" + if [ -e $DIRECTORY/$Serial_port ];then + echo -e "AT+QCFG=\"usbnet\",1" > $DIRECTORY/$Serial_port + sleep 1 + /usr/bin/quectel-CM >> /tmp/4G.log 2>&1 & + else + echo "The Serial_port not found!!!" + fi + + else + echo "This is EC200 Connection test" + ping -c 2 -W 3 -I $name 8.8.8.8 + if [ ! "$?" == "0" ];then + echo "EC200 Connect faile!!!" + Switching_mode "$device_dir" + if [ -e $DIRECTORY/$Serial_port ];then + #echo -e "AT+QCFG=\"usbnet\",0" > $DIRECTORY/$Serial_port + sleep 1 + /usr/bin/quectel-CM >> /tmp/4G.log 2>&1 & + else + echo "The Serial_port not found!!!" + fi + + else + echo "EC200 Connect success!" + exit 1 + fi + fi + fi + + if [ "$product_id" = "0125" ];then + echo "This is a EC20 module!" + for interface in ${device_dir}/*/net/*; do + name=$(basename $interface) + echo "name:$name" + + done + if [ ! -e "/sys/class/net/$name" ];then + echo "$name not found!!!" + Switching_mode "$device_dir" + if [ -e $DIRECTORY/$Serial_port ];then + echo -e "AT+QCFG=\"usbnet\",0" > $DIRECTORY/$Serial_port + sleep 1 + /usr/bin/quectel-CM >> /tmp/4G.log 2>&1 & + else + echo "The Serial_port not found!!!" + fi + else + echo "This is EC20 Connection test" + ping -c 2 -W 3 -I $name 8.8.8.8 + if [ ! "$?" == "0" ];then + echo "EC20 Connect faile!!!" + Switching_mode "$device_dir" + if [ -e $DIRECTORY/$Serial_port ];then + #echo -e "AT+QCFG=\"usbnet\",0" > $DIRECTORY/$Serial_port + sleep 1 + /usr/bin/quectel-CM >> /tmp/4G.log 2>&1 & + else + echo "The Serial_port not found!!!" + fi + else + echo "EC20 Connect success!" + exit 1 + fi + fi + fi + fi +done diff --git a/buildroot/board/rockchip/rv1126_rv1109/fs-overlay-facial/usr/bin/quectel-CM b/buildroot/board/rockchip/rv1126_rv1109/fs-overlay-facial/usr/bin/quectel-CM new file mode 100755 index 0000000000000000000000000000000000000000..54123b2af9ff0b15b513833210df152544708ab8 GIT binary patch literal 191552 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
b`&{Q{4U7 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 Z4AgCIuc(Z;otR!bu@|=~<2Jdk{|kcha6JG3 literal 0 HcmV?d00001 diff --git a/buildroot/board/rockchip/rv1126_rv1109/fs-overlay-facial/usr/bin/rpdzkj-mobilenet.sh b/buildroot/board/rockchip/rv1126_rv1109/fs-overlay-facial/usr/bin/rpdzkj-mobilenet.sh new file mode 100755 index 000000000..30a5329aa --- /dev/null +++ b/buildroot/board/rockchip/rv1126_rv1109/fs-overlay-facial/usr/bin/rpdzkj-mobilenet.sh @@ -0,0 +1,7 @@ +#!/bin/bash +while true +do + 4G_dialing.sh & + sleep 30 + +done diff --git a/kernel/arch/arm/configs/rv1126-facial-gate.config b/kernel/arch/arm/configs/rv1126-facial-gate.config index 3eb3cb12f..ad42e77d2 100644 --- a/kernel/arch/arm/configs/rv1126-facial-gate.config +++ b/kernel/arch/arm/configs/rv1126-facial-gate.config @@ -50,3 +50,7 @@ CONFIG_VIDEO_OS04A10=y CONFIG_SND_USB_AUDIO=y CONFIG_USB_SERIAL_CH341=y CONFIG_USB_SERIAL_CP210X=y +CONFIG_USB_NET_DRIVERS=y +CONFIG_USB_USBNET=y +CONFIG_USB_WDM=y +CONFIG_USB_NET_QMI_WWAN=y diff --git a/kernel/drivers/net/usb/Makefile b/kernel/drivers/net/usb/Makefile old mode 100644 new mode 100755 index 27307a4ab..800437513 --- a/kernel/drivers/net/usb/Makefile +++ b/kernel/drivers/net/usb/Makefile @@ -37,6 +37,7 @@ obj-$(CONFIG_USB_NET_CX82310_ETH) += cx82310_eth.o obj-$(CONFIG_USB_NET_CDC_NCM) += cdc_ncm.o obj-$(CONFIG_USB_NET_HUAWEI_CDC_NCM) += huawei_cdc_ncm.o obj-$(CONFIG_USB_VL600) += lg-vl600.o -obj-$(CONFIG_USB_NET_QMI_WWAN) += qmi_wwan.o +#obj-$(CONFIG_USB_NET_QMI_WWAN) += qmi_wwan.o +obj-$(CONFIG_USB_NET_QMI_WWAN) += qmi_wwan_q.o obj-$(CONFIG_USB_NET_CDC_MBIM) += cdc_mbim.o obj-$(CONFIG_USB_NET_CH9200) += ch9200.o diff --git a/kernel/drivers/net/usb/qmi_wwan_q.c b/kernel/drivers/net/usb/qmi_wwan_q.c new file mode 100755 index 000000000..ac58554f7 --- /dev/null +++ b/kernel/drivers/net/usb/qmi_wwan_q.c @@ -0,0 +1,2429 @@ +/* + * 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 +//#define CONFIG_QCA_NSS_PACKET_FILTER +#endif + +#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 __read_mostly *nss_cb = NULL; +#if defined(CONFIG_PINCTRL_IPQ807x) || defined(CONFIG_PINCTRL_IPQ5018) +#ifdef CONFIG_RMNET_DATA +#define CONFIG_QCA_NSS_DRV +/* define at qsdk/qca/src/linux-4.4/net/rmnet_data/rmnet_data_main.c */ +/* set at qsdk/qca/src/data-kernel/drivers/rmnet-nss/rmnet_nss.c */ +extern struct rmnet_nss_cb *rmnet_nss_callbacks __rcu __read_mostly; +#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.1" +#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 + uint use_qca_nss; +}; + +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; + + netdev_stats_to_stats64(stats, &net->stats); + + if (nss_cb && dev->use_qca_nss) { // 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; + + 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; + } + + 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; + char name[IFNAMSIZ]; + int use_qca_nss = !!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, rmnet_usb_ether_setup); +#else + qmap_net = alloc_netdev(sizeof(struct qmap_priv), name, + 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; + if (priv->bridge_mode) + use_qca_nss = 0; +#endif + + if (nss_cb && use_qca_nss) { + rmnet_usb_rawip_setup(qmap_net); + } + + 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); + priv->use_qca_nss = 0; + +#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 && use_qca_nss) { + 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 { + priv->use_qca_nss = 1; + 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 qmap_priv *priv = netdev_priv(qmap_net); + 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); + + if (nss_cb && priv->use_qca_nss) { + 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() do not call spin_lock_init(&list->lock), + so should not call skb_queue_tail/queue later. */ + __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; + int skip_nss = 0; + + 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: +#ifdef CONFIG_QCA_NSS_PACKET_FILTER + { + struct iphdr *ip4h = (struct iphdr *)(&skb_in->data[hdr_size]); + if (ip4h->protocol == IPPROTO_ICMP) { + skip_nss = 1; + } + } +#endif + protocol = htons(ETH_P_IP); + break; + case 0x60: +#ifdef CONFIG_QCA_NSS_PACKET_FILTER + { + struct ipv6hdr *ip6h = (struct ipv6hdr *)(&skb_in->data[hdr_size]); + if (ip6h->nexthdr == NEXTHDR_ICMP) { + skip_nss = 1; + } + } +#endif + 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(skip_nss) + qmap_skb->cb[0] = 1; + + 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 */ +#if (LINUX_VERSION_CODE > KERNEL_VERSION( 5,12,0 )) //cac6fb015f719104e60b1c68c15ca5b734f57b9c + subdriver = usb_cdc_wdm_register(info->control, &dev->status->desc, + 4096, WWAN_PORT_QMI, &qmi_wwan_cdc_wdm_manage_power); +#else + subdriver = usb_cdc_wdm_register(info->control, &dev->status->desc, + 4096, &qmi_wwan_cdc_wdm_manage_power); + +#endif + 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 == 0x030B || idProduct == 0x0512 || idProduct == 0x0620 || idProduct == 0x0800 || idProduct == 0x0801); + + 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 || idProduct == 0x0801) + pQmapDev->use_rmnet_usb = 1; //benefit for ul data agg + 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.iface_id = 4; + 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, 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, 0x0700, 3, mdm9x07) }, /* Quectel BG95 (at+qcfgext="usbnet","rmnet") */ + { QMI_FIXED_RAWIP_INTF(0x2C7C, 0x0306, 4, mdm9x40) }, /* Quectel EG06/EP06/EM06 */ + { QMI_FIXED_RAWIP_INTF(0x2C7C, 0x030B, 4, mdm9x40) }, /* Quectel EG065k/EG060K */ + { 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(0x2C7C, 0x0801, 4, sdx55) }, /* Quectel RG520 */ + { } /* 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); + /* struct usbnet *dev had free by usbnet_disconnect()->free_netdev(). + so we should access info. */ + //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, +}; + +static int __init qmi_wwan_driver_init(void) +{ +#ifdef CONFIG_QCA_NSS_DRV + nss_cb = rcu_dereference(rmnet_nss_callbacks); + if (!nss_cb) { + printk(KERN_ERR "qmi_wwan_driver_init: driver load must after '/etc/modules.d/42-rmnet-nss'\n"); + } +#endif + return usb_register(&qmi_wwan_driver); +} +module_init(qmi_wwan_driver_init); +static void __exit qmi_wwan_driver_exit(void) +{ + 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/kernel/drivers/net/usb/rmnet_nss.c b/kernel/drivers/net/usb/rmnet_nss.c new file mode 100755 index 000000000..e6e841468 --- /dev/null +++ b/kernel/drivers/net/usb/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/kernel/drivers/usb/serial/opticon.c b/kernel/drivers/usb/serial/opticon.c old mode 100644 new mode 100755 diff --git a/kernel/drivers/usb/serial/qcserial.c b/kernel/drivers/usb/serial/qcserial.c old mode 100644 new mode 100755 index 613f91add..eeecbccf7 --- a/kernel/drivers/usb/serial/qcserial.c +++ b/kernel/drivers/usb/serial/qcserial.c @@ -5,6 +5,8 @@ * Copyright (c) 2008 QUALCOMM Incorporated. * Copyright (c) 2009 Greg Kroah-Hartman * Copyright (c) 2009 Novell Inc. + + * Based on version modification, the author is Quectel */ #include @@ -63,7 +65,7 @@ static const struct usb_device_id id_table[] = { {DEVICE_G1K(0x05c6, 0x9202)}, /* Generic Gobi Modem device */ {DEVICE_G1K(0x05c6, 0x9203)}, /* Generic Gobi Modem device */ {DEVICE_G1K(0x05c6, 0x9222)}, /* Generic Gobi Modem device */ - {DEVICE_G1K(0x05c6, 0x9008)}, /* Generic Gobi QDL device */ +// {DEVICE_G1K(0x05c6, 0x9008)}, /* Generic Gobi QDL device */ {DEVICE_G1K(0x05c6, 0x9009)}, /* Generic Gobi Modem device */ {DEVICE_G1K(0x05c6, 0x9201)}, /* Generic Gobi QDL device */ {DEVICE_G1K(0x05c6, 0x9221)}, /* Generic Gobi QDL device */ diff --git a/kernel/drivers/usb/serial/usb_wwan.c b/kernel/drivers/usb/serial/usb_wwan.c old mode 100644 new mode 100755 index 25a1e1812..8d4055e01 --- a/kernel/drivers/usb/serial/usb_wwan.c +++ b/kernel/drivers/usb/serial/usb_wwan.c @@ -14,6 +14,8 @@ with GSM modems. Issues: - data loss -- one single Receive URB is not nearly enough - controlling the baud rate doesn't make sense + + Based on version modification, the author is Quectel */ #define DRIVER_AUTHOR "Matthias Urlichs " @@ -299,6 +301,8 @@ static void usb_wwan_indat_callback(struct urb *urb) if (status) { dev_dbg(dev, "%s: nonzero status: %d on endpoint %02x.\n", __func__, status, endpoint); + if (status == -ESHUTDOWN || status == -ENOENT || status == -EPROTO) + return; } else { if (urb->actual_length) { tty_insert_flip_string(&port->port, data, @@ -492,7 +496,6 @@ static struct urb *usb_wwan_setup_urb(struct usb_serial_port *port, struct usb_serial *serial = port->serial; struct usb_wwan_intf_private *intfdata = usb_get_serial_data(serial); struct urb *urb; - struct usb_device_descriptor *desc = &serial->dev->descriptor; urb = usb_alloc_urb(0, GFP_KERNEL); /* No ISO */ if (!urb) @@ -502,14 +505,26 @@ static struct urb *usb_wwan_setup_urb(struct usb_serial_port *port, usb_sndbulkpipe(serial->dev, endpoint) | dir, buf, len, callback, ctx); - if (intfdata->use_zlp && dir == USB_DIR_OUT) - urb->transfer_flags |= URB_ZERO_PACKET; - +#if 1 //Added by Quectel for Zero Packet if (dir == USB_DIR_OUT) { - if ((desc->idVendor == cpu_to_le16(0x1286) && - desc->idProduct == cpu_to_le16(0x4e3c))) + if (serial->dev->descriptor.idVendor == cpu_to_le16(0x05C6) && serial->dev->descriptor.idProduct == cpu_to_le16(0x9090)) + urb->transfer_flags |= URB_ZERO_PACKET; + if (serial->dev->descriptor.idVendor == cpu_to_le16(0x05C6) && serial->dev->descriptor.idProduct == cpu_to_le16(0x9003)) + urb->transfer_flags |= URB_ZERO_PACKET; + if (serial->dev->descriptor.idVendor == cpu_to_le16(0x05C6) && serial->dev->descriptor.idProduct == cpu_to_le16(0x9215)) + urb->transfer_flags |= URB_ZERO_PACKET; + if (serial->dev->descriptor.idVendor == cpu_to_le16(0x05C6) && serial->dev->descriptor.idProduct == cpu_to_le16(0x9091)) + urb->transfer_flags |= URB_ZERO_PACKET; + if (serial->dev->descriptor.idVendor == cpu_to_le16(0x05C6) && serial->dev->descriptor.idProduct == cpu_to_le16(0x90DB)) + urb->transfer_flags |= URB_ZERO_PACKET; + if (serial->dev->descriptor.idVendor == cpu_to_le16(0x2C7C)) urb->transfer_flags |= URB_ZERO_PACKET; } +#endif + + if (intfdata->use_zlp && dir == USB_DIR_OUT) + urb->transfer_flags |= URB_ZERO_PACKET; + return urb; } -- 2.25.1