Commit 713bdfa1 authored by Hans Verkuil's avatar Hans Verkuil Committed by Mauro Carvalho Chehab
Browse files

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: default avatarHans Verkuil <hverkuil-cisco@xs4all.nl>
Fixes: 865463fc (media: cec-pin: add error injection support)
Cc: <stable@vger.kernel.org>
Signed-off-by: default avatarMauro Carvalho Chehab <mchehab+huawei@kernel.org>
parent 3a2e4b19
Loading
Loading
Loading
Loading
+18 −13
Original line number Diff line number Diff line
@@ -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:
			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,