OSDN Git Service

media: cec-pin: fix interrupt en/disable handling
authorHans Verkuil <hverkuil-cisco@xs4all.nl>
Wed, 1 Dec 2021 12:41:25 +0000 (13:41 +0100)
committerMauro Carvalho Chehab <mchehab+huawei@kernel.org>
Tue, 7 Dec 2021 10:29:56 +0000 (11:29 +0100)
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>
drivers/media/cec/core/cec-pin.c

index 2b6459d..21f0f74 100644 (file)
@@ -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,