| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * vivid-cec.c - A Virtual Video Test Driver, cec emulation |
| * |
| * Copyright 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. |
| */ |
| |
| #include <linux/delay.h> |
| #include <media/cec.h> |
| |
| #include "vivid-core.h" |
| #include "vivid-cec.h" |
| |
| #define CEC_START_BIT_US 4500 |
| #define CEC_DATA_BIT_US 2400 |
| #define CEC_MARGIN_US 350 |
| |
| struct xfer_on_bus { |
| struct cec_adapter *adap; |
| u8 status; |
| }; |
| |
| static bool find_dest_adap(struct vivid_dev *dev, |
| struct cec_adapter *adap, u8 dest) |
| { |
| unsigned int i; |
| |
| if (dest >= 0xf) |
| return false; |
| |
| if (adap != dev->cec_rx_adap && dev->cec_rx_adap && |
| dev->cec_rx_adap->is_configured && |
| cec_has_log_addr(dev->cec_rx_adap, dest)) |
| return true; |
| |
| for (i = 0; i < MAX_OUTPUTS && dev->cec_tx_adap[i]; i++) { |
| if (adap == dev->cec_tx_adap[i]) |
| continue; |
| if (!dev->cec_tx_adap[i]->is_configured) |
| continue; |
| if (cec_has_log_addr(dev->cec_tx_adap[i], dest)) |
| return true; |
| } |
| return false; |
| } |
| |
| static bool xfer_ready(struct vivid_dev *dev) |
| { |
| unsigned int i; |
| bool ready = false; |
| |
| spin_lock(&dev->cec_xfers_slock); |
| for (i = 0; i < ARRAY_SIZE(dev->xfers); i++) { |
| if (dev->xfers[i].sft && |
| dev->xfers[i].sft <= dev->cec_sft) { |
| ready = true; |
| break; |
| } |
| } |
| spin_unlock(&dev->cec_xfers_slock); |
| |
| return ready; |
| } |
| |
| /* |
| * If an adapter tries to send successive messages, it must wait for the |
| * longest signal-free time between its transmissions. But, if another |
| * adapter sends a message in the interim, then the wait can be reduced |
| * because the messages are no longer successive. Make these adjustments |
| * if necessary. Should be called holding cec_xfers_slock. |
| */ |
| static void adjust_sfts(struct vivid_dev *dev) |
| { |
| unsigned int i; |
| u8 initiator; |
| |
| for (i = 0; i < ARRAY_SIZE(dev->xfers); i++) { |
| if (dev->xfers[i].sft <= CEC_SIGNAL_FREE_TIME_RETRY) |
| continue; |
| initiator = dev->xfers[i].msg[0] >> 4; |
| if (initiator == dev->last_initiator) |
| dev->xfers[i].sft = CEC_SIGNAL_FREE_TIME_NEXT_XFER; |
| else |
| dev->xfers[i].sft = CEC_SIGNAL_FREE_TIME_NEW_INITIATOR; |
| } |
| } |
| |
| /* |
| * The main emulation of the bus on which CEC adapters attempt to send |
| * messages to each other. The bus keeps track of how long it has been |
| * signal-free and accepts a pending transmission only if the state of |
| * the bus matches the transmission's signal-free requirements. It calls |
| * cec_transmit_attempt_done() for all transmits that enter the bus and |
| * cec_received_msg() for successful transmits. |
| */ |
| int vivid_cec_bus_thread(void *_dev) |
| { |
| u32 last_sft; |
| unsigned int i; |
| unsigned int dest; |
| ktime_t start, end; |
| s64 delta_us, retry_us; |
| struct vivid_dev *dev = _dev; |
| |
| dev->cec_sft = CEC_SIGNAL_FREE_TIME_NEXT_XFER; |
| for (;;) { |
| bool first = true; |
| int wait_xfer_us = 0; |
| bool valid_dest = false; |
| int wait_arb_lost_us = 0; |
| unsigned int first_idx = 0; |
| unsigned int first_status = 0; |
| struct cec_msg first_msg = {}; |
| struct xfer_on_bus xfers_on_bus[MAX_OUTPUTS] = {}; |
| |
| wait_event_interruptible(dev->kthread_waitq_cec, xfer_ready(dev) || |
| kthread_should_stop()); |
| if (kthread_should_stop()) |
| break; |
| last_sft = dev->cec_sft; |
| dev->cec_sft = 0; |
| /* |
| * Move the messages that are ready onto the bus. The adapter with |
| * the most leading zeros will win control of the bus and any other |
| * adapters will lose arbitration. |
| */ |
| spin_lock(&dev->cec_xfers_slock); |
| for (i = 0; i < ARRAY_SIZE(dev->xfers); i++) { |
| if (!dev->xfers[i].sft || dev->xfers[i].sft > last_sft) |
| continue; |
| if (first) { |
| first = false; |
| first_idx = i; |
| xfers_on_bus[first_idx].adap = dev->xfers[i].adap; |
| memcpy(first_msg.msg, dev->xfers[i].msg, dev->xfers[i].len); |
| first_msg.len = dev->xfers[i].len; |
| } else { |
| xfers_on_bus[i].adap = dev->xfers[i].adap; |
| xfers_on_bus[i].status = CEC_TX_STATUS_ARB_LOST; |
| /* |
| * For simplicity wait for all 4 bits of the initiator's |
| * address even though HDMI specification uses bit-level |
| * precision. |
| */ |
| wait_arb_lost_us = 4 * CEC_DATA_BIT_US + CEC_START_BIT_US; |
| } |
| dev->xfers[i].sft = 0; |
| } |
| dev->last_initiator = cec_msg_initiator(&first_msg); |
| adjust_sfts(dev); |
| spin_unlock(&dev->cec_xfers_slock); |
| |
| dest = cec_msg_destination(&first_msg); |
| valid_dest = cec_msg_is_broadcast(&first_msg); |
| if (!valid_dest) |
| valid_dest = find_dest_adap(dev, xfers_on_bus[first_idx].adap, dest); |
| if (valid_dest) { |
| first_status = CEC_TX_STATUS_OK; |
| /* |
| * Message length is in bytes, but each byte is transmitted in |
| * a block of 10 bits. |
| */ |
| wait_xfer_us = first_msg.len * 10 * CEC_DATA_BIT_US; |
| } else { |
| first_status = CEC_TX_STATUS_NACK; |
| /* |
| * A message that is not acknowledged stops transmitting after |
| * the header block of 10 bits. |
| */ |
| wait_xfer_us = 10 * CEC_DATA_BIT_US; |
| } |
| wait_xfer_us += CEC_START_BIT_US; |
| xfers_on_bus[first_idx].status = first_status; |
| |
| /* Sleep as if sending messages on a real hardware bus. */ |
| start = ktime_get(); |
| if (wait_arb_lost_us) { |
| usleep_range(wait_arb_lost_us - CEC_MARGIN_US, wait_arb_lost_us); |
| for (i = 0; i < ARRAY_SIZE(xfers_on_bus); i++) { |
| if (xfers_on_bus[i].status != CEC_TX_STATUS_ARB_LOST) |
| continue; |
| cec_transmit_attempt_done(xfers_on_bus[i].adap, |
| CEC_TX_STATUS_ARB_LOST); |
| } |
| if (kthread_should_stop()) |
| break; |
| } |
| wait_xfer_us -= wait_arb_lost_us; |
| usleep_range(wait_xfer_us - CEC_MARGIN_US, wait_xfer_us); |
| cec_transmit_attempt_done(xfers_on_bus[first_idx].adap, first_status); |
| if (kthread_should_stop()) |
| break; |
| if (first_status == CEC_TX_STATUS_OK) { |
| if (xfers_on_bus[first_idx].adap != dev->cec_rx_adap) |
| cec_received_msg(dev->cec_rx_adap, &first_msg); |
| for (i = 0; i < MAX_OUTPUTS && dev->cec_tx_adap[i]; i++) |
| if (xfers_on_bus[first_idx].adap != dev->cec_tx_adap[i]) |
| cec_received_msg(dev->cec_tx_adap[i], &first_msg); |
| } |
| end = ktime_get(); |
| /* |
| * If the emulated transfer took more or less time than it should |
| * have, then compensate by adjusting the wait time needed for the |
| * bus to be signal-free for 3 bit periods (the retry time). |
| */ |
| delta_us = div_s64(end - start, 1000); |
| delta_us -= wait_xfer_us + wait_arb_lost_us; |
| retry_us = CEC_SIGNAL_FREE_TIME_RETRY * CEC_DATA_BIT_US - delta_us; |
| if (retry_us > CEC_MARGIN_US) |
| usleep_range(retry_us - CEC_MARGIN_US, retry_us); |
| dev->cec_sft = CEC_SIGNAL_FREE_TIME_RETRY; |
| /* |
| * If there are no messages that need to be retried, check if any |
| * adapters that did not just transmit a message are ready to |
| * transmit. If none of these adapters are ready, then increase |
| * the signal-free time so that the bus is available to all |
| * adapters and go back to waiting for a transmission. |
| */ |
| while (dev->cec_sft >= CEC_SIGNAL_FREE_TIME_RETRY && |
| dev->cec_sft < CEC_SIGNAL_FREE_TIME_NEXT_XFER && |
| !xfer_ready(dev) && !kthread_should_stop()) { |
| usleep_range(2 * CEC_DATA_BIT_US - CEC_MARGIN_US, |
| 2 * CEC_DATA_BIT_US); |
| dev->cec_sft += 2; |
| } |
| } |
| return 0; |
| } |
| |
| static int vivid_cec_adap_enable(struct cec_adapter *adap, bool enable) |
| { |
| adap->cec_pin_is_high = true; |
| return 0; |
| } |
| |
| static int vivid_cec_adap_log_addr(struct cec_adapter *adap, u8 log_addr) |
| { |
| return 0; |
| } |
| |
| static int vivid_cec_adap_transmit(struct cec_adapter *adap, u8 attempts, |
| u32 signal_free_time, struct cec_msg *msg) |
| { |
| struct vivid_dev *dev = cec_get_drvdata(adap); |
| u8 idx = cec_msg_initiator(msg); |
| |
| spin_lock(&dev->cec_xfers_slock); |
| dev->xfers[idx].adap = adap; |
| memcpy(dev->xfers[idx].msg, msg->msg, CEC_MAX_MSG_SIZE); |
| dev->xfers[idx].len = msg->len; |
| dev->xfers[idx].sft = CEC_SIGNAL_FREE_TIME_RETRY; |
| if (signal_free_time > CEC_SIGNAL_FREE_TIME_RETRY) { |
| if (idx == dev->last_initiator) |
| dev->xfers[idx].sft = CEC_SIGNAL_FREE_TIME_NEXT_XFER; |
| else |
| dev->xfers[idx].sft = CEC_SIGNAL_FREE_TIME_NEW_INITIATOR; |
| } |
| spin_unlock(&dev->cec_xfers_slock); |
| wake_up_interruptible(&dev->kthread_waitq_cec); |
| |
| return 0; |
| } |
| |
| static int vivid_received(struct cec_adapter *adap, struct cec_msg *msg) |
| { |
| struct vivid_dev *dev = cec_get_drvdata(adap); |
| struct cec_msg reply; |
| u8 dest = cec_msg_destination(msg); |
| u8 disp_ctl; |
| char osd[14]; |
| |
| if (cec_msg_is_broadcast(msg)) |
| dest = adap->log_addrs.log_addr[0]; |
| cec_msg_init(&reply, dest, cec_msg_initiator(msg)); |
| |
| switch (cec_msg_opcode(msg)) { |
| case CEC_MSG_SET_OSD_STRING: |
| if (!cec_is_sink(adap)) |
| return -ENOMSG; |
| cec_ops_set_osd_string(msg, &disp_ctl, osd); |
| switch (disp_ctl) { |
| case CEC_OP_DISP_CTL_DEFAULT: |
| strscpy(dev->osd, osd, sizeof(dev->osd)); |
| dev->osd_jiffies = jiffies; |
| break; |
| case CEC_OP_DISP_CTL_UNTIL_CLEARED: |
| strscpy(dev->osd, osd, sizeof(dev->osd)); |
| dev->osd_jiffies = 0; |
| break; |
| case CEC_OP_DISP_CTL_CLEAR: |
| dev->osd[0] = 0; |
| dev->osd_jiffies = 0; |
| break; |
| default: |
| cec_msg_feature_abort(&reply, cec_msg_opcode(msg), |
| CEC_OP_ABORT_INVALID_OP); |
| cec_transmit_msg(adap, &reply, false); |
| break; |
| } |
| break; |
| default: |
| return -ENOMSG; |
| } |
| return 0; |
| } |
| |
| static const struct cec_adap_ops vivid_cec_adap_ops = { |
| .adap_enable = vivid_cec_adap_enable, |
| .adap_log_addr = vivid_cec_adap_log_addr, |
| .adap_transmit = vivid_cec_adap_transmit, |
| .received = vivid_received, |
| }; |
| |
| struct cec_adapter *vivid_cec_alloc_adap(struct vivid_dev *dev, |
| unsigned int idx, |
| bool is_source) |
| { |
| u32 caps = CEC_CAP_DEFAULTS | CEC_CAP_MONITOR_ALL | CEC_CAP_MONITOR_PIN; |
| char name[32]; |
| |
| snprintf(name, sizeof(name), "vivid-%03d-vid-%s%d", |
| dev->inst, is_source ? "out" : "cap", idx); |
| return cec_allocate_adapter(&vivid_cec_adap_ops, dev, |
| name, caps, CEC_MAX_LOG_ADDRS); |
| } |