msm: ipa: initial commit of IPA driver
This is a snapshot of IPA from kernel msm-4.4 based on
commit ebc2a18351d4 ("msm: ipa: WA to get PA of sgt_tbl from wlan")
CRs-Fixed: 1077422
Change-Id: I97cf9ee9c104ac5ab5bc0577eb9413264b08a7a5
Signed-off-by: Amir Levy <alevy@codeaurora.org>
diff --git a/drivers/platform/msm/ipa/ipa_v2/rmnet_ipa.c b/drivers/platform/msm/ipa/ipa_v2/rmnet_ipa.c
new file mode 100644
index 0000000..41f339a
--- /dev/null
+++ b/drivers/platform/msm/ipa/ipa_v2/rmnet_ipa.c
@@ -0,0 +1,2892 @@
+/* Copyright (c) 2014-2016, 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.
+ */
+
+/*
+ * WWAN Transport Network Driver.
+ */
+
+#include <linux/completion.h>
+#include <linux/errno.h>
+#include <linux/if_arp.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/of_device.h>
+#include <linux/string.h>
+#include <linux/skbuff.h>
+#include <linux/version.h>
+#include <linux/workqueue.h>
+#include <net/pkt_sched.h>
+#include <soc/qcom/subsystem_restart.h>
+#include <soc/qcom/subsystem_notif.h>
+#include "ipa_qmi_service.h"
+#include <linux/rmnet_ipa_fd_ioctl.h>
+#include <linux/ipa.h>
+#include <uapi/linux/net_map.h>
+
+#include "ipa_trace.h"
+
+#define WWAN_METADATA_SHFT 24
+#define WWAN_METADATA_MASK 0xFF000000
+#define WWAN_DATA_LEN 2000
+#define IPA_RM_INACTIVITY_TIMER 100 /* IPA_RM */
+#define HEADROOM_FOR_QMAP 8 /* for mux header */
+#define TAILROOM 0 /* for padding by mux layer */
+#define MAX_NUM_OF_MUX_CHANNEL 10 /* max mux channels */
+#define UL_FILTER_RULE_HANDLE_START 69
+#define DEFAULT_OUTSTANDING_HIGH_CTL 96
+#define DEFAULT_OUTSTANDING_HIGH 64
+#define DEFAULT_OUTSTANDING_LOW 32
+
+#define IPA_WWAN_DEV_NAME "rmnet_ipa%d"
+#define IPA_WWAN_DEVICE_COUNT (1)
+
+#define IPA_WWAN_RX_SOFTIRQ_THRESH 16
+
+#define INVALID_MUX_ID 0xFF
+#define IPA_QUOTA_REACH_ALERT_MAX_SIZE 64
+#define IPA_QUOTA_REACH_IF_NAME_MAX_SIZE 64
+#define IPA_UEVENT_NUM_EVNP 4 /* number of event pointers */
+
+#define NAPI_WEIGHT 60
+
+static struct net_device *ipa_netdevs[IPA_WWAN_DEVICE_COUNT];
+static struct ipa_sys_connect_params apps_to_ipa_ep_cfg, ipa_to_apps_ep_cfg;
+static u32 qmap_hdr_hdl, dflt_v4_wan_rt_hdl, dflt_v6_wan_rt_hdl;
+static struct rmnet_mux_val mux_channel[MAX_NUM_OF_MUX_CHANNEL];
+static int num_q6_rule, old_num_q6_rule;
+static int rmnet_index;
+static bool egress_set, a7_ul_flt_set;
+static struct workqueue_struct *ipa_rm_q6_workqueue; /* IPA_RM workqueue*/
+static atomic_t is_initialized;
+static atomic_t is_ssr;
+static void *subsys_notify_handle;
+
+u32 apps_to_ipa_hdl, ipa_to_apps_hdl; /* get handler from ipa */
+static struct mutex ipa_to_apps_pipe_handle_guard;
+static int wwan_add_ul_flt_rule_to_ipa(void);
+static int wwan_del_ul_flt_rule_to_ipa(void);
+static void ipa_wwan_msg_free_cb(void*, u32, u32);
+static void ipa_rmnet_rx_cb(void *priv);
+static int ipa_rmnet_poll(struct napi_struct *napi, int budget);
+
+static void wake_tx_queue(struct work_struct *work);
+static DECLARE_WORK(ipa_tx_wakequeue_work, wake_tx_queue);
+
+static void tethering_stats_poll_queue(struct work_struct *work);
+static DECLARE_DELAYED_WORK(ipa_tether_stats_poll_wakequeue_work,
+ tethering_stats_poll_queue);
+
+enum wwan_device_status {
+ WWAN_DEVICE_INACTIVE = 0,
+ WWAN_DEVICE_ACTIVE = 1
+};
+
+struct ipa_rmnet_plat_drv_res {
+ bool ipa_rmnet_ssr;
+ bool ipa_loaduC;
+ bool ipa_advertise_sg_support;
+ bool ipa_napi_enable;
+};
+
+static struct ipa_rmnet_plat_drv_res ipa_rmnet_res;
+/**
+ * struct wwan_private - WWAN private data
+ * @net: network interface struct implemented by this driver
+ * @stats: iface statistics
+ * @outstanding_pkts: number of packets sent to IPA without TX complete ACKed
+ * @outstanding_high: number of outstanding packets allowed
+ * @outstanding_low: number of outstanding packets which shall cause
+ * @ch_id: channel id
+ * @lock: spinlock for mutual exclusion
+ * @device_status: holds device status
+ *
+ * WWAN private - holds all relevant info about WWAN driver
+ */
+struct wwan_private {
+ struct net_device *net;
+ struct net_device_stats stats;
+ atomic_t outstanding_pkts;
+ int outstanding_high_ctl;
+ int outstanding_high;
+ int outstanding_low;
+ uint32_t ch_id;
+ spinlock_t lock;
+ struct completion resource_granted_completion;
+ enum wwan_device_status device_status;
+ struct napi_struct napi;
+};
+
+/**
+* ipa_setup_a7_qmap_hdr() - Setup default a7 qmap hdr
+*
+* Return codes:
+* 0: success
+* -ENOMEM: failed to allocate memory
+* -EPERM: failed to add the tables
+*/
+static int ipa_setup_a7_qmap_hdr(void)
+{
+ struct ipa_ioc_add_hdr *hdr;
+ struct ipa_hdr_add *hdr_entry;
+ u32 pyld_sz;
+ int ret;
+
+ /* install the basic exception header */
+ pyld_sz = sizeof(struct ipa_ioc_add_hdr) + 1 *
+ sizeof(struct ipa_hdr_add);
+ hdr = kzalloc(pyld_sz, GFP_KERNEL);
+ if (!hdr) {
+ IPAWANERR("fail to alloc exception hdr\n");
+ return -ENOMEM;
+ }
+ hdr->num_hdrs = 1;
+ hdr->commit = 1;
+ hdr_entry = &hdr->hdr[0];
+
+ strlcpy(hdr_entry->name, IPA_A7_QMAP_HDR_NAME,
+ IPA_RESOURCE_NAME_MAX);
+ hdr_entry->hdr_len = IPA_QMAP_HEADER_LENGTH; /* 4 bytes */
+
+ if (ipa2_add_hdr(hdr)) {
+ IPAWANERR("fail to add IPA_A7_QMAP hdr\n");
+ ret = -EPERM;
+ goto bail;
+ }
+
+ if (hdr_entry->status) {
+ IPAWANERR("fail to add IPA_A7_QMAP hdr\n");
+ ret = -EPERM;
+ goto bail;
+ }
+ qmap_hdr_hdl = hdr_entry->hdr_hdl;
+
+ ret = 0;
+bail:
+ kfree(hdr);
+ return ret;
+}
+
+static void ipa_del_a7_qmap_hdr(void)
+{
+ struct ipa_ioc_del_hdr *del_hdr;
+ struct ipa_hdr_del *hdl_entry;
+ u32 pyld_sz;
+ int ret;
+
+ pyld_sz = sizeof(struct ipa_ioc_del_hdr) + 1 *
+ sizeof(struct ipa_hdr_del);
+ del_hdr = kzalloc(pyld_sz, GFP_KERNEL);
+ if (!del_hdr) {
+ IPAWANERR("fail to alloc exception hdr_del\n");
+ return;
+ }
+
+ del_hdr->commit = 1;
+ del_hdr->num_hdls = 1;
+ hdl_entry = &del_hdr->hdl[0];
+ hdl_entry->hdl = qmap_hdr_hdl;
+
+ ret = ipa2_del_hdr(del_hdr);
+ if (ret || hdl_entry->status)
+ IPAWANERR("ipa2_del_hdr failed\n");
+ else
+ IPAWANDBG("hdrs deletion done\n");
+
+ qmap_hdr_hdl = 0;
+ kfree(del_hdr);
+}
+
+static void ipa_del_qmap_hdr(uint32_t hdr_hdl)
+{
+ struct ipa_ioc_del_hdr *del_hdr;
+ struct ipa_hdr_del *hdl_entry;
+ u32 pyld_sz;
+ int ret;
+
+ if (hdr_hdl == 0) {
+ IPAWANERR("Invalid hdr_hdl provided\n");
+ return;
+ }
+
+ pyld_sz = sizeof(struct ipa_ioc_del_hdr) + 1 *
+ sizeof(struct ipa_hdr_del);
+ del_hdr = kzalloc(pyld_sz, GFP_KERNEL);
+ if (!del_hdr) {
+ IPAWANERR("fail to alloc exception hdr_del\n");
+ return;
+ }
+
+ del_hdr->commit = 1;
+ del_hdr->num_hdls = 1;
+ hdl_entry = &del_hdr->hdl[0];
+ hdl_entry->hdl = hdr_hdl;
+
+ ret = ipa2_del_hdr(del_hdr);
+ if (ret || hdl_entry->status)
+ IPAWANERR("ipa2_del_hdr failed\n");
+ else
+ IPAWANDBG("header deletion done\n");
+
+ qmap_hdr_hdl = 0;
+ kfree(del_hdr);
+}
+
+static void ipa_del_mux_qmap_hdrs(void)
+{
+ int index;
+
+ for (index = 0; index < rmnet_index; index++) {
+ ipa_del_qmap_hdr(mux_channel[index].hdr_hdl);
+ mux_channel[index].hdr_hdl = 0;
+ }
+}
+
+static int ipa_add_qmap_hdr(uint32_t mux_id, uint32_t *hdr_hdl)
+{
+ struct ipa_ioc_add_hdr *hdr;
+ struct ipa_hdr_add *hdr_entry;
+ char hdr_name[IPA_RESOURCE_NAME_MAX];
+ u32 pyld_sz;
+ int ret;
+
+ pyld_sz = sizeof(struct ipa_ioc_add_hdr) + 1 *
+ sizeof(struct ipa_hdr_add);
+ hdr = kzalloc(pyld_sz, GFP_KERNEL);
+ if (!hdr) {
+ IPAWANERR("fail to alloc exception hdr\n");
+ return -ENOMEM;
+ }
+ hdr->num_hdrs = 1;
+ hdr->commit = 1;
+ hdr_entry = &hdr->hdr[0];
+
+ snprintf(hdr_name, IPA_RESOURCE_NAME_MAX, "%s%d",
+ A2_MUX_HDR_NAME_V4_PREF,
+ mux_id);
+ strlcpy(hdr_entry->name, hdr_name,
+ IPA_RESOURCE_NAME_MAX);
+
+ hdr_entry->hdr_len = IPA_QMAP_HEADER_LENGTH; /* 4 bytes */
+ hdr_entry->hdr[1] = (uint8_t) mux_id;
+ IPAWANDBG("header (%s) with mux-id: (%d)\n",
+ hdr_name,
+ hdr_entry->hdr[1]);
+ if (ipa2_add_hdr(hdr)) {
+ IPAWANERR("fail to add IPA_QMAP hdr\n");
+ ret = -EPERM;
+ goto bail;
+ }
+
+ if (hdr_entry->status) {
+ IPAWANERR("fail to add IPA_QMAP hdr\n");
+ ret = -EPERM;
+ goto bail;
+ }
+
+ ret = 0;
+ *hdr_hdl = hdr_entry->hdr_hdl;
+bail:
+ kfree(hdr);
+ return ret;
+}
+
+/**
+* ipa_setup_dflt_wan_rt_tables() - Setup default wan routing tables
+*
+* Return codes:
+* 0: success
+* -ENOMEM: failed to allocate memory
+* -EPERM: failed to add the tables
+*/
+static int ipa_setup_dflt_wan_rt_tables(void)
+{
+ struct ipa_ioc_add_rt_rule *rt_rule;
+ struct ipa_rt_rule_add *rt_rule_entry;
+
+ rt_rule =
+ kzalloc(sizeof(struct ipa_ioc_add_rt_rule) + 1 *
+ sizeof(struct ipa_rt_rule_add), GFP_KERNEL);
+ if (!rt_rule) {
+ IPAWANERR("fail to alloc mem\n");
+ return -ENOMEM;
+ }
+ /* setup a default v4 route to point to Apps */
+ rt_rule->num_rules = 1;
+ rt_rule->commit = 1;
+ rt_rule->ip = IPA_IP_v4;
+ strlcpy(rt_rule->rt_tbl_name, IPA_DFLT_WAN_RT_TBL_NAME,
+ IPA_RESOURCE_NAME_MAX);
+
+ rt_rule_entry = &rt_rule->rules[0];
+ rt_rule_entry->at_rear = 1;
+ rt_rule_entry->rule.dst = IPA_CLIENT_APPS_WAN_CONS;
+ rt_rule_entry->rule.hdr_hdl = qmap_hdr_hdl;
+
+ if (ipa2_add_rt_rule(rt_rule)) {
+ IPAWANERR("fail to add dflt_wan v4 rule\n");
+ kfree(rt_rule);
+ return -EPERM;
+ }
+
+ IPAWANDBG("dflt v4 rt rule hdl=%x\n", rt_rule_entry->rt_rule_hdl);
+ dflt_v4_wan_rt_hdl = rt_rule_entry->rt_rule_hdl;
+
+ /* setup a default v6 route to point to A5 */
+ rt_rule->ip = IPA_IP_v6;
+ if (ipa2_add_rt_rule(rt_rule)) {
+ IPAWANERR("fail to add dflt_wan v6 rule\n");
+ kfree(rt_rule);
+ return -EPERM;
+ }
+ IPAWANDBG("dflt v6 rt rule hdl=%x\n", rt_rule_entry->rt_rule_hdl);
+ dflt_v6_wan_rt_hdl = rt_rule_entry->rt_rule_hdl;
+
+ kfree(rt_rule);
+ return 0;
+}
+
+static void ipa_del_dflt_wan_rt_tables(void)
+{
+ struct ipa_ioc_del_rt_rule *rt_rule;
+ struct ipa_rt_rule_del *rt_rule_entry;
+ int len;
+
+ len = sizeof(struct ipa_ioc_del_rt_rule) + 1 *
+ sizeof(struct ipa_rt_rule_del);
+ rt_rule = kzalloc(len, GFP_KERNEL);
+ if (!rt_rule) {
+ IPAWANERR("unable to allocate memory for del route rule\n");
+ return;
+ }
+
+ memset(rt_rule, 0, len);
+ rt_rule->commit = 1;
+ rt_rule->num_hdls = 1;
+ rt_rule->ip = IPA_IP_v4;
+
+ rt_rule_entry = &rt_rule->hdl[0];
+ rt_rule_entry->status = -1;
+ rt_rule_entry->hdl = dflt_v4_wan_rt_hdl;
+
+ IPAWANERR("Deleting Route hdl:(0x%x) with ip type: %d\n",
+ rt_rule_entry->hdl, IPA_IP_v4);
+ if (ipa2_del_rt_rule(rt_rule) ||
+ (rt_rule_entry->status)) {
+ IPAWANERR("Routing rule deletion failed!\n");
+ }
+
+ rt_rule->ip = IPA_IP_v6;
+ rt_rule_entry->hdl = dflt_v6_wan_rt_hdl;
+ IPAWANERR("Deleting Route hdl:(0x%x) with ip type: %d\n",
+ rt_rule_entry->hdl, IPA_IP_v6);
+ if (ipa2_del_rt_rule(rt_rule) ||
+ (rt_rule_entry->status)) {
+ IPAWANERR("Routing rule deletion failed!\n");
+ }
+
+ kfree(rt_rule);
+}
+
+int copy_ul_filter_rule_to_ipa(struct ipa_install_fltr_rule_req_msg_v01
+ *rule_req, uint32_t *rule_hdl)
+{
+ int i, j;
+
+ if (rule_req->filter_spec_list_valid == true) {
+ num_q6_rule = rule_req->filter_spec_list_len;
+ IPAWANDBG("Received (%d) install_flt_req\n", num_q6_rule);
+ } else {
+ num_q6_rule = 0;
+ IPAWANERR("got no UL rules from modem\n");
+ return -EINVAL;
+ }
+
+ /* copy UL filter rules from Modem*/
+ for (i = 0; i < num_q6_rule; i++) {
+ /* check if rules overside the cache*/
+ if (i == MAX_NUM_Q6_RULE) {
+ IPAWANERR("Reaching (%d) max cache ",
+ MAX_NUM_Q6_RULE);
+ IPAWANERR(" however total (%d)\n",
+ num_q6_rule);
+ goto failure;
+ }
+ /* construct UL_filter_rule handler QMI use-cas */
+ ipa_qmi_ctx->q6_ul_filter_rule[i].filter_hdl =
+ UL_FILTER_RULE_HANDLE_START + i;
+ rule_hdl[i] = ipa_qmi_ctx->q6_ul_filter_rule[i].filter_hdl;
+ ipa_qmi_ctx->q6_ul_filter_rule[i].ip =
+ rule_req->filter_spec_list[i].ip_type;
+ ipa_qmi_ctx->q6_ul_filter_rule[i].action =
+ rule_req->filter_spec_list[i].filter_action;
+ if (rule_req->filter_spec_list[i].is_routing_table_index_valid
+ == true)
+ ipa_qmi_ctx->q6_ul_filter_rule[i].rt_tbl_idx =
+ rule_req->filter_spec_list[i].route_table_index;
+ if (rule_req->filter_spec_list[i].is_mux_id_valid == true)
+ ipa_qmi_ctx->q6_ul_filter_rule[i].mux_id =
+ rule_req->filter_spec_list[i].mux_id;
+ ipa_qmi_ctx->q6_ul_filter_rule[i].eq_attrib.rule_eq_bitmap =
+ rule_req->filter_spec_list[i].filter_rule.
+ rule_eq_bitmap;
+ ipa_qmi_ctx->q6_ul_filter_rule[i].eq_attrib.tos_eq_present =
+ rule_req->filter_spec_list[i].filter_rule.
+ tos_eq_present;
+ ipa_qmi_ctx->q6_ul_filter_rule[i].eq_attrib.tos_eq =
+ rule_req->filter_spec_list[i].filter_rule.tos_eq;
+ ipa_qmi_ctx->q6_ul_filter_rule[i].eq_attrib.
+ protocol_eq_present = rule_req->filter_spec_list[i].
+ filter_rule.protocol_eq_present;
+ ipa_qmi_ctx->q6_ul_filter_rule[i].eq_attrib.protocol_eq =
+ rule_req->filter_spec_list[i].filter_rule.
+ protocol_eq;
+
+ ipa_qmi_ctx->q6_ul_filter_rule[i].eq_attrib.
+ num_ihl_offset_range_16 = rule_req->filter_spec_list[i].
+ filter_rule.num_ihl_offset_range_16;
+ for (j = 0; j < ipa_qmi_ctx->q6_ul_filter_rule[i].eq_attrib.
+ num_ihl_offset_range_16; j++) {
+ ipa_qmi_ctx->q6_ul_filter_rule[i].eq_attrib.
+ ihl_offset_range_16[j].offset = rule_req->
+ filter_spec_list[i].filter_rule.
+ ihl_offset_range_16[j].offset;
+ ipa_qmi_ctx->q6_ul_filter_rule[i].eq_attrib.
+ ihl_offset_range_16[j].range_low = rule_req->
+ filter_spec_list[i].filter_rule.
+ ihl_offset_range_16[j].range_low;
+ ipa_qmi_ctx->q6_ul_filter_rule[i].eq_attrib.
+ ihl_offset_range_16[j].range_high = rule_req->
+ filter_spec_list[i].filter_rule.
+ ihl_offset_range_16[j].range_high;
+ }
+ ipa_qmi_ctx->q6_ul_filter_rule[i].eq_attrib.num_offset_meq_32 =
+ rule_req->filter_spec_list[i].filter_rule.
+ num_offset_meq_32;
+ for (j = 0; j < ipa_qmi_ctx->q6_ul_filter_rule[i].eq_attrib.
+ num_offset_meq_32; j++) {
+ ipa_qmi_ctx->q6_ul_filter_rule[i].eq_attrib.
+ offset_meq_32[j].offset = rule_req->filter_spec_list[i].
+ filter_rule.offset_meq_32[j].offset;
+ ipa_qmi_ctx->q6_ul_filter_rule[i].eq_attrib.
+ offset_meq_32[j].mask = rule_req->filter_spec_list[i].
+ filter_rule.offset_meq_32[j].mask;
+ ipa_qmi_ctx->q6_ul_filter_rule[i].eq_attrib.
+ offset_meq_32[j].value = rule_req->filter_spec_list[i].
+ filter_rule.offset_meq_32[j].value;
+ }
+
+ ipa_qmi_ctx->q6_ul_filter_rule[i].eq_attrib.tc_eq_present =
+ rule_req->filter_spec_list[i].filter_rule.tc_eq_present;
+ ipa_qmi_ctx->q6_ul_filter_rule[i].eq_attrib.tc_eq =
+ rule_req->filter_spec_list[i].filter_rule.tc_eq;
+ ipa_qmi_ctx->q6_ul_filter_rule[i].eq_attrib.fl_eq_present =
+ rule_req->filter_spec_list[i].filter_rule.
+ flow_eq_present;
+ ipa_qmi_ctx->q6_ul_filter_rule[i].eq_attrib.fl_eq =
+ rule_req->filter_spec_list[i].filter_rule.flow_eq;
+ ipa_qmi_ctx->q6_ul_filter_rule[i].eq_attrib.
+ ihl_offset_eq_16_present = rule_req->filter_spec_list[i].
+ filter_rule.ihl_offset_eq_16_present;
+ ipa_qmi_ctx->q6_ul_filter_rule[i].eq_attrib.
+ ihl_offset_eq_16.offset = rule_req->filter_spec_list[i].
+ filter_rule.ihl_offset_eq_16.offset;
+ ipa_qmi_ctx->q6_ul_filter_rule[i].eq_attrib.
+ ihl_offset_eq_16.value = rule_req->filter_spec_list[i].
+ filter_rule.ihl_offset_eq_16.value;
+
+ ipa_qmi_ctx->q6_ul_filter_rule[i].eq_attrib.
+ ihl_offset_eq_32_present = rule_req->filter_spec_list[i].
+ filter_rule.ihl_offset_eq_32_present;
+ ipa_qmi_ctx->q6_ul_filter_rule[i].eq_attrib.
+ ihl_offset_eq_32.offset = rule_req->filter_spec_list[i].
+ filter_rule.ihl_offset_eq_32.offset;
+ ipa_qmi_ctx->q6_ul_filter_rule[i].eq_attrib.
+ ihl_offset_eq_32.value = rule_req->filter_spec_list[i].
+ filter_rule.ihl_offset_eq_32.value;
+
+ ipa_qmi_ctx->q6_ul_filter_rule[i].eq_attrib.
+ num_ihl_offset_meq_32 = rule_req->filter_spec_list[i].
+ filter_rule.num_ihl_offset_meq_32;
+ for (j = 0; j < ipa_qmi_ctx->q6_ul_filter_rule[i].
+ eq_attrib.num_ihl_offset_meq_32; j++) {
+ ipa_qmi_ctx->q6_ul_filter_rule[i].eq_attrib.
+ ihl_offset_meq_32[j].offset = rule_req->
+ filter_spec_list[i].filter_rule.
+ ihl_offset_meq_32[j].offset;
+ ipa_qmi_ctx->q6_ul_filter_rule[i].eq_attrib.
+ ihl_offset_meq_32[j].mask = rule_req->
+ filter_spec_list[i].filter_rule.
+ ihl_offset_meq_32[j].mask;
+ ipa_qmi_ctx->q6_ul_filter_rule[i].eq_attrib.
+ ihl_offset_meq_32[j].value = rule_req->
+ filter_spec_list[i].filter_rule.
+ ihl_offset_meq_32[j].value;
+ }
+ ipa_qmi_ctx->q6_ul_filter_rule[i].eq_attrib.num_offset_meq_128 =
+ rule_req->filter_spec_list[i].filter_rule.
+ num_offset_meq_128;
+ for (j = 0; j < ipa_qmi_ctx->q6_ul_filter_rule[i].eq_attrib.
+ num_offset_meq_128; j++) {
+ ipa_qmi_ctx->q6_ul_filter_rule[i].eq_attrib.
+ offset_meq_128[j].offset = rule_req->
+ filter_spec_list[i].filter_rule.
+ offset_meq_128[j].offset;
+ memcpy(ipa_qmi_ctx->q6_ul_filter_rule[i].eq_attrib.
+ offset_meq_128[j].mask,
+ rule_req->filter_spec_list[i].
+ filter_rule.offset_meq_128[j].mask, 16);
+ memcpy(ipa_qmi_ctx->q6_ul_filter_rule[i].eq_attrib.
+ offset_meq_128[j].value, rule_req->
+ filter_spec_list[i].filter_rule.
+ offset_meq_128[j].value, 16);
+ }
+
+ ipa_qmi_ctx->q6_ul_filter_rule[i].eq_attrib.
+ metadata_meq32_present = rule_req->filter_spec_list[i].
+ filter_rule.metadata_meq32_present;
+ ipa_qmi_ctx->q6_ul_filter_rule[i].eq_attrib.
+ metadata_meq32.offset = rule_req->filter_spec_list[i].
+ filter_rule.metadata_meq32.offset;
+ ipa_qmi_ctx->q6_ul_filter_rule[i].eq_attrib.
+ metadata_meq32.mask = rule_req->filter_spec_list[i].
+ filter_rule.metadata_meq32.mask;
+ ipa_qmi_ctx->q6_ul_filter_rule[i].eq_attrib.metadata_meq32.
+ value = rule_req->filter_spec_list[i].filter_rule.
+ metadata_meq32.value;
+ ipa_qmi_ctx->q6_ul_filter_rule[i].eq_attrib.
+ ipv4_frag_eq_present = rule_req->filter_spec_list[i].
+ filter_rule.ipv4_frag_eq_present;
+ }
+
+ if (rule_req->xlat_filter_indices_list_valid) {
+ if (rule_req->xlat_filter_indices_list_len > num_q6_rule) {
+ IPAWANERR("Number of xlat indices is not valid: %d\n",
+ rule_req->xlat_filter_indices_list_len);
+ goto failure;
+ }
+ IPAWANDBG("Receive %d XLAT indices: ",
+ rule_req->xlat_filter_indices_list_len);
+ for (i = 0; i < rule_req->xlat_filter_indices_list_len; i++)
+ IPAWANDBG("%d ", rule_req->xlat_filter_indices_list[i]);
+ IPAWANDBG("\n");
+
+ for (i = 0; i < rule_req->xlat_filter_indices_list_len; i++) {
+ if (rule_req->xlat_filter_indices_list[i]
+ >= num_q6_rule) {
+ IPAWANERR("Xlat rule idx is wrong: %d\n",
+ rule_req->xlat_filter_indices_list[i]);
+ goto failure;
+ } else {
+ ipa_qmi_ctx->q6_ul_filter_rule
+ [rule_req->xlat_filter_indices_list[i]]
+ .is_xlat_rule = 1;
+ IPAWANDBG("Rule %d is xlat rule\n",
+ rule_req->xlat_filter_indices_list[i]);
+ }
+ }
+ }
+ goto success;
+
+failure:
+ num_q6_rule = 0;
+ memset(ipa_qmi_ctx->q6_ul_filter_rule, 0,
+ sizeof(ipa_qmi_ctx->q6_ul_filter_rule));
+ return -EINVAL;
+
+success:
+ return 0;
+}
+
+static int wwan_add_ul_flt_rule_to_ipa(void)
+{
+ u32 pyld_sz;
+ int i, retval = 0;
+ int num_v4_rule = 0, num_v6_rule = 0;
+ struct ipa_ioc_add_flt_rule *param;
+ struct ipa_flt_rule_add flt_rule_entry;
+ struct ipa_fltr_installed_notif_req_msg_v01 *req;
+
+ if (ipa_qmi_ctx == NULL) {
+ IPAWANERR("ipa_qmi_ctx is NULL!\n");
+ return -EFAULT;
+ }
+
+ pyld_sz = sizeof(struct ipa_ioc_add_flt_rule) +
+ sizeof(struct ipa_flt_rule_add);
+ param = kzalloc(pyld_sz, GFP_KERNEL);
+ if (!param)
+ return -ENOMEM;
+
+ req = (struct ipa_fltr_installed_notif_req_msg_v01 *)
+ kzalloc(sizeof(struct ipa_fltr_installed_notif_req_msg_v01),
+ GFP_KERNEL);
+ if (!req) {
+ kfree(param);
+ return -ENOMEM;
+ }
+
+ param->commit = 1;
+ param->ep = IPA_CLIENT_APPS_LAN_WAN_PROD;
+ param->global = false;
+ param->num_rules = (uint8_t)1;
+
+ mutex_lock(&ipa_qmi_lock);
+ for (i = 0; i < num_q6_rule; i++) {
+ param->ip = ipa_qmi_ctx->q6_ul_filter_rule[i].ip;
+ memset(&flt_rule_entry, 0, sizeof(struct ipa_flt_rule_add));
+ flt_rule_entry.at_rear = true;
+ flt_rule_entry.rule.action =
+ ipa_qmi_ctx->q6_ul_filter_rule[i].action;
+ flt_rule_entry.rule.rt_tbl_idx
+ = ipa_qmi_ctx->q6_ul_filter_rule[i].rt_tbl_idx;
+ flt_rule_entry.rule.retain_hdr = true;
+
+ /* debug rt-hdl*/
+ IPAWANDBG("install-IPA index(%d),rt-tbl:(%d)\n",
+ i, flt_rule_entry.rule.rt_tbl_idx);
+ flt_rule_entry.rule.eq_attrib_type = true;
+ memcpy(&(flt_rule_entry.rule.eq_attrib),
+ &ipa_qmi_ctx->q6_ul_filter_rule[i].eq_attrib,
+ sizeof(struct ipa_ipfltri_rule_eq));
+ memcpy(&(param->rules[0]), &flt_rule_entry,
+ sizeof(struct ipa_flt_rule_add));
+ if (ipa2_add_flt_rule((struct ipa_ioc_add_flt_rule *)param)) {
+ retval = -EFAULT;
+ IPAWANERR("add A7 UL filter rule(%d) failed\n", i);
+ } else {
+ /* store the rule handler */
+ ipa_qmi_ctx->q6_ul_filter_rule_hdl[i] =
+ param->rules[0].flt_rule_hdl;
+ }
+ }
+ mutex_unlock(&ipa_qmi_lock);
+
+ /* send ipa_fltr_installed_notif_req_msg_v01 to Q6*/
+ req->source_pipe_index =
+ ipa2_get_ep_mapping(IPA_CLIENT_APPS_LAN_WAN_PROD);
+ req->install_status = QMI_RESULT_SUCCESS_V01;
+ req->filter_index_list_len = num_q6_rule;
+ mutex_lock(&ipa_qmi_lock);
+ for (i = 0; i < num_q6_rule; i++) {
+ if (ipa_qmi_ctx->q6_ul_filter_rule[i].ip == IPA_IP_v4) {
+ req->filter_index_list[i].filter_index = num_v4_rule;
+ num_v4_rule++;
+ } else {
+ req->filter_index_list[i].filter_index = num_v6_rule;
+ num_v6_rule++;
+ }
+ req->filter_index_list[i].filter_handle =
+ ipa_qmi_ctx->q6_ul_filter_rule[i].filter_hdl;
+ }
+ mutex_unlock(&ipa_qmi_lock);
+ if (qmi_filter_notify_send(req)) {
+ IPAWANDBG("add filter rule index on A7-RX failed\n");
+ retval = -EFAULT;
+ }
+ old_num_q6_rule = num_q6_rule;
+ IPAWANDBG("add (%d) filter rule index on A7-RX\n",
+ old_num_q6_rule);
+ kfree(param);
+ kfree(req);
+ return retval;
+}
+
+static int wwan_del_ul_flt_rule_to_ipa(void)
+{
+ u32 pyld_sz;
+ int i, retval = 0;
+ struct ipa_ioc_del_flt_rule *param;
+ struct ipa_flt_rule_del flt_rule_entry;
+
+ pyld_sz = sizeof(struct ipa_ioc_del_flt_rule) +
+ sizeof(struct ipa_flt_rule_del);
+ param = kzalloc(pyld_sz, GFP_KERNEL);
+ if (!param) {
+ IPAWANERR("kzalloc failed\n");
+ return -ENOMEM;
+ }
+
+ param->commit = 1;
+ param->num_hdls = (uint8_t) 1;
+
+ for (i = 0; i < old_num_q6_rule; i++) {
+ param->ip = ipa_qmi_ctx->q6_ul_filter_rule[i].ip;
+ memset(&flt_rule_entry, 0, sizeof(struct ipa_flt_rule_del));
+ flt_rule_entry.hdl = ipa_qmi_ctx->q6_ul_filter_rule_hdl[i];
+ /* debug rt-hdl*/
+ IPAWANDBG("delete-IPA rule index(%d)\n", i);
+ memcpy(&(param->hdl[0]), &flt_rule_entry,
+ sizeof(struct ipa_flt_rule_del));
+ if (ipa2_del_flt_rule((struct ipa_ioc_del_flt_rule *)param)) {
+ IPAWANERR("del A7 UL filter rule(%d) failed\n", i);
+ kfree(param);
+ return -EFAULT;
+ }
+ }
+
+ /* set UL filter-rule add-indication */
+ a7_ul_flt_set = false;
+ old_num_q6_rule = 0;
+
+ kfree(param);
+ return retval;
+}
+
+static int find_mux_channel_index(uint32_t mux_id)
+{
+ int i;
+
+ for (i = 0; i < MAX_NUM_OF_MUX_CHANNEL; i++) {
+ if (mux_id == mux_channel[i].mux_id)
+ return i;
+ }
+ return MAX_NUM_OF_MUX_CHANNEL;
+}
+
+static int find_vchannel_name_index(const char *vchannel_name)
+{
+ int i;
+
+ for (i = 0; i < MAX_NUM_OF_MUX_CHANNEL; i++) {
+ if (strcmp(mux_channel[i].vchannel_name, vchannel_name == 0))
+ return i;
+ }
+ return MAX_NUM_OF_MUX_CHANNEL;
+}
+
+static int wwan_register_to_ipa(int index)
+{
+ struct ipa_tx_intf tx_properties = {0};
+ struct ipa_ioc_tx_intf_prop tx_ioc_properties[2] = { {0}, {0} };
+ struct ipa_ioc_tx_intf_prop *tx_ipv4_property;
+ struct ipa_ioc_tx_intf_prop *tx_ipv6_property;
+ struct ipa_rx_intf rx_properties = {0};
+ struct ipa_ioc_rx_intf_prop rx_ioc_properties[2] = { {0}, {0} };
+ struct ipa_ioc_rx_intf_prop *rx_ipv4_property;
+ struct ipa_ioc_rx_intf_prop *rx_ipv6_property;
+ struct ipa_ext_intf ext_properties = {0};
+ struct ipa_ioc_ext_intf_prop *ext_ioc_properties;
+ u32 pyld_sz;
+ int ret = 0, i;
+
+ IPAWANDBG("index(%d) device[%s]:\n", index,
+ mux_channel[index].vchannel_name);
+ if (!mux_channel[index].mux_hdr_set) {
+ ret = ipa_add_qmap_hdr(mux_channel[index].mux_id,
+ &mux_channel[index].hdr_hdl);
+ if (ret) {
+ IPAWANERR("ipa_add_mux_hdr failed (%d)\n", index);
+ return ret;
+ }
+ mux_channel[index].mux_hdr_set = true;
+ }
+ tx_properties.prop = tx_ioc_properties;
+ tx_ipv4_property = &tx_properties.prop[0];
+ tx_ipv4_property->ip = IPA_IP_v4;
+ tx_ipv4_property->dst_pipe = IPA_CLIENT_APPS_WAN_CONS;
+ snprintf(tx_ipv4_property->hdr_name, IPA_RESOURCE_NAME_MAX, "%s%d",
+ A2_MUX_HDR_NAME_V4_PREF,
+ mux_channel[index].mux_id);
+ tx_ipv6_property = &tx_properties.prop[1];
+ tx_ipv6_property->ip = IPA_IP_v6;
+ tx_ipv6_property->dst_pipe = IPA_CLIENT_APPS_WAN_CONS;
+ /* no need use A2_MUX_HDR_NAME_V6_PREF, same header */
+ snprintf(tx_ipv6_property->hdr_name, IPA_RESOURCE_NAME_MAX, "%s%d",
+ A2_MUX_HDR_NAME_V4_PREF,
+ mux_channel[index].mux_id);
+ tx_properties.num_props = 2;
+
+ rx_properties.prop = rx_ioc_properties;
+ rx_ipv4_property = &rx_properties.prop[0];
+ rx_ipv4_property->ip = IPA_IP_v4;
+ rx_ipv4_property->attrib.attrib_mask |= IPA_FLT_META_DATA;
+ rx_ipv4_property->attrib.meta_data =
+ mux_channel[index].mux_id << WWAN_METADATA_SHFT;
+ rx_ipv4_property->attrib.meta_data_mask = WWAN_METADATA_MASK;
+ rx_ipv4_property->src_pipe = IPA_CLIENT_APPS_LAN_WAN_PROD;
+ rx_ipv6_property = &rx_properties.prop[1];
+ rx_ipv6_property->ip = IPA_IP_v6;
+ rx_ipv6_property->attrib.attrib_mask |= IPA_FLT_META_DATA;
+ rx_ipv6_property->attrib.meta_data =
+ mux_channel[index].mux_id << WWAN_METADATA_SHFT;
+ rx_ipv6_property->attrib.meta_data_mask = WWAN_METADATA_MASK;
+ rx_ipv6_property->src_pipe = IPA_CLIENT_APPS_LAN_WAN_PROD;
+ rx_properties.num_props = 2;
+
+ pyld_sz = num_q6_rule *
+ sizeof(struct ipa_ioc_ext_intf_prop);
+ ext_ioc_properties = kmalloc(pyld_sz, GFP_KERNEL);
+ if (!ext_ioc_properties) {
+ IPAWANERR("Error allocate memory\n");
+ return -ENOMEM;
+ }
+
+ ext_properties.prop = ext_ioc_properties;
+ ext_properties.excp_pipe_valid = true;
+ ext_properties.excp_pipe = IPA_CLIENT_APPS_WAN_CONS;
+ ext_properties.num_props = num_q6_rule;
+ for (i = 0; i < num_q6_rule; i++) {
+ memcpy(&(ext_properties.prop[i]),
+ &(ipa_qmi_ctx->q6_ul_filter_rule[i]),
+ sizeof(struct ipa_ioc_ext_intf_prop));
+ ext_properties.prop[i].mux_id = mux_channel[index].mux_id;
+ IPAWANDBG("index %d ip: %d rt-tbl:%d\n", i,
+ ext_properties.prop[i].ip,
+ ext_properties.prop[i].rt_tbl_idx);
+ IPAWANDBG("action: %d mux:%d\n",
+ ext_properties.prop[i].action,
+ ext_properties.prop[i].mux_id);
+ }
+ ret = ipa2_register_intf_ext(mux_channel[index].
+ vchannel_name, &tx_properties,
+ &rx_properties, &ext_properties);
+ if (ret) {
+ IPAWANERR("[%s]:ipa2_register_intf failed %d\n",
+ mux_channel[index].vchannel_name, ret);
+ goto fail;
+ }
+ mux_channel[index].ul_flt_reg = true;
+fail:
+ kfree(ext_ioc_properties);
+ return ret;
+}
+
+static void ipa_cleanup_deregister_intf(void)
+{
+ int i;
+ int ret;
+
+ for (i = 0; i < rmnet_index; i++) {
+ if (mux_channel[i].ul_flt_reg) {
+ ret = ipa2_deregister_intf(
+ mux_channel[i].vchannel_name);
+ if (ret < 0) {
+ IPAWANERR("de-register device %s(%d) failed\n",
+ mux_channel[i].vchannel_name,
+ i);
+ return;
+ }
+ IPAWANDBG("de-register device %s(%d) success\n",
+ mux_channel[i].vchannel_name,
+ i);
+ }
+ mux_channel[i].ul_flt_reg = false;
+ }
+}
+
+int wwan_update_mux_channel_prop(void)
+{
+ int ret = 0, i;
+ /* install UL filter rules */
+ if (egress_set) {
+ if (ipa_qmi_ctx &&
+ ipa_qmi_ctx->modem_cfg_emb_pipe_flt == false) {
+ IPAWANDBG("setup UL filter rules\n");
+ if (a7_ul_flt_set) {
+ IPAWANDBG("del previous UL filter rules\n");
+ /* delete rule hdlers */
+ ret = wwan_del_ul_flt_rule_to_ipa();
+ if (ret) {
+ IPAWANERR("failed to del old rules\n");
+ return -EINVAL;
+ }
+ IPAWANDBG("deleted old UL rules\n");
+ }
+ ret = wwan_add_ul_flt_rule_to_ipa();
+ }
+ if (ret)
+ IPAWANERR("failed to install UL rules\n");
+ else
+ a7_ul_flt_set = true;
+ }
+ /* update Tx/Rx/Ext property */
+ IPAWANDBG("update Tx/Rx/Ext property in IPA\n");
+ if (rmnet_index == 0) {
+ IPAWANDBG("no Tx/Rx/Ext property registered in IPA\n");
+ return ret;
+ }
+
+ ipa_cleanup_deregister_intf();
+
+ for (i = 0; i < rmnet_index; i++) {
+ ret = wwan_register_to_ipa(i);
+ if (ret < 0) {
+ IPAWANERR("failed to re-regist %s, mux %d, index %d\n",
+ mux_channel[i].vchannel_name,
+ mux_channel[i].mux_id,
+ i);
+ return -ENODEV;
+ }
+ IPAWANERR("dev(%s) has registered to IPA\n",
+ mux_channel[i].vchannel_name);
+ mux_channel[i].ul_flt_reg = true;
+ }
+ return ret;
+}
+
+#ifdef INIT_COMPLETION
+#define reinit_completion(x) INIT_COMPLETION(*(x))
+#endif /* INIT_COMPLETION */
+
+static int __ipa_wwan_open(struct net_device *dev)
+{
+ struct wwan_private *wwan_ptr = netdev_priv(dev);
+
+ IPAWANDBG("[%s] __wwan_open()\n", dev->name);
+ if (wwan_ptr->device_status != WWAN_DEVICE_ACTIVE)
+ reinit_completion(&wwan_ptr->resource_granted_completion);
+ wwan_ptr->device_status = WWAN_DEVICE_ACTIVE;
+
+ if (ipa_rmnet_res.ipa_napi_enable)
+ napi_enable(&(wwan_ptr->napi));
+ return 0;
+}
+
+/**
+ * wwan_open() - Opens the wwan network interface. Opens logical
+ * channel on A2 MUX driver and starts the network stack queue
+ *
+ * @dev: network device
+ *
+ * Return codes:
+ * 0: success
+ * -ENODEV: Error while opening logical channel on A2 MUX driver
+ */
+static int ipa_wwan_open(struct net_device *dev)
+{
+ int rc = 0;
+
+ IPAWANDBG("[%s] wwan_open()\n", dev->name);
+ rc = __ipa_wwan_open(dev);
+ if (rc == 0)
+ netif_start_queue(dev);
+ return rc;
+}
+
+static int __ipa_wwan_close(struct net_device *dev)
+{
+ struct wwan_private *wwan_ptr = netdev_priv(dev);
+ int rc = 0;
+
+ if (wwan_ptr->device_status == WWAN_DEVICE_ACTIVE) {
+ wwan_ptr->device_status = WWAN_DEVICE_INACTIVE;
+ /* do not close wwan port once up, this causes
+ * remote side to hang if tried to open again
+ */
+ reinit_completion(&wwan_ptr->resource_granted_completion);
+ if (ipa_rmnet_res.ipa_napi_enable)
+ napi_disable(&(wwan_ptr->napi));
+ rc = ipa2_deregister_intf(dev->name);
+ if (rc) {
+ IPAWANERR("[%s]: ipa2_deregister_intf failed %d\n",
+ dev->name, rc);
+ return rc;
+ }
+ return rc;
+ } else {
+ return -EBADF;
+ }
+}
+
+/**
+ * ipa_wwan_stop() - Stops the wwan network interface. Closes
+ * logical channel on A2 MUX driver and stops the network stack
+ * queue
+ *
+ * @dev: network device
+ *
+ * Return codes:
+ * 0: success
+ * -ENODEV: Error while opening logical channel on A2 MUX driver
+ */
+static int ipa_wwan_stop(struct net_device *dev)
+{
+ IPAWANDBG("[%s] ipa_wwan_stop()\n", dev->name);
+ __ipa_wwan_close(dev);
+ netif_stop_queue(dev);
+ return 0;
+}
+
+static int ipa_wwan_change_mtu(struct net_device *dev, int new_mtu)
+{
+ if (0 > new_mtu || WWAN_DATA_LEN < new_mtu)
+ return -EINVAL;
+ IPAWANDBG("[%s] MTU change: old=%d new=%d\n",
+ dev->name, dev->mtu, new_mtu);
+ dev->mtu = new_mtu;
+ return 0;
+}
+
+/**
+ * ipa_wwan_xmit() - Transmits an skb.
+ *
+ * @skb: skb to be transmitted
+ * @dev: network device
+ *
+ * Return codes:
+ * 0: success
+ * NETDEV_TX_BUSY: Error while transmitting the skb. Try again
+ * later
+ * -EFAULT: Error while transmitting the skb
+ */
+static int ipa_wwan_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ int ret = 0;
+ bool qmap_check;
+ struct wwan_private *wwan_ptr = netdev_priv(dev);
+ struct ipa_tx_meta meta;
+
+ if (skb->protocol != htons(ETH_P_MAP)) {
+ IPAWANDBG
+ ("SW filtering out none QMAP packet received from %s",
+ current->comm);
+ return NETDEV_TX_OK;
+ }
+
+ qmap_check = RMNET_MAP_GET_CD_BIT(skb);
+ if (netif_queue_stopped(dev)) {
+ if (qmap_check &&
+ atomic_read(&wwan_ptr->outstanding_pkts) <
+ wwan_ptr->outstanding_high_ctl) {
+ pr_err("[%s]Queue stop, send ctrl pkts\n", dev->name);
+ goto send;
+ } else {
+ pr_err("[%s]fatal: ipa_wwan_xmit stopped\n", dev->name);
+ return NETDEV_TX_BUSY;
+ }
+ }
+
+ /* checking High WM hit */
+ if (atomic_read(&wwan_ptr->outstanding_pkts) >=
+ wwan_ptr->outstanding_high) {
+ if (!qmap_check) {
+ IPAWANDBG("pending(%d)/(%d)- stop(%d), qmap_chk(%d)\n",
+ atomic_read(&wwan_ptr->outstanding_pkts),
+ wwan_ptr->outstanding_high,
+ netif_queue_stopped(dev),
+ qmap_check);
+ netif_stop_queue(dev);
+ return NETDEV_TX_BUSY;
+ }
+ }
+
+send:
+ /* IPA_RM checking start */
+ ret = ipa_rm_inactivity_timer_request_resource(
+ IPA_RM_RESOURCE_WWAN_0_PROD);
+ if (ret == -EINPROGRESS) {
+ netif_stop_queue(dev);
+ return NETDEV_TX_BUSY;
+ }
+ if (ret) {
+ pr_err("[%s] fatal: ipa rm timer request resource failed %d\n",
+ dev->name, ret);
+ return -EFAULT;
+ }
+ /* IPA_RM checking end */
+
+ if (qmap_check) {
+ memset(&meta, 0, sizeof(meta));
+ meta.pkt_init_dst_ep_valid = true;
+ meta.pkt_init_dst_ep_remote = true;
+ ret = ipa2_tx_dp(IPA_CLIENT_Q6_LAN_CONS, skb, &meta);
+ } else {
+ ret = ipa2_tx_dp(IPA_CLIENT_APPS_LAN_WAN_PROD, skb, NULL);
+ }
+
+ if (ret) {
+ ret = NETDEV_TX_BUSY;
+ dev->stats.tx_dropped++;
+ goto out;
+ }
+
+ atomic_inc(&wwan_ptr->outstanding_pkts);
+ dev->stats.tx_packets++;
+ dev->stats.tx_bytes += skb->len;
+ ret = NETDEV_TX_OK;
+out:
+ ipa_rm_inactivity_timer_release_resource(
+ IPA_RM_RESOURCE_WWAN_0_PROD);
+ return ret;
+}
+
+static void ipa_wwan_tx_timeout(struct net_device *dev)
+{
+ IPAWANERR("[%s] ipa_wwan_tx_timeout(), data stall in UL\n", dev->name);
+}
+
+/**
+ * apps_ipa_tx_complete_notify() - Rx notify
+ *
+ * @priv: driver context
+ * @evt: event type
+ * @data: data provided with event
+ *
+ * Check that the packet is the one we sent and release it
+ * This function will be called in defered context in IPA wq.
+ */
+static void apps_ipa_tx_complete_notify(void *priv,
+ enum ipa_dp_evt_type evt,
+ unsigned long data)
+{
+ struct sk_buff *skb = (struct sk_buff *)data;
+ struct net_device *dev = (struct net_device *)priv;
+ struct wwan_private *wwan_ptr;
+
+ if (dev != ipa_netdevs[0]) {
+ IPAWANDBG("Received pre-SSR packet completion\n");
+ dev_kfree_skb_any(skb);
+ return;
+ }
+
+ if (evt != IPA_WRITE_DONE) {
+ IPAWANERR("unsupported evt on Tx callback, Drop the packet\n");
+ dev_kfree_skb_any(skb);
+ dev->stats.tx_dropped++;
+ return;
+ }
+
+ wwan_ptr = netdev_priv(dev);
+ atomic_dec(&wwan_ptr->outstanding_pkts);
+ __netif_tx_lock_bh(netdev_get_tx_queue(dev, 0));
+ if (!atomic_read(&is_ssr) &&
+ netif_queue_stopped(wwan_ptr->net) &&
+ atomic_read(&wwan_ptr->outstanding_pkts) <
+ (wwan_ptr->outstanding_low)) {
+ IPAWANDBG("Outstanding low (%d) - wake up queue\n",
+ wwan_ptr->outstanding_low);
+ netif_wake_queue(wwan_ptr->net);
+ }
+ __netif_tx_unlock_bh(netdev_get_tx_queue(dev, 0));
+ dev_kfree_skb_any(skb);
+ ipa_rm_inactivity_timer_release_resource(
+ IPA_RM_RESOURCE_WWAN_0_PROD);
+}
+
+/**
+ * apps_ipa_packet_receive_notify() - Rx notify
+ *
+ * @priv: driver context
+ * @evt: event type
+ * @data: data provided with event
+ *
+ * IPA will pass a packet to the Linux network stack with skb->data
+ */
+static void apps_ipa_packet_receive_notify(void *priv,
+ enum ipa_dp_evt_type evt,
+ unsigned long data)
+{
+ struct net_device *dev = (struct net_device *)priv;
+
+ if (evt == IPA_RECEIVE) {
+ struct sk_buff *skb = (struct sk_buff *)data;
+ int result;
+ unsigned int packet_len = skb->len;
+
+ IPAWANDBG("Rx packet was received");
+ skb->dev = ipa_netdevs[0];
+ skb->protocol = htons(ETH_P_MAP);
+
+ if (ipa_rmnet_res.ipa_napi_enable) {
+ trace_rmnet_ipa_netif_rcv_skb(dev->stats.rx_packets);
+ result = netif_receive_skb(skb);
+ } else {
+ if (dev->stats.rx_packets % IPA_WWAN_RX_SOFTIRQ_THRESH
+ == 0) {
+ trace_rmnet_ipa_netifni(dev->stats.rx_packets);
+ result = netif_rx_ni(skb);
+ } else {
+ trace_rmnet_ipa_netifrx(dev->stats.rx_packets);
+ result = netif_rx(skb);
+ }
+ }
+
+ if (result) {
+ pr_err_ratelimited(DEV_NAME " %s:%d fail on netif_receive_skb\n",
+ __func__, __LINE__);
+ dev->stats.rx_dropped++;
+ }
+ dev->stats.rx_packets++;
+ dev->stats.rx_bytes += packet_len;
+ } else if (evt == IPA_CLIENT_START_POLL)
+ ipa_rmnet_rx_cb(priv);
+ else if (evt == IPA_CLIENT_COMP_NAPI) {
+ struct wwan_private *wwan_ptr = netdev_priv(dev);
+
+ if (ipa_rmnet_res.ipa_napi_enable)
+ napi_complete(&(wwan_ptr->napi));
+ } else
+ IPAWANERR("Invalid evt %d received in wan_ipa_receive\n", evt);
+
+}
+
+/**
+ * ipa_wwan_ioctl() - I/O control for wwan network driver.
+ *
+ * @dev: network device
+ * @ifr: ignored
+ * @cmd: cmd to be excecuded. can be one of the following:
+ * IPA_WWAN_IOCTL_OPEN - Open the network interface
+ * IPA_WWAN_IOCTL_CLOSE - Close the network interface
+ *
+ * Return codes:
+ * 0: success
+ * NETDEV_TX_BUSY: Error while transmitting the skb. Try again
+ * later
+ * -EFAULT: Error while transmitting the skb
+ */
+static int ipa_wwan_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+ int rc = 0;
+ int mru = 1000, epid = 1, mux_index, len;
+ struct ipa_msg_meta msg_meta;
+ struct ipa_wan_msg *wan_msg = NULL;
+ struct rmnet_ioctl_extended_s extend_ioctl_data;
+ struct rmnet_ioctl_data_s ioctl_data;
+
+ IPAWANDBG("rmnet_ipa got ioctl number 0x%08x", cmd);
+ switch (cmd) {
+ /* Set Ethernet protocol */
+ case RMNET_IOCTL_SET_LLP_ETHERNET:
+ break;
+ /* Set RAWIP protocol */
+ case RMNET_IOCTL_SET_LLP_IP:
+ break;
+ /* Get link protocol */
+ case RMNET_IOCTL_GET_LLP:
+ ioctl_data.u.operation_mode = RMNET_MODE_LLP_IP;
+ if (copy_to_user(ifr->ifr_ifru.ifru_data, &ioctl_data,
+ sizeof(struct rmnet_ioctl_data_s)))
+ rc = -EFAULT;
+ break;
+ /* Set QoS header enabled */
+ case RMNET_IOCTL_SET_QOS_ENABLE:
+ return -EINVAL;
+ /* Set QoS header disabled */
+ case RMNET_IOCTL_SET_QOS_DISABLE:
+ break;
+ /* Get QoS header state */
+ case RMNET_IOCTL_GET_QOS:
+ ioctl_data.u.operation_mode = RMNET_MODE_NONE;
+ if (copy_to_user(ifr->ifr_ifru.ifru_data, &ioctl_data,
+ sizeof(struct rmnet_ioctl_data_s)))
+ rc = -EFAULT;
+ break;
+ /* Get operation mode */
+ case RMNET_IOCTL_GET_OPMODE:
+ ioctl_data.u.operation_mode = RMNET_MODE_LLP_IP;
+ if (copy_to_user(ifr->ifr_ifru.ifru_data, &ioctl_data,
+ sizeof(struct rmnet_ioctl_data_s)))
+ rc = -EFAULT;
+ break;
+ /* Open transport port */
+ case RMNET_IOCTL_OPEN:
+ break;
+ /* Close transport port */
+ case RMNET_IOCTL_CLOSE:
+ break;
+ /* Flow enable */
+ case RMNET_IOCTL_FLOW_ENABLE:
+ IPAWANDBG("Received flow enable\n");
+ if (copy_from_user(&ioctl_data, ifr->ifr_ifru.ifru_data,
+ sizeof(struct rmnet_ioctl_data_s))) {
+ rc = -EFAULT;
+ break;
+ }
+ ipa_flow_control(IPA_CLIENT_USB_PROD, true,
+ ioctl_data.u.tcm_handle);
+ break;
+ /* Flow disable */
+ case RMNET_IOCTL_FLOW_DISABLE:
+ IPAWANDBG("Received flow disable\n");
+ if (copy_from_user(&ioctl_data, ifr->ifr_ifru.ifru_data,
+ sizeof(struct rmnet_ioctl_data_s))) {
+ rc = -EFAULT;
+ break;
+ }
+ ipa_flow_control(IPA_CLIENT_USB_PROD, false,
+ ioctl_data.u.tcm_handle);
+ break;
+ /* Set flow handle */
+ case RMNET_IOCTL_FLOW_SET_HNDL:
+ break;
+
+ /* Extended IOCTLs */
+ case RMNET_IOCTL_EXTENDED:
+ IPAWANDBG("get ioctl: RMNET_IOCTL_EXTENDED\n");
+ if (copy_from_user(&extend_ioctl_data,
+ (u8 *)ifr->ifr_ifru.ifru_data,
+ sizeof(struct rmnet_ioctl_extended_s))) {
+ IPAWANERR("failed to copy extended ioctl data\n");
+ rc = -EFAULT;
+ break;
+ }
+ switch (extend_ioctl_data.extended_ioctl) {
+ /* Get features */
+ case RMNET_IOCTL_GET_SUPPORTED_FEATURES:
+ IPAWANDBG("get RMNET_IOCTL_GET_SUPPORTED_FEATURES\n");
+ extend_ioctl_data.u.data =
+ (RMNET_IOCTL_FEAT_NOTIFY_MUX_CHANNEL |
+ RMNET_IOCTL_FEAT_SET_EGRESS_DATA_FORMAT |
+ RMNET_IOCTL_FEAT_SET_INGRESS_DATA_FORMAT);
+ if (copy_to_user((u8 *)ifr->ifr_ifru.ifru_data,
+ &extend_ioctl_data,
+ sizeof(struct rmnet_ioctl_extended_s)))
+ rc = -EFAULT;
+ break;
+ /* Set MRU */
+ case RMNET_IOCTL_SET_MRU:
+ mru = extend_ioctl_data.u.data;
+ IPAWANDBG("get MRU size %d\n",
+ extend_ioctl_data.u.data);
+ break;
+ /* Get MRU */
+ case RMNET_IOCTL_GET_MRU:
+ extend_ioctl_data.u.data = mru;
+ if (copy_to_user((u8 *)ifr->ifr_ifru.ifru_data,
+ &extend_ioctl_data,
+ sizeof(struct rmnet_ioctl_extended_s)))
+ rc = -EFAULT;
+ break;
+ /* GET SG support */
+ case RMNET_IOCTL_GET_SG_SUPPORT:
+ extend_ioctl_data.u.data =
+ ipa_rmnet_res.ipa_advertise_sg_support;
+ if (copy_to_user((u8 *)ifr->ifr_ifru.ifru_data,
+ &extend_ioctl_data,
+ sizeof(struct rmnet_ioctl_extended_s)))
+ rc = -EFAULT;
+ break;
+ /* Get endpoint ID */
+ case RMNET_IOCTL_GET_EPID:
+ IPAWANDBG("get ioctl: RMNET_IOCTL_GET_EPID\n");
+ extend_ioctl_data.u.data = epid;
+ if (copy_to_user((u8 *)ifr->ifr_ifru.ifru_data,
+ &extend_ioctl_data,
+ sizeof(struct rmnet_ioctl_extended_s)))
+ rc = -EFAULT;
+ if (copy_from_user(&extend_ioctl_data,
+ (u8 *)ifr->ifr_ifru.ifru_data,
+ sizeof(struct rmnet_ioctl_extended_s))) {
+ IPAWANERR("copy extended ioctl data failed\n");
+ rc = -EFAULT;
+ break;
+ }
+ IPAWANDBG("RMNET_IOCTL_GET_EPID return %d\n",
+ extend_ioctl_data.u.data);
+ break;
+ /* Endpoint pair */
+ case RMNET_IOCTL_GET_EP_PAIR:
+ IPAWANDBG("get ioctl: RMNET_IOCTL_GET_EP_PAIR\n");
+ extend_ioctl_data.u.ipa_ep_pair.consumer_pipe_num =
+ ipa2_get_ep_mapping(IPA_CLIENT_APPS_LAN_WAN_PROD);
+ extend_ioctl_data.u.ipa_ep_pair.producer_pipe_num =
+ ipa2_get_ep_mapping(IPA_CLIENT_APPS_WAN_CONS);
+ if (copy_to_user((u8 *)ifr->ifr_ifru.ifru_data,
+ &extend_ioctl_data,
+ sizeof(struct rmnet_ioctl_extended_s)))
+ rc = -EFAULT;
+ if (copy_from_user(&extend_ioctl_data,
+ (u8 *)ifr->ifr_ifru.ifru_data,
+ sizeof(struct rmnet_ioctl_extended_s))) {
+ IPAWANERR("copy extended ioctl data failed\n");
+ rc = -EFAULT;
+ break;
+ }
+ IPAWANDBG("RMNET_IOCTL_GET_EP_PAIR c: %d p: %d\n",
+ extend_ioctl_data.u.ipa_ep_pair.consumer_pipe_num,
+ extend_ioctl_data.u.ipa_ep_pair.producer_pipe_num);
+ break;
+ /* Get driver name */
+ case RMNET_IOCTL_GET_DRIVER_NAME:
+ memcpy(&extend_ioctl_data.u.if_name,
+ ipa_netdevs[0]->name,
+ sizeof(IFNAMSIZ));
+ if (copy_to_user((u8 *)ifr->ifr_ifru.ifru_data,
+ &extend_ioctl_data,
+ sizeof(struct rmnet_ioctl_extended_s)))
+ rc = -EFAULT;
+ break;
+ /* Add MUX ID */
+ case RMNET_IOCTL_ADD_MUX_CHANNEL:
+ mux_index = find_mux_channel_index(
+ extend_ioctl_data.u.rmnet_mux_val.mux_id);
+ if (mux_index < MAX_NUM_OF_MUX_CHANNEL) {
+ IPAWANDBG("already setup mux(%d)\n",
+ extend_ioctl_data.u.
+ rmnet_mux_val.mux_id);
+ return rc;
+ }
+ if (rmnet_index >= MAX_NUM_OF_MUX_CHANNEL) {
+ IPAWANERR("Exceed mux_channel limit(%d)\n",
+ rmnet_index);
+ return -EFAULT;
+ }
+ IPAWANDBG("ADD_MUX_CHANNEL(%d, name: %s)\n",
+ extend_ioctl_data.u.rmnet_mux_val.mux_id,
+ extend_ioctl_data.u.rmnet_mux_val.vchannel_name);
+ /* cache the mux name and id */
+ mux_channel[rmnet_index].mux_id =
+ extend_ioctl_data.u.rmnet_mux_val.mux_id;
+ memcpy(mux_channel[rmnet_index].vchannel_name,
+ extend_ioctl_data.u.rmnet_mux_val.vchannel_name,
+ sizeof(mux_channel[rmnet_index].vchannel_name));
+ IPAWANDBG("cashe device[%s:%d] in IPA_wan[%d]\n",
+ mux_channel[rmnet_index].vchannel_name,
+ mux_channel[rmnet_index].mux_id,
+ rmnet_index);
+ /* check if UL filter rules coming*/
+ if (num_q6_rule != 0) {
+ IPAWANERR("dev(%s) register to IPA\n",
+ extend_ioctl_data.u.rmnet_mux_val.
+ vchannel_name);
+ rc = wwan_register_to_ipa(rmnet_index);
+ if (rc < 0) {
+ IPAWANERR("device %s reg IPA failed\n",
+ extend_ioctl_data.u.
+ rmnet_mux_val.vchannel_name);
+ return -ENODEV;
+ }
+ mux_channel[rmnet_index].mux_channel_set = true;
+ mux_channel[rmnet_index].ul_flt_reg = true;
+ } else {
+ IPAWANDBG("dev(%s) haven't registered to IPA\n",
+ extend_ioctl_data.u.
+ rmnet_mux_val.vchannel_name);
+ mux_channel[rmnet_index].mux_channel_set = true;
+ mux_channel[rmnet_index].ul_flt_reg = false;
+ }
+ rmnet_index++;
+ break;
+ case RMNET_IOCTL_SET_EGRESS_DATA_FORMAT:
+ IPAWANDBG("get RMNET_IOCTL_SET_EGRESS_DATA_FORMAT\n");
+ if ((extend_ioctl_data.u.data) &
+ RMNET_IOCTL_EGRESS_FORMAT_CHECKSUM) {
+ apps_to_ipa_ep_cfg.ipa_ep_cfg.hdr.hdr_len = 8;
+ apps_to_ipa_ep_cfg.ipa_ep_cfg.cfg.
+ cs_offload_en =
+ IPA_ENABLE_CS_OFFLOAD_UL;
+ apps_to_ipa_ep_cfg.ipa_ep_cfg.cfg.
+ cs_metadata_hdr_offset = 1;
+ } else {
+ apps_to_ipa_ep_cfg.ipa_ep_cfg.hdr.hdr_len = 4;
+ }
+ if ((extend_ioctl_data.u.data) &
+ RMNET_IOCTL_EGRESS_FORMAT_AGGREGATION)
+ apps_to_ipa_ep_cfg.ipa_ep_cfg.aggr.aggr_en =
+ IPA_ENABLE_AGGR;
+ else
+ apps_to_ipa_ep_cfg.ipa_ep_cfg.aggr.aggr_en =
+ IPA_BYPASS_AGGR;
+ apps_to_ipa_ep_cfg.ipa_ep_cfg.hdr.
+ hdr_ofst_metadata_valid = 1;
+ /* modem want offset at 0! */
+ apps_to_ipa_ep_cfg.ipa_ep_cfg.hdr.hdr_ofst_metadata = 0;
+ apps_to_ipa_ep_cfg.ipa_ep_cfg.mode.dst =
+ IPA_CLIENT_APPS_LAN_WAN_PROD;
+ apps_to_ipa_ep_cfg.ipa_ep_cfg.mode.mode = IPA_BASIC;
+
+ apps_to_ipa_ep_cfg.client =
+ IPA_CLIENT_APPS_LAN_WAN_PROD;
+ apps_to_ipa_ep_cfg.notify =
+ apps_ipa_tx_complete_notify;
+ apps_to_ipa_ep_cfg.desc_fifo_sz =
+ IPA_SYS_TX_DATA_DESC_FIFO_SZ;
+ apps_to_ipa_ep_cfg.priv = dev;
+
+ rc = ipa2_setup_sys_pipe(&apps_to_ipa_ep_cfg,
+ &apps_to_ipa_hdl);
+ if (rc)
+ IPAWANERR("failed to config egress endpoint\n");
+
+ if (num_q6_rule != 0) {
+ /* already got Q6 UL filter rules*/
+ if (ipa_qmi_ctx &&
+ ipa_qmi_ctx->modem_cfg_emb_pipe_flt
+ == false)
+ rc = wwan_add_ul_flt_rule_to_ipa();
+ else
+ rc = 0;
+ egress_set = true;
+ if (rc)
+ IPAWANERR("install UL rules failed\n");
+ else
+ a7_ul_flt_set = true;
+ } else {
+ /* wait Q6 UL filter rules*/
+ egress_set = true;
+ IPAWANDBG("no UL-rules, egress_set(%d)\n",
+ egress_set);
+ }
+ break;
+ case RMNET_IOCTL_SET_INGRESS_DATA_FORMAT:/* Set IDF */
+ IPAWANDBG("get RMNET_IOCTL_SET_INGRESS_DATA_FORMAT\n");
+ if ((extend_ioctl_data.u.data) &
+ RMNET_IOCTL_INGRESS_FORMAT_CHECKSUM)
+ ipa_to_apps_ep_cfg.ipa_ep_cfg.cfg.
+ cs_offload_en =
+ IPA_ENABLE_CS_OFFLOAD_DL;
+
+ if ((extend_ioctl_data.u.data) &
+ RMNET_IOCTL_INGRESS_FORMAT_AGG_DATA) {
+ IPAWANERR("get AGG size %d count %d\n",
+ extend_ioctl_data.u.
+ ingress_format.agg_size,
+ extend_ioctl_data.u.
+ ingress_format.agg_count);
+ if (!ipa_disable_apps_wan_cons_deaggr(
+ extend_ioctl_data.u.
+ ingress_format.agg_size,
+ extend_ioctl_data.
+ u.ingress_format.agg_count)) {
+ ipa_to_apps_ep_cfg.ipa_ep_cfg.aggr.
+ aggr_byte_limit = extend_ioctl_data.
+ u.ingress_format.agg_size;
+ ipa_to_apps_ep_cfg.ipa_ep_cfg.aggr.
+ aggr_pkt_limit = extend_ioctl_data.
+ u.ingress_format.agg_count;
+ }
+ }
+
+ ipa_to_apps_ep_cfg.ipa_ep_cfg.hdr.hdr_len = 4;
+ ipa_to_apps_ep_cfg.ipa_ep_cfg.hdr.
+ hdr_ofst_metadata_valid = 1;
+ ipa_to_apps_ep_cfg.ipa_ep_cfg.
+ hdr.hdr_ofst_metadata = 1;
+ ipa_to_apps_ep_cfg.ipa_ep_cfg.hdr.
+ hdr_ofst_pkt_size_valid = 1;
+ ipa_to_apps_ep_cfg.ipa_ep_cfg.hdr.
+ hdr_ofst_pkt_size = 2;
+
+ ipa_to_apps_ep_cfg.ipa_ep_cfg.hdr_ext.
+ hdr_total_len_or_pad_valid = true;
+ ipa_to_apps_ep_cfg.ipa_ep_cfg.hdr_ext.
+ hdr_total_len_or_pad = 0;
+ ipa_to_apps_ep_cfg.ipa_ep_cfg.hdr_ext.
+ hdr_payload_len_inc_padding = true;
+ ipa_to_apps_ep_cfg.ipa_ep_cfg.hdr_ext.
+ hdr_total_len_or_pad_offset = 0;
+ ipa_to_apps_ep_cfg.ipa_ep_cfg.hdr_ext.
+ hdr_little_endian = 0;
+ ipa_to_apps_ep_cfg.ipa_ep_cfg.metadata_mask.
+ metadata_mask = 0xFF000000;
+
+ ipa_to_apps_ep_cfg.client = IPA_CLIENT_APPS_WAN_CONS;
+ ipa_to_apps_ep_cfg.notify =
+ apps_ipa_packet_receive_notify;
+ ipa_to_apps_ep_cfg.priv = dev;
+
+ ipa_to_apps_ep_cfg.napi_enabled =
+ ipa_rmnet_res.ipa_napi_enable;
+ if (ipa_to_apps_ep_cfg.napi_enabled)
+ ipa_to_apps_ep_cfg.desc_fifo_sz =
+ IPA_WAN_CONS_DESC_FIFO_SZ;
+ else
+ ipa_to_apps_ep_cfg.desc_fifo_sz =
+ IPA_SYS_DESC_FIFO_SZ;
+
+ mutex_lock(&ipa_to_apps_pipe_handle_guard);
+ if (atomic_read(&is_ssr)) {
+ IPAWANDBG("In SSR sequence/recovery\n");
+ mutex_unlock(&ipa_to_apps_pipe_handle_guard);
+ rc = -EFAULT;
+ break;
+ }
+ rc = ipa2_setup_sys_pipe(
+ &ipa_to_apps_ep_cfg, &ipa_to_apps_hdl);
+ mutex_unlock(&ipa_to_apps_pipe_handle_guard);
+ if (rc)
+ IPAWANERR("failed to configure ingress\n");
+ break;
+ case RMNET_IOCTL_SET_XLAT_DEV_INFO:
+ wan_msg = kzalloc(sizeof(struct ipa_wan_msg),
+ GFP_KERNEL);
+ if (!wan_msg) {
+ IPAWANERR("Failed to allocate memory.\n");
+ return -ENOMEM;
+ }
+ len = sizeof(wan_msg->upstream_ifname) >
+ sizeof(extend_ioctl_data.u.if_name) ?
+ sizeof(extend_ioctl_data.u.if_name) :
+ sizeof(wan_msg->upstream_ifname);
+ strlcpy(wan_msg->upstream_ifname,
+ extend_ioctl_data.u.if_name, len);
+ memset(&msg_meta, 0, sizeof(struct ipa_msg_meta));
+ msg_meta.msg_type = WAN_XLAT_CONNECT;
+ msg_meta.msg_len = sizeof(struct ipa_wan_msg);
+ rc = ipa2_send_msg(&msg_meta, wan_msg,
+ ipa_wwan_msg_free_cb);
+ if (rc) {
+ IPAWANERR("Failed to send XLAT_CONNECT msg\n");
+ kfree(wan_msg);
+ }
+ break;
+ /* Get agg count */
+ case RMNET_IOCTL_GET_AGGREGATION_COUNT:
+ break;
+ /* Set agg count */
+ case RMNET_IOCTL_SET_AGGREGATION_COUNT:
+ break;
+ /* Get agg size */
+ case RMNET_IOCTL_GET_AGGREGATION_SIZE:
+ break;
+ /* Set agg size */
+ case RMNET_IOCTL_SET_AGGREGATION_SIZE:
+ break;
+ /* Do flow control */
+ case RMNET_IOCTL_FLOW_CONTROL:
+ break;
+ /* For legacy use */
+ case RMNET_IOCTL_GET_DFLT_CONTROL_CHANNEL:
+ break;
+ /* Get HW/SW map */
+ case RMNET_IOCTL_GET_HWSW_MAP:
+ break;
+ /* Set RX Headroom */
+ case RMNET_IOCTL_SET_RX_HEADROOM:
+ break;
+ default:
+ IPAWANERR("[%s] unsupported extended cmd[%d]",
+ dev->name,
+ extend_ioctl_data.extended_ioctl);
+ rc = -EINVAL;
+ }
+ break;
+ default:
+ IPAWANERR("[%s] unsupported cmd[%d]",
+ dev->name, cmd);
+ rc = -EINVAL;
+ }
+ return rc;
+}
+
+static const struct net_device_ops ipa_wwan_ops_ip = {
+ .ndo_open = ipa_wwan_open,
+ .ndo_stop = ipa_wwan_stop,
+ .ndo_start_xmit = ipa_wwan_xmit,
+ .ndo_tx_timeout = ipa_wwan_tx_timeout,
+ .ndo_do_ioctl = ipa_wwan_ioctl,
+ .ndo_change_mtu = ipa_wwan_change_mtu,
+ .ndo_set_mac_address = 0,
+ .ndo_validate_addr = 0,
+};
+
+/**
+ * wwan_setup() - Setups the wwan network driver.
+ *
+ * @dev: network device
+ *
+ * Return codes:
+ * None
+ */
+
+static void ipa_wwan_setup(struct net_device *dev)
+{
+ dev->netdev_ops = &ipa_wwan_ops_ip;
+ ether_setup(dev);
+ /* set this after calling ether_setup */
+ dev->header_ops = 0; /* No header */
+ dev->type = ARPHRD_RAWIP;
+ dev->hard_header_len = 0;
+ dev->mtu = WWAN_DATA_LEN;
+ dev->addr_len = 0;
+ dev->flags &= ~(IFF_BROADCAST | IFF_MULTICAST);
+ dev->needed_headroom = HEADROOM_FOR_QMAP;
+ dev->needed_tailroom = TAILROOM;
+ dev->watchdog_timeo = 1000;
+}
+
+/* IPA_RM related functions start*/
+static void q6_prod_rm_request_resource(struct work_struct *work);
+static DECLARE_DELAYED_WORK(q6_con_rm_request, q6_prod_rm_request_resource);
+static void q6_prod_rm_release_resource(struct work_struct *work);
+static DECLARE_DELAYED_WORK(q6_con_rm_release, q6_prod_rm_release_resource);
+
+static void q6_prod_rm_request_resource(struct work_struct *work)
+{
+ int ret = 0;
+
+ ret = ipa_rm_request_resource(IPA_RM_RESOURCE_Q6_PROD);
+ if (ret < 0 && ret != -EINPROGRESS) {
+ IPAWANERR("%s: ipa_rm_request_resource failed %d\n", __func__,
+ ret);
+ return;
+ }
+}
+
+static int q6_rm_request_resource(void)
+{
+ queue_delayed_work(ipa_rm_q6_workqueue,
+ &q6_con_rm_request, 0);
+ return 0;
+}
+
+static void q6_prod_rm_release_resource(struct work_struct *work)
+{
+ int ret = 0;
+
+ ret = ipa_rm_release_resource(IPA_RM_RESOURCE_Q6_PROD);
+ if (ret < 0 && ret != -EINPROGRESS) {
+ IPAWANERR("%s: ipa_rm_release_resource failed %d\n", __func__,
+ ret);
+ return;
+ }
+}
+
+
+static int q6_rm_release_resource(void)
+{
+ queue_delayed_work(ipa_rm_q6_workqueue,
+ &q6_con_rm_release, 0);
+ return 0;
+}
+
+
+static void q6_rm_notify_cb(void *user_data,
+ enum ipa_rm_event event,
+ unsigned long data)
+{
+ switch (event) {
+ case IPA_RM_RESOURCE_GRANTED:
+ IPAWANDBG("%s: Q6_PROD GRANTED CB\n", __func__);
+ break;
+ case IPA_RM_RESOURCE_RELEASED:
+ IPAWANDBG("%s: Q6_PROD RELEASED CB\n", __func__);
+ break;
+ default:
+ return;
+ }
+}
+static int q6_initialize_rm(void)
+{
+ struct ipa_rm_create_params create_params;
+ struct ipa_rm_perf_profile profile;
+ int result;
+
+ /* Initialize IPA_RM workqueue */
+ ipa_rm_q6_workqueue = create_singlethread_workqueue("clnt_req");
+ if (!ipa_rm_q6_workqueue)
+ return -ENOMEM;
+
+ memset(&create_params, 0, sizeof(create_params));
+ create_params.name = IPA_RM_RESOURCE_Q6_PROD;
+ create_params.reg_params.notify_cb = &q6_rm_notify_cb;
+ result = ipa_rm_create_resource(&create_params);
+ if (result)
+ goto create_rsrc_err1;
+ memset(&create_params, 0, sizeof(create_params));
+ create_params.name = IPA_RM_RESOURCE_Q6_CONS;
+ create_params.release_resource = &q6_rm_release_resource;
+ create_params.request_resource = &q6_rm_request_resource;
+ result = ipa_rm_create_resource(&create_params);
+ if (result)
+ goto create_rsrc_err2;
+ /* add dependency*/
+ result = ipa_rm_add_dependency(IPA_RM_RESOURCE_Q6_PROD,
+ IPA_RM_RESOURCE_APPS_CONS);
+ if (result)
+ goto add_dpnd_err;
+ /* setup Performance profile */
+ memset(&profile, 0, sizeof(profile));
+ profile.max_supported_bandwidth_mbps = 100;
+ result = ipa_rm_set_perf_profile(IPA_RM_RESOURCE_Q6_PROD,
+ &profile);
+ if (result)
+ goto set_perf_err;
+ result = ipa_rm_set_perf_profile(IPA_RM_RESOURCE_Q6_CONS,
+ &profile);
+ if (result)
+ goto set_perf_err;
+ return result;
+
+set_perf_err:
+ ipa_rm_delete_dependency(IPA_RM_RESOURCE_Q6_PROD,
+ IPA_RM_RESOURCE_APPS_CONS);
+add_dpnd_err:
+ result = ipa_rm_delete_resource(IPA_RM_RESOURCE_Q6_CONS);
+ if (result < 0)
+ IPAWANERR("Error deleting resource %d, ret=%d\n",
+ IPA_RM_RESOURCE_Q6_CONS, result);
+create_rsrc_err2:
+ result = ipa_rm_delete_resource(IPA_RM_RESOURCE_Q6_PROD);
+ if (result < 0)
+ IPAWANERR("Error deleting resource %d, ret=%d\n",
+ IPA_RM_RESOURCE_Q6_PROD, result);
+create_rsrc_err1:
+ destroy_workqueue(ipa_rm_q6_workqueue);
+ return result;
+}
+
+void q6_deinitialize_rm(void)
+{
+ int ret;
+
+ ret = ipa_rm_delete_dependency(IPA_RM_RESOURCE_Q6_PROD,
+ IPA_RM_RESOURCE_APPS_CONS);
+ if (ret < 0)
+ IPAWANERR("Error deleting dependency %d->%d, ret=%d\n",
+ IPA_RM_RESOURCE_Q6_PROD, IPA_RM_RESOURCE_APPS_CONS,
+ ret);
+ ret = ipa_rm_delete_resource(IPA_RM_RESOURCE_Q6_CONS);
+ if (ret < 0)
+ IPAWANERR("Error deleting resource %d, ret=%d\n",
+ IPA_RM_RESOURCE_Q6_CONS, ret);
+ ret = ipa_rm_delete_resource(IPA_RM_RESOURCE_Q6_PROD);
+ if (ret < 0)
+ IPAWANERR("Error deleting resource %d, ret=%d\n",
+ IPA_RM_RESOURCE_Q6_PROD, ret);
+ destroy_workqueue(ipa_rm_q6_workqueue);
+}
+
+static void wake_tx_queue(struct work_struct *work)
+{
+ if (ipa_netdevs[0]) {
+ __netif_tx_lock_bh(netdev_get_tx_queue(ipa_netdevs[0], 0));
+ netif_wake_queue(ipa_netdevs[0]);
+ __netif_tx_unlock_bh(netdev_get_tx_queue(ipa_netdevs[0], 0));
+ }
+}
+
+/**
+ * ipa_rm_resource_granted() - Called upon
+ * IPA_RM_RESOURCE_GRANTED event. Wakes up queue is was stopped.
+ *
+ * @work: work object supplied ny workqueue
+ *
+ * Return codes:
+ * None
+ */
+static void ipa_rm_resource_granted(void *dev)
+{
+ IPAWANDBG("Resource Granted - starting queue\n");
+ schedule_work(&ipa_tx_wakequeue_work);
+}
+
+/**
+ * ipa_rm_notify() - Callback function for RM events. Handles
+ * IPA_RM_RESOURCE_GRANTED and IPA_RM_RESOURCE_RELEASED events.
+ * IPA_RM_RESOURCE_GRANTED is handled in the context of shared
+ * workqueue.
+ *
+ * @dev: network device
+ * @event: IPA RM event
+ * @data: Additional data provided by IPA RM
+ *
+ * Return codes:
+ * None
+ */
+static void ipa_rm_notify(void *dev, enum ipa_rm_event event,
+ unsigned long data)
+{
+ struct wwan_private *wwan_ptr = netdev_priv(dev);
+
+ pr_debug("%s: event %d\n", __func__, event);
+ switch (event) {
+ case IPA_RM_RESOURCE_GRANTED:
+ if (wwan_ptr->device_status == WWAN_DEVICE_INACTIVE) {
+ complete_all(&wwan_ptr->resource_granted_completion);
+ break;
+ }
+ ipa_rm_resource_granted(dev);
+ break;
+ case IPA_RM_RESOURCE_RELEASED:
+ break;
+ default:
+ pr_err("%s: unknown event %d\n", __func__, event);
+ break;
+ }
+}
+
+/* IPA_RM related functions end*/
+
+static int ssr_notifier_cb(struct notifier_block *this,
+ unsigned long code,
+ void *data);
+
+static struct notifier_block ssr_notifier = {
+ .notifier_call = ssr_notifier_cb,
+};
+
+static int get_ipa_rmnet_dts_configuration(struct platform_device *pdev,
+ struct ipa_rmnet_plat_drv_res *ipa_rmnet_drv_res)
+{
+ ipa_rmnet_drv_res->ipa_rmnet_ssr =
+ of_property_read_bool(pdev->dev.of_node,
+ "qcom,rmnet-ipa-ssr");
+ pr_info("IPA SSR support = %s\n",
+ ipa_rmnet_drv_res->ipa_rmnet_ssr ? "True" : "False");
+ ipa_rmnet_drv_res->ipa_loaduC =
+ of_property_read_bool(pdev->dev.of_node,
+ "qcom,ipa-loaduC");
+ pr_info("IPA ipa-loaduC = %s\n",
+ ipa_rmnet_drv_res->ipa_loaduC ? "True" : "False");
+
+ ipa_rmnet_drv_res->ipa_advertise_sg_support =
+ of_property_read_bool(pdev->dev.of_node,
+ "qcom,ipa-advertise-sg-support");
+ pr_info("IPA SG support = %s\n",
+ ipa_rmnet_drv_res->ipa_advertise_sg_support ? "True" : "False");
+
+ ipa_rmnet_drv_res->ipa_napi_enable =
+ of_property_read_bool(pdev->dev.of_node,
+ "qcom,ipa-napi-enable");
+ pr_info("IPA Napi Enable = %s\n",
+ ipa_rmnet_drv_res->ipa_napi_enable ? "True" : "False");
+ return 0;
+}
+
+struct ipa_rmnet_context ipa_rmnet_ctx;
+
+/**
+ * ipa_wwan_probe() - Initialized the module and registers as a
+ * network interface to the network stack
+ *
+ * Return codes:
+ * 0: success
+ * -ENOMEM: No memory available
+ * -EFAULT: Internal error
+ * -ENODEV: IPA driver not loaded
+ */
+static int ipa_wwan_probe(struct platform_device *pdev)
+{
+ int ret, i;
+ struct net_device *dev;
+ struct wwan_private *wwan_ptr;
+ struct ipa_rm_create_params ipa_rm_params; /* IPA_RM */
+ struct ipa_rm_perf_profile profile; /* IPA_RM */
+
+ pr_info("rmnet_ipa started initialization\n");
+
+ if (!ipa2_is_ready()) {
+ IPAWANERR("IPA driver not loaded\n");
+ return -ENODEV;
+ }
+
+ ret = get_ipa_rmnet_dts_configuration(pdev, &ipa_rmnet_res);
+ ipa_rmnet_ctx.ipa_rmnet_ssr = ipa_rmnet_res.ipa_rmnet_ssr;
+
+ ret = ipa_init_q6_smem();
+ if (ret) {
+ IPAWANERR("ipa_init_q6_smem failed!\n");
+ return ret;
+ }
+
+ /* initialize tx/rx enpoint setup */
+ memset(&apps_to_ipa_ep_cfg, 0, sizeof(struct ipa_sys_connect_params));
+ memset(&ipa_to_apps_ep_cfg, 0, sizeof(struct ipa_sys_connect_params));
+
+ /* initialize ex property setup */
+ num_q6_rule = 0;
+ old_num_q6_rule = 0;
+ rmnet_index = 0;
+ egress_set = false;
+ a7_ul_flt_set = false;
+ for (i = 0; i < MAX_NUM_OF_MUX_CHANNEL; i++)
+ memset(&mux_channel[i], 0, sizeof(struct rmnet_mux_val));
+
+ /* start A7 QMI service/client */
+ if (ipa_rmnet_res.ipa_loaduC)
+ /* Android platform loads uC */
+ ipa_qmi_service_init(QMI_IPA_PLATFORM_TYPE_MSM_ANDROID_V01);
+ else
+ /* LE platform not loads uC */
+ ipa_qmi_service_init(QMI_IPA_PLATFORM_TYPE_LE_V01);
+
+ /* construct default WAN RT tbl for IPACM */
+ ret = ipa_setup_a7_qmap_hdr();
+ if (ret)
+ goto setup_a7_qmap_hdr_err;
+ ret = ipa_setup_dflt_wan_rt_tables();
+ if (ret)
+ goto setup_dflt_wan_rt_tables_err;
+
+ if (!atomic_read(&is_ssr)) {
+ /* Start transport-driver fd ioctl for ipacm for first init */
+ ret = wan_ioctl_init();
+ if (ret)
+ goto wan_ioctl_init_err;
+ } else {
+ /* Enable sending QMI messages after SSR */
+ wan_ioctl_enable_qmi_messages();
+ }
+
+ /* initialize wan-driver netdev */
+ dev = alloc_netdev(sizeof(struct wwan_private),
+ IPA_WWAN_DEV_NAME,
+ NET_NAME_UNKNOWN,
+ ipa_wwan_setup);
+ if (!dev) {
+ IPAWANERR("no memory for netdev\n");
+ ret = -ENOMEM;
+ goto alloc_netdev_err;
+ }
+ ipa_netdevs[0] = dev;
+ wwan_ptr = netdev_priv(dev);
+ memset(wwan_ptr, 0, sizeof(*wwan_ptr));
+ IPAWANDBG("wwan_ptr (private) = %p", wwan_ptr);
+ wwan_ptr->net = dev;
+ wwan_ptr->outstanding_high_ctl = DEFAULT_OUTSTANDING_HIGH_CTL;
+ wwan_ptr->outstanding_high = DEFAULT_OUTSTANDING_HIGH;
+ wwan_ptr->outstanding_low = DEFAULT_OUTSTANDING_LOW;
+ atomic_set(&wwan_ptr->outstanding_pkts, 0);
+ spin_lock_init(&wwan_ptr->lock);
+ init_completion(&wwan_ptr->resource_granted_completion);
+
+ if (!atomic_read(&is_ssr)) {
+ /* IPA_RM configuration starts */
+ ret = q6_initialize_rm();
+ if (ret) {
+ IPAWANERR("%s: q6_initialize_rm failed, ret: %d\n",
+ __func__, ret);
+ goto q6_init_err;
+ }
+ }
+
+ memset(&ipa_rm_params, 0, sizeof(struct ipa_rm_create_params));
+ ipa_rm_params.name = IPA_RM_RESOURCE_WWAN_0_PROD;
+ ipa_rm_params.reg_params.user_data = dev;
+ ipa_rm_params.reg_params.notify_cb = ipa_rm_notify;
+ ret = ipa_rm_create_resource(&ipa_rm_params);
+ if (ret) {
+ pr_err("%s: unable to create resourse %d in IPA RM\n",
+ __func__, IPA_RM_RESOURCE_WWAN_0_PROD);
+ goto create_rsrc_err;
+ }
+ ret = ipa_rm_inactivity_timer_init(IPA_RM_RESOURCE_WWAN_0_PROD,
+ IPA_RM_INACTIVITY_TIMER);
+ if (ret) {
+ pr_err("%s: ipa rm timer init failed %d on resourse %d\n",
+ __func__, ret, IPA_RM_RESOURCE_WWAN_0_PROD);
+ goto timer_init_err;
+ }
+ /* add dependency */
+ ret = ipa_rm_add_dependency(IPA_RM_RESOURCE_WWAN_0_PROD,
+ IPA_RM_RESOURCE_Q6_CONS);
+ if (ret)
+ goto add_dpnd_err;
+ /* setup Performance profile */
+ memset(&profile, 0, sizeof(profile));
+ profile.max_supported_bandwidth_mbps = IPA_APPS_MAX_BW_IN_MBPS;
+ ret = ipa_rm_set_perf_profile(IPA_RM_RESOURCE_WWAN_0_PROD,
+ &profile);
+ if (ret)
+ goto set_perf_err;
+ /* IPA_RM configuration ends */
+
+ /* Enable SG support in netdevice. */
+ if (ipa_rmnet_res.ipa_advertise_sg_support)
+ dev->hw_features |= NETIF_F_SG;
+
+ /* Enable NAPI support in netdevice. */
+ if (ipa_rmnet_res.ipa_napi_enable) {
+ netif_napi_add(dev, &(wwan_ptr->napi),
+ ipa_rmnet_poll, NAPI_WEIGHT);
+ }
+
+ ret = register_netdev(dev);
+ if (ret) {
+ IPAWANERR("unable to register ipa_netdev %d rc=%d\n",
+ 0, ret);
+ goto set_perf_err;
+ }
+
+ IPAWANDBG("IPA-WWAN devices (%s) initialization ok :>>>>\n",
+ ipa_netdevs[0]->name);
+ if (ret) {
+ IPAWANERR("default configuration failed rc=%d\n",
+ ret);
+ goto config_err;
+ }
+ atomic_set(&is_initialized, 1);
+ if (!atomic_read(&is_ssr)) {
+ /* offline charging mode */
+ ipa2_proxy_clk_unvote();
+ }
+ atomic_set(&is_ssr, 0);
+
+ pr_info("rmnet_ipa completed initialization\n");
+ return 0;
+config_err:
+ if (ipa_rmnet_res.ipa_napi_enable)
+ netif_napi_del(&(wwan_ptr->napi));
+ unregister_netdev(ipa_netdevs[0]);
+set_perf_err:
+ ret = ipa_rm_delete_dependency(IPA_RM_RESOURCE_WWAN_0_PROD,
+ IPA_RM_RESOURCE_Q6_CONS);
+ if (ret)
+ IPAWANERR("Error deleting dependency %d->%d, ret=%d\n",
+ IPA_RM_RESOURCE_WWAN_0_PROD, IPA_RM_RESOURCE_Q6_CONS,
+ ret);
+add_dpnd_err:
+ ret = ipa_rm_inactivity_timer_destroy(
+ IPA_RM_RESOURCE_WWAN_0_PROD); /* IPA_RM */
+ if (ret)
+ IPAWANERR("Error ipa_rm_inactivity_timer_destroy %d, ret=%d\n",
+ IPA_RM_RESOURCE_WWAN_0_PROD, ret);
+timer_init_err:
+ ret = ipa_rm_delete_resource(IPA_RM_RESOURCE_WWAN_0_PROD);
+ if (ret)
+ IPAWANERR("Error deleting resource %d, ret=%d\n",
+ IPA_RM_RESOURCE_WWAN_0_PROD, ret);
+create_rsrc_err:
+ q6_deinitialize_rm();
+q6_init_err:
+ free_netdev(ipa_netdevs[0]);
+ ipa_netdevs[0] = NULL;
+alloc_netdev_err:
+ wan_ioctl_deinit();
+wan_ioctl_init_err:
+ ipa_del_dflt_wan_rt_tables();
+setup_dflt_wan_rt_tables_err:
+ ipa_del_a7_qmap_hdr();
+setup_a7_qmap_hdr_err:
+ ipa_qmi_service_exit();
+ atomic_set(&is_ssr, 0);
+ return ret;
+}
+
+static int ipa_wwan_remove(struct platform_device *pdev)
+{
+ int ret;
+ struct wwan_private *wwan_ptr;
+
+ wwan_ptr = netdev_priv(ipa_netdevs[0]);
+
+ pr_info("rmnet_ipa started deinitialization\n");
+ mutex_lock(&ipa_to_apps_pipe_handle_guard);
+ ret = ipa2_teardown_sys_pipe(ipa_to_apps_hdl);
+ if (ret < 0)
+ IPAWANERR("Failed to teardown IPA->APPS pipe\n");
+ else
+ ipa_to_apps_hdl = -1;
+ if (ipa_rmnet_res.ipa_napi_enable)
+ netif_napi_del(&(wwan_ptr->napi));
+ mutex_unlock(&ipa_to_apps_pipe_handle_guard);
+ unregister_netdev(ipa_netdevs[0]);
+ ret = ipa_rm_delete_dependency(IPA_RM_RESOURCE_WWAN_0_PROD,
+ IPA_RM_RESOURCE_Q6_CONS);
+ if (ret < 0)
+ IPAWANERR("Error deleting dependency %d->%d, ret=%d\n",
+ IPA_RM_RESOURCE_WWAN_0_PROD, IPA_RM_RESOURCE_Q6_CONS,
+ ret);
+ ret = ipa_rm_inactivity_timer_destroy(IPA_RM_RESOURCE_WWAN_0_PROD);
+ if (ret < 0)
+ IPAWANERR(
+ "Error ipa_rm_inactivity_timer_destroy resource %d, ret=%d\n",
+ IPA_RM_RESOURCE_WWAN_0_PROD, ret);
+ ret = ipa_rm_delete_resource(IPA_RM_RESOURCE_WWAN_0_PROD);
+ if (ret < 0)
+ IPAWANERR("Error deleting resource %d, ret=%d\n",
+ IPA_RM_RESOURCE_WWAN_0_PROD, ret);
+ cancel_work_sync(&ipa_tx_wakequeue_work);
+ cancel_delayed_work(&ipa_tether_stats_poll_wakequeue_work);
+ free_netdev(ipa_netdevs[0]);
+ ipa_netdevs[0] = NULL;
+ /* No need to remove wwan_ioctl during SSR */
+ if (!atomic_read(&is_ssr))
+ wan_ioctl_deinit();
+ ipa_del_dflt_wan_rt_tables();
+ ipa_del_a7_qmap_hdr();
+ ipa_del_mux_qmap_hdrs();
+ if (ipa_qmi_ctx && ipa_qmi_ctx->modem_cfg_emb_pipe_flt == false)
+ wwan_del_ul_flt_rule_to_ipa();
+ ipa_cleanup_deregister_intf();
+ atomic_set(&is_initialized, 0);
+ pr_info("rmnet_ipa completed deinitialization\n");
+ return 0;
+}
+
+/**
+* rmnet_ipa_ap_suspend() - suspend callback for runtime_pm
+* @dev: pointer to device
+*
+* This callback will be invoked by the runtime_pm framework when an AP suspend
+* operation is invoked, usually by pressing a suspend button.
+*
+* Returns -EAGAIN to runtime_pm framework in case there are pending packets
+* in the Tx queue. This will postpone the suspend operation until all the
+* pending packets will be transmitted.
+*
+* In case there are no packets to send, releases the WWAN0_PROD entity.
+* As an outcome, the number of IPA active clients should be decremented
+* until IPA clocks can be gated.
+*/
+static int rmnet_ipa_ap_suspend(struct device *dev)
+{
+ struct net_device *netdev = ipa_netdevs[0];
+ struct wwan_private *wwan_ptr = netdev_priv(netdev);
+
+ IPAWANDBG("Enter...\n");
+ /* Do not allow A7 to suspend in case there are oustanding packets */
+ if (atomic_read(&wwan_ptr->outstanding_pkts) != 0) {
+ IPAWANDBG("Outstanding packets, postponing AP suspend.\n");
+ return -EAGAIN;
+ }
+
+ /* Make sure that there is no Tx operation ongoing */
+ netif_tx_lock_bh(netdev);
+ ipa_rm_release_resource(IPA_RM_RESOURCE_WWAN_0_PROD);
+ netif_tx_unlock_bh(netdev);
+ IPAWANDBG("Exit\n");
+
+ return 0;
+}
+
+/**
+* rmnet_ipa_ap_resume() - resume callback for runtime_pm
+* @dev: pointer to device
+*
+* This callback will be invoked by the runtime_pm framework when an AP resume
+* operation is invoked.
+*
+* Enables the network interface queue and returns success to the
+* runtime_pm framework.
+*/
+static int rmnet_ipa_ap_resume(struct device *dev)
+{
+ struct net_device *netdev = ipa_netdevs[0];
+
+ IPAWANDBG("Enter...\n");
+ netif_wake_queue(netdev);
+ IPAWANDBG("Exit\n");
+
+ return 0;
+}
+
+static void ipa_stop_polling_stats(void)
+{
+ cancel_delayed_work(&ipa_tether_stats_poll_wakequeue_work);
+ ipa_rmnet_ctx.polling_interval = 0;
+}
+
+static const struct of_device_id rmnet_ipa_dt_match[] = {
+ {.compatible = "qcom,rmnet-ipa"},
+ {},
+};
+MODULE_DEVICE_TABLE(of, rmnet_ipa_dt_match);
+
+static const struct dev_pm_ops rmnet_ipa_pm_ops = {
+ .suspend_noirq = rmnet_ipa_ap_suspend,
+ .resume_noirq = rmnet_ipa_ap_resume,
+};
+
+static struct platform_driver rmnet_ipa_driver = {
+ .driver = {
+ .name = "rmnet_ipa",
+ .owner = THIS_MODULE,
+ .pm = &rmnet_ipa_pm_ops,
+ .of_match_table = rmnet_ipa_dt_match,
+ },
+ .probe = ipa_wwan_probe,
+ .remove = ipa_wwan_remove,
+};
+
+static int ssr_notifier_cb(struct notifier_block *this,
+ unsigned long code,
+ void *data)
+{
+ if (ipa_rmnet_ctx.ipa_rmnet_ssr) {
+ if (code == SUBSYS_BEFORE_SHUTDOWN) {
+ pr_info("IPA received MPSS BEFORE_SHUTDOWN\n");
+ atomic_set(&is_ssr, 1);
+ ipa_q6_pre_shutdown_cleanup();
+ if (ipa_netdevs[0])
+ netif_stop_queue(ipa_netdevs[0]);
+ ipa_qmi_stop_workqueues();
+ wan_ioctl_stop_qmi_messages();
+ ipa_stop_polling_stats();
+ if (atomic_read(&is_initialized))
+ platform_driver_unregister(&rmnet_ipa_driver);
+ pr_info("IPA BEFORE_SHUTDOWN handling is complete\n");
+ return NOTIFY_DONE;
+ }
+ if (code == SUBSYS_AFTER_SHUTDOWN) {
+ pr_info("IPA received MPSS AFTER_SHUTDOWN\n");
+ if (atomic_read(&is_ssr))
+ ipa_q6_post_shutdown_cleanup();
+ pr_info("IPA AFTER_SHUTDOWN handling is complete\n");
+ return NOTIFY_DONE;
+ }
+ if (code == SUBSYS_AFTER_POWERUP) {
+ pr_info("IPA received MPSS AFTER_POWERUP\n");
+ if (!atomic_read(&is_initialized)
+ && atomic_read(&is_ssr))
+ platform_driver_register(&rmnet_ipa_driver);
+ pr_info("IPA AFTER_POWERUP handling is complete\n");
+ return NOTIFY_DONE;
+ }
+ if (code == SUBSYS_BEFORE_POWERUP) {
+ pr_info("IPA received MPSS BEFORE_POWERUP\n");
+ if (atomic_read(&is_ssr))
+ /* clean up cached QMI msg/handlers */
+ ipa_qmi_service_exit();
+ ipa2_proxy_clk_vote();
+ pr_info("IPA BEFORE_POWERUP handling is complete\n");
+ return NOTIFY_DONE;
+ }
+ }
+ return NOTIFY_DONE;
+}
+
+/**
+ * rmnet_ipa_free_msg() - Free the msg sent to user space via ipa2_send_msg
+ * @buff: pointer to buffer containing the message
+ * @len: message len
+ * @type: message type
+ *
+ * This function is invoked when ipa2_send_msg is complete (Provided as a
+ * free function pointer along with the message).
+ */
+static void rmnet_ipa_free_msg(void *buff, u32 len, u32 type)
+{
+ if (!buff) {
+ IPAWANERR("Null buffer\n");
+ return;
+ }
+
+ if (type != IPA_TETHERING_STATS_UPDATE_STATS &&
+ type != IPA_TETHERING_STATS_UPDATE_NETWORK_STATS) {
+ IPAWANERR("Wrong type given. buff %p type %d\n",
+ buff, type);
+ }
+ kfree(buff);
+}
+
+/**
+ * rmnet_ipa_get_stats_and_update(bool reset) - Gets pipe stats from Modem
+ *
+ * This function queries the IPA Modem driver for the pipe stats
+ * via QMI, and updates the user space IPA entity.
+ */
+static void rmnet_ipa_get_stats_and_update(bool reset)
+{
+ struct ipa_get_data_stats_req_msg_v01 req;
+ struct ipa_get_data_stats_resp_msg_v01 *resp;
+ struct ipa_msg_meta msg_meta;
+ int rc;
+
+ resp = kzalloc(sizeof(struct ipa_get_data_stats_resp_msg_v01),
+ GFP_KERNEL);
+ if (!resp) {
+ IPAWANERR("Can't allocate memory for stats message\n");
+ return;
+ }
+
+ memset(&req, 0, sizeof(struct ipa_get_data_stats_req_msg_v01));
+ memset(resp, 0, sizeof(struct ipa_get_data_stats_resp_msg_v01));
+
+ req.ipa_stats_type = QMI_IPA_STATS_TYPE_PIPE_V01;
+ if (reset == true) {
+ req.reset_stats_valid = true;
+ req.reset_stats = true;
+ IPAWANERR("Get the latest pipe-stats and reset it\n");
+ }
+
+ rc = ipa_qmi_get_data_stats(&req, resp);
+
+ if (!rc) {
+ memset(&msg_meta, 0, sizeof(struct ipa_msg_meta));
+ msg_meta.msg_type = IPA_TETHERING_STATS_UPDATE_STATS;
+ msg_meta.msg_len =
+ sizeof(struct ipa_get_data_stats_resp_msg_v01);
+ rc = ipa2_send_msg(&msg_meta, resp, rmnet_ipa_free_msg);
+ if (rc) {
+ IPAWANERR("ipa2_send_msg failed: %d\n", rc);
+ kfree(resp);
+ return;
+ }
+ }
+}
+
+/**
+ * tethering_stats_poll_queue() - Stats polling function
+ * @work - Work entry
+ *
+ * This function is scheduled periodically (per the interval) in
+ * order to poll the IPA Modem driver for the pipe stats.
+ */
+static void tethering_stats_poll_queue(struct work_struct *work)
+{
+ rmnet_ipa_get_stats_and_update(false);
+
+ /* Schedule again only if there's an active polling interval */
+ if (ipa_rmnet_ctx.polling_interval != 0)
+ schedule_delayed_work(&ipa_tether_stats_poll_wakequeue_work,
+ msecs_to_jiffies(ipa_rmnet_ctx.polling_interval*1000));
+}
+
+/**
+ * rmnet_ipa_get_network_stats_and_update() - Get network stats from IPA Modem
+ *
+ * This function retrieves the data usage (used quota) from the IPA Modem driver
+ * via QMI, and updates IPA user space entity.
+ */
+static void rmnet_ipa_get_network_stats_and_update(void)
+{
+ struct ipa_get_apn_data_stats_req_msg_v01 req;
+ struct ipa_get_apn_data_stats_resp_msg_v01 *resp;
+ struct ipa_msg_meta msg_meta;
+ int rc;
+
+ resp = kzalloc(sizeof(struct ipa_get_apn_data_stats_resp_msg_v01),
+ GFP_KERNEL);
+ if (!resp) {
+ IPAWANERR("Can't allocate memory for network stats message\n");
+ return;
+ }
+
+ memset(&req, 0, sizeof(struct ipa_get_apn_data_stats_req_msg_v01));
+ memset(resp, 0, sizeof(struct ipa_get_apn_data_stats_resp_msg_v01));
+
+ req.mux_id_list_valid = true;
+ req.mux_id_list_len = 1;
+ req.mux_id_list[0] = ipa_rmnet_ctx.metered_mux_id;
+
+ rc = ipa_qmi_get_network_stats(&req, resp);
+
+ if (!rc) {
+ memset(&msg_meta, 0, sizeof(struct ipa_msg_meta));
+ msg_meta.msg_type = IPA_TETHERING_STATS_UPDATE_NETWORK_STATS;
+ msg_meta.msg_len =
+ sizeof(struct ipa_get_apn_data_stats_resp_msg_v01);
+ rc = ipa2_send_msg(&msg_meta, resp, rmnet_ipa_free_msg);
+ if (rc) {
+ IPAWANERR("ipa2_send_msg failed: %d\n", rc);
+ kfree(resp);
+ return;
+ }
+ }
+}
+
+/**
+ * rmnet_ipa_poll_tethering_stats() - Tethering stats polling IOCTL handler
+ * @data - IOCTL data
+ *
+ * This function handles WAN_IOC_POLL_TETHERING_STATS.
+ * In case polling interval received is 0, polling will stop
+ * (If there's a polling in progress, it will allow it to finish), and then will
+ * fetch network stats, and update the IPA user space.
+ *
+ * Return codes:
+ * 0: Success
+ */
+int rmnet_ipa_poll_tethering_stats(struct wan_ioctl_poll_tethering_stats *data)
+{
+ ipa_rmnet_ctx.polling_interval = data->polling_interval_secs;
+
+ cancel_delayed_work_sync(&ipa_tether_stats_poll_wakequeue_work);
+
+ if (ipa_rmnet_ctx.polling_interval == 0) {
+ ipa_qmi_stop_data_qouta();
+ rmnet_ipa_get_network_stats_and_update();
+ rmnet_ipa_get_stats_and_update(true);
+ return 0;
+ }
+
+ schedule_delayed_work(&ipa_tether_stats_poll_wakequeue_work, 0);
+ return 0;
+}
+
+/**
+ * rmnet_ipa_set_data_quota() - Data quota setting IOCTL handler
+ * @data - IOCTL data
+ *
+ * This function handles WAN_IOC_SET_DATA_QUOTA.
+ * It translates the given interface name to the Modem MUX ID and
+ * sends the request of the quota to the IPA Modem driver via QMI.
+ *
+ * Return codes:
+ * 0: Success
+ * -EFAULT: Invalid interface name provided
+ * other: See ipa_qmi_set_data_quota
+ */
+int rmnet_ipa_set_data_quota(struct wan_ioctl_set_data_quota *data)
+{
+ u32 mux_id;
+ int index;
+ struct ipa_set_data_usage_quota_req_msg_v01 req;
+
+ index = find_vchannel_name_index(data->interface_name);
+ IPAWANERR("iface name %s, quota %lu\n",
+ data->interface_name,
+ (unsigned long int) data->quota_mbytes);
+
+ if (index == MAX_NUM_OF_MUX_CHANNEL) {
+ IPAWANERR("%s is an invalid iface name\n",
+ data->interface_name);
+ return -EFAULT;
+ }
+
+ mux_id = mux_channel[index].mux_id;
+
+ ipa_rmnet_ctx.metered_mux_id = mux_id;
+
+ memset(&req, 0, sizeof(struct ipa_set_data_usage_quota_req_msg_v01));
+ req.apn_quota_list_valid = true;
+ req.apn_quota_list_len = 1;
+ req.apn_quota_list[0].mux_id = mux_id;
+ req.apn_quota_list[0].num_Mbytes = data->quota_mbytes;
+
+ return ipa_qmi_set_data_quota(&req);
+}
+
+ /* rmnet_ipa_set_tether_client_pipe() -
+ * @data - IOCTL data
+ *
+ * This function handles WAN_IOC_SET_DATA_QUOTA.
+ * It translates the given interface name to the Modem MUX ID and
+ * sends the request of the quota to the IPA Modem driver via QMI.
+ *
+ * Return codes:
+ * 0: Success
+ * -EFAULT: Invalid interface name provided
+ * other: See ipa_qmi_set_data_quota
+ */
+int rmnet_ipa_set_tether_client_pipe(
+ struct wan_ioctl_set_tether_client_pipe *data)
+{
+ int number, i;
+
+ IPAWANDBG("client %d, UL %d, DL %d, reset %d\n",
+ data->ipa_client,
+ data->ul_src_pipe_len,
+ data->dl_dst_pipe_len,
+ data->reset_client);
+ number = data->ul_src_pipe_len;
+ for (i = 0; i < number; i++) {
+ IPAWANDBG("UL index-%d pipe %d\n", i,
+ data->ul_src_pipe_list[i]);
+ if (data->reset_client)
+ ipa_set_client(data->ul_src_pipe_list[i],
+ 0, false);
+ else
+ ipa_set_client(data->ul_src_pipe_list[i],
+ data->ipa_client, true);
+ }
+ number = data->dl_dst_pipe_len;
+ for (i = 0; i < number; i++) {
+ IPAWANDBG("DL index-%d pipe %d\n", i,
+ data->dl_dst_pipe_list[i]);
+ if (data->reset_client)
+ ipa_set_client(data->dl_dst_pipe_list[i],
+ 0, false);
+ else
+ ipa_set_client(data->dl_dst_pipe_list[i],
+ data->ipa_client, false);
+ }
+ return 0;
+}
+
+int rmnet_ipa_query_tethering_stats(struct wan_ioctl_query_tether_stats *data,
+ bool reset)
+{
+ struct ipa_get_data_stats_req_msg_v01 *req;
+ struct ipa_get_data_stats_resp_msg_v01 *resp;
+ int pipe_len, rc;
+
+ req = kzalloc(sizeof(struct ipa_get_data_stats_req_msg_v01),
+ GFP_KERNEL);
+ if (!req) {
+ IPAWANERR("failed to allocate memory for stats message\n");
+ return -ENOMEM;
+ }
+ resp = kzalloc(sizeof(struct ipa_get_data_stats_resp_msg_v01),
+ GFP_KERNEL);
+ if (!resp) {
+ IPAWANERR("failed to allocate memory for stats message\n");
+ kfree(req);
+ return -ENOMEM;
+ }
+ memset(req, 0, sizeof(struct ipa_get_data_stats_req_msg_v01));
+ memset(resp, 0, sizeof(struct ipa_get_data_stats_resp_msg_v01));
+
+ req->ipa_stats_type = QMI_IPA_STATS_TYPE_PIPE_V01;
+ if (reset) {
+ req->reset_stats_valid = true;
+ req->reset_stats = true;
+ IPAWANERR("reset the pipe stats\n");
+ } else {
+ /* print tethered-client enum */
+ IPAWANDBG("Tethered-client enum(%d)\n", data->ipa_client);
+ }
+
+ rc = ipa_qmi_get_data_stats(req, resp);
+ if (rc) {
+ IPAWANERR("can't get ipa_qmi_get_data_stats\n");
+ kfree(req);
+ kfree(resp);
+ return rc;
+ } else if (reset) {
+ kfree(req);
+ kfree(resp);
+ return 0;
+ }
+
+ if (resp->dl_dst_pipe_stats_list_valid) {
+ for (pipe_len = 0; pipe_len < resp->dl_dst_pipe_stats_list_len;
+ pipe_len++) {
+ IPAWANDBG("Check entry(%d) dl_dst_pipe(%d)\n",
+ pipe_len, resp->dl_dst_pipe_stats_list
+ [pipe_len].pipe_index);
+ IPAWANDBG("dl_p_v4(%lu)v6(%lu) dl_b_v4(%lu)v6(%lu)\n",
+ (unsigned long int) resp->
+ dl_dst_pipe_stats_list[pipe_len].
+ num_ipv4_packets,
+ (unsigned long int) resp->
+ dl_dst_pipe_stats_list[pipe_len].
+ num_ipv6_packets,
+ (unsigned long int) resp->
+ dl_dst_pipe_stats_list[pipe_len].
+ num_ipv4_bytes,
+ (unsigned long int) resp->
+ dl_dst_pipe_stats_list[pipe_len].
+ num_ipv6_bytes);
+ if (ipa_get_client_uplink(resp->
+ dl_dst_pipe_stats_list[pipe_len].
+ pipe_index) == false) {
+ if (data->ipa_client == ipa_get_client(resp->
+ dl_dst_pipe_stats_list[pipe_len].
+ pipe_index)) {
+ /* update the DL stats */
+ data->ipv4_rx_packets += resp->
+ dl_dst_pipe_stats_list[pipe_len].
+ num_ipv4_packets;
+ data->ipv6_rx_packets += resp->
+ dl_dst_pipe_stats_list[pipe_len].
+ num_ipv6_packets;
+ data->ipv4_rx_bytes += resp->
+ dl_dst_pipe_stats_list[pipe_len].
+ num_ipv4_bytes;
+ data->ipv6_rx_bytes += resp->
+ dl_dst_pipe_stats_list[pipe_len].
+ num_ipv6_bytes;
+ }
+ }
+ }
+ }
+ IPAWANDBG("v4_rx_p(%lu) v6_rx_p(%lu) v4_rx_b(%lu) v6_rx_b(%lu)\n",
+ (unsigned long int) data->ipv4_rx_packets,
+ (unsigned long int) data->ipv6_rx_packets,
+ (unsigned long int) data->ipv4_rx_bytes,
+ (unsigned long int) data->ipv6_rx_bytes);
+
+ if (resp->ul_src_pipe_stats_list_valid) {
+ for (pipe_len = 0; pipe_len < resp->ul_src_pipe_stats_list_len;
+ pipe_len++) {
+ IPAWANDBG("Check entry(%d) ul_dst_pipe(%d)\n",
+ pipe_len,
+ resp->ul_src_pipe_stats_list[pipe_len].
+ pipe_index);
+ IPAWANDBG("ul_p_v4(%lu)v6(%lu)ul_b_v4(%lu)v6(%lu)\n",
+ (unsigned long int) resp->
+ ul_src_pipe_stats_list[pipe_len].
+ num_ipv4_packets,
+ (unsigned long int) resp->
+ ul_src_pipe_stats_list[pipe_len].
+ num_ipv6_packets,
+ (unsigned long int) resp->
+ ul_src_pipe_stats_list[pipe_len].
+ num_ipv4_bytes,
+ (unsigned long int) resp->
+ ul_src_pipe_stats_list[pipe_len].
+ num_ipv6_bytes);
+ if (ipa_get_client_uplink(resp->
+ ul_src_pipe_stats_list[pipe_len].
+ pipe_index) == true) {
+ if (data->ipa_client == ipa_get_client(resp->
+ ul_src_pipe_stats_list[pipe_len].
+ pipe_index)) {
+ /* update the DL stats */
+ data->ipv4_tx_packets += resp->
+ ul_src_pipe_stats_list[pipe_len].
+ num_ipv4_packets;
+ data->ipv6_tx_packets += resp->
+ ul_src_pipe_stats_list[pipe_len].
+ num_ipv6_packets;
+ data->ipv4_tx_bytes += resp->
+ ul_src_pipe_stats_list[pipe_len].
+ num_ipv4_bytes;
+ data->ipv6_tx_bytes += resp->
+ ul_src_pipe_stats_list[pipe_len].
+ num_ipv6_bytes;
+ }
+ }
+ }
+ }
+ IPAWANDBG("tx_p_v4(%lu)v6(%lu)tx_b_v4(%lu) v6(%lu)\n",
+ (unsigned long int) data->ipv4_tx_packets,
+ (unsigned long int) data->ipv6_tx_packets,
+ (unsigned long int) data->ipv4_tx_bytes,
+ (unsigned long int) data->ipv6_tx_bytes);
+ kfree(req);
+ kfree(resp);
+ return 0;
+}
+
+/**
+ * ipa_broadcast_quota_reach_ind() - Send Netlink broadcast on Quota
+ * @mux_id - The MUX ID on which the quota has been reached
+ *
+ * This function broadcasts a Netlink event using the kobject of the
+ * rmnet_ipa interface in order to alert the user space that the quota
+ * on the specific interface which matches the mux_id has been reached.
+ *
+ */
+void ipa_broadcast_quota_reach_ind(u32 mux_id)
+{
+ char alert_msg[IPA_QUOTA_REACH_ALERT_MAX_SIZE];
+ char iface_name_l[IPA_QUOTA_REACH_IF_NAME_MAX_SIZE];
+ char iface_name_m[IPA_QUOTA_REACH_IF_NAME_MAX_SIZE];
+ char *envp[IPA_UEVENT_NUM_EVNP] = {
+ alert_msg, iface_name_l, iface_name_m, NULL };
+ int res;
+ int index;
+
+ index = find_mux_channel_index(mux_id);
+
+ if (index == MAX_NUM_OF_MUX_CHANNEL) {
+ IPAWANERR("%u is an mux ID\n", mux_id);
+ return;
+ }
+
+ res = snprintf(alert_msg, IPA_QUOTA_REACH_ALERT_MAX_SIZE,
+ "ALERT_NAME=%s", "quotaReachedAlert");
+ if (res >= IPA_QUOTA_REACH_ALERT_MAX_SIZE) {
+ IPAWANERR("message too long (%d)", res);
+ return;
+ }
+ /* posting msg for L-release for CNE */
+ res = snprintf(iface_name_l, IPA_QUOTA_REACH_IF_NAME_MAX_SIZE,
+ "UPSTREAM=%s", mux_channel[index].vchannel_name);
+ if (res >= IPA_QUOTA_REACH_IF_NAME_MAX_SIZE) {
+ IPAWANERR("message too long (%d)", res);
+ return;
+ }
+ /* posting msg for M-release for CNE */
+ res = snprintf(iface_name_m, IPA_QUOTA_REACH_IF_NAME_MAX_SIZE,
+ "INTERFACE=%s", mux_channel[index].vchannel_name);
+ if (res >= IPA_QUOTA_REACH_IF_NAME_MAX_SIZE) {
+ IPAWANERR("message too long (%d)", res);
+ return;
+ }
+
+ IPAWANERR("putting nlmsg: <%s> <%s> <%s>\n",
+ alert_msg, iface_name_l, iface_name_m);
+ kobject_uevent_env(&(ipa_netdevs[0]->dev.kobj), KOBJ_CHANGE, envp);
+}
+
+/**
+ * ipa_q6_handshake_complete() - Perform operations once Q6 is up
+ * @ssr_bootup - Indicates whether this is a cold boot-up or post-SSR.
+ *
+ * This function is invoked once the handshake between the IPA AP driver
+ * and IPA Q6 driver is complete. At this point, it is possible to perform
+ * operations which can't be performed until IPA Q6 driver is up.
+ *
+ */
+void ipa_q6_handshake_complete(bool ssr_bootup)
+{
+ /* It is required to recover the network stats after SSR recovery */
+ if (ssr_bootup) {
+ /*
+ * In case the uC is required to be loaded by the Modem,
+ * the proxy vote will be removed only when uC loading is
+ * complete and indication is received by the AP. After SSR,
+ * uC is already loaded. Therefore, proxy vote can be removed
+ * once Modem init is complete.
+ */
+ ipa2_proxy_clk_unvote();
+
+ /*
+ * It is required to recover the network stats after
+ * SSR recovery
+ */
+ rmnet_ipa_get_network_stats_and_update();
+
+ /* Enable holb monitoring on Q6 pipes. */
+ ipa_q6_monitor_holb_mitigation(true);
+ }
+}
+
+static int __init ipa_wwan_init(void)
+{
+ atomic_set(&is_initialized, 0);
+ atomic_set(&is_ssr, 0);
+
+ mutex_init(&ipa_to_apps_pipe_handle_guard);
+ ipa_to_apps_hdl = -1;
+
+ ipa_qmi_init();
+
+ /* Register for Modem SSR */
+ subsys_notify_handle = subsys_notif_register_notifier(SUBSYS_MODEM,
+ &ssr_notifier);
+ if (!IS_ERR(subsys_notify_handle))
+ return platform_driver_register(&rmnet_ipa_driver);
+ else
+ return (int)PTR_ERR(subsys_notify_handle);
+}
+
+static void __exit ipa_wwan_cleanup(void)
+{
+ int ret;
+
+ ipa_qmi_cleanup();
+ mutex_destroy(&ipa_to_apps_pipe_handle_guard);
+ ret = subsys_notif_unregister_notifier(subsys_notify_handle,
+ &ssr_notifier);
+ if (ret)
+ IPAWANERR(
+ "Error subsys_notif_unregister_notifier system %s, ret=%d\n",
+ SUBSYS_MODEM, ret);
+ platform_driver_unregister(&rmnet_ipa_driver);
+}
+
+static void ipa_wwan_msg_free_cb(void *buff, u32 len, u32 type)
+{
+ if (!buff)
+ IPAWANERR("Null buffer.\n");
+ kfree(buff);
+}
+
+static void ipa_rmnet_rx_cb(void *priv)
+{
+ struct net_device *dev = priv;
+ struct wwan_private *wwan_ptr;
+
+ IPAWANDBG("\n");
+
+ if (dev != ipa_netdevs[0]) {
+ IPAWANERR("Not matching with netdev\n");
+ return;
+ }
+
+ wwan_ptr = netdev_priv(dev);
+ napi_schedule(&(wwan_ptr->napi));
+}
+
+static int ipa_rmnet_poll(struct napi_struct *napi, int budget)
+{
+ int rcvd_pkts = 0;
+
+ rcvd_pkts = ipa_rx_poll(ipa_to_apps_hdl, NAPI_WEIGHT);
+ IPAWANDBG("rcvd packets: %d\n", rcvd_pkts);
+ return rcvd_pkts;
+}
+
+late_initcall(ipa_wwan_init);
+module_exit(ipa_wwan_cleanup);
+MODULE_DESCRIPTION("WWAN Network Interface");
+MODULE_LICENSE("GPL v2");