summaryrefslogtreecommitdiff
path: root/drivers/tty
diff options
context:
space:
mode:
authorGirish Mahadevan <girishm@codeaurora.org>2016-02-11 15:28:31 -0700
committerDavid Keitel <dkeitel@codeaurora.org>2016-03-25 16:04:18 -0700
commitb39662b7c399ef773b8a089fe1ba562a6f01aef2 (patch)
treea1212dac8bece49a7128e7733817d59854ac5a98 /drivers/tty
parent18076633657f3cc4fdebbe49935f4e26327071d1 (diff)
msm_serial_hs: Fix race condition blocking suspend and remove wakeup source
The __pm_stay_awake/relax() calls were moved to runtime callbacks as a means to fix data loss due to race conditions between incoming userspace commands and executing PM related callbacks. However calling the __pm_relax (to notify wakeup event processing) as part of the runtime suspend callback could be race prone between the system suspend and the runtime framework. If the system suspend gets to run before the runtime callback, the wakeup event will block system suspend. Remove the use of the wakeup source altogether, instead do these: 1. Block system suspend based on the current clk request count and the RPM state of the device (to detect potential inbound userspace requests). 2. If the driver is in the process of executing the system suspend callback, ignore any userspace requests. 3. If the client calls a shutdown without an unvote ioctl, zero out the client_count vote forcefully to allow suspend. CRs-Fixed: 977421 Change-Id: I17de85f29b555c1a4563dd59bec3ba3084c3604f Signed-off-by: Girish Mahadevan <girishm@codeaurora.org>
Diffstat (limited to 'drivers/tty')
-rw-r--r--drivers/tty/serial/msm_serial_hs.c125
1 files changed, 84 insertions, 41 deletions
diff --git a/drivers/tty/serial/msm_serial_hs.c b/drivers/tty/serial/msm_serial_hs.c
index 856deefe0a8e..6843711774b2 100644
--- a/drivers/tty/serial/msm_serial_hs.c
+++ b/drivers/tty/serial/msm_serial_hs.c
@@ -3,7 +3,7 @@
* MSM 7k High speed uart driver
*
* Copyright (c) 2008 Google Inc.
- * Copyright (c) 2007-2015, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2007-2016, The Linux Foundation. All rights reserved.
* Modified: Nick Pelly <npelly@google.com>
*
* All source code in this file is licensed under the following license
@@ -216,7 +216,6 @@ struct msm_hs_port {
struct msm_hs_rx rx;
atomic_t clk_count;
struct msm_hs_wakeup wakeup;
- struct wakeup_source ws;
struct dentry *loopback_dir;
struct work_struct clock_off_w; /* work for actual clock off */
@@ -292,13 +291,16 @@ static int msm_hs_ioctl(struct uart_port *uport, unsigned int cmd,
int ret = 0, state = 1;
struct msm_hs_port *msm_uport = UARTDM_TO_MSM(uport);
+ if (!msm_uport)
+ return -ENODEV;
+
switch (cmd) {
case MSM_ENABLE_UART_CLOCK: {
- msm_hs_request_clock_on(&msm_uport->uport);
+ ret = msm_hs_request_clock_on(&msm_uport->uport);
break;
}
case MSM_DISABLE_UART_CLOCK: {
- msm_hs_request_clock_off(&msm_uport->uport);
+ ret = msm_hs_request_clock_off(&msm_uport->uport);
break;
}
case MSM_GET_UART_CLOCK_STATUS: {
@@ -398,8 +400,8 @@ static void msm_hs_resource_vote(struct msm_hs_port *msm_uport)
struct uart_port *uport = &(msm_uport->uport);
ret = pm_runtime_get_sync(uport->dev);
if (ret < 0 || msm_uport->pm_state != MSM_HS_PM_ACTIVE) {
- MSM_HS_WARN("%s(): %p runtime PM callback not invoked",
- __func__, uport->dev);
+ MSM_HS_WARN("%s(): %p runtime PM callback not invoked(%d)",
+ __func__, uport->dev, ret);
msm_hs_pm_resume(uport->dev);
}
@@ -719,7 +721,6 @@ static int msm_hs_remove(struct platform_device *pdev)
msm_uport->rx.buffer = NULL;
msm_uport->rx.rbuffer = 0;
- wakeup_source_trash(&msm_uport->ws);
destroy_workqueue(msm_uport->hsuart_wq);
mutex_destroy(&msm_uport->mtx);
@@ -2262,14 +2263,30 @@ void msm_hs_resource_on(struct msm_hs_port *msm_uport)
}
/* Request to turn off uart clock once pending TX is flushed */
-void msm_hs_request_clock_off(struct uart_port *uport)
+int msm_hs_request_clock_off(struct uart_port *uport)
{
struct msm_hs_port *msm_uport = UARTDM_TO_MSM(uport);
+ int ret = 0;
+
+ mutex_lock(&msm_uport->mtx);
+ /*
+ * If we're in the middle of a system suspend, don't process these
+ * userspace/kernel API commands.
+ */
+ if (msm_uport->pm_state == MSM_HS_PM_SYS_SUSPENDED) {
+ MSM_HS_WARN("%s:Can't process clk request during suspend",
+ __func__);
+ ret = -EIO;
+ }
+ mutex_unlock(&msm_uport->mtx);
+ if (ret)
+ goto exit_request_clock_off;
if (atomic_read(&msm_uport->client_count) <= 0) {
MSM_HS_WARN("%s(): ioctl count -ve, client check voting",
__func__);
- return;
+ ret = -EPERM;
+ goto exit_request_clock_off;
}
/* Set the flag to disable flow control and wakeup irq */
if (msm_uport->obs)
@@ -2278,23 +2295,42 @@ void msm_hs_request_clock_off(struct uart_port *uport)
atomic_dec(&msm_uport->client_count);
MSM_HS_INFO("%s():DISABLE UART CLOCK: ioc %d\n",
__func__, atomic_read(&msm_uport->client_count));
+exit_request_clock_off:
+ return ret;
}
EXPORT_SYMBOL(msm_hs_request_clock_off);
-void msm_hs_request_clock_on(struct uart_port *uport)
+int msm_hs_request_clock_on(struct uart_port *uport)
{
struct msm_hs_port *msm_uport = UARTDM_TO_MSM(uport);
int client_count;
+ int ret = 0;
+ mutex_lock(&msm_uport->mtx);
+ /*
+ * If we're in the middle of a system suspend, don't process these
+ * userspace/kernel API commands.
+ */
+ if (msm_uport->pm_state == MSM_HS_PM_SYS_SUSPENDED) {
+ MSM_HS_WARN("%s:Can't process clk request during suspend",
+ __func__);
+ ret = -EIO;
+ }
+ mutex_unlock(&msm_uport->mtx);
+ if (ret)
+ goto exit_request_clock_on;
+
+ msm_hs_resource_vote(UARTDM_TO_MSM(uport));
atomic_inc(&msm_uport->client_count);
client_count = atomic_read(&msm_uport->client_count);
MSM_HS_INFO("%s():ENABLE UART CLOCK: ioc %d\n",
__func__, client_count);
- msm_hs_resource_vote(UARTDM_TO_MSM(uport));
/* Clear the flag */
if (msm_uport->obs)
atomic_set(&msm_uport->client_req_state, 0);
+exit_request_clock_on:
+ return ret;
}
EXPORT_SYMBOL(msm_hs_request_clock_on);
@@ -2548,7 +2584,7 @@ static int msm_hs_startup(struct uart_port *uport)
ret = msm_hs_config_uart_gpios(uport);
if (ret) {
MSM_HS_ERR("Uart GPIO request failed\n");
- goto deinit_ws;
+ goto free_uart_irq;
}
msm_hs_write(uport, UART_DM_DMEN, 0);
@@ -2651,8 +2687,6 @@ sps_disconnect_tx:
sps_disconnect(sps_pipe_handle_tx);
unconfig_uart_gpios:
msm_hs_unconfig_uart_gpios(uport);
-deinit_ws:
- wakeup_source_trash(&msm_uport->ws);
free_uart_irq:
free_irq(uport->irq, msm_uport);
unvote_exit:
@@ -3061,6 +3095,7 @@ static void msm_hs_pm_suspend(struct device *dev)
if (!msm_uport)
goto err_suspend;
+ mutex_lock(&msm_uport->mtx);
/* For OBS, don't use wakeup interrupt, set gpio to suspended state */
if (msm_uport->obs) {
@@ -3077,8 +3112,8 @@ static void msm_hs_pm_suspend(struct device *dev)
msm_hs_clk_bus_unvote(msm_uport);
if (!atomic_read(&msm_uport->client_req_state))
toggle_wakeup_interrupt(msm_uport);
- __pm_relax(&msm_uport->ws);
MSM_HS_DBG("%s(): return suspend\n", __func__);
+ mutex_unlock(&msm_uport->mtx);
return;
err_suspend:
pr_err("%s(): invalid uport", __func__);
@@ -3093,12 +3128,13 @@ static int msm_hs_pm_resume(struct device *dev)
if (!msm_uport)
goto err_resume;
+
+ mutex_lock(&msm_uport->mtx);
if (msm_uport->pm_state == MSM_HS_PM_ACTIVE)
- return 0;
+ goto exit_pm_resume;
if (!atomic_read(&msm_uport->client_req_state))
toggle_wakeup_interrupt(msm_uport);
msm_hs_clk_bus_vote(msm_uport);
- __pm_stay_awake(&msm_uport->ws);
obs_manage_irq(msm_uport, true);
msm_uport->pm_state = MSM_HS_PM_ACTIVE;
msm_hs_resource_on(msm_uport);
@@ -3113,6 +3149,8 @@ static int msm_hs_pm_resume(struct device *dev)
}
MSM_HS_DBG("%s(): return resume\n", __func__);
+exit_pm_resume:
+ mutex_unlock(&msm_uport->mtx);
return 0;
err_resume:
pr_err("%s(): invalid uport", __func__);
@@ -3125,36 +3163,34 @@ static int msm_hs_pm_sys_suspend_noirq(struct device *dev)
struct platform_device *pdev = to_platform_device(dev);
struct msm_hs_port *msm_uport = get_matching_hs_port(pdev);
enum msm_hs_pm_state prev_pwr_state;
- struct uart_port *uport;
+ int clk_cnt, client_count, ret = 0;
if (IS_ERR_OR_NULL(msm_uport))
return -ENODEV;
- /* client vote is active, fail sys suspend */
- if (atomic_read(&msm_uport->client_count))
- return -EBUSY;
+ mutex_lock(&msm_uport->mtx);
+
+ /*
+ * If there is an active clk request or an impending userspace request
+ * fail the suspend callback.
+ */
+ clk_cnt = atomic_read(&msm_uport->clk_count);
+ client_count = atomic_read(&msm_uport->client_count);
+ if (clk_cnt || (pm_runtime_enabled(dev) &&
+ !pm_runtime_suspended(dev))) {
+ MSM_HS_WARN("%s:Fail Suspend.clk_cnt:%d,clnt_count:%d,RPM:%d\n",
+ __func__, clk_cnt, client_count,
+ dev->power.runtime_status);
+ ret = -EBUSY;
+ goto exit_suspend_noirq;
+ }
- MSM_HS_DBG("%s(): suspending", __func__);
prev_pwr_state = msm_uport->pm_state;
- uport = &(msm_uport->uport);
- mutex_lock(&msm_uport->mtx);
msm_uport->pm_state = MSM_HS_PM_SYS_SUSPENDED;
+ MSM_HS_DBG("%s(): suspending", __func__);
+exit_suspend_noirq:
mutex_unlock(&msm_uport->mtx);
-
- if (prev_pwr_state == MSM_HS_PM_ACTIVE) {
- msm_hs_pm_suspend(dev);
- /*
- * Synchronize runtime pm and system pm states:
- * at this point we are already suspended. However
- * the RT PM framework still thinks that we are active.
- * The calls below let RT PM know that we are suspended
- * without re-invoking the suspend callback
- */
- pm_runtime_disable(uport->dev);
- pm_runtime_set_suspended(uport->dev);
- pm_runtime_enable(uport->dev);
- }
- return 0;
+ return ret;
};
static int msm_hs_pm_sys_resume_noirq(struct device *dev)
@@ -3169,8 +3205,12 @@ static int msm_hs_pm_sys_resume_noirq(struct device *dev)
* variable. Resource activation will be done
* when transfer is requested.
*/
+
+ mutex_lock(&msm_uport->mtx);
MSM_HS_DBG("%s(): system resume", __func__);
- msm_uport->pm_state = MSM_HS_PM_SUSPENDED;
+ if (msm_uport->pm_state == MSM_HS_PM_SYS_SUSPENDED)
+ msm_uport->pm_state = MSM_HS_PM_SUSPENDED;
+ mutex_unlock(&msm_uport->mtx);
return 0;
}
#endif
@@ -3440,7 +3480,6 @@ static int msm_hs_probe(struct platform_device *pdev)
uport->line = pdata->userid;
ret = uart_add_one_port(&msm_hs_driver, uport);
if (!ret) {
- wakeup_source_init(&msm_uport->ws, dev_name(&pdev->dev));
msm_hs_clk_bus_unvote(msm_uport);
msm_serial_hs_rt_init(uport);
return ret;
@@ -3593,6 +3632,10 @@ static void msm_hs_shutdown(struct uart_port *uport)
MSM_HS_WARN("%s: Client clock vote imbalance\n", __func__);
atomic_set(&msm_uport->client_req_state, 0);
}
+ if (atomic_read(&msm_uport->client_count)) {
+ MSM_HS_WARN("%s: Client vote on, forcing to 0\n", __func__);
+ atomic_set(&msm_uport->client_count, 0);
+ }
msm_hs_unconfig_uart_gpios(uport);
MSM_HS_INFO("%s:UART port closed successfully\n", __func__);
}