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 */