aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHans Verkuil <hverkuil-cisco@xs4all.nl>2018-10-19 03:55:34 -0400
committerMauro Carvalho Chehab <mchehab+samsung@kernel.org>2018-11-23 05:56:14 -0500
commit32804fcb612bf867034a093f459415e485cf044b (patch)
tree262dcb0b582428b81508ad14a81fa136c3f88c60
parentdb07c5ca5596901a9723245c0068668a7f2403c6 (diff)
media: cec: keep track of outstanding transmits
I noticed that repeatedly running 'cec-ctl --playback' would occasionally select 'Playback Device 2' instead of 'Playback Device 1', even though there were no other Playback devices in the HDMI topology. This happened both with 'real' hardware and with the vivid CEC emulation, suggesting that this was an issue in the core code that claims a logical address. What 'cec-ctl --playback' does is to first clear all existing logical addresses, and immediately after that configure the new desired device type. The core code will poll the logical addresses trying to find a free address. When found it will issue a few standard messages as per the CEC spec and return. Those messages are queued up and will be transmitted asynchronously. What happens is that if you run two 'cec-ctl --playback' commands in quick succession, there is still a message of the first cec-ctl command being transmitted when you reconfigure the adapter again in the second cec-ctl command. When the logical addresses are cleared, then all information about outstanding transmits inside the CEC core is also cleared, and the core is no longer aware that there is still a transmit in flight. When the hardware finishes the transmit it calls transmit_done and the CEC core thinks it is actually in response of a POLL messages that is trying to find a free logical address. The result of all this is that the core thinks that the logical address for Playback Device 1 is in use, when it is really an earlier transmit that ended. The main transmit thread looks at adap->transmitting to check if a transmit is in progress, but that is set to NULL when the adapter is unconfigured. adap->transmitting represents the view of userspace, not that of the hardware. So when unconfiguring the adapter the message is marked aborted from the point of view of userspace, but seen from the PoV of the hardware it is still ongoing. So introduce a new bool transmit_in_progress that represents the hardware state and use that instead of adap->transmitting. Now the CEC core waits until the hardware finishes the transmit before starting a new transmit. Signed-off-by: Hans Verkuil <hverkuil-cisco@xs4all.nl> Cc: <stable@vger.kernel.org> # for v4.18 and up Signed-off-by: Mauro Carvalho Chehab <mchehab+samsung@kernel.org>
-rw-r--r--drivers/media/cec/cec-adap.c27
-rw-r--r--include/media/cec.h1
2 files changed, 19 insertions, 9 deletions
diff --git a/drivers/media/cec/cec-adap.c b/drivers/media/cec/cec-adap.c
index 5b7fe4796022..f1261cc2b6fa 100644
--- a/drivers/media/cec/cec-adap.c
+++ b/drivers/media/cec/cec-adap.c
@@ -455,7 +455,7 @@ int cec_thread_func(void *_adap)
455 (adap->needs_hpd && 455 (adap->needs_hpd &&
456 (!adap->is_configured && !adap->is_configuring)) || 456 (!adap->is_configured && !adap->is_configuring)) ||
457 kthread_should_stop() || 457 kthread_should_stop() ||
458 (!adap->transmitting && 458 (!adap->transmit_in_progress &&
459 !list_empty(&adap->transmit_queue)), 459 !list_empty(&adap->transmit_queue)),
460 msecs_to_jiffies(CEC_XFER_TIMEOUT_MS)); 460 msecs_to_jiffies(CEC_XFER_TIMEOUT_MS));
461 timeout = err == 0; 461 timeout = err == 0;
@@ -463,7 +463,7 @@ int cec_thread_func(void *_adap)
463 /* Otherwise we just wait for something to happen. */ 463 /* Otherwise we just wait for something to happen. */
464 wait_event_interruptible(adap->kthread_waitq, 464 wait_event_interruptible(adap->kthread_waitq,
465 kthread_should_stop() || 465 kthread_should_stop() ||
466 (!adap->transmitting && 466 (!adap->transmit_in_progress &&
467 !list_empty(&adap->transmit_queue))); 467 !list_empty(&adap->transmit_queue)));
468 } 468 }
469 469
@@ -488,6 +488,7 @@ int cec_thread_func(void *_adap)
488 pr_warn("cec-%s: message %*ph timed out\n", adap->name, 488 pr_warn("cec-%s: message %*ph timed out\n", adap->name,
489 adap->transmitting->msg.len, 489 adap->transmitting->msg.len,
490 adap->transmitting->msg.msg); 490 adap->transmitting->msg.msg);
491 adap->transmit_in_progress = false;
491 adap->tx_timeouts++; 492 adap->tx_timeouts++;
492 /* Just give up on this. */ 493 /* Just give up on this. */
493 cec_data_cancel(adap->transmitting, 494 cec_data_cancel(adap->transmitting,
@@ -499,7 +500,7 @@ int cec_thread_func(void *_adap)
499 * If we are still transmitting, or there is nothing new to 500 * If we are still transmitting, or there is nothing new to
500 * transmit, then just continue waiting. 501 * transmit, then just continue waiting.
501 */ 502 */
502 if (adap->transmitting || list_empty(&adap->transmit_queue)) 503 if (adap->transmit_in_progress || list_empty(&adap->transmit_queue))
503 goto unlock; 504 goto unlock;
504 505
505 /* Get a new message to transmit */ 506 /* Get a new message to transmit */
@@ -545,6 +546,8 @@ int cec_thread_func(void *_adap)
545 if (adap->ops->adap_transmit(adap, data->attempts, 546 if (adap->ops->adap_transmit(adap, data->attempts,
546 signal_free_time, &data->msg)) 547 signal_free_time, &data->msg))
547 cec_data_cancel(data, CEC_TX_STATUS_ABORTED); 548 cec_data_cancel(data, CEC_TX_STATUS_ABORTED);
549 else
550 adap->transmit_in_progress = true;
548 551
549unlock: 552unlock:
550 mutex_unlock(&adap->lock); 553 mutex_unlock(&adap->lock);
@@ -575,14 +578,17 @@ void cec_transmit_done_ts(struct cec_adapter *adap, u8 status,
575 data = adap->transmitting; 578 data = adap->transmitting;
576 if (!data) { 579 if (!data) {
577 /* 580 /*
578 * This can happen if a transmit was issued and the cable is 581 * This might happen if a transmit was issued and the cable is
579 * unplugged while the transmit is ongoing. Ignore this 582 * unplugged while the transmit is ongoing. Ignore this
580 * transmit in that case. 583 * transmit in that case.
581 */ 584 */
582 dprintk(1, "%s was called without an ongoing transmit!\n", 585 if (!adap->transmit_in_progress)
583 __func__); 586 dprintk(1, "%s was called without an ongoing transmit!\n",
584 goto unlock; 587 __func__);
588 adap->transmit_in_progress = false;
589 goto wake_thread;
585 } 590 }
591 adap->transmit_in_progress = false;
586 592
587 msg = &data->msg; 593 msg = &data->msg;
588 594
@@ -648,7 +654,6 @@ wake_thread:
648 * for transmitting or to retry the current message. 654 * for transmitting or to retry the current message.
649 */ 655 */
650 wake_up_interruptible(&adap->kthread_waitq); 656 wake_up_interruptible(&adap->kthread_waitq);
651unlock:
652 mutex_unlock(&adap->lock); 657 mutex_unlock(&adap->lock);
653} 658}
654EXPORT_SYMBOL_GPL(cec_transmit_done_ts); 659EXPORT_SYMBOL_GPL(cec_transmit_done_ts);
@@ -1503,8 +1508,11 @@ void __cec_s_phys_addr(struct cec_adapter *adap, u16 phys_addr, bool block)
1503 if (adap->monitor_all_cnt) 1508 if (adap->monitor_all_cnt)
1504 WARN_ON(call_op(adap, adap_monitor_all_enable, false)); 1509 WARN_ON(call_op(adap, adap_monitor_all_enable, false));
1505 mutex_lock(&adap->devnode.lock); 1510 mutex_lock(&adap->devnode.lock);
1506 if (adap->needs_hpd || list_empty(&adap->devnode.fhs)) 1511 if (adap->needs_hpd || list_empty(&adap->devnode.fhs)) {
1507 WARN_ON(adap->ops->adap_enable(adap, false)); 1512 WARN_ON(adap->ops->adap_enable(adap, false));
1513 adap->transmit_in_progress = false;
1514 wake_up_interruptible(&adap->kthread_waitq);
1515 }
1508 mutex_unlock(&adap->devnode.lock); 1516 mutex_unlock(&adap->devnode.lock);
1509 if (phys_addr == CEC_PHYS_ADDR_INVALID) 1517 if (phys_addr == CEC_PHYS_ADDR_INVALID)
1510 return; 1518 return;
@@ -1512,6 +1520,7 @@ void __cec_s_phys_addr(struct cec_adapter *adap, u16 phys_addr, bool block)
1512 1520
1513 mutex_lock(&adap->devnode.lock); 1521 mutex_lock(&adap->devnode.lock);
1514 adap->last_initiator = 0xff; 1522 adap->last_initiator = 0xff;
1523 adap->transmit_in_progress = false;
1515 1524
1516 if ((adap->needs_hpd || list_empty(&adap->devnode.fhs)) && 1525 if ((adap->needs_hpd || list_empty(&adap->devnode.fhs)) &&
1517 adap->ops->adap_enable(adap, true)) { 1526 adap->ops->adap_enable(adap, true)) {
diff --git a/include/media/cec.h b/include/media/cec.h
index 3fe5e5d2bb7e..707411ef8ba2 100644
--- a/include/media/cec.h
+++ b/include/media/cec.h
@@ -155,6 +155,7 @@ struct cec_adapter {
155 unsigned int transmit_queue_sz; 155 unsigned int transmit_queue_sz;
156 struct list_head wait_queue; 156 struct list_head wait_queue;
157 struct cec_data *transmitting; 157 struct cec_data *transmitting;
158 bool transmit_in_progress;
158 159
159 struct task_struct *kthread_config; 160 struct task_struct *kthread_config;
160 struct completion config_completion; 161 struct completion config_completion;