tty: smux_ctl: Support removing line discipline gracefully

SMUX_CTL module currently does not handle removal of the driver
gracefully. If there are any pending blocked calls on wait_event,
and the line discipline is removed, this results in accessing
destroyed wake queues.

Add support for aborting wait in response to device removal.

Change-Id: I3213306f6ed401f3abe5770ce324d9329e4dbc3f
Signed-off-by: Eric Holmberg <eholmber@codeaurora.org>
diff --git a/drivers/tty/smux_ctl.c b/drivers/tty/smux_ctl.c
index 69adbf3..ae25f11 100644
--- a/drivers/tty/smux_ctl.c
+++ b/drivers/tty/smux_ctl.c
@@ -78,6 +78,7 @@
 	uint32_t read_avail;
 	struct list_head rx_list;
 
+	int abort_wait;
 	wait_queue_head_t read_wait_queue;
 	wait_queue_head_t write_wait_queue;
 
@@ -359,7 +360,8 @@
 
 		r = wait_event_interruptible_timeout(
 				devp->write_wait_queue,
-				(devp->state == SMUX_CONNECTED),
+				(devp->state == SMUX_CONNECTED ||
+				 devp->abort_wait),
 				(5 * HZ));
 		if (r == 0)
 			r = -ETIMEDOUT;
@@ -372,6 +374,13 @@
 			msm_smux_close(devp->id);
 			return r;
 
+		} else if (devp->abort_wait) {
+			pr_err("%s: %s: Open command aborted\n",
+					SMUX_CTL_MODULE_NAME, __func__);
+			r = -EIO;
+			atomic_dec(&devp->ref_count);
+			msm_smux_close(devp->id);
+			return r;
 		} else if (devp->state != SMUX_CONNECTED) {
 			pr_err(SMUX_CTL_MODULE_NAME ": %s: "
 				"Invalid open notification\n", __func__);
@@ -440,8 +449,9 @@
 
 	if (signal_pending(current))
 		r = -ERESTARTSYS;
-
-	if (smux_ctl_devp[dev_index]->state == SMUX_DISCONNECTED &&
+	else if (smux_ctl_devp[dev_index]->abort_wait)
+		r = -ENETRESET;
+	else if (smux_ctl_devp[dev_index]->state == SMUX_DISCONNECTED &&
 	    smux_ctl_devp[dev_index]->is_channel_reset != 0)
 		r = -ENETRESET;
 
@@ -560,6 +570,9 @@
 
 	if (signal_pending(current))
 		r = -ERESTARTSYS;
+
+	else if (smux_ctl_devp[dev_index]->abort_wait)
+		r = -ENETRESET;
 	else if (smux_ctl_devp[dev_index]->state == SMUX_DISCONNECTED &&
 	    smux_ctl_devp[dev_index]->is_channel_reset != 0)
 		r = -ENETRESET;
@@ -645,6 +658,13 @@
 
 	r = wait_event_interruptible(devp->write_wait_queue,
 			0 != (write_err = smux_ctl_writeable(id)));
+
+	if (-EIO == r) {
+		pr_err("%s: %s: wait_event_interruptible ret %i\n",
+				SMUX_CTL_MODULE_NAME, __func__, r);
+		return -EIO;
+	}
+
 	if (r < 0) {
 		pr_err(SMUX_CTL_MODULE_NAME " :%s: wait_event_interruptible "
 				"ret %i\n", __func__, r);
@@ -699,6 +719,25 @@
 	.unlocked_ioctl = smux_ctl_ioctl,
 };
 
+static void smux_ctl_reset_channel(struct smux_ctl_dev *devp)
+{
+	devp->is_high_wm = 0;
+	devp->write_pending = 0;
+	devp->is_channel_reset = 0;
+	devp->state = SMUX_DISCONNECTED;
+	devp->read_avail = 0;
+
+	devp->stats.bytes_tx = 0;
+	devp->stats.bytes_rx = 0;
+	devp->stats.pkts_tx = 0;
+	devp->stats.pkts_rx = 0;
+	devp->stats.cnt_ssr = 0;
+	devp->stats.cnt_read_fail = 0;
+	devp->stats.cnt_write_fail = 0;
+	devp->stats.cnt_high_wm_hit = 0;
+	devp->abort_wait = 0;
+}
+
 static int smux_ctl_probe(struct platform_device *pdev)
 {
 	int i;
@@ -706,6 +745,27 @@
 
 	SMUXCTL_DBG(SMUX_CTL_MODULE_NAME ": %s Begins\n", __func__);
 
+	if (smux_ctl_inited) {
+		/* Already loaded once - reinitialize channels */
+		for (i = 0; i < SMUX_CTL_NUM_CHANNELS; ++i) {
+			struct smux_ctl_dev *devp = smux_ctl_devp[i];
+
+			smux_ctl_reset_channel(devp);
+
+			if (atomic_read(&devp->ref_count)) {
+				r = msm_smux_open(devp->id,
+						devp,
+						smux_ctl_notify_cb,
+						smux_ctl_get_rx_buf_cb);
+				if (r)
+					pr_err("%s: unable to reopen ch %d, ret %d\n",
+							__func__, devp->id, r);
+			}
+		}
+		return 0;
+	}
+
+	/* Create character devices */
 	for (i = 0; i < SMUX_CTL_NUM_CHANNELS; ++i) {
 		smux_ctl_devp[i] = kzalloc(sizeof(struct smux_ctl_dev),
 							GFP_KERNEL);
@@ -718,26 +778,13 @@
 
 		smux_ctl_devp[i]->id = smux_ctl_ch_id[i];
 		atomic_set(&smux_ctl_devp[i]->ref_count, 0);
-		smux_ctl_devp[i]->is_high_wm = 0;
-		smux_ctl_devp[i]->write_pending = 0;
-		smux_ctl_devp[i]->is_channel_reset = 0;
-		smux_ctl_devp[i]->state = SMUX_DISCONNECTED;
-		smux_ctl_devp[i]->read_avail = 0;
-
-		smux_ctl_devp[i]->stats.bytes_tx = 0;
-		smux_ctl_devp[i]->stats.bytes_rx = 0;
-		smux_ctl_devp[i]->stats.pkts_tx = 0;
-		smux_ctl_devp[i]->stats.pkts_rx = 0;
-		smux_ctl_devp[i]->stats.cnt_ssr = 0;
-		smux_ctl_devp[i]->stats.cnt_read_fail = 0;
-		smux_ctl_devp[i]->stats.cnt_write_fail = 0;
-		smux_ctl_devp[i]->stats.cnt_high_wm_hit = 0;
 
 		mutex_init(&smux_ctl_devp[i]->dev_lock);
 		init_waitqueue_head(&smux_ctl_devp[i]->read_wait_queue);
 		init_waitqueue_head(&smux_ctl_devp[i]->write_wait_queue);
 		mutex_init(&smux_ctl_devp[i]->rx_lock);
 		INIT_LIST_HEAD(&smux_ctl_devp[i]->rx_list);
+		smux_ctl_reset_channel(smux_ctl_devp[i]);
 	}
 
 	r = alloc_chrdev_region(&smux_ctl_number, 0, SMUX_CTL_NUM_CHANNELS,
@@ -761,7 +808,8 @@
 		cdev_init(&smux_ctl_devp[i]->cdev, &smux_ctl_fops);
 		smux_ctl_devp[i]->cdev.owner = THIS_MODULE;
 
-		r = cdev_add(&smux_ctl_devp[i]->cdev, (smux_ctl_number + i), 1);
+		r = cdev_add(&smux_ctl_devp[i]->cdev,
+				(smux_ctl_number + i), 1);
 
 		if (IS_ERR_VALUE(r)) {
 			pr_err(SMUX_CTL_MODULE_NAME ": %s: "
@@ -818,15 +866,32 @@
 	SMUXCTL_DBG(SMUX_CTL_MODULE_NAME ": %s Begins\n", __func__);
 
 	for (i = 0; i < SMUX_CTL_NUM_CHANNELS; ++i) {
-		cdev_del(&smux_ctl_devp[i]->cdev);
-		kfree(smux_ctl_devp[i]);
-		device_destroy(smux_ctl_classp,
-			MKDEV(MAJOR(smux_ctl_number), i));
-	}
-	class_destroy(smux_ctl_classp);
-	unregister_chrdev_region(MAJOR(smux_ctl_number),
-			SMUX_CTL_NUM_CHANNELS);
+		struct smux_ctl_dev *devp = smux_ctl_devp[i];
 
+		mutex_lock(&devp->dev_lock);
+		devp->abort_wait = 1;
+		wake_up(&devp->write_wait_queue);
+		wake_up(&devp->read_wait_queue);
+		mutex_unlock(&devp->dev_lock);
+
+		/* Empty RX queue */
+		mutex_lock(&devp->rx_lock);
+		while (!list_empty(&devp->rx_list)) {
+			struct smux_ctl_list_elem *list_elem;
+
+			list_elem = list_first_entry(
+					&devp->rx_list,
+					struct smux_ctl_list_elem,
+					list);
+			list_del(&list_elem->list);
+			kfree(list_elem->ctl_pkt.data);
+			kfree(list_elem);
+		}
+		devp->read_avail = 0;
+		mutex_unlock(&devp->rx_lock);
+	}
+
+	SMUXCTL_DBG(SMUX_CTL_MODULE_NAME ": %s Ends\n", __func__);
 	return 0;
 }