msm: smd_pkt: fix wake lock locking race condition

When smd_pkt receives data for a client in userspace, smd_pkt locks a wake
lock to give the userspace client time to lock their own wake lock if
necessary.  Clients using poll() will trigger the unlocking of the wake
lock in smd_pkt, otherwise smd_pkt modifies the wake lock to expire in two
seconds.

Fix a race condition where smd_pkt locks the wake lock, the client reads
their data and triggers the unlocking, and then smd_pkt relocks the wake
lock when it modifies the timeout to two seconds.  This causes suspend to
be unnecessarily delayed.  The fix is to bypass setting the timeout if the
client has already read their data and triggered the unlocking of the wake
lock.

CRs-Fixed: 367001
Change-Id: I4e8f6975732a6139534fbeadc539e6f74ff3fce5
Signed-off-by: Jeffrey Hugo <jhugo@codeaurora.org>
diff --git a/arch/arm/mach-msm/smd_pkt.c b/arch/arm/mach-msm/smd_pkt.c
index b9cba8c..8d567f8 100644
--- a/arch/arm/mach-msm/smd_pkt.c
+++ b/arch/arm/mach-msm/smd_pkt.c
@@ -76,6 +76,7 @@
 	struct wake_lock pa_wake_lock;		/* Packet Arrival Wake lock*/
 	struct work_struct packet_arrival_work;
 	struct spinlock pa_spinlock;
+	int wakelock_locked;
 } *smd_pkt_devp[NUM_SMD_PKT_PORTS];
 
 struct class *smd_pkt_classp;
@@ -233,16 +234,19 @@
 static void packet_arrival_worker(struct work_struct *work)
 {
 	struct smd_pkt_dev *smd_pkt_devp;
+	unsigned long flags;
 
 	smd_pkt_devp = container_of(work, struct smd_pkt_dev,
 				    packet_arrival_work);
 	mutex_lock(&smd_pkt_devp->ch_lock);
-	if (smd_pkt_devp->ch) {
+	spin_lock_irqsave(&smd_pkt_devp->pa_spinlock, flags);
+	if (smd_pkt_devp->ch && smd_pkt_devp->wakelock_locked) {
 		D_READ("%s locking smd_pkt_dev id:%d wakelock\n",
 			__func__, smd_pkt_devp->i);
 		wake_lock_timeout(&smd_pkt_devp->pa_wake_lock,
 				  WAKELOCK_TIMEOUT);
 	}
+	spin_unlock_irqrestore(&smd_pkt_devp->pa_spinlock, flags);
 	mutex_unlock(&smd_pkt_devp->ch_lock);
 }
 
@@ -398,6 +402,7 @@
 	if (smd_pkt_devp->poll_mode &&
 	    !smd_cur_packet_size(smd_pkt_devp->ch)) {
 		wake_unlock(&smd_pkt_devp->pa_wake_lock);
+		smd_pkt_devp->wakelock_locked = 0;
 		smd_pkt_devp->poll_mode = 0;
 		D_READ("%s unlocked smd_pkt_dev id:%d wakelock\n",
 			__func__, smd_pkt_devp->i);
@@ -570,10 +575,11 @@
 	}
 
 	/* here we have a packet of size sz ready */
-	wake_up(&smd_pkt_devp->ch_read_wait_queue);
 	spin_lock_irqsave(&smd_pkt_devp->pa_spinlock, flags);
 	wake_lock(&smd_pkt_devp->pa_wake_lock);
+	smd_pkt_devp->wakelock_locked = 1;
 	spin_unlock_irqrestore(&smd_pkt_devp->pa_spinlock, flags);
+	wake_up(&smd_pkt_devp->ch_read_wait_queue);
 	schedule_work(&smd_pkt_devp->packet_arrival_work);
 	D_READ("%s: wake_up smd_pkt_dev id:%d\n", __func__, smd_pkt_devp->i);
 }
@@ -907,6 +913,7 @@
 
 	smd_pkt_devp->has_reset = 0;
 	smd_pkt_devp->do_reset_notification = 0;
+	smd_pkt_devp->wakelock_locked = 0;
 	wake_lock_destroy(&smd_pkt_devp->pa_wake_lock);
 	D_STATUS("Finished %s on smd_pkt_dev id:%d\n",
 		 __func__, smd_pkt_devp->i);
@@ -962,6 +969,7 @@
 		init_waitqueue_head(&smd_pkt_devp[i]->ch_write_wait_queue);
 		smd_pkt_devp[i]->is_open = 0;
 		smd_pkt_devp[i]->poll_mode = 0;
+		smd_pkt_devp[i]->wakelock_locked = 0;
 		init_waitqueue_head(&smd_pkt_devp[i]->ch_opened_wait_queue);
 
 		spin_lock_init(&smd_pkt_devp[i]->pa_spinlock);