[media] img-ir/hw: Fix potential deadlock stopping timer
The end timer is used for switching back from repeat code timings when no repeat codes have been received for a certain amount of time. When the protocol is changed, the end timer is deleted synchronously with del_timer_sync(), however this takes place while holding the main spin lock, and the timer handler also needs to acquire the spin lock. This opens the possibility of a deadlock on an SMP system if the protocol is changed just as the repeat timer is expiring. One CPU could end up in img_ir_set_decoder() holding the lock and waiting for the end timer to complete, while the other CPU is stuck in the timer handler spinning on the lock held by the first CPU. Lockdep also spots a possible lock inversion in the same code, since img_ir_set_decoder() acquires the img-ir lock before the timer lock, but the timer handler will try and acquire them the other way around: ========================================================= [ INFO: possible irq lock inversion dependency detected ] 3.18.0-rc5+ #957 Not tainted --------------------------------------------------------- swapper/0/0 just changed the state of lock: (((&hw->end_timer))){+.-...}, at: [<4006ae5c>] _call_timer_fn+0x0/0xfc but this lock was taken by another, HARDIRQ-safe lock in the past: (&(&priv->lock)->rlock#2){-.....} and interrupts could create inverse lock ordering between them. other info that might help us debug this: Possible interrupt unsafe locking scenario: CPU0 CPU1 ---- ---- lock(((&hw->end_timer))); local_irq_disable(); lock(&(&priv->lock)->rlock#2); lock(((&hw->end_timer))); <Interrupt> lock(&(&priv->lock)->rlock#2); *** DEADLOCK *** This is fixed by releasing the main spin lock while performing the del_timer_sync() call. The timer is prevented from restarting before the lock is reacquired by a new "stopping" flag which img_ir_handle_data() checks before updating the timer. --------------------------------------------------------- swapper/0/0 just changed the state of lock: (((&hw->end_timer))){+.-...}, at: [<4006ae5c>] _call_timer_fn+0x0/0xfc but this lock was taken by another, HARDIRQ-safe lock in the past: (&(&priv->lock)->rlock#2){-.....} and interrupts could create inverse lock ordering between them. other info that might help us debug this: Possible interrupt unsafe locking scenario: CPU0 CPU1 ---- ---- lock(((&hw->end_timer))); local_irq_disable(); lock(&(&priv->lock)->rlock#2); lock(((&hw->end_timer))); <Interrupt> lock(&(&priv->lock)->rlock#2); *** DEADLOCK *** This is fixed by releasing the main spin lock while performing the del_timer_sync() call. The timer is prevented from restarting before the lock is reacquired by a new "stopping" flag which img_ir_handle_data() checks before updating the timer. Signed-off-by: James Hogan <james.hogan@imgtec.com> Cc: Sifan Naeem <sifan.naeem@imgtec.com> Cc: <stable@vger.kernel.org> # v3.15+ Signed-off-by: Mauro Carvalho Chehab <mchehab@osg.samsung.com>
This commit is contained in:
parent
ea0de4ec54
commit
ac03086067
|
@ -530,6 +530,22 @@ static void img_ir_set_decoder(struct img_ir_priv *priv,
|
|||
u32 ir_status, irq_en;
|
||||
spin_lock_irq(&priv->lock);
|
||||
|
||||
/*
|
||||
* First record that the protocol is being stopped so that the end timer
|
||||
* isn't restarted while we're trying to stop it.
|
||||
*/
|
||||
hw->stopping = true;
|
||||
|
||||
/*
|
||||
* Release the lock to stop the end timer, since the end timer handler
|
||||
* acquires the lock and we don't want to deadlock waiting for it.
|
||||
*/
|
||||
spin_unlock_irq(&priv->lock);
|
||||
del_timer_sync(&hw->end_timer);
|
||||
spin_lock_irq(&priv->lock);
|
||||
|
||||
hw->stopping = false;
|
||||
|
||||
/* switch off and disable interrupts */
|
||||
img_ir_write(priv, IMG_IR_CONTROL, 0);
|
||||
irq_en = img_ir_read(priv, IMG_IR_IRQ_ENABLE);
|
||||
|
@ -547,8 +563,7 @@ static void img_ir_set_decoder(struct img_ir_priv *priv,
|
|||
img_ir_read(priv, IMG_IR_DATA_LW);
|
||||
img_ir_read(priv, IMG_IR_DATA_UP);
|
||||
|
||||
/* stop the end timer and switch back to normal mode */
|
||||
del_timer_sync(&hw->end_timer);
|
||||
/* switch back to normal mode */
|
||||
hw->mode = IMG_IR_M_NORMAL;
|
||||
|
||||
/* clear the wakeup scancode filter */
|
||||
|
@ -819,7 +834,8 @@ static void img_ir_handle_data(struct img_ir_priv *priv, u32 len, u64 raw)
|
|||
}
|
||||
|
||||
|
||||
if (dec->repeat) {
|
||||
/* we mustn't update the end timer while trying to stop it */
|
||||
if (dec->repeat && !hw->stopping) {
|
||||
unsigned long interval;
|
||||
|
||||
img_ir_begin_repeat(priv);
|
||||
|
|
|
@ -214,6 +214,8 @@ enum img_ir_mode {
|
|||
* @flags: IMG_IR_F_*.
|
||||
* @filters: HW filters (derived from scancode filters).
|
||||
* @mode: Current decode mode.
|
||||
* @stopping: Indicates that decoder is being taken down and timers
|
||||
* should not be restarted.
|
||||
* @suspend_irqen: Saved IRQ enable mask over suspend.
|
||||
*/
|
||||
struct img_ir_priv_hw {
|
||||
|
@ -229,6 +231,7 @@ struct img_ir_priv_hw {
|
|||
struct img_ir_filter filters[RC_FILTER_MAX];
|
||||
|
||||
enum img_ir_mode mode;
|
||||
bool stopping;
|
||||
u32 suspend_irqen;
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue