aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/media/cec/core/cec-adap.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/media/cec/core/cec-adap.c')
-rw-r--r--drivers/media/cec/core/cec-adap.c294
1 files changed, 180 insertions, 114 deletions
diff --git a/drivers/media/cec/core/cec-adap.c b/drivers/media/cec/core/cec-adap.c
index 2e12331c12a9..8bf91b5a7d0e 100644
--- a/drivers/media/cec/core/cec-adap.c
+++ b/drivers/media/cec/core/cec-adap.c
@@ -27,27 +27,6 @@ static void cec_fill_msg_report_features(struct cec_adapter *adap,
struct cec_msg *msg,
unsigned int la_idx);
-/*
- * 400 ms is the time it takes for one 16 byte message to be
- * transferred and 5 is the maximum number of retries. Add
- * another 100 ms as a margin. So if the transmit doesn't
- * finish before that time something is really wrong and we
- * have to time out.
- *
- * This is a sign that something it really wrong and a warning
- * will be issued.
- */
-#define CEC_XFER_TIMEOUT_MS (5 * 400 + 100)
-
-#define call_op(adap, op, arg...) \
- (adap->ops->op ? adap->ops->op(adap, ## arg) : 0)
-
-#define call_void_op(adap, op, arg...) \
- do { \
- if (adap->ops->op) \
- adap->ops->op(adap, ## arg); \
- } while (0)
-
static int cec_log_addr2idx(const struct cec_adapter *adap, u8 log_addr)
{
int i;
@@ -366,38 +345,48 @@ static void cec_data_completed(struct cec_data *data)
/*
* A pending CEC transmit needs to be cancelled, either because the CEC
* adapter is disabled or the transmit takes an impossibly long time to
- * finish.
+ * finish, or the reply timed out.
*
* This function is called with adap->lock held.
*/
-static void cec_data_cancel(struct cec_data *data, u8 tx_status)
+static void cec_data_cancel(struct cec_data *data, u8 tx_status, u8 rx_status)
{
+ struct cec_adapter *adap = data->adap;
+
/*
* It's either the current transmit, or it is a pending
* transmit. Take the appropriate action to clear it.
*/
- if (data->adap->transmitting == data) {
- data->adap->transmitting = NULL;
+ if (adap->transmitting == data) {
+ adap->transmitting = NULL;
} else {
list_del_init(&data->list);
if (!(data->msg.tx_status & CEC_TX_STATUS_OK))
- if (!WARN_ON(!data->adap->transmit_queue_sz))
- data->adap->transmit_queue_sz--;
+ if (!WARN_ON(!adap->transmit_queue_sz))
+ adap->transmit_queue_sz--;
}
if (data->msg.tx_status & CEC_TX_STATUS_OK) {
data->msg.rx_ts = ktime_get_ns();
- data->msg.rx_status = CEC_RX_STATUS_ABORTED;
+ data->msg.rx_status = rx_status;
+ if (!data->blocking)
+ data->msg.tx_status = 0;
} else {
data->msg.tx_ts = ktime_get_ns();
data->msg.tx_status |= tx_status |
CEC_TX_STATUS_MAX_RETRIES;
data->msg.tx_error_cnt++;
data->attempts = 0;
+ if (!data->blocking)
+ data->msg.rx_status = 0;
}
/* Queue transmitted message for monitoring purposes */
- cec_queue_msg_monitor(data->adap, &data->msg, 1);
+ cec_queue_msg_monitor(adap, &data->msg, 1);
+
+ if (!data->blocking && data->msg.sequence)
+ /* Allow drivers to process the message first */
+ call_op(adap, received, &data->msg);
cec_data_completed(data);
}
@@ -418,15 +407,15 @@ static void cec_flush(struct cec_adapter *adap)
while (!list_empty(&adap->transmit_queue)) {
data = list_first_entry(&adap->transmit_queue,
struct cec_data, list);
- cec_data_cancel(data, CEC_TX_STATUS_ABORTED);
+ cec_data_cancel(data, CEC_TX_STATUS_ABORTED, 0);
}
if (adap->transmitting)
- cec_data_cancel(adap->transmitting, CEC_TX_STATUS_ABORTED);
+ adap->transmit_in_progress_aborted = true;
/* Cancel the pending timeout work. */
list_for_each_entry_safe(data, n, &adap->wait_queue, list) {
if (cancel_delayed_work(&data->work))
- cec_data_cancel(data, CEC_TX_STATUS_OK);
+ cec_data_cancel(data, CEC_TX_STATUS_OK, CEC_RX_STATUS_ABORTED);
/*
* If cancel_delayed_work returned false, then
* the cec_wait_timeout function is running,
@@ -482,7 +471,7 @@ int cec_thread_func(void *_adap)
kthread_should_stop() ||
(!adap->transmit_in_progress &&
!list_empty(&adap->transmit_queue)),
- msecs_to_jiffies(CEC_XFER_TIMEOUT_MS));
+ msecs_to_jiffies(adap->xfer_timeout_ms));
timeout = err == 0;
} else {
/* Otherwise we just wait for something to happen. */
@@ -508,7 +497,8 @@ int cec_thread_func(void *_adap)
* adapter driver, or the CEC bus is in some weird
* state. On rare occasions it can happen if there is
* so much traffic on the bus that the adapter was
- * unable to transmit for CEC_XFER_TIMEOUT_MS (2.1s).
+ * unable to transmit for xfer_timeout_ms (2.1s by
+ * default).
*/
if (adap->transmitting) {
pr_warn("cec-%s: message %*ph timed out\n", adap->name,
@@ -516,7 +506,7 @@ int cec_thread_func(void *_adap)
adap->transmitting->msg.msg);
/* Just give up on this. */
cec_data_cancel(adap->transmitting,
- CEC_TX_STATUS_TIMEOUT);
+ CEC_TX_STATUS_TIMEOUT, 0);
} else {
pr_warn("cec-%s: transmit timed out\n", adap->name);
}
@@ -572,10 +562,11 @@ int cec_thread_func(void *_adap)
if (data->attempts == 0)
data->attempts = attempts;
+ adap->transmit_in_progress_aborted = false;
/* Tell the adapter to transmit, cancel on error */
- if (adap->ops->adap_transmit(adap, data->attempts,
- signal_free_time, &data->msg))
- cec_data_cancel(data, CEC_TX_STATUS_ABORTED);
+ if (call_op(adap, adap_transmit, data->attempts,
+ signal_free_time, &data->msg))
+ cec_data_cancel(data, CEC_TX_STATUS_ABORTED, 0);
else
adap->transmit_in_progress = true;
@@ -599,6 +590,8 @@ void cec_transmit_done_ts(struct cec_adapter *adap, u8 status,
struct cec_msg *msg;
unsigned int attempts_made = arb_lost_cnt + nack_cnt +
low_drive_cnt + error_cnt;
+ bool done = status & (CEC_TX_STATUS_MAX_RETRIES | CEC_TX_STATUS_OK);
+ bool aborted = adap->transmit_in_progress_aborted;
dprintk(2, "%s: status 0x%02x\n", __func__, status);
if (attempts_made < 1)
@@ -619,6 +612,7 @@ void cec_transmit_done_ts(struct cec_adapter *adap, u8 status,
goto wake_thread;
}
adap->transmit_in_progress = false;
+ adap->transmit_in_progress_aborted = false;
msg = &data->msg;
@@ -639,8 +633,7 @@ void cec_transmit_done_ts(struct cec_adapter *adap, u8 status,
* the hardware didn't signal that it retried itself (by setting
* CEC_TX_STATUS_MAX_RETRIES), then we will retry ourselves.
*/
- if (data->attempts > attempts_made &&
- !(status & (CEC_TX_STATUS_MAX_RETRIES | CEC_TX_STATUS_OK))) {
+ if (!aborted && data->attempts > attempts_made && !done) {
/* Retry this message */
data->attempts -= attempts_made;
if (msg->timeout)
@@ -655,6 +648,8 @@ void cec_transmit_done_ts(struct cec_adapter *adap, u8 status,
goto wake_thread;
}
+ if (aborted && !done)
+ status |= CEC_TX_STATUS_ABORTED;
data->attempts = 0;
/* Always set CEC_TX_STATUS_MAX_RETRIES on error */
@@ -733,9 +728,7 @@ static void cec_wait_timeout(struct work_struct *work)
/* Mark the message as timed out */
list_del_init(&data->list);
- data->msg.rx_ts = ktime_get_ns();
- data->msg.rx_status = CEC_RX_STATUS_TIMEOUT;
- cec_data_completed(data);
+ cec_data_cancel(data, CEC_TX_STATUS_OK, CEC_RX_STATUS_TIMEOUT);
unlock:
mutex_unlock(&adap->lock);
}
@@ -921,8 +914,12 @@ int cec_transmit_msg_fh(struct cec_adapter *adap, struct cec_msg *msg,
mutex_lock(&adap->lock);
/* Cancel the transmit if it was interrupted */
- if (!data->completed)
- cec_data_cancel(data, CEC_TX_STATUS_ABORTED);
+ if (!data->completed) {
+ if (data->msg.tx_status & CEC_TX_STATUS_OK)
+ cec_data_cancel(data, CEC_TX_STATUS_OK, CEC_RX_STATUS_ABORTED);
+ else
+ cec_data_cancel(data, CEC_TX_STATUS_ABORTED, 0);
+ }
/* The transmit completed (possibly with an error) */
*msg = data->msg;
@@ -1278,17 +1275,22 @@ static int cec_config_log_addr(struct cec_adapter *adap,
* While trying to poll the physical address was reset
* and the adapter was unconfigured, so bail out.
*/
- if (!adap->is_configuring)
+ if (adap->phys_addr == CEC_PHYS_ADDR_INVALID)
+ return -EINTR;
+
+ /* Also bail out if the PA changed while configuring. */
+ if (adap->must_reconfigure)
return -EINTR;
if (err)
return err;
/*
- * The message was aborted due to a disconnect or
+ * The message was aborted or timed out due to a disconnect or
* unconfigure, just bail out.
*/
- if (msg.tx_status & CEC_TX_STATUS_ABORTED)
+ if (msg.tx_status &
+ (CEC_TX_STATUS_ABORTED | CEC_TX_STATUS_TIMEOUT))
return -EINTR;
if (msg.tx_status & CEC_TX_STATUS_OK)
return 0;
@@ -1314,7 +1316,7 @@ static int cec_config_log_addr(struct cec_adapter *adap,
* Message not acknowledged, so this logical
* address is free to use.
*/
- err = adap->ops->adap_log_addr(adap, log_addr);
+ err = call_op(adap, adap_log_addr, log_addr);
if (err)
return err;
@@ -1331,15 +1333,14 @@ static int cec_config_log_addr(struct cec_adapter *adap,
*/
static void cec_adap_unconfigure(struct cec_adapter *adap)
{
- if (!adap->needs_hpd ||
- adap->phys_addr != CEC_PHYS_ADDR_INVALID)
- WARN_ON(adap->ops->adap_log_addr(adap, CEC_LOG_ADDR_INVALID));
+ if (!adap->needs_hpd || adap->phys_addr != CEC_PHYS_ADDR_INVALID)
+ WARN_ON(call_op(adap, adap_log_addr, CEC_LOG_ADDR_INVALID));
adap->log_addrs.log_addr_mask = 0;
- adap->is_configuring = false;
adap->is_configured = false;
cec_flush(adap);
wake_up_interruptible(&adap->kthread_waitq);
cec_post_state_event(adap);
+ call_void_op(adap, adap_configured, false);
}
/*
@@ -1408,6 +1409,7 @@ static int cec_config_thread_func(void *arg)
if (las->log_addr_type[0] == CEC_LOG_ADDR_TYPE_UNREGISTERED)
goto configured;
+reconfigure:
for (i = 0; i < las->num_log_addrs; i++) {
unsigned int type = las->log_addr_type[i];
const u8 *la_list;
@@ -1430,6 +1432,13 @@ static int cec_config_thread_func(void *arg)
last_la = la_list[0];
err = cec_config_log_addr(adap, i, last_la);
+
+ if (adap->must_reconfigure) {
+ adap->must_reconfigure = false;
+ las->log_addr_mask = 0;
+ goto reconfigure;
+ }
+
if (err > 0) /* Reused last LA */
continue;
@@ -1475,6 +1484,7 @@ configured:
las->log_addr[i] = CEC_LOG_ADDR_INVALID;
adap->is_configured = true;
adap->is_configuring = false;
+ adap->must_reconfigure = false;
cec_post_state_event(adap);
/*
@@ -1521,15 +1531,18 @@ configured:
adap->kthread_config = NULL;
complete(&adap->config_completion);
mutex_unlock(&adap->lock);
+ call_void_op(adap, adap_configured, true);
return 0;
unconfigure:
for (i = 0; i < las->num_log_addrs; i++)
las->log_addr[i] = CEC_LOG_ADDR_INVALID;
cec_adap_unconfigure(adap);
+ adap->is_configuring = false;
+ adap->must_reconfigure = false;
adap->kthread_config = NULL;
- mutex_unlock(&adap->lock);
complete(&adap->config_completion);
+ mutex_unlock(&adap->lock);
return 0;
}
@@ -1552,6 +1565,7 @@ static void cec_claim_log_addrs(struct cec_adapter *adap, bool block)
"ceccfg-%s", adap->name);
if (IS_ERR(adap->kthread_config)) {
adap->kthread_config = NULL;
+ adap->is_configuring = false;
} else if (block) {
mutex_unlock(&adap->lock);
wait_for_completion(&adap->config_completion);
@@ -1559,63 +1573,96 @@ static void cec_claim_log_addrs(struct cec_adapter *adap, bool block)
}
}
+/*
+ * Helper function to enable/disable the CEC adapter.
+ *
+ * This function is called with adap->lock held.
+ */
+static int cec_adap_enable(struct cec_adapter *adap)
+{
+ bool enable;
+ int ret = 0;
+
+ enable = adap->monitor_all_cnt || adap->monitor_pin_cnt ||
+ adap->log_addrs.num_log_addrs;
+ if (adap->needs_hpd)
+ enable = enable && adap->phys_addr != CEC_PHYS_ADDR_INVALID;
+
+ if (enable == adap->is_enabled)
+ return 0;
+
+ /* serialize adap_enable */
+ mutex_lock(&adap->devnode.lock);
+ if (enable) {
+ adap->last_initiator = 0xff;
+ adap->transmit_in_progress = false;
+ ret = adap->ops->adap_enable(adap, true);
+ if (!ret) {
+ /*
+ * Enable monitor-all/pin modes if needed. We warn, but
+ * continue if this fails as this is not a critical error.
+ */
+ if (adap->monitor_all_cnt)
+ WARN_ON(call_op(adap, adap_monitor_all_enable, true));
+ if (adap->monitor_pin_cnt)
+ WARN_ON(call_op(adap, adap_monitor_pin_enable, true));
+ }
+ } else {
+ /* Disable monitor-all/pin modes if needed (needs_hpd == 1) */
+ if (adap->monitor_all_cnt)
+ WARN_ON(call_op(adap, adap_monitor_all_enable, false));
+ if (adap->monitor_pin_cnt)
+ WARN_ON(call_op(adap, adap_monitor_pin_enable, false));
+ WARN_ON(adap->ops->adap_enable(adap, false));
+ adap->last_initiator = 0xff;
+ adap->transmit_in_progress = false;
+ adap->transmit_in_progress_aborted = false;
+ if (adap->transmitting)
+ cec_data_cancel(adap->transmitting, CEC_TX_STATUS_ABORTED, 0);
+ }
+ if (!ret)
+ adap->is_enabled = enable;
+ wake_up_interruptible(&adap->kthread_waitq);
+ mutex_unlock(&adap->devnode.lock);
+ return ret;
+}
+
/* Set a new physical address and send an event notifying userspace of this.
*
* This function is called with adap->lock held.
*/
void __cec_s_phys_addr(struct cec_adapter *adap, u16 phys_addr, bool block)
{
+ bool becomes_invalid = phys_addr == CEC_PHYS_ADDR_INVALID;
+ bool is_invalid = adap->phys_addr == CEC_PHYS_ADDR_INVALID;
+
if (phys_addr == adap->phys_addr)
return;
- if (phys_addr != CEC_PHYS_ADDR_INVALID && adap->devnode.unregistered)
+ if (!becomes_invalid && adap->devnode.unregistered)
return;
dprintk(1, "new physical address %x.%x.%x.%x\n",
cec_phys_addr_exp(phys_addr));
- if (phys_addr == CEC_PHYS_ADDR_INVALID ||
- adap->phys_addr != CEC_PHYS_ADDR_INVALID) {
+ if (becomes_invalid || !is_invalid) {
adap->phys_addr = CEC_PHYS_ADDR_INVALID;
cec_post_state_event(adap);
cec_adap_unconfigure(adap);
- /* Disabling monitor all mode should always succeed */
- if (adap->monitor_all_cnt)
- WARN_ON(call_op(adap, adap_monitor_all_enable, false));
- /* serialize adap_enable */
- mutex_lock(&adap->devnode.lock);
- if (adap->needs_hpd || list_empty(&adap->devnode.fhs)) {
- WARN_ON(adap->ops->adap_enable(adap, false));
- adap->transmit_in_progress = false;
- wake_up_interruptible(&adap->kthread_waitq);
- }
- mutex_unlock(&adap->devnode.lock);
- if (phys_addr == CEC_PHYS_ADDR_INVALID)
- return;
- }
-
- /* serialize adap_enable */
- mutex_lock(&adap->devnode.lock);
- adap->last_initiator = 0xff;
- adap->transmit_in_progress = false;
-
- if (adap->needs_hpd || list_empty(&adap->devnode.fhs)) {
- if (adap->ops->adap_enable(adap, true)) {
- mutex_unlock(&adap->devnode.lock);
+ if (becomes_invalid) {
+ cec_adap_enable(adap);
return;
}
}
- if (adap->monitor_all_cnt &&
- call_op(adap, adap_monitor_all_enable, true)) {
- if (adap->needs_hpd || list_empty(&adap->devnode.fhs))
- WARN_ON(adap->ops->adap_enable(adap, false));
- mutex_unlock(&adap->devnode.lock);
- return;
- }
- mutex_unlock(&adap->devnode.lock);
-
adap->phys_addr = phys_addr;
+ if (is_invalid)
+ cec_adap_enable(adap);
+
cec_post_state_event(adap);
- if (adap->log_addrs.num_log_addrs)
+ if (!adap->log_addrs.num_log_addrs)
+ return;
+ if (adap->is_configuring)
+ adap->must_reconfigure = true;
+ else
cec_claim_log_addrs(adap, block);
}
@@ -1670,19 +1717,24 @@ int __cec_s_log_addrs(struct cec_adapter *adap,
struct cec_log_addrs *log_addrs, bool block)
{
u16 type_mask = 0;
+ int err;
int i;
if (adap->devnode.unregistered)
return -ENODEV;
if (!log_addrs || log_addrs->num_log_addrs == 0) {
- cec_adap_unconfigure(adap);
+ if (!adap->log_addrs.num_log_addrs)
+ return 0;
+ if (adap->is_configuring || adap->is_configured)
+ cec_adap_unconfigure(adap);
adap->log_addrs.num_log_addrs = 0;
for (i = 0; i < CEC_MAX_LOG_ADDRS; i++)
adap->log_addrs.log_addr[i] = CEC_LOG_ADDR_INVALID;
adap->log_addrs.osd_name[0] = '\0';
adap->log_addrs.vendor_id = CEC_VENDOR_ID_NONE;
adap->log_addrs.cec_version = CEC_OP_CEC_VERSION_2_0;
+ cec_adap_enable(adap);
return 0;
}
@@ -1818,9 +1870,10 @@ int __cec_s_log_addrs(struct cec_adapter *adap,
log_addrs->log_addr_mask = adap->log_addrs.log_addr_mask;
adap->log_addrs = *log_addrs;
- if (adap->phys_addr != CEC_PHYS_ADDR_INVALID)
+ err = cec_adap_enable(adap);
+ if (!err && adap->phys_addr != CEC_PHYS_ADDR_INVALID)
cec_claim_log_addrs(adap, block);
- return 0;
+ return err;
}
int cec_s_log_addrs(struct cec_adapter *adap,
@@ -1922,11 +1975,10 @@ static int cec_receive_notify(struct cec_adapter *adap, struct cec_msg *msg,
msg->msg[1] != CEC_MSG_CDC_MESSAGE)
return 0;
- if (adap->ops->received) {
- /* Allow drivers to process the message first */
- if (adap->ops->received(adap, msg) != -ENOMSG)
- return 0;
- }
+ /* Allow drivers to process the message first */
+ if (adap->ops->received && !adap->devnode.unregistered &&
+ adap->ops->received(adap, msg) != -ENOMSG)
+ return 0;
/*
* REPORT_PHYSICAL_ADDR, CEC_MSG_USER_CONTROL_PRESSED and
@@ -2119,20 +2171,25 @@ skip_processing:
*/
int cec_monitor_all_cnt_inc(struct cec_adapter *adap)
{
- int ret = 0;
+ int ret;
+
+ if (adap->monitor_all_cnt++)
+ return 0;
- if (adap->monitor_all_cnt == 0)
- ret = call_op(adap, adap_monitor_all_enable, 1);
- if (ret == 0)
- adap->monitor_all_cnt++;
+ ret = cec_adap_enable(adap);
+ if (ret)
+ adap->monitor_all_cnt--;
return ret;
}
void cec_monitor_all_cnt_dec(struct cec_adapter *adap)
{
- adap->monitor_all_cnt--;
- if (adap->monitor_all_cnt == 0)
- WARN_ON(call_op(adap, adap_monitor_all_enable, 0));
+ if (WARN_ON(!adap->monitor_all_cnt))
+ return;
+ if (--adap->monitor_all_cnt)
+ return;
+ WARN_ON(call_op(adap, adap_monitor_all_enable, false));
+ cec_adap_enable(adap);
}
/*
@@ -2142,20 +2199,25 @@ void cec_monitor_all_cnt_dec(struct cec_adapter *adap)
*/
int cec_monitor_pin_cnt_inc(struct cec_adapter *adap)
{
- int ret = 0;
+ int ret;
+
+ if (adap->monitor_pin_cnt++)
+ return 0;
- if (adap->monitor_pin_cnt == 0)
- ret = call_op(adap, adap_monitor_pin_enable, 1);
- if (ret == 0)
- adap->monitor_pin_cnt++;
+ ret = cec_adap_enable(adap);
+ if (ret)
+ adap->monitor_pin_cnt--;
return ret;
}
void cec_monitor_pin_cnt_dec(struct cec_adapter *adap)
{
- adap->monitor_pin_cnt--;
- if (adap->monitor_pin_cnt == 0)
- WARN_ON(call_op(adap, adap_monitor_pin_enable, 0));
+ if (WARN_ON(!adap->monitor_pin_cnt))
+ return;
+ if (--adap->monitor_pin_cnt)
+ return;
+ WARN_ON(call_op(adap, adap_monitor_pin_enable, false));
+ cec_adap_enable(adap);
}
#ifdef CONFIG_DEBUG_FS
@@ -2169,6 +2231,7 @@ int cec_adap_status(struct seq_file *file, void *priv)
struct cec_data *data;
mutex_lock(&adap->lock);
+ seq_printf(file, "enabled: %d\n", adap->is_enabled);
seq_printf(file, "configured: %d\n", adap->is_configured);
seq_printf(file, "configuring: %d\n", adap->is_configuring);
seq_printf(file, "phys_addr: %x.%x.%x.%x\n",
@@ -2183,6 +2246,9 @@ int cec_adap_status(struct seq_file *file, void *priv)
if (adap->monitor_all_cnt)
seq_printf(file, "file handles in Monitor All mode: %u\n",
adap->monitor_all_cnt);
+ if (adap->monitor_pin_cnt)
+ seq_printf(file, "file handles in Monitor Pin mode: %u\n",
+ adap->monitor_pin_cnt);
if (adap->tx_timeouts) {
seq_printf(file, "transmit timeouts: %u\n",
adap->tx_timeouts);