tty: n_smux: Fix packet close synchronization issue

If a channel is logical channel open or close ack is pending in the
transmit queue when a logical channel is closed, then they are currently
purged and the remote and local channel states are mismatched and the
port cannot be re-opened.  Note that this issue only occurs during rapid
open/close/open sequences that are typically only seen in remote
loopback stress testing.

Add code to not purge ACK commands from a logical channel unless the
logical channel purge is due to a subsystem restart (at which point the
remote state is known to be power-on default).

Change-Id: I52763323642bb7c505630bb994ecc1e021270d17
Signed-off-by: Eric Holmberg <eholmber@codeaurora.org>
(cherry picked from commit 0e91408cb6343d5ac18206ff3d6639ccec5464fd)
(cherry picked from commit ef0445e15b20369c7189a61d63fb284c5a51c3fb)
diff --git a/drivers/tty/n_smux.c b/drivers/tty/n_smux.c
index 1cf7f3b..706ea04 100644
--- a/drivers/tty/n_smux.c
+++ b/drivers/tty/n_smux.c
@@ -351,7 +351,7 @@
 static int smux_send_status_cmd(struct smux_lch_t *ch);
 static int smux_dispatch_rx_pkt(struct smux_pkt_t *pkt);
 static void smux_flush_tty(void);
-static void smux_purge_ch_tx_queue(struct smux_lch_t *ch);
+static void smux_purge_ch_tx_queue(struct smux_lch_t *ch, int is_ssr);
 static int schedule_notify(uint8_t lcid, int event,
 			const union notifier_metadata *metadata);
 static int ssr_notifier_cb(struct notifier_block *this,
@@ -503,7 +503,7 @@
 
 		/* Purge TX queue */
 		spin_lock(&ch->tx_lock_lhb2);
-		smux_purge_ch_tx_queue(ch);
+		smux_purge_ch_tx_queue(ch, 1);
 		spin_unlock(&ch->tx_lock_lhb2);
 
 		/* Notify user of disconnect and reset channel state */
@@ -2158,25 +2158,35 @@
  * Purge TX queue for logical channel.
  *
  * @ch     Logical channel pointer
+ * @is_ssr 1 = this is a subsystem restart purge
  *
  * Must be called with the following spinlocks locked:
  *  state_lock_lhb1
  *  tx_lock_lhb2
  */
-static void smux_purge_ch_tx_queue(struct smux_lch_t *ch)
+static void smux_purge_ch_tx_queue(struct smux_lch_t *ch, int is_ssr)
 {
 	struct smux_pkt_t *pkt;
 	int send_disconnect = 0;
+	struct smux_pkt_t *pkt_tmp;
+	int is_state_pkt;
 
-	while (!list_empty(&ch->tx_queue)) {
-		pkt = list_first_entry(&ch->tx_queue, struct smux_pkt_t,
-							list);
-		list_del(&pkt->list);
-
+	list_for_each_entry_safe(pkt, pkt_tmp, &ch->tx_queue, list) {
+		is_state_pkt = 0;
 		if (pkt->hdr.cmd == SMUX_CMD_OPEN_LCH) {
-			/* Open was never sent, just force to closed state */
-			ch->local_state = SMUX_LCH_LOCAL_CLOSED;
-			send_disconnect = 1;
+			if (pkt->hdr.flags & SMUX_CMD_OPEN_ACK) {
+				/* Open ACK must still be sent */
+				is_state_pkt = 1;
+			} else {
+				/* Open never sent -- force to closed state */
+				ch->local_state = SMUX_LCH_LOCAL_CLOSED;
+				send_disconnect = 1;
+			}
+		} else if (pkt->hdr.cmd == SMUX_CMD_CLOSE_LCH) {
+			if (pkt->hdr.flags & SMUX_CMD_CLOSE_ACK)
+				is_state_pkt = 1;
+			if (!send_disconnect)
+				is_state_pkt = 1;
 		} else if (pkt->hdr.cmd == SMUX_CMD_DATA) {
 			/* Notify client of failed write */
 			union notifier_metadata meta_write;
@@ -2186,7 +2196,11 @@
 			meta_write.write.len = pkt->hdr.payload_len;
 			schedule_notify(ch->lcid, SMUX_WRITE_FAIL, &meta_write);
 		}
-		smux_free_pkt(pkt);
+
+		if (!is_state_pkt || is_ssr) {
+			list_del(&pkt->list);
+			smux_free_pkt(pkt);
+		}
 	}
 
 	if (send_disconnect) {
@@ -3020,7 +3034,7 @@
 
 	/* Purge TX queue */
 	spin_lock(&ch->tx_lock_lhb2);
-	smux_purge_ch_tx_queue(ch);
+	smux_purge_ch_tx_queue(ch, 0);
 	spin_unlock(&ch->tx_lock_lhb2);
 
 	/* Send Close Command */