diff options
Diffstat (limited to 'drivers/media/cec/core/cec-adap.c')
| -rw-r--r-- | drivers/media/cec/core/cec-adap.c | 521 |
1 files changed, 343 insertions, 178 deletions
diff --git a/drivers/media/cec/core/cec-adap.c b/drivers/media/cec/core/cec-adap.c index 6a04d19a96b2..ba6828ef540e 100644 --- a/drivers/media/cec/core/cec-adap.c +++ b/drivers/media/cec/core/cec-adap.c @@ -7,12 +7,13 @@ #include <linux/errno.h> #include <linux/init.h> -#include <linux/module.h> #include <linux/kernel.h> #include <linux/kmod.h> #include <linux/ktime.h> -#include <linux/slab.h> #include <linux/mm.h> +#include <linux/module.h> +#include <linux/seq_file.h> +#include <linux/slab.h> #include <linux/string.h> #include <linux/types.h> @@ -27,27 +28,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; @@ -161,10 +141,10 @@ static void cec_queue_event(struct cec_adapter *adap, u64 ts = ktime_get_ns(); struct cec_fh *fh; - mutex_lock(&adap->devnode.lock); + mutex_lock(&adap->devnode.lock_fhs); list_for_each_entry(fh, &adap->devnode.fhs, list) cec_queue_event_fh(fh, ev, ts); - mutex_unlock(&adap->devnode.lock); + mutex_unlock(&adap->devnode.lock_fhs); } /* Notify userspace that the CEC pin changed state at the given time. */ @@ -178,11 +158,12 @@ void cec_queue_pin_cec_event(struct cec_adapter *adap, bool is_high, }; struct cec_fh *fh; - mutex_lock(&adap->devnode.lock); - list_for_each_entry(fh, &adap->devnode.fhs, list) + mutex_lock(&adap->devnode.lock_fhs); + list_for_each_entry(fh, &adap->devnode.fhs, list) { if (fh->mode_follower == CEC_MODE_MONITOR_PIN) cec_queue_event_fh(fh, &ev, ktime_to_ns(ts)); - mutex_unlock(&adap->devnode.lock); + } + mutex_unlock(&adap->devnode.lock_fhs); } EXPORT_SYMBOL_GPL(cec_queue_pin_cec_event); @@ -195,10 +176,10 @@ void cec_queue_pin_hpd_event(struct cec_adapter *adap, bool is_high, ktime_t ts) }; struct cec_fh *fh; - mutex_lock(&adap->devnode.lock); + mutex_lock(&adap->devnode.lock_fhs); list_for_each_entry(fh, &adap->devnode.fhs, list) cec_queue_event_fh(fh, &ev, ktime_to_ns(ts)); - mutex_unlock(&adap->devnode.lock); + mutex_unlock(&adap->devnode.lock_fhs); } EXPORT_SYMBOL_GPL(cec_queue_pin_hpd_event); @@ -211,10 +192,10 @@ void cec_queue_pin_5v_event(struct cec_adapter *adap, bool is_high, ktime_t ts) }; struct cec_fh *fh; - mutex_lock(&adap->devnode.lock); + mutex_lock(&adap->devnode.lock_fhs); list_for_each_entry(fh, &adap->devnode.fhs, list) cec_queue_event_fh(fh, &ev, ktime_to_ns(ts)); - mutex_unlock(&adap->devnode.lock); + mutex_unlock(&adap->devnode.lock_fhs); } EXPORT_SYMBOL_GPL(cec_queue_pin_5v_event); @@ -286,12 +267,12 @@ static void cec_queue_msg_monitor(struct cec_adapter *adap, u32 monitor_mode = valid_la ? CEC_MODE_MONITOR : CEC_MODE_MONITOR_ALL; - mutex_lock(&adap->devnode.lock); + mutex_lock(&adap->devnode.lock_fhs); list_for_each_entry(fh, &adap->devnode.fhs, list) { if (fh->mode_follower >= monitor_mode) cec_queue_msg_fh(fh, msg); } - mutex_unlock(&adap->devnode.lock); + mutex_unlock(&adap->devnode.lock_fhs); } /* @@ -302,12 +283,12 @@ static void cec_queue_msg_followers(struct cec_adapter *adap, { struct cec_fh *fh; - mutex_lock(&adap->devnode.lock); + mutex_lock(&adap->devnode.lock_fhs); list_for_each_entry(fh, &adap->devnode.fhs, list) { if (fh->mode_follower == CEC_MODE_FOLLOWER) cec_queue_msg_fh(fh, msg); } - mutex_unlock(&adap->devnode.lock); + mutex_unlock(&adap->devnode.lock_fhs); } /* Notify userspace of an adapter state change. */ @@ -342,7 +323,7 @@ static void cec_data_completed(struct cec_data *data) * Without that we would be referring to a closed filehandle. */ if (data->fh) - list_del(&data->xfer_list); + list_del_init(&data->xfer_list); if (data->blocking) { /* @@ -365,38 +346,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 react to a canceled transmit */ + call_void_op(adap, adap_nb_transmit_canceled, &data->msg); cec_data_completed(data); } @@ -417,15 +408,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, @@ -481,7 +472,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. */ @@ -500,6 +491,15 @@ int cec_thread_func(void *_adap) goto unlock; } + if (adap->transmit_in_progress && + adap->transmit_in_progress_aborted) { + if (adap->transmitting) + cec_data_cancel(adap->transmitting, + CEC_TX_STATUS_ABORTED, 0); + adap->transmit_in_progress = false; + adap->transmit_in_progress_aborted = false; + goto unlock; + } if (adap->transmit_in_progress && timeout) { /* * If we timeout, then log that. Normally this does @@ -507,7 +507,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, @@ -515,12 +516,12 @@ 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); } adap->transmit_in_progress = false; - adap->tx_timeouts++; + adap->tx_timeout_cnt++; goto unlock; } @@ -571,10 +572,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; @@ -598,6 +600,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) @@ -618,6 +622,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; @@ -630,6 +635,33 @@ void cec_transmit_done_ts(struct cec_adapter *adap, u8 status, msg->tx_low_drive_cnt += low_drive_cnt; msg->tx_error_cnt += error_cnt; + adap->tx_arb_lost_cnt += arb_lost_cnt; + adap->tx_low_drive_cnt += low_drive_cnt; + adap->tx_error_cnt += error_cnt; + + /* + * Low Drive transmission errors should really not happen for + * well-behaved CEC devices and proper HDMI cables. + * + * Ditto for the 'Error' status. + * + * For the first few times that this happens, log this. + * Stop logging after that, since that will not add any more + * useful information and instead it will just flood the kernel log. + */ + if (done && adap->tx_low_drive_log_cnt < 8 && msg->tx_low_drive_cnt) { + adap->tx_low_drive_log_cnt++; + dprintk(0, "low drive counter: %u (seq %u: %*ph)\n", + msg->tx_low_drive_cnt, msg->sequence, + msg->len, msg->msg); + } + if (done && adap->tx_error_log_cnt < 8 && msg->tx_error_cnt) { + adap->tx_error_log_cnt++; + dprintk(0, "error counter: %u (seq %u: %*ph)\n", + msg->tx_error_cnt, msg->sequence, + msg->len, msg->msg); + } + /* Mark that we're done with this transmit */ adap->transmitting = NULL; @@ -638,13 +670,13 @@ 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) - dprintk(2, "retransmit: %*ph (attempts: %d, wait for 0x%02x)\n", - msg->len, msg->msg, data->attempts, msg->reply); + dprintk(2, "retransmit: %*ph (attempts: %d, wait for %*ph)\n", + msg->len, msg->msg, data->attempts, + data->match_len, data->match_reply); else dprintk(2, "retransmit: %*ph (attempts: %d)\n", msg->len, msg->msg, data->attempts); @@ -654,6 +686,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 */ @@ -732,9 +766,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); } @@ -750,6 +782,12 @@ int cec_transmit_msg_fh(struct cec_adapter *adap, struct cec_msg *msg, { struct cec_data *data; bool is_raw = msg_is_raw(msg); + bool reply_vendor_id = (msg->flags & CEC_MSG_FL_REPLY_VENDOR_ID) && + msg->len > 1 && msg->msg[1] == CEC_MSG_VENDOR_COMMAND_WITH_ID; + int err; + + if (adap->devnode.unregistered) + return -ENODEV; msg->rx_ts = 0; msg->tx_ts = 0; @@ -760,12 +798,13 @@ int cec_transmit_msg_fh(struct cec_adapter *adap, struct cec_msg *msg, msg->tx_low_drive_cnt = 0; msg->tx_error_cnt = 0; msg->sequence = 0; + msg->flags &= CEC_MSG_FL_REPLY_TO_FOLLOWERS | CEC_MSG_FL_RAW | + (reply_vendor_id ? CEC_MSG_FL_REPLY_VENDOR_ID : 0); - if (msg->reply && msg->timeout == 0) { + if ((reply_vendor_id || msg->reply) && msg->timeout == 0) { /* Make sure the timeout isn't 0. */ msg->timeout = 1000; } - msg->flags &= CEC_MSG_FL_REPLY_TO_FOLLOWERS | CEC_MSG_FL_RAW; if (!msg->timeout) msg->flags &= ~CEC_MSG_FL_REPLY_TO_FOLLOWERS; @@ -775,6 +814,11 @@ int cec_transmit_msg_fh(struct cec_adapter *adap, struct cec_msg *msg, dprintk(1, "%s: invalid length %d\n", __func__, msg->len); return -EINVAL; } + if (reply_vendor_id && msg->len < 6) { + dprintk(1, "%s: <Vendor Command With ID> message too short\n", + __func__); + return -EINVAL; + } memset(msg->msg + msg->len, 0, sizeof(msg->msg) - msg->len); @@ -866,8 +910,9 @@ int cec_transmit_msg_fh(struct cec_adapter *adap, struct cec_msg *msg, __func__); return -ENONET; } - if (msg->reply) { - dprintk(1, "%s: invalid msg->reply\n", __func__); + if (reply_vendor_id || msg->reply) { + dprintk(1, "%s: adapter is unconfigured so reply is not supported\n", + __func__); return -EINVAL; } } @@ -889,12 +934,22 @@ int cec_transmit_msg_fh(struct cec_adapter *adap, struct cec_msg *msg, data->fh = fh; data->adap = adap; data->blocking = block; + if (reply_vendor_id) { + memcpy(data->match_reply, msg->msg + 1, 4); + data->match_reply[4] = msg->reply; + data->match_len = 5; + } else if (msg->timeout) { + data->match_reply[0] = msg->reply; + data->match_len = 1; + } init_completion(&data->c); INIT_DELAYED_WORK(&data->work, cec_wait_timeout); if (fh) list_add_tail(&data->xfer_list, &fh->xfer_list); + else + INIT_LIST_HEAD(&data->xfer_list); list_add_tail(&data->list, &adap->transmit_queue); adap->transmit_queue_sz++; @@ -909,17 +964,27 @@ int cec_transmit_msg_fh(struct cec_adapter *adap, struct cec_msg *msg, * Release the lock and wait, retake the lock afterwards. */ mutex_unlock(&adap->lock); - wait_for_completion_killable(&data->c); - if (!data->completed) - cancel_delayed_work_sync(&data->work); + err = wait_for_completion_killable(&data->c); + cancel_delayed_work_sync(&data->work); mutex_lock(&adap->lock); + if (err) + adap->transmit_in_progress_aborted = true; + /* 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; + if (WARN_ON(!list_empty(&data->list))) + list_del(&data->list); + if (WARN_ON(!list_empty(&data->xfer_list))) + list_del(&data->xfer_list); kfree(data); return 0; } @@ -1020,6 +1085,7 @@ static const u8 cec_msg_size[256] = { [CEC_MSG_REPORT_SHORT_AUDIO_DESCRIPTOR] = 2 | DIRECTED, [CEC_MSG_REQUEST_SHORT_AUDIO_DESCRIPTOR] = 2 | DIRECTED, [CEC_MSG_SET_SYSTEM_AUDIO_MODE] = 3 | BOTH, + [CEC_MSG_SET_AUDIO_VOLUME_LEVEL] = 3 | DIRECTED, [CEC_MSG_SYSTEM_AUDIO_MODE_REQUEST] = 2 | DIRECTED, [CEC_MSG_SYSTEM_AUDIO_MODE_STATUS] = 3 | DIRECTED, [CEC_MSG_SET_AUDIO_RATE] = 3 | DIRECTED, @@ -1044,11 +1110,15 @@ void cec_received_msg_ts(struct cec_adapter *adap, u8 cmd = msg->msg[1]; bool is_reply = false; bool valid_la = true; + bool monitor_valid_la = true; u8 min_len = 0; if (WARN_ON(!msg->len || msg->len > CEC_MAX_MSG_SIZE)) return; + if (adap->devnode.unregistered) + return; + /* * Some CEC adapters will receive the messages that they transmitted. * This test filters out those messages by checking if we are the @@ -1079,11 +1149,14 @@ void cec_received_msg_ts(struct cec_adapter *adap, mutex_lock(&adap->lock); dprintk(2, "%s: %*ph\n", __func__, msg->len, msg->msg); - adap->last_initiator = 0xff; + if (!adap->transmit_in_progress) + adap->last_initiator = 0xff; /* Check if this message was for us (directed or broadcast). */ - if (!cec_msg_is_broadcast(msg)) + if (!cec_msg_is_broadcast(msg)) { valid_la = cec_has_log_addr(adap, msg_dest); + monitor_valid_la = valid_la; + } /* * Check if the length is not too short or if the message is a @@ -1109,20 +1182,6 @@ void cec_received_msg_ts(struct cec_adapter *adap, if (valid_la && min_len) { /* These messages have special length requirements */ switch (cmd) { - case CEC_MSG_TIMER_STATUS: - if (msg->msg[2] & 0x10) { - switch (msg->msg[2] & 0xf) { - case CEC_OP_PROG_INFO_NOT_ENOUGH_SPACE: - case CEC_OP_PROG_INFO_MIGHT_NOT_BE_ENOUGH_SPACE: - if (msg->len < 5) - valid_la = false; - break; - } - } else if ((msg->msg[2] & 0xf) == CEC_OP_PROG_ERROR_DUPLICATE) { - if (msg->len < 5) - valid_la = false; - } - break; case CEC_MSG_RECORD_ON: switch (msg->msg[2]) { case CEC_OP_RECORD_SRC_OWN: @@ -1171,13 +1230,15 @@ void cec_received_msg_ts(struct cec_adapter *adap, if (!abort && dst->msg[1] == CEC_MSG_INITIATE_ARC && (cmd == CEC_MSG_REPORT_ARC_INITIATED || cmd == CEC_MSG_REPORT_ARC_TERMINATED) && - (dst->reply == CEC_MSG_REPORT_ARC_INITIATED || - dst->reply == CEC_MSG_REPORT_ARC_TERMINATED)) + (data->match_reply[0] == CEC_MSG_REPORT_ARC_INITIATED || + data->match_reply[0] == CEC_MSG_REPORT_ARC_TERMINATED)) { dst->reply = cmd; + data->match_reply[0] = cmd; + } /* Does the command match? */ if ((abort && cmd != dst->msg[1]) || - (!abort && cmd != dst->reply)) + (!abort && memcmp(data->match_reply, msg->msg + 1, data->match_len))) continue; /* Does the addressing match? */ @@ -1193,13 +1254,14 @@ void cec_received_msg_ts(struct cec_adapter *adap, if (abort) dst->rx_status |= CEC_RX_STATUS_FEATURE_ABORT; msg->flags = dst->flags; + msg->sequence = dst->sequence; /* Remove it from the wait_queue */ list_del_init(&data->list); /* Cancel the pending timeout work */ if (!cancel_delayed_work(&data->work)) { mutex_unlock(&adap->lock); - flush_scheduled_work(); + cancel_delayed_work_sync(&data->work); mutex_lock(&adap->lock); } /* @@ -1215,7 +1277,7 @@ void cec_received_msg_ts(struct cec_adapter *adap, mutex_unlock(&adap->lock); /* Pass the message on to any monitoring filehandles */ - cec_queue_msg_monitor(adap, msg, valid_la); + cec_queue_msg_monitor(adap, msg, monitor_valid_la); /* We're done if it is not for us or a poll message */ if (!valid_la || msg->len <= 1) @@ -1264,17 +1326,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; @@ -1290,23 +1357,25 @@ static int cec_config_log_addr(struct cec_adapter *adap, /* * If we are unable to get an OK or a NACK after max_retries attempts * (and note that each attempt already consists of four polls), then - * then we assume that something is really weird and that it is not a + * we assume that something is really weird and that it is not a * good idea to try and claim this logical address. */ - if (i == max_retries) + if (i == max_retries) { + dprintk(0, "polling for LA %u failed with tx_status=0x%04x\n", + log_addr, msg.tx_status); return 0; + } /* * 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; las->log_addr[idx] = log_addr; las->log_addr_mask |= 1 << log_addr; - adap->phys_addrs[log_addr] = adap->phys_addr; return 1; } @@ -1318,16 +1387,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; - memset(adap->phys_addrs, 0xff, sizeof(adap->phys_addrs)); cec_flush(adap); wake_up_interruptible(&adap->kthread_waitq); cec_post_state_event(adap); + call_void_op(adap, adap_unconfigured); } /* @@ -1396,6 +1463,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; @@ -1418,6 +1486,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; @@ -1463,6 +1538,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); /* @@ -1509,15 +1585,18 @@ configured: adap->kthread_config = NULL; complete(&adap->config_completion); mutex_unlock(&adap->lock); + call_void_op(adap, configured); 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; } @@ -1529,9 +1608,12 @@ unconfigure: */ static void cec_claim_log_addrs(struct cec_adapter *adap, bool block) { - if (WARN_ON(adap->is_configuring || adap->is_configured)) + if (WARN_ON(adap->is_claiming_log_addrs || + adap->is_configuring || adap->is_configured)) return; + adap->is_claiming_log_addrs = true; + init_completion(&adap->config_completion); /* Ready to kick off the thread */ @@ -1540,11 +1622,72 @@ 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); mutex_lock(&adap->lock); } + adap->is_claiming_log_addrs = false; +} + +/* + * Helper function to enable/disable the CEC adapter. + * + * This function is called with adap->lock held. + */ +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 (adap->devnode.unregistered) + enable = false; + + 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; + adap->tx_low_drive_log_cnt = 0; + adap->tx_error_log_cnt = 0; + 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. @@ -1553,54 +1696,36 @@ static void cec_claim_log_addrs(struct cec_adapter *adap, bool block) */ 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)); - 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) + if (becomes_invalid) { + cec_adap_enable(adap); return; + } } - mutex_lock(&adap->devnode.lock); - adap->last_initiator = 0xff; - adap->transmit_in_progress = false; - - if ((adap->needs_hpd || list_empty(&adap->devnode.fhs)) && - adap->ops->adap_enable(adap, true)) { - mutex_unlock(&adap->devnode.lock); - 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); } @@ -1615,6 +1740,11 @@ void cec_s_phys_addr(struct cec_adapter *adap, u16 phys_addr, bool block) } EXPORT_SYMBOL_GPL(cec_s_phys_addr); +/* + * Note: In the drm subsystem, prefer calling (if possible): + * + * cec_s_phys_addr(adap, connector->display_info.source_physical_address, false); + */ void cec_s_phys_addr_from_edid(struct cec_adapter *adap, const struct edid *edid) { @@ -1655,19 +1785,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; } @@ -1731,7 +1866,7 @@ int __cec_s_log_addrs(struct cec_adapter *adap, const u8 feature_sz = ARRAY_SIZE(log_addrs->features[0]); u8 *features = log_addrs->features[i]; bool op_is_dev_features = false; - unsigned j; + unsigned int j; log_addrs->log_addr[i] = CEC_LOG_ADDR_INVALID; if (log_addrs->log_addr_type[i] > CEC_LOG_ADDR_TYPE_UNREGISTERED) { @@ -1803,9 +1938,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, @@ -1907,11 +2043,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 @@ -1930,7 +2065,7 @@ static int cec_receive_notify(struct cec_adapter *adap, struct cec_msg *msg, */ if (!adap->passthrough && from_unregistered) return 0; - /* Fall through */ + fallthrough; case CEC_MSG_GIVE_DEVICE_VENDOR_ID: case CEC_MSG_GIVE_FEATURES: case CEC_MSG_GIVE_PHYSICAL_ADDR: @@ -1974,8 +2109,6 @@ static int cec_receive_notify(struct cec_adapter *adap, struct cec_msg *msg, case CEC_MSG_REPORT_PHYSICAL_ADDR: { u16 pa = (msg->msg[2] << 8) | msg->msg[3]; - if (!from_unregistered) - adap->phys_addrs[init_laddr] = pa; dprintk(1, "reported physical address %x.%x.%x.%x for logical address %d\n", cec_phys_addr_exp(pa), init_laddr); break; @@ -2106,20 +2239,25 @@ skip_processing: */ int cec_monitor_all_cnt_inc(struct cec_adapter *adap) { - int ret = 0; + int ret; - if (adap->monitor_all_cnt == 0) - ret = call_op(adap, adap_monitor_all_enable, 1); - if (ret == 0) - adap->monitor_all_cnt++; + if (adap->monitor_all_cnt++) + return 0; + + 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); } /* @@ -2129,20 +2267,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 == 0) - ret = call_op(adap, adap_monitor_pin_enable, 1); - if (ret == 0) - adap->monitor_pin_cnt++; + if (adap->monitor_pin_cnt++) + return 0; + + 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 @@ -2156,6 +2299,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", @@ -2170,25 +2314,46 @@ 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->tx_timeouts) { - seq_printf(file, "transmit timeouts: %u\n", - adap->tx_timeouts); - adap->tx_timeouts = 0; + if (adap->monitor_pin_cnt) + seq_printf(file, "file handles in Monitor Pin mode: %u\n", + adap->monitor_pin_cnt); + if (adap->tx_timeout_cnt) { + seq_printf(file, "transmit timeout count: %u\n", + adap->tx_timeout_cnt); + adap->tx_timeout_cnt = 0; + } + if (adap->tx_low_drive_cnt) { + seq_printf(file, "transmit low drive count: %u\n", + adap->tx_low_drive_cnt); + adap->tx_low_drive_cnt = 0; + } + if (adap->tx_arb_lost_cnt) { + seq_printf(file, "transmit arbitration lost count: %u\n", + adap->tx_arb_lost_cnt); + adap->tx_arb_lost_cnt = 0; + } + if (adap->tx_error_cnt) { + seq_printf(file, "transmit error count: %u\n", + adap->tx_error_cnt); + adap->tx_error_cnt = 0; } data = adap->transmitting; if (data) - seq_printf(file, "transmitting message: %*ph (reply: %02x, timeout: %ums)\n", - data->msg.len, data->msg.msg, data->msg.reply, + seq_printf(file, "transmitting message: %*ph (reply: %*ph, timeout: %ums)\n", + data->msg.len, data->msg.msg, + data->match_len, data->match_reply, data->msg.timeout); seq_printf(file, "pending transmits: %u\n", adap->transmit_queue_sz); list_for_each_entry(data, &adap->transmit_queue, list) { - seq_printf(file, "queued tx message: %*ph (reply: %02x, timeout: %ums)\n", - data->msg.len, data->msg.msg, data->msg.reply, + seq_printf(file, "queued tx message: %*ph (reply: %*ph, timeout: %ums)\n", + data->msg.len, data->msg.msg, + data->match_len, data->match_reply, data->msg.timeout); } list_for_each_entry(data, &adap->wait_queue, list) { - seq_printf(file, "message waiting for reply: %*ph (reply: %02x, timeout: %ums)\n", - data->msg.len, data->msg.msg, data->msg.reply, + seq_printf(file, "message waiting for reply: %*ph (reply: %*ph, timeout: %ums)\n", + data->msg.len, data->msg.msg, + data->match_len, data->match_reply, data->msg.timeout); } |
