Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 15fa5a8a authored by Eric Holmberg's avatar Eric Holmberg Committed by Stephen Boyd
Browse files

tty: n_smux: Fix workqueue flush deadlock and local mode reset



During a subsystem restart or a line-discipline unload with active data
transfer, the logical channel purge function (smux_lch_purge) could lead
to a deadlock while flushing the workqueues if it is called with
mutex_lha0 locked and one of the worker functions calls a function that
locks mutex_lha0.  In addition, the local mode was reset which is not
expected behavior.

This change aborts all work functions in the case of a reset allowing
the flush to be performed without any locks held.  Once the flush is
complete, then the appropriate locks can be obtained before the reset
flag is cleared and work items can once again get scheduled.

Change-Id: I2afbcc3885907cd0cc8695f7c7a85b61a999e1fb
Signed-off-by: default avatarEric Holmberg <eholmber@codeaurora.org>
parent b726fc0b
Loading
Loading
Loading
Loading
+56 −12
Original line number Diff line number Diff line
@@ -358,6 +358,7 @@ static int ssr_notifier_cb(struct notifier_block *this,
				void *data);
static void smux_uart_power_on_atomic(void);
static int smux_rx_flow_control_updated(struct smux_lch_t *ch);
static void smux_flush_workqueues(void);

/**
 * Convert TTY Error Flags to string for logging purposes.
@@ -513,7 +514,6 @@ static void smux_lch_purge(void)
		}

		ch->local_state = SMUX_LCH_LOCAL_CLOSED;
		ch->local_mode = SMUX_LCH_MODE_NORMAL;
		ch->remote_state = SMUX_LCH_REMOTE_CLOSED;
		ch->remote_mode = SMUX_LCH_MODE_NORMAL;
		ch->tx_flow_control = 0;
@@ -526,12 +526,6 @@ static void smux_lch_purge(void)

		spin_unlock_irqrestore(&ch->state_lock_lhb1, flags);
	}

	/* Flush TX/RX workqueues */
	SMUX_DBG("%s: flushing tx wq\n", __func__);
	flush_workqueue(smux_tx_wq);
	SMUX_DBG("%s: flushing rx wq\n", __func__);
	flush_workqueue(smux_rx_wq);
}

int smux_assert_lch_id(uint32_t lcid)
@@ -2232,12 +2226,13 @@ static void smux_uart_power_on(void)

/**
 * Power down the UART.
 *
 * Must be called with mutex_lha0 locked.
 */
static void smux_uart_power_off(void)
static void smux_uart_power_off_atomic(void)
{
	struct uart_state *state;

	mutex_lock(&smux.mutex_lha0);
	if (!smux.tty || !smux.tty->driver_data) {
		pr_err("%s: unable to find UART port for tty %p\n",
				__func__, smux.tty);
@@ -2246,6 +2241,15 @@ static void smux_uart_power_off(void)
	}
	state = smux.tty->driver_data;
	msm_hs_request_clock_off(state->uart_port);
}

/**
 * Power down the UART.
 */
static void smux_uart_power_off(void)
{
	mutex_lock(&smux.mutex_lha0);
	smux_uart_power_off_atomic();
	mutex_unlock(&smux.mutex_lha0);
}

@@ -2327,6 +2331,9 @@ static void smux_inactivity_worker(struct work_struct *work)
	struct smux_pkt_t *pkt;
	unsigned long flags;

	if (smux.in_reset)
		return;

	spin_lock_irqsave(&smux.rx_lock_lha1, flags);
	spin_lock(&smux.tx_lock_lha2);

@@ -2446,6 +2453,12 @@ static void smux_rx_worker(struct work_struct *work)
	SMUX_DBG("%s: %p, len=%d, flag=%d\n", __func__, data, len, flag);
	used = 0;
	do {
		if (smux.in_reset) {
			SMUX_DBG("%s: abort RX due to reset\n", __func__);
			smux.rx_state = SMUX_RX_IDLE;
			break;
		}

		SMUX_DBG("%s: state %d; %d of %d\n",
				__func__, smux.rx_state, used, len);
		initial_rx_state = smux.rx_state;
@@ -2494,7 +2507,7 @@ static void smux_rx_retry_worker(struct work_struct *work)

	/* get next retry packet */
	spin_lock_irqsave(&ch->state_lock_lhb1, flags);
	if (ch->local_state != SMUX_LCH_LOCAL_OPENED) {
	if ((ch->local_state != SMUX_LCH_LOCAL_OPENED) || smux.in_reset) {
		/* port has been closed - remove all retries */
		while (!list_empty(&ch->rx_retry_queue)) {
			retry = list_first_entry(&ch->rx_retry_queue,
@@ -2797,6 +2810,26 @@ static int smux_rx_flow_control_updated(struct smux_lch_t *ch)
	return updated;
}

/**
 * Flush all SMUX workqueues.
 *
 * This sets the reset bit to abort any processing loops and then
 * flushes the workqueues to ensure that no new pending work is
 * running.  Do not call with any locks used by workers held as
 * this will result in a deadlock.
 */
static void smux_flush_workqueues(void)
{
	smux.in_reset = 1;

	SMUX_DBG("%s: flushing tx wq\n", __func__);
	flush_workqueue(smux_tx_wq);
	SMUX_DBG("%s: flushing rx wq\n", __func__);
	flush_workqueue(smux_rx_wq);
	SMUX_DBG("%s: flushing notify wq\n", __func__);
	flush_workqueue(smux_notify_wq);
}

/**********************************************************************/
/* Kernel API                                                         */
/**********************************************************************/
@@ -2922,6 +2955,7 @@ int msm_smux_open(uint8_t lcid, void *priv,
			ch->local_state,
			SMUX_LCH_LOCAL_OPENING);

	ch->rx_flow_control_auto = 0;
	ch->local_state = SMUX_LCH_LOCAL_OPENING;

	ch->priv = priv;
@@ -2948,6 +2982,7 @@ int msm_smux_open(uint8_t lcid, void *priv,

out:
	spin_unlock_irqrestore(&ch->state_lock_lhb1, flags);
	smux_rx_flow_control_updated(ch);
	if (tx_ready)
		list_channel(ch);
	return ret;
@@ -3341,6 +3376,7 @@ static int ssr_notifier_cb(struct notifier_block *this,
	SMUX_DBG("%s: ssr - after shutdown\n", __func__);

	/* Cleanup channels */
	smux_flush_workqueues();
	mutex_lock(&smux.mutex_lha0);
	smux_lch_purge();
	if (smux.tty)
@@ -3357,8 +3393,11 @@ static int ssr_notifier_cb(struct notifier_block *this,
	spin_unlock_irqrestore(&smux.tx_lock_lha2, flags);

	if (power_off_uart)
		smux_uart_power_off();
		smux_uart_power_off_atomic();

	smux.tx_activity_flag = 0;
	smux.rx_activity_flag = 0;
	smux.rx_state = SMUX_RX_IDLE;
	smux.in_reset = 0;
	mutex_unlock(&smux.mutex_lha0);

@@ -3440,6 +3479,8 @@ static void smuxld_close(struct tty_struct *tty)
	int i;

	SMUX_DBG("%s: ldisc unload\n", __func__);
	smux_flush_workqueues();

	mutex_lock(&smux.mutex_lha0);
	if (smux.ld_open_count <= 0) {
		pr_err("%s: invalid ld count %d\n", __func__,
@@ -3447,7 +3488,6 @@ static void smuxld_close(struct tty_struct *tty)
		mutex_unlock(&smux.mutex_lha0);
		return;
	}
	smux.in_reset = 1;
	--smux.ld_open_count;

	/* Cleanup channels */
@@ -3466,11 +3506,15 @@ static void smuxld_close(struct tty_struct *tty)
		power_up_uart = 1;
	smux.power_state = SMUX_PWR_OFF;
	smux.powerdown_enabled = 0;
	smux.tx_activity_flag = 0;
	smux.rx_activity_flag = 0;
	spin_unlock_irqrestore(&smux.tx_lock_lha2, flags);

	if (power_up_uart)
		smux_uart_power_on_atomic();

	smux.rx_state = SMUX_RX_IDLE;

	/* Disconnect from TTY */
	smux.tty = NULL;
	mutex_unlock(&smux.mutex_lha0);
+202 −0
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@
#include <linux/completion.h>
#include <linux/termios.h>
#include <linux/smux.h>
#include <mach/subsystem_restart.h>
#include "smux_private.h"

#define DEBUG_BUFMAX 4096
@@ -207,6 +208,9 @@ struct smux_mock_callback {
	struct list_head write_events;
};

static int get_rx_buffer_mock(void *priv, void **pkt_priv,
		void **buffer, int size);

/**
 * Initialize mock callback data. Only call once.
 *
@@ -672,6 +676,198 @@ static int smux_ut_remote_basic(char *buf, int max)
	return i;
}

/**
 * Verify Basic Subsystem Restart Support
 *
 * Run a basic loopback test followed by a subsystem restart and then another
 * loopback test.
 */
static int smux_ut_remote_ssr_basic(char *buf, int max)
{
	const struct test_vector test_data[] = {
		{"hello\0world\n", sizeof("hello\0world\n")},
		{0, 0},
	};
	int i = 0;
	int failed = 0;
	int ret;

	i += scnprintf(buf + i, max - i, "Running %s\n", __func__);
	while (!failed) {
		/* enable remote mode */
		ret = msm_smux_set_ch_option(SMUX_TEST_LCID,
				SMUX_CH_OPTION_REMOTE_LOOPBACK, 0);
		UT_ASSERT_INT(ret, ==, 0);

		i += smux_ut_basic_core(buf + i, max - i, test_data, __func__);
		subsystem_restart("external_modem");
		msleep(5000);
		i += smux_ut_basic_core(buf + i, max - i, test_data, __func__);
		break;
	}

	if (failed) {
		pr_err("%s: Failed\n", __func__);
		i += scnprintf(buf + i, max - i, "\tFailed\n");
	}
	return i;
}

/**
 * Verify Subsystem Restart Support During Port Open
 */
static int smux_ut_remote_ssr_open(char *buf, int max)
{
	static struct smux_mock_callback cb_data;
	static int cb_initialized;
	int ret;
	int i = 0;
	int failed = 0;

	i += scnprintf(buf + i, max - i, "Running %s\n", __func__);

	if (!cb_initialized)
		mock_cb_data_init(&cb_data);

	mock_cb_data_reset(&cb_data);
	while (!failed) {
		ret = msm_smux_set_ch_option(SMUX_TEST_LCID,
				SMUX_CH_OPTION_REMOTE_LOOPBACK, 0);
		UT_ASSERT_INT(ret, ==, 0);

		/* open port */
		ret = msm_smux_open(SMUX_TEST_LCID, &cb_data, smux_mock_cb,
					get_rx_buffer);
		UT_ASSERT_INT(ret, ==, 0);
		UT_ASSERT_INT(
			(int)wait_for_completion_timeout(
					&cb_data.cb_completion, HZ), >, 0);
		UT_ASSERT_INT(cb_data.cb_count, ==, 1);
		UT_ASSERT_INT(cb_data.event_connected, ==, 1);
		mock_cb_data_reset(&cb_data);

		/* restart modem */
		subsystem_restart("external_modem");

		/* verify SSR events */
		UT_ASSERT_INT(ret, ==, 0);
		UT_ASSERT_INT(
			(int)wait_for_completion_timeout(
				&cb_data.cb_completion, 5*HZ),
			>, 0);
		UT_ASSERT_INT(cb_data.cb_count, ==, 1);
		UT_ASSERT_INT(cb_data.event_disconnected, ==, 1);
		UT_ASSERT_INT(cb_data.event_disconnected_ssr, ==, 1);
		mock_cb_data_reset(&cb_data);

		/* close port */
		ret = msm_smux_close(SMUX_TEST_LCID);
		UT_ASSERT_INT(ret, ==, 0);
		break;
	}

	if (!failed) {
		i += scnprintf(buf + i, max - i, "\tOK\n");
	} else {
		pr_err("%s: Failed\n", __func__);
		i += scnprintf(buf + i, max - i, "\tFailed\n");
		i += mock_cb_data_print(&cb_data, buf + i, max - i);
		msm_smux_close(SMUX_TEST_LCID);
	}

	mock_cb_data_reset(&cb_data);

	return i;
}

/**
 * Verify get_rx_buffer callback retry doesn't livelock SSR
 * until all RX Bufffer Retries have timed out.
 *
 * @buf  Buffer for status message
 * @max  Size of buffer
 *
 * @returns Number of bytes written to @buf
 */
static int smux_ut_remote_ssr_rx_buff_retry(char *buf, int max)
{
	static struct smux_mock_callback cb_data;
	static int cb_initialized;
	int i = 0;
	int failed = 0;
	int ret;

	i += scnprintf(buf + i, max - i, "Running %s\n", __func__);
	pr_err("%s", buf);

	if (!cb_initialized)
		mock_cb_data_init(&cb_data);

	mock_cb_data_reset(&cb_data);
	while (!failed) {
		/* open port for loopback */
		ret = msm_smux_set_ch_option(SMUX_TEST_LCID,
				SMUX_CH_OPTION_REMOTE_LOOPBACK,
				0);
		UT_ASSERT_INT(ret, ==, 0);

		ret = msm_smux_open(SMUX_TEST_LCID, &cb_data,
				smux_mock_cb, get_rx_buffer_mock);
		UT_ASSERT_INT(ret, ==, 0);
		UT_ASSERT_INT(
				(int)wait_for_completion_timeout(
					&cb_data.cb_completion, HZ), >, 0);
		UT_ASSERT_INT(cb_data.cb_count, ==, 1);
		UT_ASSERT_INT(cb_data.event_connected, ==, 1);
		mock_cb_data_reset(&cb_data);

		/* Queue up an RX buffer retry */
		get_rx_buffer_mock_fail = 1;
		ret = msm_smux_write(SMUX_TEST_LCID, (void *)1,
					test_array, sizeof(test_array));
		UT_ASSERT_INT(ret, ==, 0);
		while (!cb_data.get_rx_buff_retry_count) {
			UT_ASSERT_INT(
				(int)wait_for_completion_timeout(
					&cb_data.cb_completion, HZ),
				>, 0);
			INIT_COMPLETION(cb_data.cb_completion);
		}
		if (failed)
			break;
		mock_cb_data_reset(&cb_data);

		/* trigger SSR */
		subsystem_restart("external_modem");

		/* verify SSR completed */
		UT_ASSERT_INT(ret, ==, 0);
		UT_ASSERT_INT(
			(int)wait_for_completion_timeout(
				&cb_data.cb_completion, 5*HZ),
			>, 0);
		UT_ASSERT_INT(cb_data.cb_count, ==, 1);
		UT_ASSERT_INT(cb_data.event_disconnected, ==, 1);
		UT_ASSERT_INT(cb_data.event_disconnected_ssr, ==, 1);
		mock_cb_data_reset(&cb_data);

		/* close port */
		ret = msm_smux_close(SMUX_TEST_LCID);
		UT_ASSERT_INT(ret, ==, 0);
		break;
	}

	if (!failed) {
		i += scnprintf(buf + i, max - i, "\tOK\n");
	} else {
		pr_err("%s: Failed\n", __func__);
		i += scnprintf(buf + i, max - i, "\tFailed\n");
		i += mock_cb_data_print(&cb_data, buf + i, max - i);
		msm_smux_close(SMUX_TEST_LCID);
	}
	mock_cb_data_reset(&cb_data);
	return i;
}
/**
 * Fill test pattern into provided buffer including an optional
 * redzone 16 bytes before and 16 bytes after the buffer.
@@ -1793,6 +1989,12 @@ static int __init smux_debugfs_init(void)
			smux_ut_local_get_rx_buff_retry);
	debug_create("ut_local_get_rx_buff_retry_auto", 0444, dent,
			smux_ut_local_get_rx_buff_retry_auto);
	debug_create("ut_remote_ssr_basic", 0444, dent,
			smux_ut_remote_ssr_basic);
	debug_create("ut_remote_ssr_open", 0444, dent,
			smux_ut_remote_ssr_open);
	debug_create("ut_remote_ssr_rx_buff_retry", 0444, dent,
			smux_ut_remote_ssr_rx_buff_retry);

	return 0;
}