The IPA uses "rmnet" as a way to present remote network resources as if they were local to the AP. IPA interfaces representing networks accessible via the modem are represented as rmnet interfaces, implemented by the "rmnet data driver" found here: drivers/net/ethernet/qualcomm/rmnet/ The IPA is able to perform aggregation of packets, as well as checksum offload. These options (plus others, such as configuring MTU size) are configurable using an ioctl interface. In addition, rmnet devices support multiplexing. TX packets are handed to the data path layer, and when their transmission is complete the notification callback will be called. The data path code posts RX packets to the hardware, and when they are filled they are supplied here by a receive notification callback. The IPA driver currently does not support the modem shutting down (or crashing). But the rmnet_ipa device roughly represents the availability of networks reachable by the modem. If the modem is operational, an ipa_rmnet network device will be available. Modem operation is managed by the remoteproc subsystem. Note: This portion of the driver will be heavily affected by planned rework on the data path code. Signed-off-by: Alex Elder <elder@xxxxxxxxxx> --- drivers/net/ipa/msm_rmnet.h | 120 +++++ drivers/net/ipa/rmnet_config.h | 31 ++ drivers/net/ipa/rmnet_ipa.c | 805 +++++++++++++++++++++++++++++++++ 3 files changed, 956 insertions(+) create mode 100644 drivers/net/ipa/msm_rmnet.h create mode 100644 drivers/net/ipa/rmnet_config.h create mode 100644 drivers/net/ipa/rmnet_ipa.c diff --git a/drivers/net/ipa/msm_rmnet.h b/drivers/net/ipa/msm_rmnet.h new file mode 100644 index 000000000000..042380fd53fb --- /dev/null +++ b/drivers/net/ipa/msm_rmnet.h @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* Copyright (c) 2018, The Linux Foundation. All rights reserved. + * Copyright (C) 2018 Linaro Ltd. + */ +#ifndef _MSM_RMNET_H_ +#define _MSM_RMNET_H_ + +/* Bitmap macros for RmNET driver operation mode. */ +#define RMNET_MODE_NONE 0x00 +#define RMNET_MODE_LLP_ETH 0x01 +#define RMNET_MODE_LLP_IP 0x02 +#define RMNET_MODE_QOS 0x04 + +/* IOCTL commands + * Values chosen to not conflict with other drivers in the ecosystem + */ + +#define RMNET_IOCTL_SET_LLP_ETHERNET 0x000089f1 /* Set Ethernet protocol */ +#define RMNET_IOCTL_SET_LLP_IP 0x000089f2 /* Set RAWIP protocol */ +#define RMNET_IOCTL_GET_LLP 0x000089f3 /* Get link protocol */ +#define RMNET_IOCTL_SET_QOS_ENABLE 0x000089f4 /* Set QoS header enabled */ +#define RMNET_IOCTL_SET_QOS_DISABLE 0x000089f5 /* Set QoS header disabled*/ +#define RMNET_IOCTL_GET_QOS 0x000089f6 /* Get QoS header state */ +#define RMNET_IOCTL_GET_OPMODE 0x000089f7 /* Get operation mode */ +#define RMNET_IOCTL_OPEN 0x000089f8 /* Open transport port */ +#define RMNET_IOCTL_CLOSE 0x000089f9 /* Close transport port */ +#define RMNET_IOCTL_FLOW_ENABLE 0x000089fa /* Flow enable */ +#define RMNET_IOCTL_FLOW_DISABLE 0x000089fb /* Flow disable */ +#define RMNET_IOCTL_FLOW_SET_HNDL 0x000089fc /* Set flow handle */ +#define RMNET_IOCTL_EXTENDED 0x000089fd /* Extended IOCTLs */ + +/* RmNet Data Required IOCTLs */ +#define RMNET_IOCTL_GET_SUPPORTED_FEATURES 0x0000 /* Get features */ +#define RMNET_IOCTL_SET_MRU 0x0001 /* Set MRU */ +#define RMNET_IOCTL_GET_MRU 0x0002 /* Get MRU */ +#define RMNET_IOCTL_GET_EPID 0x0003 /* Get endpoint ID */ +#define RMNET_IOCTL_GET_DRIVER_NAME 0x0004 /* Get driver name */ +#define RMNET_IOCTL_ADD_MUX_CHANNEL 0x0005 /* Add MUX ID */ +#define RMNET_IOCTL_SET_EGRESS_DATA_FORMAT 0x0006 /* Set EDF */ +#define RMNET_IOCTL_SET_INGRESS_DATA_FORMAT 0x0007 /* Set IDF */ +#define RMNET_IOCTL_SET_AGGREGATION_COUNT 0x0008 /* Set agg count */ +#define RMNET_IOCTL_GET_AGGREGATION_COUNT 0x0009 /* Get agg count */ +#define RMNET_IOCTL_SET_AGGREGATION_SIZE 0x000a /* Set agg size */ +#define RMNET_IOCTL_GET_AGGREGATION_SIZE 0x000b /* Get agg size */ +#define RMNET_IOCTL_FLOW_CONTROL 0x000c /* Do flow control */ +#define RMNET_IOCTL_GET_DFLT_CONTROL_CHANNEL 0x000d /* For legacy use */ +#define RMNET_IOCTL_GET_HWSW_MAP 0x000e /* Get HW/SW map */ +#define RMNET_IOCTL_SET_RX_HEADROOM 0x000f /* RX Headroom */ +#define RMNET_IOCTL_GET_EP_PAIR 0x0010 /* Endpoint pair */ +#define RMNET_IOCTL_SET_QOS_VERSION 0x0011 /* 8/6 byte QoS hdr*/ +#define RMNET_IOCTL_GET_QOS_VERSION 0x0012 /* 8/6 byte QoS hdr*/ +#define RMNET_IOCTL_GET_SUPPORTED_QOS_MODES 0x0013 /* Get QoS modes */ +#define RMNET_IOCTL_SET_SLEEP_STATE 0x0014 /* Set sleep state */ +#define RMNET_IOCTL_SET_XLAT_DEV_INFO 0x0015 /* xlat dev name */ +#define RMNET_IOCTL_DEREGISTER_DEV 0x0016 /* Dereg a net dev */ +#define RMNET_IOCTL_GET_SG_SUPPORT 0x0017 /* Query sg support*/ + +/* Return values for the RMNET_IOCTL_GET_SUPPORTED_FEATURES IOCTL */ +#define RMNET_IOCTL_FEAT_NOTIFY_MUX_CHANNEL BIT(0) +#define RMNET_IOCTL_FEAT_SET_EGRESS_DATA_FORMAT BIT(1) +#define RMNET_IOCTL_FEAT_SET_INGRESS_DATA_FORMAT BIT(2) + +/* Input values for the RMNET_IOCTL_SET_EGRESS_DATA_FORMAT IOCTL */ +#define RMNET_IOCTL_EGRESS_FORMAT_AGGREGATION BIT(2) +#define RMNET_IOCTL_EGRESS_FORMAT_CHECKSUM BIT(4) + +/* Input values for the RMNET_IOCTL_SET_INGRESS_DATA_FORMAT IOCTL */ +#define RMNET_IOCTL_INGRESS_FORMAT_CHECKSUM BIT(4) +#define RMNET_IOCTL_INGRESS_FORMAT_AGG_DATA BIT(5) + +/* User space may not have this defined. */ +#ifndef IFNAMSIZ +#define IFNAMSIZ 16 +#endif + +struct rmnet_ioctl_extended_s { + u32 extended_ioctl; + union { + u32 data; /* Generic data field for most extended IOCTLs */ + + /* Return values for + * RMNET_IOCTL_GET_DRIVER_NAME + * RMNET_IOCTL_GET_DFLT_CONTROL_CHANNEL + */ + char if_name[IFNAMSIZ]; + + /* Input values for the RMNET_IOCTL_ADD_MUX_CHANNEL IOCTL */ + struct { + u32 mux_id; + char vchannel_name[IFNAMSIZ]; + } rmnet_mux_val; + + /* Input values for the RMNET_IOCTL_FLOW_CONTROL IOCTL */ + struct { + u8 flow_mode; + u8 mux_id; + } flow_control_prop; + + /* Return values for RMNET_IOCTL_GET_EP_PAIR */ + struct { + u32 consumer_pipe_num; + u32 producer_pipe_num; + } ipa_ep_pair; + + struct { + u32 __data; /* Placeholder for legacy data*/ + u32 agg_size; + u32 agg_count; + } ingress_format; + } u; +}; + +struct rmnet_ioctl_data_s { + union { + u32 operation_mode; + u32 tcm_handle; + } u; +}; +#endif /* _MSM_RMNET_H_ */ diff --git a/drivers/net/ipa/rmnet_config.h b/drivers/net/ipa/rmnet_config.h new file mode 100644 index 000000000000..3b9a549ca1bd --- /dev/null +++ b/drivers/net/ipa/rmnet_config.h @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. + * Copyright (C) 2018 Linaro Ltd. + */ +#ifndef _RMNET_CONFIG_H_ +#define _RMNET_CONFIG_H_ + +#include <linux/types.h> + +/* XXX We want to use struct rmnet_map_header, but that's currently defined in + * XXX drivers/net/ethernet/qualcomm/rmnet/rmnet_map.h + * XXX We also want to use RMNET_MAP_GET_CD_BIT(Y), defined in the same file. + */ +struct rmnet_map_header_s { +#ifndef RMNET_USE_BIG_ENDIAN_STRUCTS + u8 pad_len : 6, + reserved_bit : 1, + cd_bit : 1; +#else + u8 cd_bit : 1, + reserved_bit : 1, + pad_len : 6; +#endif /* RMNET_USE_BIG_ENDIAN_STRUCTS */ + u8 mux_id; + u16 pkt_len; +} __aligned(1); + +#define RMNET_MAP_GET_CD_BIT(Y) (((struct rmnet_map_header_s *)Y->data)->cd_bit) + +#endif /* _RMNET_CONFIG_H_ */ diff --git a/drivers/net/ipa/rmnet_ipa.c b/drivers/net/ipa/rmnet_ipa.c new file mode 100644 index 000000000000..7006afe3a5ea --- /dev/null +++ b/drivers/net/ipa/rmnet_ipa.c @@ -0,0 +1,805 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* Copyright (c) 2014-2018, The Linux Foundation. All rights reserved. + * Copyright (C) 2018 Linaro Ltd. + */ + +/* 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 "msm_rmnet.h" +#include "rmnet_config.h" +#include "ipa_qmi.h" +#include "ipa_i.h" + +#define DRIVER_NAME "wwan_ioctl" +#define IPA_WWAN_DEV_NAME "rmnet_ipa%d" + +#define MUX_CHANNEL_MAX 10 /* max mux channels */ + +#define NAPI_WEIGHT 60 + +#define WWAN_DATA_LEN 2000 +#define HEADROOM_FOR_QMAP 8 /* for mux header */ +#define TAILROOM 0 /* for padding by mux layer */ + +#define DEFAULT_OUTSTANDING_HIGH 128 +#define DEFAULT_OUTSTANDING_HIGH_CTL (DEFAULT_OUTSTANDING_HIGH + 32) +#define DEFAULT_OUTSTANDING_LOW 64 + +#define IPA_APPS_WWAN_CONS_RING_COUNT 256 +#define IPA_APPS_WWAN_PROD_RING_COUNT 512 + +static int ipa_rmnet_poll(struct napi_struct *napi, int budget); + +/** struct ipa_wwan_private - WWAN private data + * @net: network interface struct implemented by this driver + * @stats: iface statistics + * @outstanding_high: number of outstanding packets allowed + * @outstanding_low: number of outstanding packets which shall cause + * + * WWAN private - holds all relevant info about WWAN driver + */ +struct ipa_wwan_private { + struct net_device_stats stats; + atomic_t outstanding_pkts; + int outstanding_high_ctl; + int outstanding_high; + int outstanding_low; + struct napi_struct napi; +}; + +struct rmnet_ipa_context { + struct net_device *dev; + struct mutex mux_id_mutex; /* protects mux_id[] */ + u32 mux_id_count; + u32 mux_id[MUX_CHANNEL_MAX]; + u32 wan_prod_ep_id; + u32 wan_cons_ep_id; + struct mutex ep_setup_mutex; /* endpoint setup/teardown */ +}; + +static bool initialized; /* Avoid duplicate initialization */ + +static struct rmnet_ipa_context rmnet_ipa_ctx_struct; +static struct rmnet_ipa_context *rmnet_ipa_ctx = &rmnet_ipa_ctx_struct; + +/** wwan_open() - Opens the wwan network interface */ +static int ipa_wwan_open(struct net_device *dev) +{ + struct ipa_wwan_private *wwan_ptr = netdev_priv(dev); + + napi_enable(&wwan_ptr->napi); + netif_start_queue(dev); + + return 0; +} + +/** ipa_wwan_stop() - Stops the wwan network interface. */ +static int ipa_wwan_stop(struct net_device *dev) +{ + netif_stop_queue(dev); + + return 0; +} + +/** ipa_wwan_xmit() - Transmits an skb. + * + * @skb: skb to be transmitted + * @dev: network device + * + * Return codes: + * NETDEV_TX_OK: Success + * NETDEV_TX_BUSY: Error while transmitting the skb. Try again later + */ +static int ipa_wwan_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct ipa_wwan_private *wwan_ptr = netdev_priv(dev); + unsigned int skb_len; + int outstanding; + + if (skb->protocol != htons(ETH_P_MAP)) { + dev_kfree_skb_any(skb); + dev->stats.tx_dropped++; + return NETDEV_TX_OK; + } + + /* Control packets are sent even if queue is stopped. We + * always honor the data and control high-water marks. + */ + outstanding = atomic_read(&wwan_ptr->outstanding_pkts); + if (!RMNET_MAP_GET_CD_BIT(skb)) { /* Data packet? */ + if (netif_queue_stopped(dev)) + return NETDEV_TX_BUSY; + if (outstanding >= wwan_ptr->outstanding_high) + return NETDEV_TX_BUSY; + } else if (outstanding >= wwan_ptr->outstanding_high_ctl) { + return NETDEV_TX_BUSY; + } + + /* both data packets and commands will be routed to + * IPA_CLIENT_Q6_WAN_CONS based on status configuration. + */ + skb_len = skb->len; + if (ipa_tx_dp(IPA_CLIENT_APPS_WAN_PROD, skb)) + return NETDEV_TX_BUSY; + + atomic_inc(&wwan_ptr->outstanding_pkts); + dev->stats.tx_packets++; + dev->stats.tx_bytes += skb_len; + + return NETDEV_TX_OK; +} + +/** 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 ipa_wwan_private *wwan_ptr; + struct net_device *dev = priv; + struct sk_buff *skb; + + skb = (struct sk_buff *)data; + + if (dev != rmnet_ipa_ctx->dev) { + dev_kfree_skb_any(skb); + return; + } + + if (evt != IPA_WRITE_DONE) { + ipa_err("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 (netif_queue_stopped(dev) && + atomic_read(&wwan_ptr->outstanding_pkts) < + wwan_ptr->outstanding_low) { + netif_wake_queue(dev); + } + + __netif_tx_unlock_bh(netdev_get_tx_queue(dev, 0)); + dev_kfree_skb_any(skb); +} + +/** 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 ipa_wwan_private *wwan_ptr; + struct net_device *dev = priv; + + wwan_ptr = netdev_priv(dev); + if (evt == IPA_RECEIVE) { + struct sk_buff *skb = (struct sk_buff *)data; + int ret; + unsigned int packet_len = skb->len; + + skb->dev = rmnet_ipa_ctx->dev; + skb->protocol = htons(ETH_P_MAP); + + ret = netif_receive_skb(skb); + if (ret) { + pr_err_ratelimited("fail on netif_receive_skb\n"); + dev->stats.rx_dropped++; + } + dev->stats.rx_packets++; + dev->stats.rx_bytes += packet_len; + } else if (evt == IPA_CLIENT_START_POLL) { + napi_schedule(&wwan_ptr->napi); + } else if (evt == IPA_CLIENT_COMP_NAPI) { + napi_complete(&wwan_ptr->napi); + } else { + ipa_err("Invalid evt %d received in wan_ipa_receive\n", evt); + } +} + +/** handle_ingress_format() - Ingress data format configuration */ +static int handle_ingress_format(struct net_device *dev, + struct rmnet_ioctl_extended_s *in) +{ + enum ipa_cs_offload_en offload_type; + enum ipa_client_type client; + u32 metadata_offset; + u32 rx_buffer_size; + u32 channel_count; + u32 length_offset; + u32 header_size; + bool aggr_active; + u32 aggr_bytes; + u32 aggr_count; + u32 aggr_size; /* in KB */ + u32 ep_id; + int ret; + + client = IPA_CLIENT_APPS_WAN_CONS; + channel_count = IPA_APPS_WWAN_CONS_RING_COUNT; + header_size = sizeof(struct rmnet_map_header_s); + metadata_offset = offsetof(struct rmnet_map_header_s, mux_id); + length_offset = offsetof(struct rmnet_map_header_s, pkt_len); + offload_type = IPA_CS_OFFLOAD_NONE; + aggr_bytes = IPA_GENERIC_AGGR_BYTE_LIMIT; + aggr_count = IPA_GENERIC_AGGR_PKT_LIMIT; + aggr_active = false; + + if (in->u.data & RMNET_IOCTL_INGRESS_FORMAT_CHECKSUM) + offload_type = IPA_CS_OFFLOAD_DL; + + if (in->u.data & RMNET_IOCTL_INGRESS_FORMAT_AGG_DATA) { + aggr_bytes = in->u.ingress_format.agg_size; + aggr_count = in->u.ingress_format.agg_count; + aggr_active = true; + } + + if (aggr_bytes > ipa_reg_aggr_max_byte_limit()) + return -EINVAL; + + if (aggr_count > ipa_reg_aggr_max_packet_limit()) + return -EINVAL; + + /* Compute the buffer size required to handle the requested + * aggregation byte limit. The aggr_byte_limit value is + * expressed as a number of KB, but we derive that value + * after computing the buffer size to use (in bytes). The + * buffer must be sufficient to hold one IPA_MTU-sized + * packet *after* the limit is reached. + * + * (Note that the rx_buffer_size value reflects only the + * space for data, not any standard metadata or headers.) + */ + rx_buffer_size = ipa_aggr_byte_limit_buf_size(aggr_bytes); + + /* Account for the extra IPA_MTU past the limit in the + * buffer, and convert the result to the KB units the + * aggr_byte_limit uses. + */ + aggr_size = (rx_buffer_size - IPA_MTU) / SZ_1K; + + mutex_lock(&rmnet_ipa_ctx->ep_setup_mutex); + + if (rmnet_ipa_ctx->wan_cons_ep_id != IPA_EP_ID_BAD) { + ret = -EBUSY; + goto out_unlock; + } + + ret = ipa_ep_alloc(client); + if (ret < 0) + goto out_unlock; + ep_id = ret; + + /* Record our endpoint configuration parameters */ + ipa_endp_init_hdr_cons(ep_id, header_size, metadata_offset, + length_offset); + ipa_endp_init_hdr_ext_cons(ep_id, 0, true); + ipa_endp_init_aggr_cons(ep_id, aggr_size, aggr_count, true); + ipa_endp_init_cfg_cons(ep_id, offload_type); + ipa_endp_init_hdr_metadata_mask_cons(ep_id, 0xff000000); + ipa_endp_status_cons(ep_id, !aggr_active); + + ipa_ctx->ipa_client_apps_wan_cons_agg_gro = aggr_active; + + ret = ipa_ep_setup(ep_id, channel_count, 1, rx_buffer_size, + apps_ipa_packet_receive_notify, dev); + if (ret) + ipa_ep_free(ep_id); + else + rmnet_ipa_ctx->wan_cons_ep_id = ep_id; +out_unlock: + mutex_unlock(&rmnet_ipa_ctx->ep_setup_mutex); + + return ret; +} + +/** handle_egress_format() - Egress data format configuration */ +static int handle_egress_format(struct net_device *dev, + struct rmnet_ioctl_extended_s *e) +{ + enum ipa_cs_offload_en offload_type; + enum ipa_client_type dst_client; + enum ipa_client_type client; + enum ipa_aggr_type aggr_type; + enum ipa_aggr_en aggr_en; + u32 channel_count; + u32 length_offset; + u32 header_align; + u32 header_offset; + u32 header_size; + u32 ep_id; + int ret; + + client = IPA_CLIENT_APPS_WAN_PROD; + dst_client = IPA_CLIENT_APPS_LAN_CONS; + channel_count = IPA_APPS_WWAN_PROD_RING_COUNT; + header_size = sizeof(struct rmnet_map_header_s); + offload_type = IPA_CS_OFFLOAD_NONE; + aggr_en = IPA_BYPASS_AGGR; + aggr_type = 0; /* ignored if BYPASS */ + header_offset = 0; + length_offset = 0; + header_align = 0; + + if (e->u.data & RMNET_IOCTL_EGRESS_FORMAT_CHECKSUM) { + offload_type = IPA_CS_OFFLOAD_UL; + header_offset = sizeof(struct rmnet_map_header_s) / 4; + header_size += sizeof(u32); + } + + if (e->u.data & RMNET_IOCTL_EGRESS_FORMAT_AGGREGATION) { + aggr_en = IPA_ENABLE_DEAGGR; + aggr_type = IPA_QCMAP; + length_offset = offsetof(struct rmnet_map_header_s, pkt_len); + header_align = ilog2(sizeof(u32)); + } + + mutex_lock(&rmnet_ipa_ctx->ep_setup_mutex); + + if (rmnet_ipa_ctx->wan_prod_ep_id != IPA_EP_ID_BAD) { + ret = -EBUSY; + goto out_unlock; + } + + ret = ipa_ep_alloc(client); + if (ret < 0) + goto out_unlock; + ep_id = ret; + + if (aggr_en == IPA_ENABLE_DEAGGR && !ipa_endp_aggr_support(ep_id)) { + ret = -ENOTSUPP; + goto out_unlock; + } + + /* We really do want 0 metadata offset */ + ipa_endp_init_hdr_prod(ep_id, header_size, 0, length_offset); + ipa_endp_init_hdr_ext_prod(ep_id, header_align); + ipa_endp_init_mode_prod(ep_id, IPA_BASIC, dst_client); + ipa_endp_init_aggr_prod(ep_id, aggr_en, aggr_type); + ipa_endp_init_cfg_prod(ep_id, offload_type, header_offset); + ipa_endp_init_seq_prod(ep_id); + ipa_endp_init_deaggr_prod(ep_id); + /* Enable source notification status for exception packets + * (i.e. QMAP commands) to be routed to modem. + */ + ipa_endp_status_prod(ep_id, true, IPA_CLIENT_Q6_WAN_CONS); + + /* Use a deferred interrupting no-op to reduce completion interrupts */ + ipa_no_intr_init(ep_id); + + ret = ipa_ep_setup(ep_id, channel_count, 1, 0, + apps_ipa_tx_complete_notify, dev); + if (ret) + ipa_ep_free(ep_id); + else + rmnet_ipa_ctx->wan_prod_ep_id = ep_id; + +out_unlock: + mutex_unlock(&rmnet_ipa_ctx->ep_setup_mutex); + + return ret; +} + +/** ipa_wwan_add_mux_channel() - add a mux_id */ +static int ipa_wwan_add_mux_channel(u32 mux_id) +{ + int ret; + u32 i; + + mutex_lock(&rmnet_ipa_ctx->mux_id_mutex); + + if (rmnet_ipa_ctx->mux_id_count >= MUX_CHANNEL_MAX) { + ret = -EFAULT; + goto out; + } + + for (i = 0; i < rmnet_ipa_ctx->mux_id_count; i++) + if (mux_id == rmnet_ipa_ctx->mux_id[i]) + break; + + /* Record the mux_id if it hasn't already been seen */ + if (i == rmnet_ipa_ctx->mux_id_count) + rmnet_ipa_ctx->mux_id[rmnet_ipa_ctx->mux_id_count++] = mux_id; + ret = 0; +out: + mutex_unlock(&rmnet_ipa_ctx->mux_id_mutex); + + return ret; +} + +/** ipa_wwan_ioctl_extended() - rmnet extended I/O control */ +static int ipa_wwan_ioctl_extended(struct net_device *dev, void __user *data) +{ + struct rmnet_ioctl_extended_s edata = { }; + size_t size = sizeof(edata); + + if (copy_from_user(&edata, data, size)) + return -EFAULT; + + switch (edata.extended_ioctl) { + case RMNET_IOCTL_GET_SUPPORTED_FEATURES: /* Get features */ + edata.u.data = RMNET_IOCTL_FEAT_NOTIFY_MUX_CHANNEL; + edata.u.data |= RMNET_IOCTL_FEAT_SET_EGRESS_DATA_FORMAT; + edata.u.data |= RMNET_IOCTL_FEAT_SET_INGRESS_DATA_FORMAT; + goto copy_out; + + case RMNET_IOCTL_GET_EPID: /* Get endpoint ID */ + edata.u.data = 1; + goto copy_out; + + case RMNET_IOCTL_GET_DRIVER_NAME: /* Get driver name */ + memcpy(&edata.u.if_name, rmnet_ipa_ctx->dev->name, IFNAMSIZ); + goto copy_out; + + case RMNET_IOCTL_ADD_MUX_CHANNEL: /* Add MUX ID */ + return ipa_wwan_add_mux_channel(edata.u.rmnet_mux_val.mux_id); + + case RMNET_IOCTL_SET_EGRESS_DATA_FORMAT: /* Egress data format */ + return handle_egress_format(dev, &edata) ? -EFAULT : 0; + + case RMNET_IOCTL_SET_INGRESS_DATA_FORMAT: /* Ingress format */ + return handle_ingress_format(dev, &edata) ? -EFAULT : 0; + + case RMNET_IOCTL_GET_EP_PAIR: /* Get endpoint pair */ + edata.u.ipa_ep_pair.consumer_pipe_num = + ipa_client_ep_id(IPA_CLIENT_APPS_WAN_PROD); + edata.u.ipa_ep_pair.producer_pipe_num = + ipa_client_ep_id(IPA_CLIENT_APPS_WAN_CONS); + goto copy_out; + + case RMNET_IOCTL_GET_SG_SUPPORT: /* Get SG support */ + edata.u.data = 1; /* Scatter/gather is always supported */ + goto copy_out; + + /* Unsupported requests */ + case RMNET_IOCTL_SET_MRU: /* Set MRU */ + case RMNET_IOCTL_GET_MRU: /* Get MRU */ + case RMNET_IOCTL_GET_AGGREGATION_COUNT: /* Get agg count */ + case RMNET_IOCTL_SET_AGGREGATION_COUNT: /* Set agg count */ + case RMNET_IOCTL_GET_AGGREGATION_SIZE: /* Get agg size */ + case RMNET_IOCTL_SET_AGGREGATION_SIZE: /* Set agg size */ + case RMNET_IOCTL_FLOW_CONTROL: /* Do flow control */ + case RMNET_IOCTL_GET_DFLT_CONTROL_CHANNEL: /* For legacy use */ + case RMNET_IOCTL_GET_HWSW_MAP: /* Get HW/SW map */ + case RMNET_IOCTL_SET_RX_HEADROOM: /* Set RX Headroom */ + case RMNET_IOCTL_SET_QOS_VERSION: /* Set 8/6 byte QoS */ + case RMNET_IOCTL_GET_QOS_VERSION: /* Get 8/6 byte QoS */ + case RMNET_IOCTL_GET_SUPPORTED_QOS_MODES: /* Get QoS modes */ + case RMNET_IOCTL_SET_SLEEP_STATE: /* Set sleep state */ + case RMNET_IOCTL_SET_XLAT_DEV_INFO: /* xlat dev name */ + case RMNET_IOCTL_DEREGISTER_DEV: /* Deregister netdev */ + return -ENOTSUPP; /* Defined, but unsupported command */ + + default: + return -EINVAL; /* Invalid (unrecognized) command */ + } + +copy_out: + return copy_to_user(data, &edata, size) ? -EFAULT : 0; +} + +/** ipa_wwan_ioctl() - I/O control for wwan network driver */ +static int ipa_wwan_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) +{ + struct rmnet_ioctl_data_s ioctl_data = { }; + void __user *data; + size_t size; + + data = ifr->ifr_ifru.ifru_data; + size = sizeof(ioctl_data); + + switch (cmd) { + /* These features are implied; alternatives are not supported */ + case RMNET_IOCTL_SET_LLP_IP: /* RAW IP protocol */ + case RMNET_IOCTL_SET_QOS_DISABLE: /* QoS header disabled */ + return 0; + + /* These features are not supported; use alternatives */ + case RMNET_IOCTL_SET_LLP_ETHERNET: /* Ethernet protocol */ + case RMNET_IOCTL_SET_QOS_ENABLE: /* QoS header enabled */ + case RMNET_IOCTL_GET_OPMODE: /* Get operation mode */ + case RMNET_IOCTL_FLOW_ENABLE: /* Flow enable */ + case RMNET_IOCTL_FLOW_DISABLE: /* Flow disable */ + case RMNET_IOCTL_FLOW_SET_HNDL: /* Set flow handle */ + return -ENOTSUPP; + + case RMNET_IOCTL_GET_LLP: /* Get link protocol */ + ioctl_data.u.operation_mode = RMNET_MODE_LLP_IP; + goto copy_out; + + case RMNET_IOCTL_GET_QOS: /* Get QoS header state */ + ioctl_data.u.operation_mode = RMNET_MODE_NONE; + goto copy_out; + + case RMNET_IOCTL_OPEN: /* Open transport port */ + case RMNET_IOCTL_CLOSE: /* Close transport port */ + return 0; + + case RMNET_IOCTL_EXTENDED: /* Extended IOCTLs */ + return ipa_wwan_ioctl_extended(dev, data); + + default: + return -EINVAL; + } + +copy_out: + return copy_to_user(data, &ioctl_data, size) ? -EFAULT : 0; +} + +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_do_ioctl = ipa_wwan_ioctl, +}; + +/** wwan_setup() - Setup the wwan network driver */ +static void ipa_wwan_setup(struct net_device *dev) +{ + dev->netdev_ops = &ipa_wwan_ops_ip; + ether_setup(dev); + dev->header_ops = NULL; /* No header (override ether_setup() value) */ + dev->type = ARPHRD_RAWIP; + dev->hard_header_len = 0; + dev->max_mtu = WWAN_DATA_LEN; + dev->mtu = dev->max_mtu; + dev->addr_len = 0; + dev->flags &= ~(IFF_BROADCAST | IFF_MULTICAST); + dev->needed_headroom = HEADROOM_FOR_QMAP; + dev->needed_tailroom = TAILROOM; + dev->watchdog_timeo = msecs_to_jiffies(10 * MSEC_PER_SEC); +} + +/** ipa_wwan_probe() - Network probe function */ +static int ipa_wwan_probe(struct platform_device *pdev) +{ + struct ipa_wwan_private *wwan_ptr; + struct net_device *dev; + int ret; + + mutex_init(&rmnet_ipa_ctx->ep_setup_mutex); + mutex_init(&rmnet_ipa_ctx->mux_id_mutex); + + /* Mark client handles bad until we initialize them */ + rmnet_ipa_ctx->wan_prod_ep_id = IPA_EP_ID_BAD; + rmnet_ipa_ctx->wan_cons_ep_id = IPA_EP_ID_BAD; + + ret = ipa_modem_smem_init(); + if (ret) + goto err_clear_ctx; + + /* start A7 QMI service/client */ + ipa_qmi_init(); + + /* initialize wan-driver netdev */ + dev = alloc_netdev(sizeof(struct ipa_wwan_private), + IPA_WWAN_DEV_NAME, + NET_NAME_UNKNOWN, + ipa_wwan_setup); + if (!dev) { + ipa_err("no memory for netdev\n"); + ret = -ENOMEM; + goto err_clear_ctx; + } + rmnet_ipa_ctx->dev = dev; + wwan_ptr = netdev_priv(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); + + /* Enable SG support in netdevice. */ + dev->hw_features |= NETIF_F_SG; + + netif_napi_add(dev, &wwan_ptr->napi, ipa_rmnet_poll, NAPI_WEIGHT); + ret = register_netdev(dev); + if (ret) { + ipa_err("unable to register ipa_netdev %d rc=%d\n", 0, ret); + goto err_napi_del; + } + + /* offline charging mode */ + ipa_proxy_clk_unvote(); + + /* Till the system is suspended, we keep the clock open */ + ipa_client_add(); + + initialized = true; + + return 0; + +err_napi_del: + netif_napi_del(&wwan_ptr->napi); + free_netdev(dev); +err_clear_ctx: + memset(&rmnet_ipa_ctx_struct, 0, sizeof(rmnet_ipa_ctx_struct)); + + return ret; +} + +static int ipa_wwan_remove(struct platform_device *pdev) +{ + struct ipa_wwan_private *wwan_ptr = netdev_priv(rmnet_ipa_ctx->dev); + + dev_info(&pdev->dev, "rmnet_ipa started deinitialization\n"); + + mutex_lock(&rmnet_ipa_ctx->ep_setup_mutex); + + ipa_client_add(); + + if (rmnet_ipa_ctx->wan_cons_ep_id != IPA_EP_ID_BAD) { + ipa_ep_teardown(rmnet_ipa_ctx->wan_cons_ep_id); + rmnet_ipa_ctx->wan_cons_ep_id = IPA_EP_ID_BAD; + } + + if (rmnet_ipa_ctx->wan_prod_ep_id != IPA_EP_ID_BAD) { + ipa_ep_teardown(rmnet_ipa_ctx->wan_prod_ep_id); + rmnet_ipa_ctx->wan_prod_ep_id = IPA_EP_ID_BAD; + } + + ipa_client_remove(); + + netif_napi_del(&wwan_ptr->napi); + mutex_unlock(&rmnet_ipa_ctx->ep_setup_mutex); + unregister_netdev(rmnet_ipa_ctx->dev); + + if (rmnet_ipa_ctx->dev) + free_netdev(rmnet_ipa_ctx->dev); + rmnet_ipa_ctx->dev = NULL; + + mutex_destroy(&rmnet_ipa_ctx->mux_id_mutex); + mutex_destroy(&rmnet_ipa_ctx->ep_setup_mutex); + + initialized = false; + + dev_info(&pdev->dev, "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 = rmnet_ipa_ctx->dev; + struct ipa_wwan_private *wwan_ptr; + int ret; + + if (!netdev) { + ipa_err("netdev is NULL.\n"); + ret = 0; + goto bail; + } + + netif_tx_lock_bh(netdev); + wwan_ptr = netdev_priv(netdev); + if (!wwan_ptr) { + ipa_err("wwan_ptr is NULL.\n"); + ret = 0; + goto unlock_and_bail; + } + + /* Do not allow A7 to suspend in case there are outstanding packets */ + if (atomic_read(&wwan_ptr->outstanding_pkts) != 0) { + ret = -EAGAIN; + goto unlock_and_bail; + } + + /* Make sure that there is no Tx operation ongoing */ + netif_stop_queue(netdev); + + ret = 0; + ipa_client_remove(); + +unlock_and_bail: + netif_tx_unlock_bh(netdev); +bail: + + return ret; +} + +/** 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 = rmnet_ipa_ctx->dev; + + ipa_client_add(); + if (netdev) + netif_wake_queue(netdev); + + return 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, +}; + +int ipa_wwan_init(void) +{ + if (initialized) + return 0; + + return platform_driver_register(&rmnet_ipa_driver); +} + +void ipa_wwan_cleanup(void) +{ + platform_driver_unregister(&rmnet_ipa_driver); + memset(&rmnet_ipa_ctx_struct, 0, sizeof(rmnet_ipa_ctx_struct)); +} + +static int ipa_rmnet_poll(struct napi_struct *napi, int budget) +{ + return ipa_rx_poll(rmnet_ipa_ctx->wan_cons_ep_id, budget); +} + +MODULE_DESCRIPTION("WWAN Network Interface"); +MODULE_LICENSE("GPL v2"); -- 2.17.1