aboutsummaryrefslogtreecommitdiff
path: root/drivers/media/cec
diff options
context:
space:
mode:
authorHans Verkuil2021-12-01 13:41:25 +0100
committerMauro Carvalho Chehab2021-12-07 11:29:56 +0100
commit713bdfa10b5957053811470d298def9537d9ff13 (patch)
tree2009bdac538ece5c9155c36b785149d71ecb120e /drivers/media/cec
parent3a2e4b193690ff2e44e95856d90bdeaf337211f6 (diff)
media: cec-pin: fix interrupt en/disable handling
The en/disable_irq() functions keep track of the 'depth': i.e. if interrupts are disabled twice, then it needs to enable_irq() calls to enable them again. The cec-pin framework didn't take this into accound and could disable irqs multiple times, and it expected that a single enable_irq() would enable them again. Move all calls to en/disable_irq() to the kthread where it is easy to keep track of the current irq state and ensure that multiple en/disable_irq calls never happen. If interrupts where disabled twice, then they would never turn on again, leaving the CEC adapter in a dead state. Signed-off-by: Hans Verkuil <hverkuil-cisco@xs4all.nl> Fixes: 865463fc03ed (media: cec-pin: add error injection support) Cc: <stable@vger.kernel.org> Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
Diffstat (limited to 'drivers/media/cec')
-rw-r--r--drivers/media/cec/core/cec-pin.c31
1 files changed, 18 insertions, 13 deletions
diff --git a/drivers/media/cec/core/cec-pin.c b/drivers/media/cec/core/cec-pin.c
index 2b6459df1e94..21f0f749713e 100644
--- a/drivers/media/cec/core/cec-pin.c
+++ b/drivers/media/cec/core/cec-pin.c
@@ -1033,6 +1033,7 @@ static int cec_pin_thread_func(void *_adap)
{
struct cec_adapter *adap = _adap;
struct cec_pin *pin = adap->pin;
+ bool irq_enabled = false;
for (;;) {
wait_event_interruptible(pin->kthread_waitq,
@@ -1060,6 +1061,7 @@ static int cec_pin_thread_func(void *_adap)
ns_to_ktime(pin->work_rx_msg.rx_ts));
msg->len = 0;
}
+
if (pin->work_tx_status) {
unsigned int tx_status = pin->work_tx_status;
@@ -1083,27 +1085,39 @@ static int cec_pin_thread_func(void *_adap)
switch (atomic_xchg(&pin->work_irq_change,
CEC_PIN_IRQ_UNCHANGED)) {
case CEC_PIN_IRQ_DISABLE:
- pin->ops->disable_irq(adap);
+ if (irq_enabled) {
+ pin->ops->disable_irq(adap);
+ irq_enabled = false;
+ }
cec_pin_high(pin);
cec_pin_to_idle(pin);
hrtimer_start(&pin->timer, ns_to_ktime(0),
HRTIMER_MODE_REL);
break;
case CEC_PIN_IRQ_ENABLE:
+ if (irq_enabled)
+ break;
pin->enable_irq_failed = !pin->ops->enable_irq(adap);
if (pin->enable_irq_failed) {
cec_pin_to_idle(pin);
hrtimer_start(&pin->timer, ns_to_ktime(0),
HRTIMER_MODE_REL);
+ } else {
+ irq_enabled = true;
}
break;
default:
break;
}
-
if (kthread_should_stop())
break;
}
+ if (pin->ops->disable_irq && irq_enabled)
+ pin->ops->disable_irq(adap);
+ hrtimer_cancel(&pin->timer);
+ cec_pin_read(pin);
+ cec_pin_to_idle(pin);
+ pin->state = CEC_ST_OFF;
return 0;
}
@@ -1129,13 +1143,7 @@ static int cec_pin_adap_enable(struct cec_adapter *adap, bool enable)
hrtimer_start(&pin->timer, ns_to_ktime(0),
HRTIMER_MODE_REL);
} else {
- if (pin->ops->disable_irq)
- pin->ops->disable_irq(adap);
- hrtimer_cancel(&pin->timer);
kthread_stop(pin->kthread);
- cec_pin_read(pin);
- cec_pin_to_idle(pin);
- pin->state = CEC_ST_OFF;
}
return 0;
}
@@ -1156,11 +1164,8 @@ void cec_pin_start_timer(struct cec_pin *pin)
if (pin->state != CEC_ST_RX_IRQ)
return;
- atomic_set(&pin->work_irq_change, CEC_PIN_IRQ_UNCHANGED);
- pin->ops->disable_irq(pin->adap);
- cec_pin_high(pin);
- cec_pin_to_idle(pin);
- hrtimer_start(&pin->timer, ns_to_ktime(0), HRTIMER_MODE_REL);
+ atomic_set(&pin->work_irq_change, CEC_PIN_IRQ_DISABLE);
+ wake_up_interruptible(&pin->kthread_waitq);
}
static int cec_pin_adap_transmit(struct cec_adapter *adap, u8 attempts,