brintos

brintos / linux-shallow public Read only

0
0
Text · 11.9 KiB · 356a988 Raw
418 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * vivid-cec.c - A Virtual Video Test Driver, cec emulation4 *5 * Copyright 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved.6 */7 8#include <linux/delay.h>9#include <media/cec.h>10 11#include "vivid-core.h"12#include "vivid-cec.h"13 14#define CEC_START_BIT_US		450015#define CEC_DATA_BIT_US			240016#define CEC_MARGIN_US			35017 18struct xfer_on_bus {19	struct cec_adapter	*adap;20	u8			status;21};22 23static bool find_dest_adap(struct vivid_dev *dev,24			   struct cec_adapter *adap, u8 dest)25{26	unsigned int i, j;27 28	if (dest >= 0xf)29		return false;30 31	if (adap != dev->cec_rx_adap && dev->cec_rx_adap &&32	    dev->cec_rx_adap->is_configured &&33	    cec_has_log_addr(dev->cec_rx_adap, dest))34		return true;35 36	for (i = 0, j = 0; i < dev->num_inputs; i++) {37		unsigned int menu_idx =38			dev->input_is_connected_to_output[i];39 40		if (dev->input_type[i] != HDMI)41			continue;42		j++;43		if (menu_idx < FIXED_MENU_ITEMS)44			continue;45 46		struct vivid_dev *dev_tx = vivid_ctrl_hdmi_to_output_instance[menu_idx];47		unsigned int output = vivid_ctrl_hdmi_to_output_index[menu_idx];48 49		if (!dev_tx)50			continue;51 52		unsigned int hdmi_output = dev_tx->output_to_iface_index[output];53 54		if (adap == dev_tx->cec_tx_adap[hdmi_output])55			continue;56		if (!dev_tx->cec_tx_adap[hdmi_output]->is_configured)57			continue;58		if (cec_has_log_addr(dev_tx->cec_tx_adap[hdmi_output], dest))59			return true;60	}61	return false;62}63 64static bool xfer_ready(struct vivid_dev *dev)65{66	unsigned int i;67	bool ready = false;68 69	spin_lock(&dev->cec_xfers_slock);70	for (i = 0; i < ARRAY_SIZE(dev->xfers); i++) {71		if (dev->xfers[i].sft &&72		    dev->xfers[i].sft <= dev->cec_sft) {73			ready = true;74			break;75		}76	}77	spin_unlock(&dev->cec_xfers_slock);78 79	return ready;80}81 82/*83 * If an adapter tries to send successive messages, it must wait for the84 * longest signal-free time between its transmissions. But, if another85 * adapter sends a message in the interim, then the wait can be reduced86 * because the messages are no longer successive. Make these adjustments87 * if necessary. Should be called holding cec_xfers_slock.88 */89static void adjust_sfts(struct vivid_dev *dev)90{91	unsigned int i;92	u8 initiator;93 94	for (i = 0; i < ARRAY_SIZE(dev->xfers); i++) {95		if (dev->xfers[i].sft <= CEC_SIGNAL_FREE_TIME_RETRY)96			continue;97		initiator = dev->xfers[i].msg[0] >> 4;98		if (initiator == dev->last_initiator)99			dev->xfers[i].sft = CEC_SIGNAL_FREE_TIME_NEXT_XFER;100		else101			dev->xfers[i].sft = CEC_SIGNAL_FREE_TIME_NEW_INITIATOR;102	}103}104 105/*106 * The main emulation of the bus on which CEC adapters attempt to send107 * messages to each other. The bus keeps track of how long it has been108 * signal-free and accepts a pending transmission only if the state of109 * the bus matches the transmission's signal-free requirements. It calls110 * cec_transmit_attempt_done() for all transmits that enter the bus and111 * cec_received_msg() for successful transmits.112 */113int vivid_cec_bus_thread(void *_dev)114{115	u32 last_sft;116	unsigned int i, j;117	unsigned int dest;118	ktime_t start, end;119	s64 delta_us, retry_us;120	struct vivid_dev *dev = _dev;121 122	dev->cec_sft = CEC_SIGNAL_FREE_TIME_NEXT_XFER;123	for (;;) {124		bool first = true;125		int wait_xfer_us = 0;126		bool valid_dest = false;127		int wait_arb_lost_us = 0;128		unsigned int first_idx = 0;129		unsigned int first_status = 0;130		struct cec_msg first_msg = {};131		struct xfer_on_bus xfers_on_bus[MAX_OUTPUTS] = {};132 133		wait_event_interruptible(dev->kthread_waitq_cec, xfer_ready(dev) ||134					 kthread_should_stop());135		if (kthread_should_stop())136			break;137		last_sft = dev->cec_sft;138		dev->cec_sft = 0;139		/*140		 * Move the messages that are ready onto the bus. The adapter with141		 * the most leading zeros will win control of the bus and any other142		 * adapters will lose arbitration.143		 */144		spin_lock(&dev->cec_xfers_slock);145		for (i = 0; i < ARRAY_SIZE(dev->xfers); i++) {146			if (!dev->xfers[i].sft || dev->xfers[i].sft > last_sft)147				continue;148			if (first) {149				first = false;150				first_idx = i;151				xfers_on_bus[first_idx].adap = dev->xfers[i].adap;152				memcpy(first_msg.msg, dev->xfers[i].msg, dev->xfers[i].len);153				first_msg.len = dev->xfers[i].len;154			} else {155				xfers_on_bus[i].adap = dev->xfers[i].adap;156				xfers_on_bus[i].status = CEC_TX_STATUS_ARB_LOST;157				/*158				 * For simplicity wait for all 4 bits of the initiator's159				 * address even though HDMI specification uses bit-level160				 * precision.161				 */162				wait_arb_lost_us = 4 * CEC_DATA_BIT_US + CEC_START_BIT_US;163			}164			dev->xfers[i].sft = 0;165		}166		dev->last_initiator = cec_msg_initiator(&first_msg);167		adjust_sfts(dev);168		spin_unlock(&dev->cec_xfers_slock);169 170		dest = cec_msg_destination(&first_msg);171		valid_dest = cec_msg_is_broadcast(&first_msg);172		if (!valid_dest)173			valid_dest = find_dest_adap(dev, xfers_on_bus[first_idx].adap, dest);174		if (valid_dest) {175			first_status = CEC_TX_STATUS_OK;176			/*177			 * Message length is in bytes, but each byte is transmitted in178			 * a block of 10 bits.179			 */180			wait_xfer_us = first_msg.len * 10 * CEC_DATA_BIT_US;181		} else {182			first_status = CEC_TX_STATUS_NACK;183			/*184			 * A message that is not acknowledged stops transmitting after185			 * the header block of 10 bits.186			 */187			wait_xfer_us = 10 * CEC_DATA_BIT_US;188		}189		wait_xfer_us += CEC_START_BIT_US;190		xfers_on_bus[first_idx].status = first_status;191 192		/* Sleep as if sending messages on a real hardware bus. */193		start = ktime_get();194		if (wait_arb_lost_us) {195			usleep_range(wait_arb_lost_us - CEC_MARGIN_US, wait_arb_lost_us);196			for (i = 0; i < ARRAY_SIZE(xfers_on_bus); i++) {197				if (xfers_on_bus[i].status != CEC_TX_STATUS_ARB_LOST)198					continue;199				cec_transmit_attempt_done(xfers_on_bus[i].adap,200							  CEC_TX_STATUS_ARB_LOST);201			}202			if (kthread_should_stop())203				break;204		}205		wait_xfer_us -= wait_arb_lost_us;206		usleep_range(wait_xfer_us - CEC_MARGIN_US, wait_xfer_us);207		cec_transmit_attempt_done(xfers_on_bus[first_idx].adap, first_status);208		if (kthread_should_stop())209			break;210		if (first_status == CEC_TX_STATUS_OK) {211			if (xfers_on_bus[first_idx].adap != dev->cec_rx_adap)212				cec_received_msg(dev->cec_rx_adap, &first_msg);213			for (i = 0, j = 0; i < dev->num_inputs; i++) {214				unsigned int menu_idx =215					dev->input_is_connected_to_output[i];216 217				if (dev->input_type[i] != HDMI)218					continue;219				j++;220				if (menu_idx < FIXED_MENU_ITEMS)221					continue;222 223				struct vivid_dev *dev_tx = vivid_ctrl_hdmi_to_output_instance[menu_idx];224				unsigned int output = vivid_ctrl_hdmi_to_output_index[menu_idx];225 226				if (!dev_tx)227					continue;228 229				unsigned int hdmi_output = dev_tx->output_to_iface_index[output];230 231				if (xfers_on_bus[first_idx].adap != dev_tx->cec_tx_adap[hdmi_output])232					cec_received_msg(dev_tx->cec_tx_adap[hdmi_output], &first_msg);233			}234		}235		end = ktime_get();236		/*237		 * If the emulated transfer took more or less time than it should238		 * have, then compensate by adjusting the wait time needed for the239		 * bus to be signal-free for 3 bit periods (the retry time).240		 */241		delta_us = div_s64(end - start, 1000);242		delta_us -= wait_xfer_us + wait_arb_lost_us;243		retry_us = CEC_SIGNAL_FREE_TIME_RETRY * CEC_DATA_BIT_US - delta_us;244		if (retry_us > CEC_MARGIN_US)245			usleep_range(retry_us - CEC_MARGIN_US, retry_us);246		dev->cec_sft = CEC_SIGNAL_FREE_TIME_RETRY;247		/*248		 * If there are no messages that need to be retried, check if any249		 * adapters that did not just transmit a message are ready to250		 * transmit. If none of these adapters are ready, then increase251		 * the signal-free time so that the bus is available to all252		 * adapters and go back to waiting for a transmission.253		 */254		while (dev->cec_sft >= CEC_SIGNAL_FREE_TIME_RETRY &&255		       dev->cec_sft < CEC_SIGNAL_FREE_TIME_NEXT_XFER &&256		       !xfer_ready(dev) && !kthread_should_stop()) {257			usleep_range(2 * CEC_DATA_BIT_US - CEC_MARGIN_US,258				     2 * CEC_DATA_BIT_US);259			dev->cec_sft += 2;260		}261	}262	return 0;263}264 265static int vivid_cec_adap_enable(struct cec_adapter *adap, bool enable)266{267	adap->cec_pin_is_high = true;268	return 0;269}270 271static int vivid_cec_adap_log_addr(struct cec_adapter *adap, u8 log_addr)272{273	return 0;274}275 276static int vivid_cec_adap_transmit(struct cec_adapter *adap, u8 attempts,277				   u32 signal_free_time, struct cec_msg *msg)278{279	struct vivid_dev *dev = cec_get_drvdata(adap);280	struct vivid_dev *dev_rx = dev;281	u8 idx = cec_msg_initiator(msg);282	u8 output = 0;283 284	if (dev->cec_rx_adap != adap) {285		int i;286 287		for (i = 0; i < dev->num_hdmi_outputs; i++)288			if (dev->cec_tx_adap[i] == adap)289				break;290		if (i == dev->num_hdmi_outputs)291			return -ENONET;292		output = dev->hdmi_index_to_output_index[i];293		dev_rx = dev->output_to_input_instance[output];294		if (!dev_rx)295			return -ENONET;296	}297	spin_lock(&dev_rx->cec_xfers_slock);298	dev_rx->xfers[idx].adap = adap;299	memcpy(dev_rx->xfers[idx].msg, msg->msg, CEC_MAX_MSG_SIZE);300	dev_rx->xfers[idx].len = msg->len;301	dev_rx->xfers[idx].sft = CEC_SIGNAL_FREE_TIME_RETRY;302	if (signal_free_time > CEC_SIGNAL_FREE_TIME_RETRY) {303		if (idx == dev_rx->last_initiator)304			dev_rx->xfers[idx].sft = CEC_SIGNAL_FREE_TIME_NEXT_XFER;305		else306			dev_rx->xfers[idx].sft = CEC_SIGNAL_FREE_TIME_NEW_INITIATOR;307	}308	spin_unlock(&dev_rx->cec_xfers_slock);309	wake_up_interruptible(&dev_rx->kthread_waitq_cec);310 311	return 0;312}313 314static int vivid_received(struct cec_adapter *adap, struct cec_msg *msg)315{316	struct vivid_dev *dev = cec_get_drvdata(adap);317	struct cec_msg reply;318	u8 dest = cec_msg_destination(msg);319 320	if (cec_msg_is_broadcast(msg))321		dest = adap->log_addrs.log_addr[0];322	cec_msg_init(&reply, dest, cec_msg_initiator(msg));323 324	switch (cec_msg_opcode(msg)) {325	case CEC_MSG_SET_OSD_STRING: {326		u8 disp_ctl;327		char osd[14];328 329		if (!cec_is_sink(adap))330			return -ENOMSG;331		cec_ops_set_osd_string(msg, &disp_ctl, osd);332		switch (disp_ctl) {333		case CEC_OP_DISP_CTL_DEFAULT:334			strscpy(dev->osd, osd, sizeof(dev->osd));335			dev->osd_jiffies = jiffies;336			break;337		case CEC_OP_DISP_CTL_UNTIL_CLEARED:338			strscpy(dev->osd, osd, sizeof(dev->osd));339			dev->osd_jiffies = 0;340			break;341		case CEC_OP_DISP_CTL_CLEAR:342			dev->osd[0] = 0;343			dev->osd_jiffies = 0;344			break;345		default:346			cec_msg_feature_abort(&reply, cec_msg_opcode(msg),347					      CEC_OP_ABORT_INVALID_OP);348			cec_transmit_msg(adap, &reply, false);349			break;350		}351		break;352	}353	case CEC_MSG_VENDOR_COMMAND_WITH_ID: {354		u32 vendor_id;355		u8 size;356		const u8 *vendor_cmd;357 358		/*359		 * If we receive <Vendor Command With ID> with our vendor ID360		 * and with a payload of size 1, and the payload value is odd,361		 * then we reply with the same message, but with the payload362		 * byte incremented by 1.363		 *364		 * If the size is 1 and the payload value is even, then we365		 * ignore the message.366		 *367		 * The reason we reply to odd instead of even payload values368		 * is that it allows for testing of the corner case where the369		 * reply value is 0 (0xff + 1 % 256).370		 *371		 * For other sizes we Feature Abort.372		 *373		 * This is added for the specific purpose of testing the374		 * CEC_MSG_FL_REPLY_VENDOR_ID flag using vivid.375		 */376		cec_ops_vendor_command_with_id(msg, &vendor_id, &size, &vendor_cmd);377		if (vendor_id != adap->log_addrs.vendor_id)378			break;379		if (size == 1) {380			// Ignore even op values381			if (!(vendor_cmd[0] & 1))382				break;383			reply.len = msg->len;384			memcpy(reply.msg + 1, msg->msg + 1, msg->len - 1);385			reply.msg[msg->len - 1]++;386		} else {387			cec_msg_feature_abort(&reply, cec_msg_opcode(msg),388					      CEC_OP_ABORT_INVALID_OP);389		}390		cec_transmit_msg(adap, &reply, false);391		break;392	}393	default:394		return -ENOMSG;395	}396	return 0;397}398 399static const struct cec_adap_ops vivid_cec_adap_ops = {400	.adap_enable = vivid_cec_adap_enable,401	.adap_log_addr = vivid_cec_adap_log_addr,402	.adap_transmit = vivid_cec_adap_transmit,403	.received = vivid_received,404};405 406struct cec_adapter *vivid_cec_alloc_adap(struct vivid_dev *dev,407					 unsigned int idx,408					 bool is_source)409{410	u32 caps = CEC_CAP_DEFAULTS | CEC_CAP_MONITOR_ALL | CEC_CAP_MONITOR_PIN;411	char name[32];412 413	snprintf(name, sizeof(name), "vivid-%03d-vid-%s%d",414		 dev->inst, is_source ? "out" : "cap", idx);415	return cec_allocate_adapter(&vivid_cec_adap_ops, dev,416				    name, caps, CEC_MAX_LOG_ADDRS);417}418