Merge "msm: bam_dmux: Introduce watermarking in client transmit queue" into msm-3.0
diff --git a/arch/arm/mach-msm/bam_dmux.c b/arch/arm/mach-msm/bam_dmux.c
index ede298d..e3ca6d1 100644
--- a/arch/arm/mach-msm/bam_dmux.c
+++ b/arch/arm/mach-msm/bam_dmux.c
@@ -45,6 +45,8 @@
 #define POLLING_MAX_SLEEP	1050	/* 1.05 ms */
 #define POLLING_INACTIVITY	40	/* cycles before switch to intr mode */
 
+#define LOW_WATERMARK		2
+#define HIGH_WATERMARK		4
 
 static int msm_bam_dmux_debug_enable;
 module_param_named(debug_enable, msm_bam_dmux_debug_enable,
@@ -104,6 +106,8 @@
 	spinlock_t lock;
 	struct platform_device *pdev;
 	char name[BAM_DMUX_CH_NAME_MAX_LEN];
+	int num_tx_pkts;
+	int use_wm;
 };
 
 struct tx_pkt_info {
@@ -318,6 +322,7 @@
 	case BAM_MUX_HDR_CMD_OPEN:
 		spin_lock_irqsave(&bam_ch[rx_hdr->ch_id].lock, flags);
 		bam_ch[rx_hdr->ch_id].status |= BAM_CH_REMOTE_OPEN;
+		bam_ch[rx_hdr->ch_id].num_tx_pkts = 0;
 		spin_unlock_irqrestore(&bam_ch[rx_hdr->ch_id].lock, flags);
 		queue_rx();
 		ret = platform_device_add(bam_ch[rx_hdr->ch_id].pdev);
@@ -400,6 +405,7 @@
 	struct tx_pkt_info *info;
 	unsigned long event_data;
 	struct list_head *node;
+	unsigned long flags;
 
 	if (in_global_reset)
 		return;
@@ -418,6 +424,9 @@
 	hdr = (struct bam_mux_hdr *)skb->data;
 	DBG_INC_WRITE_CNT(skb->data_len);
 	event_data = (unsigned long)(skb);
+	spin_lock_irqsave(&bam_ch[hdr->ch_id].lock, flags);
+	bam_ch[hdr->ch_id].num_tx_pkts--;
+	spin_unlock_irqrestore(&bam_ch[hdr->ch_id].lock, flags);
 	if (bam_ch[hdr->ch_id].notify)
 		bam_ch[hdr->ch_id].notify(
 			bam_ch[hdr->ch_id].priv, BAM_DMUX_WRITE_DONE,
@@ -449,6 +458,13 @@
 		pr_err("%s: port not open: %d\n", __func__, bam_ch[id].status);
 		return -ENODEV;
 	}
+
+	if (bam_ch[id].use_wm &&
+	    (bam_ch[id].num_tx_pkts >= HIGH_WATERMARK)) {
+		spin_unlock_irqrestore(&bam_ch[id].lock, flags);
+		pr_err("%s: watermark exceeded: %d\n", __func__, id);
+		return -EAGAIN;
+	}
 	spin_unlock_irqrestore(&bam_ch[id].lock, flags);
 
 	read_lock(&ul_wakeup_lock);
@@ -520,6 +536,10 @@
 		DBG_INC_TX_SPS_FAILURE_CNT();
 		spin_unlock(&bam_tx_pool_spinlock);
 		kfree(pkt);
+	} else {
+		spin_lock_irqsave(&bam_ch[id].lock, flags);
+		bam_ch[id].num_tx_pkts++;
+		spin_unlock_irqrestore(&bam_ch[id].lock, flags);
 	}
 	ul_packet_written = 1;
 	read_unlock(&ul_wakeup_lock);
@@ -578,6 +598,8 @@
 	bam_ch[id].notify = notify;
 	bam_ch[id].priv = priv;
 	bam_ch[id].status |= BAM_CH_LOCAL_OPEN;
+	bam_ch[id].num_tx_pkts = 0;
+	bam_ch[id].use_wm = 0;
 	spin_unlock_irqrestore(&bam_ch[id].lock, flags);
 
 	read_lock(&ul_wakeup_lock);
@@ -655,6 +677,47 @@
 	return rc;
 }
 
+int msm_bam_dmux_is_ch_full(uint32_t id)
+{
+	unsigned long flags;
+	int ret;
+
+	if (id >= BAM_DMUX_NUM_CHANNELS)
+		return -EINVAL;
+
+	spin_lock_irqsave(&bam_ch[id].lock, flags);
+	bam_ch[id].use_wm = 1;
+	ret = bam_ch[id].num_tx_pkts >= HIGH_WATERMARK;
+	DBG("%s: ch %d num tx pkts=%d, HWM=%d\n", __func__,
+	     id, bam_ch[id].num_tx_pkts, ret);
+	if (!bam_ch_is_local_open(id)) {
+		ret = -ENODEV;
+		pr_err("%s: port not open: %d\n", __func__, bam_ch[id].status);
+	}
+	spin_unlock_irqrestore(&bam_ch[id].lock, flags);
+
+	return ret;
+}
+
+int msm_bam_dmux_is_ch_low(uint32_t id)
+{
+	int ret;
+
+	if (id >= BAM_DMUX_NUM_CHANNELS)
+		return -EINVAL;
+
+	bam_ch[id].use_wm = 1;
+	ret = bam_ch[id].num_tx_pkts <= LOW_WATERMARK;
+	DBG("%s: ch %d num tx pkts=%d, LWM=%d\n", __func__,
+	     id, bam_ch[id].num_tx_pkts, ret);
+	if (!bam_ch_is_local_open(id)) {
+		ret = -ENODEV;
+		pr_err("%s: port not open: %d\n", __func__, bam_ch[id].status);
+	}
+
+	return ret;
+}
+
 static void rx_timer_work_func(struct work_struct *work)
 {
 	struct sps_iovec iov;
@@ -1034,6 +1097,7 @@
 	for (i = 0; i < BAM_DMUX_NUM_CHANNELS; ++i) {
 		temp_remote_status = bam_ch_is_remote_open(i);
 		bam_ch[i].status &= ~BAM_CH_REMOTE_OPEN;
+		bam_ch[i].num_tx_pkts = 0;
 		if (bam_ch_is_local_open(i))
 			bam_ch[i].status |= BAM_CH_IN_RESET;
 		if (temp_remote_status) {
diff --git a/arch/arm/mach-msm/include/mach/bam_dmux.h b/arch/arm/mach-msm/include/mach/bam_dmux.h
index a2b0126..fb70da4 100644
--- a/arch/arm/mach-msm/include/mach/bam_dmux.h
+++ b/arch/arm/mach-msm/include/mach/bam_dmux.h
@@ -59,6 +59,10 @@
 int msm_bam_dmux_write(uint32_t id, struct sk_buff *skb);
 
 void msm_bam_dmux_kickoff_ul_wakeup(void);
+
+int msm_bam_dmux_is_ch_full(uint32_t id);
+
+int msm_bam_dmux_is_ch_low(uint32_t id);
 #else
 int msm_bam_dmux_open(uint32_t id, void *priv,
 		       void (*notify)(void *priv, int event_type,
@@ -80,5 +84,15 @@
 void msm_bam_dmux_kickoff_ul_wakeup(void)
 {
 }
+
+int msm_bam_dmux_is_ch_full(uint32_t id)
+{
+	return -ENODEV;
+}
+
+int msm_bam_dmux_is_ch_low(uint32_t id)
+{
+	return -ENODEV;
+}
 #endif
 #endif /* _BAM_DMUX_H */
diff --git a/drivers/net/msm_rmnet_bam.c b/drivers/net/msm_rmnet_bam.c
index bb20a3f..99a2c04 100644
--- a/drivers/net/msm_rmnet_bam.c
+++ b/drivers/net/msm_rmnet_bam.c
@@ -342,7 +342,12 @@
 	    ((struct net_device *)(dev))->name, p->stats.tx_packets,
 	    skb->len, skb->mark);
 	dev_kfree_skb_any(skb);
-	netif_wake_queue(dev);
+	if (netif_queue_stopped(dev) &&
+	    msm_bam_dmux_is_ch_low(p->ch_id)) {
+		DBG0("%s: Low WM hit, waking queue=%p\n",
+		      __func__, skb);
+		netif_wake_queue(dev);
+	}
 }
 
 static void bam_notify(void *dev, int event, unsigned long data)
@@ -452,7 +457,6 @@
 		return 0;
 	}
 
-	netif_stop_queue(dev);
 	if (!ul_is_connected) {
 		p->waiting_for_ul = 1;
 		msm_bam_dmux_kickoff_ul_wakeup();
@@ -460,6 +464,11 @@
 	}
 	_rmnet_xmit(skb, dev);
 
+	if (msm_bam_dmux_is_ch_full(p->ch_id)) {
+		netif_stop_queue(dev);
+		DBG0("%s: High WM hit, stopping queue=%p\n",    __func__, skb);
+	}
+
 	return 0;
 }