/* * Copyright 2021-2024 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
*/
#include <media/cec.h>
#include"cec-splitter.h"
/* * Helper function to reply to a received message with a Feature Abort * message.
*/ staticint cec_feature_abort_reason(struct cec_adapter *adap, struct cec_msg *msg, u8 reason)
{ struct cec_msg tx_msg = { };
/* * Don't reply with CEC_MSG_FEATURE_ABORT to a CEC_MSG_FEATURE_ABORT * message!
*/ if (msg->msg[1] == CEC_MSG_FEATURE_ABORT) return 0; /* Don't Feature Abort messages from 'Unregistered' */ if (cec_msg_initiator(msg) == CEC_LOG_ADDR_UNREGISTERED) return 0;
cec_msg_set_reply_to(&tx_msg, msg);
cec_msg_feature_abort(&tx_msg, msg->msg[1], reason); return cec_transmit_msg(adap, &tx_msg, false);
}
/* Transmit an Active Source message from this output port to a sink */ staticvoid cec_port_out_active_source(struct cec_splitter_port *p)
{ struct cec_adapter *adap = p->adap; struct cec_msg msg;
/* Transmit Active Source messages from all output ports to the sinks */ staticvoid cec_out_active_source(struct cec_splitter *splitter)
{ unsignedint i;
for (i = 0; i < splitter->num_out_ports; i++)
cec_port_out_active_source(splitter->ports[i]);
}
/* Transmit a Standby message from this output port to a sink */ staticvoid cec_port_out_standby(struct cec_splitter_port *p)
{ struct cec_adapter *adap = p->adap; struct cec_msg msg;
/* Transmit Standby messages from all output ports to the sinks */ staticvoid cec_out_standby(struct cec_splitter *splitter)
{ unsignedint i;
for (i = 0; i < splitter->num_out_ports; i++)
cec_port_out_standby(splitter->ports[i]);
}
/* Transmit an Image/Text View On message from this output port to a sink */ staticvoid cec_port_out_wakeup(struct cec_splitter_port *p, u8 opcode)
{ struct cec_adapter *adap = p->adap;
u8 la = adap->log_addrs.log_addr[0]; struct cec_msg msg;
if (la == CEC_LOG_ADDR_INVALID)
la = CEC_LOG_ADDR_UNREGISTERED;
cec_msg_init(&msg, la, 0);
msg.len = 2;
msg.msg[1] = opcode;
cec_transmit_msg(adap, &msg, false);
}
/* Transmit Image/Text View On messages from all output ports to the sinks */ staticvoid cec_out_wakeup(struct cec_splitter *splitter, u8 opcode)
{ unsignedint i;
for (i = 0; i < splitter->num_out_ports; i++)
cec_port_out_wakeup(splitter->ports[i], opcode);
}
/* * Update the power state of the unconfigured CEC device to either * Off or On depending on the current state of the splitter. * This keeps the outputs in a consistent state.
*/ void cec_splitter_unconfigured_output(struct cec_splitter_port *p)
{
p->video_latency = 1;
p->power_status = p->splitter->is_standby ?
CEC_OP_POWER_STATUS_TO_STANDBY : CEC_OP_POWER_STATUS_TO_ON;
/* The adapter was unconfigured, so clear the sequence and ts values */
p->out_give_device_power_status_seq = 0;
p->out_give_device_power_status_ts = ktime_set(0, 0);
p->out_request_current_latency_seq = 0;
p->out_request_current_latency_ts = ktime_set(0, 0);
}
/* * Update the power state of the newly configured CEC device to either * Off or On depending on the current state of the splitter. * This keeps the outputs in a consistent state.
*/ void cec_splitter_configured_output(struct cec_splitter_port *p)
{
p->video_latency = 1;
p->power_status = p->splitter->is_standby ?
CEC_OP_POWER_STATUS_TO_STANDBY : CEC_OP_POWER_STATUS_TO_ON;
if (p->splitter->is_standby) { /* * Some sinks only obey Standby if it comes from the * active source.
*/
cec_port_out_active_source(p);
cec_port_out_standby(p);
} else {
cec_port_out_wakeup(p, CEC_MSG_IMAGE_VIEW_ON);
}
}
/* Pass the in_msg on to all output ports */ staticvoid cec_out_passthrough(struct cec_splitter *splitter, conststruct cec_msg *in_msg)
{ unsignedint i;
for (i = 0; i < splitter->num_out_ports; i++) { struct cec_splitter_port *p = splitter->ports[i]; struct cec_adapter *adap = p->adap; struct cec_msg msg;
/* * See if all output ports received the Report Current Latency message, * and if so, transmit the result from the input port to the video source.
*/ staticvoid cec_out_report_current_latency(struct cec_splitter *splitter, struct cec_adapter *input_adap)
{ struct cec_msg reply = {}; unsignedint reply_lat = 0; unsignedint cnt = 0; unsignedint i;
for (i = 0; i < splitter->num_out_ports; i++) { struct cec_splitter_port *p = splitter->ports[i]; struct cec_adapter *adap = p->adap;
/* Skip unconfigured ports */ if (!adap->is_configured) continue; /* Return if a port is still waiting for a reply */ if (p->out_request_current_latency_seq) return;
reply_lat += p->video_latency - 1;
cnt++;
}
/* * All ports that can reply, replied, so clear the sequence * and timestamp values.
*/ for (i = 0; i < splitter->num_out_ports; i++) { struct cec_splitter_port *p = splitter->ports[i];
/* * Return if there were no replies or the input port is no longer * configured.
*/ if (!cnt || !input_adap->is_configured) return;
/* Reply with the average latency */
reply_lat = 1 + reply_lat / cnt;
cec_msg_init(&reply, input_adap->log_addrs.log_addr[0],
splitter->request_current_latency_dest);
cec_msg_report_current_latency(&reply, input_adap->phys_addr,
reply_lat, 1, 1, 1);
cec_transmit_msg(input_adap, &reply, false);
}
/* Transmit Request Current Latency to all output ports */ staticint cec_out_request_current_latency(struct cec_splitter *splitter)
{
ktime_t now = ktime_get(); bool error = true; unsignedint i;
for (i = 0; i < splitter->num_out_ports; i++) { struct cec_splitter_port *p = splitter->ports[i]; struct cec_adapter *adap = p->adap;
if (!adap->is_configured) { /* Clear if not configured */
p->out_request_current_latency_seq = 0;
p->out_request_current_latency_ts = ktime_set(0, 0);
} elseif (!p->out_request_current_latency_seq) { /* * Keep the old ts if an earlier request is still * pending. This ensures that the request will * eventually time out based on the timestamp of * the first request if the sink is unresponsive.
*/
p->out_request_current_latency_ts = now;
}
}
for (i = 0; i < splitter->num_out_ports; i++) { struct cec_splitter_port *p = splitter->ports[i]; struct cec_adapter *adap = p->adap; struct cec_msg msg;
/* * See if all output ports received the Report Power Status message, * and if so, transmit the result from the input port to the video source.
*/ staticvoid cec_out_report_power_status(struct cec_splitter *splitter, struct cec_adapter *input_adap)
{ struct cec_msg reply = {}; /* The target power status of the splitter itself */
u8 splitter_pwr = splitter->is_standby ?
CEC_OP_POWER_STATUS_STANDBY : CEC_OP_POWER_STATUS_ON; /* * The transient power status of the splitter, used if not all * output report the target power status.
*/
u8 splitter_transient_pwr = splitter->is_standby ?
CEC_OP_POWER_STATUS_TO_STANDBY : CEC_OP_POWER_STATUS_TO_ON;
u8 reply_pwr = splitter_pwr; unsignedint i;
for (i = 0; i < splitter->num_out_ports; i++) { struct cec_splitter_port *p = splitter->ports[i];
/* Skip if no sink was found (HPD was low for more than 5s) */ if (!p->found_sink) continue;
/* Return if a port is still waiting for a reply */ if (p->out_give_device_power_status_seq) return; if (p->power_status != splitter_pwr)
reply_pwr = splitter_transient_pwr;
}
/* * All ports that can reply, replied, so clear the sequence * and timestamp values.
*/ for (i = 0; i < splitter->num_out_ports; i++) { struct cec_splitter_port *p = splitter->ports[i];
/* Return if the input port is no longer configured. */ if (!input_adap->is_configured) return;
/* Reply with the new power status */
cec_msg_init(&reply, input_adap->log_addrs.log_addr[0],
splitter->give_device_power_status_dest);
cec_msg_report_power_status(&reply, reply_pwr);
cec_transmit_msg(input_adap, &reply, false);
}
/* Transmit Give Device Power Status to all output ports */ staticint cec_out_give_device_power_status(struct cec_splitter *splitter)
{
ktime_t now = ktime_get(); bool error = true; unsignedint i;
for (i = 0; i < splitter->num_out_ports; i++) { struct cec_splitter_port *p = splitter->ports[i]; struct cec_adapter *adap = p->adap;
/* * Keep the old ts if an earlier request is still * pending. This ensures that the request will * eventually time out based on the timestamp of * the first request if the sink is unresponsive.
*/ if (adap->is_configured && !p->out_give_device_power_status_seq)
p->out_give_device_power_status_ts = now;
}
for (i = 0; i < splitter->num_out_ports; i++) { struct cec_splitter_port *p = splitter->ports[i]; struct cec_adapter *adap = p->adap; struct cec_msg msg;
/* * CEC messages received on the HDMI input of the splitter are * forwarded (if relevant) to the HDMI outputs of the splitter.
*/ int cec_splitter_received_input(struct cec_splitter_port *p, struct cec_msg *msg)
{ if (!cec_msg_status_is_ok(msg)) return 0;
if (msg->len < 2) return -ENOMSG;
switch (msg->msg[1]) { case CEC_MSG_DEVICE_VENDOR_ID: case CEC_MSG_REPORT_POWER_STATUS: case CEC_MSG_SET_STREAM_PATH: case CEC_MSG_ROUTING_CHANGE: case CEC_MSG_REQUEST_ACTIVE_SOURCE: case CEC_MSG_SYSTEM_AUDIO_MODE_STATUS: return 0;
case CEC_MSG_STANDBY:
p->splitter->is_standby = true;
cec_out_standby(p->splitter); return 0;
case CEC_MSG_IMAGE_VIEW_ON: case CEC_MSG_TEXT_VIEW_ON:
p->splitter->is_standby = false;
cec_out_wakeup(p->splitter, msg->msg[1]); return 0;
case CEC_MSG_ACTIVE_SOURCE:
cec_out_active_source(p->splitter); return 0;
case CEC_MSG_SET_SYSTEM_AUDIO_MODE:
cec_out_passthrough(p->splitter, msg); return 0;
case CEC_MSG_GIVE_DEVICE_POWER_STATUS:
p->splitter->give_device_power_status_dest =
cec_msg_initiator(msg); if (cec_out_give_device_power_status(p->splitter))
cec_feature_abort_reason(p->adap, msg,
CEC_OP_ABORT_INCORRECT_MODE); return 0;
/* * If this is the result of a failed non-blocking transmit, or it is * the result of the failed reply to a non-blocking transmit, then * check if the original transmit was to get the current power status * or latency and, if so, assume that the remove device is for one * reason or another unavailable and assume that it is in the same * power status as the splitter, or has no video latency.
*/ if ((cec_msg_recv_is_tx_result(msg) && !(msg->tx_status & CEC_TX_STATUS_OK)) ||
(cec_msg_recv_is_rx_result(msg) && !(msg->rx_status & CEC_RX_STATUS_OK))) {
u8 tx_op = msg->msg[1];
case CEC_MSG_SET_STREAM_PATH:
cec_ops_set_stream_path(msg, &pa); if (pa == adap->phys_addr) {
cec_msg_set_reply_to(&reply, msg);
cec_msg_active_source(&reply, pa);
cec_transmit_msg(adap, &reply, false);
} return 0;
default: return -ENOMSG;
} return -ENOMSG;
}
/* * Called every second to check for timed out messages and whether there * still is a video sink connected or not. * * Returns true if sinks were lost.
*/ bool cec_splitter_poll(struct cec_splitter *splitter, struct cec_adapter *input_adap, bool debug)
{
ktime_t now = ktime_get();
u8 pwr = splitter->is_standby ?
CEC_OP_POWER_STATUS_STANDBY : CEC_OP_POWER_STATUS_ON; unsignedint max_delay_ms = input_adap->xfer_timeout_ms + 2000; unsignedint i; bool res = false;
for (i = 0; i < splitter->num_out_ports; i++) { struct cec_splitter_port *p = splitter->ports[i];
s64 pwr_delta, lat_delta; bool pwr_timeout, lat_timeout;
/* * If the HPD is low for more than 5 seconds, then assume no display * is connected.
*/ if (p->found_sink && ktime_to_ns(p->lost_sink_ts) &&
ktime_ms_delta(now, p->lost_sink_ts) > 5000) { if (debug)
dev_info(splitter->dev, "port %u: HPD low for more than 5s, assume no sink is connected.\n",
p->port);
p->found_sink = false;
p->lost_sink_ts = ktime_set(0, 0);
res = true;
}
/* * If the power status request timed out, then set the port's * power status to that of the splitter, ensuring a consistent * power state.
*/ if (pwr_timeout) {
mutex_lock(&p->adap->lock); if (debug)
dev_info(splitter->dev, "port %u: give up on power status for seq %u\n",
p->port,
p->out_give_device_power_status_seq & ~(1 << 31));
p->power_status = pwr;
p->out_give_device_power_status_seq = 0;
p->out_give_device_power_status_ts = ktime_set(0, 0);
mutex_unlock(&p->adap->lock);
cec_out_report_power_status(splitter, input_adap);
}
/* * If the current latency request timed out, then set the port's * latency to 1.
*/ if (lat_timeout) {
mutex_lock(&p->adap->lock); if (debug)
dev_info(splitter->dev, "port %u: give up on latency for seq %u\n",
p->port,
p->out_request_current_latency_seq & ~(1 << 31));
p->video_latency = 1;
p->out_request_current_latency_seq = 0;
p->out_request_current_latency_ts = ktime_set(0, 0);
mutex_unlock(&p->adap->lock);
cec_out_report_current_latency(splitter, input_adap);
}
} return res;
}
Messung V0.5
¤ Dauer der Verarbeitung: 0.3 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.