aboutsummaryrefslogtreecommitdiffstats
path: root/utils
diff options
context:
space:
mode:
authorJohan Fjeldtvedt <jaffe1@gmail.com>2016-08-15 11:20:09 +0200
committerHans Verkuil <hans.verkuil@cisco.com>2016-08-15 13:05:39 +0200
commitd96c9c5425c1fa40f273f1e84bf1e64165e1dea4 (patch)
tree7585366a011f7411a59de4a77a522ecb02ed8b93 /utils
parenta934cfa57541bb9b8284a7a8bb129b8fe67583e8 (diff)
cec-compliance: add CEC compliance utility
This utility makes it possible to test the local CEC adapter and/or a remote CEC device. It verifies as much as possible whether the CEC implementation follows the CEC specification. Note that these CEC features have currently limited test coverage: One Touch Record Deck Control Device Menu Control Audio Rate Control Tuner Control Timer Programming Capability Discovery and Control Vendor Specific Commands There is also no or very limited testing for Unregistered devices, CEC switches and CDC-only devices. Many thanks to Johan Fjeldtvedt who did this work during his Cisco Summer internship! Signed-off-by: Johan Fjeldtvedt <jaffe1@gmail.com> Signed-off-by: Hans Verkuil <hans.verkuil@cisco.com>
Diffstat (limited to 'utils')
-rw-r--r--utils/cec-compliance/.gitignore2
-rw-r--r--utils/cec-compliance/Makefile.am11
-rw-r--r--utils/cec-compliance/cec-compliance.cpp1342
-rw-r--r--utils/cec-compliance/cec-compliance.h459
-rw-r--r--utils/cec-compliance/cec-test-adapter.cpp916
-rw-r--r--utils/cec-compliance/cec-test-audio.cpp831
-rw-r--r--utils/cec-compliance/cec-test-power.cpp441
-rw-r--r--utils/cec-compliance/cec-test.cpp1493
8 files changed, 5495 insertions, 0 deletions
diff --git a/utils/cec-compliance/.gitignore b/utils/cec-compliance/.gitignore
new file mode 100644
index 00000000..35073175
--- /dev/null
+++ b/utils/cec-compliance/.gitignore
@@ -0,0 +1,2 @@
+cec-compliance
+cec-table.h
diff --git a/utils/cec-compliance/Makefile.am b/utils/cec-compliance/Makefile.am
new file mode 100644
index 00000000..331b03e3
--- /dev/null
+++ b/utils/cec-compliance/Makefile.am
@@ -0,0 +1,11 @@
+bin_PROGRAMS = cec-compliance
+
+cec_compliance_SOURCES = cec-compliance.cpp cec-test.cpp cec-test-adapter.cpp cec-test-audio.cpp cec-test-power.cpp
+
+cec-compliance.cpp: cec-table.h
+
+cec-table.h: ../cec-ctl/msg2ctl.pl ../../include/linux/cec.h ../../include/linux/cec-funcs.h
+ ../cec-ctl/msg2ctl.pl 2 ../../include/linux/cec.h ../../include/linux/cec-funcs.h >$@
+
+clean-local:
+ -rm -vf cec-table.h
diff --git a/utils/cec-compliance/cec-compliance.cpp b/utils/cec-compliance/cec-compliance.cpp
new file mode 100644
index 00000000..d94e06ec
--- /dev/null
+++ b/utils/cec-compliance/cec-compliance.cpp
@@ -0,0 +1,1342 @@
+/*
+ * Copyright 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
+ *
+ * This program is free software; you may redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <inttypes.h>
+#include <getopt.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+#include <sys/wait.h>
+#include <sstream>
+
+#include "cec-compliance.h"
+
+#include "cec-table.h"
+
+/* Short option list
+
+ Please keep in alphabetical order.
+ That makes it easier to see which short options are still free.
+
+ In general the lower case is used to set something and the upper
+ case is used to retrieve a setting. */
+enum Option {
+ OptTestAdapter = 'A',
+ OptSetDevice = 'd',
+ OptHelp = 'h',
+ OptNoWarnings = 'n',
+ OptRemote = 'r',
+ OptTrace = 'T',
+ OptVerbose = 'v',
+ OptInteractive = 'i',
+
+ OptTestCore = 128,
+ OptTestAudioRateControl,
+ OptTestARCControl,
+ OptTestCapDiscoveryControl,
+ OptTestDeckControl,
+ OptTestDeviceMenuControl,
+ OptTestDeviceOSDTransfer,
+ OptTestDynamicAutoLipsync,
+ OptTestOSDDisplay,
+ OptTestOneTouchPlay,
+ OptTestOneTouchRecord,
+ OptTestPowerStatus,
+ OptTestRemoteControlPassthrough,
+ OptTestRoutingControl,
+ OptTestSystemAudioControl,
+ OptTestSystemInformation,
+ OptTestTimerProgramming,
+ OptTestTunerControl,
+ OptTestVendorSpecificCommands,
+ OptTestStandbyResume,
+ OptLast = 256
+};
+
+
+static char options[OptLast];
+
+static int app_result;
+static int tests_total, tests_ok;
+
+bool show_info;
+bool show_warnings = true;
+unsigned warnings;
+
+static struct option long_options[] = {
+ {"device", required_argument, 0, OptSetDevice},
+ {"help", no_argument, 0, OptHelp},
+ {"no-warnings", no_argument, 0, OptNoWarnings},
+ {"remote", optional_argument, 0, OptRemote},
+ {"trace", no_argument, 0, OptTrace},
+ {"verbose", no_argument, 0, OptVerbose},
+ {"interactive", no_argument, 0, OptInteractive},
+
+ {"test-adapter", no_argument, 0, OptTestAdapter},
+ {"test-core", no_argument, 0, OptTestCore},
+ {"test-audio-rate-control", no_argument, 0, OptTestAudioRateControl},
+ {"test-audio-return-channel-control", no_argument, 0, OptTestARCControl},
+ {"test-capability-discovery-and-control", no_argument, 0, OptTestCapDiscoveryControl},
+ {"test-deck-control", no_argument, 0, OptTestDeckControl},
+ {"test-device-menu-control", no_argument, 0, OptTestDeviceMenuControl},
+ {"test-device-osd-transfer", no_argument, 0, OptTestDeviceOSDTransfer},
+ {"test-dynamic-auto-lipsync", no_argument, 0, OptTestDynamicAutoLipsync},
+ {"test-osd-display", no_argument, 0, OptTestOSDDisplay},
+ {"test-one-touch-play", no_argument, 0, OptTestOneTouchPlay},
+ {"test-one-touch-record", no_argument, 0, OptTestOneTouchRecord},
+ {"test-power-status", no_argument, 0, OptTestPowerStatus},
+ {"test-remote-control-passthrough", no_argument, 0, OptTestRemoteControlPassthrough},
+ {"test-routing-control", no_argument, 0, OptTestRoutingControl},
+ {"test-system-audio-control", no_argument, 0, OptTestSystemAudioControl},
+ {"test-system-information", no_argument, 0, OptTestSystemInformation},
+ {"test-timer-programming", no_argument, 0, OptTestTimerProgramming},
+ {"test-tuner-control", no_argument, 0, OptTestTunerControl},
+ {"test-vendor-specific-commands", no_argument, 0, OptTestVendorSpecificCommands},
+ {"test-standby-resume", no_argument, 0, OptTestStandbyResume},
+ {0, 0, 0, 0}
+};
+
+static void usage(void)
+{
+ printf("Usage:\n"
+ " -d, --device=<dev> Use device <dev> instead of /dev/cec0\n"
+ " If <dev> starts with a digit, then /dev/cec<dev> is used.\n"
+ " -r, --remote[=<la>] As initiator test the remote logical address or all LAs if no LA was given\n"
+ " -i, --interactive Interactive mode when doing remote tests\n"
+ "\n"
+ " -A, --test-adapter Test the CEC adapter API\n"
+ " --test-core Test the core functionality\n"
+ " --test-audio-rate-control Test the Audio Rate Control feature\n"
+ " --test-audio-return-channel-control Test the Audio Return Channel Control feature\n"
+ " --test-capability-discovery-and-control Test the Capability Discovery and Control feature\n"
+ " --test-deck-control Test the Deck Control feature\n"
+ " --test-device-menu-control Test the Device Menu Control feature\n"
+ " --test-device-osd-transfer Test the Device OSD Transfer feature\n"
+ " --test-dynamic-audio-lipsync Test the Dynamic Audio Lipsync feature\n"
+ " --test-osd-display Test the OSD Display feature\n"
+ " --test-one-touch-play Test the One Touch Play feature\n"
+ " --test-one-touch-record Test the One Touch Record feature\n"
+ " --test-power-status Test the Power Status feature\n"
+ " --test-remote-control-passthrough Test the Remote Control Passthrough feature\n"
+ " --test-routing-control Test the Routing Control feature\n"
+ " --test-system-audio-control Test the System Audio Control feature\n"
+ " --test-system-information Test the System Information feature\n"
+ " --test-timer-programming Test the Timer Programming feature\n"
+ " --test-tuner-control Test the Tuner Control feature\n"
+ " --test-vendor-specific-commands Test the Vendor Specific Commands feature\n"
+ " --test-standby-resume Test standby and resume functionality. This will activate\n"
+ " testing of Standby, Give Device Power Status and One Touch Play.\n"
+ "\n"
+ " -h, --help Display this help message\n"
+ " -n, --no-warnings Turn off warning messages\n"
+ " -T, --trace Trace all called ioctls\n"
+ " -v, --verbose Turn on verbose reporting\n"
+ );
+}
+
+static std::string caps2s(unsigned caps)
+{
+ std::string s;
+
+ if (caps & CEC_CAP_PHYS_ADDR)
+ s += "\t\tPhysical Address\n";
+ if (caps & CEC_CAP_LOG_ADDRS)
+ s += "\t\tLogical Addresses\n";
+ if (caps & CEC_CAP_TRANSMIT)
+ s += "\t\tTransmit\n";
+ if (caps & CEC_CAP_PASSTHROUGH)
+ s += "\t\tPassthrough\n";
+ if (caps & CEC_CAP_RC)
+ s += "\t\tRemote Control Support\n";
+ if (caps & CEC_CAP_MONITOR_ALL)
+ s += "\t\tMonitor All\n";
+ return s;
+}
+
+const char *version2s(unsigned version)
+{
+ switch (version) {
+ case CEC_OP_CEC_VERSION_1_3A:
+ return "1.3a";
+ case CEC_OP_CEC_VERSION_1_4:
+ return "1.4";
+ case CEC_OP_CEC_VERSION_2_0:
+ return "2.0";
+ default:
+ return "Unknown";
+ }
+}
+
+const char *prim_type2s(unsigned type)
+{
+ switch (type) {
+ case CEC_OP_PRIM_DEVTYPE_TV:
+ return "TV";
+ case CEC_OP_PRIM_DEVTYPE_RECORD:
+ return "Record";
+ case CEC_OP_PRIM_DEVTYPE_TUNER:
+ return "Tuner";
+ case CEC_OP_PRIM_DEVTYPE_PLAYBACK:
+ return "Playback";
+ case CEC_OP_PRIM_DEVTYPE_AUDIOSYSTEM:
+ return "Audio System";
+ case CEC_OP_PRIM_DEVTYPE_SWITCH:
+ return "Switch";
+ case CEC_OP_PRIM_DEVTYPE_PROCESSOR:
+ return "Processor";
+ default:
+ return "Unknown";
+ }
+}
+
+const char *la_type2s(unsigned type)
+{
+ switch (type) {
+ case CEC_LOG_ADDR_TYPE_TV:
+ return "TV";
+ case CEC_LOG_ADDR_TYPE_RECORD:
+ return "Record";
+ case CEC_LOG_ADDR_TYPE_TUNER:
+ return "Tuner";
+ case CEC_LOG_ADDR_TYPE_PLAYBACK:
+ return "Playback";
+ case CEC_LOG_ADDR_TYPE_AUDIOSYSTEM:
+ return "Audio System";
+ case CEC_LOG_ADDR_TYPE_SPECIFIC:
+ return "Specific";
+ case CEC_LOG_ADDR_TYPE_UNREGISTERED:
+ return "Unregistered";
+ default:
+ return "Unknown";
+ }
+}
+
+const char *la2s(unsigned la)
+{
+ switch (la & 0xf) {
+ case 0:
+ return "TV";
+ case 1:
+ return "Recording Device 1";
+ case 2:
+ return "Recording Device 2";
+ case 3:
+ return "Tuner 1";
+ case 4:
+ return "Playback Device 1";
+ case 5:
+ return "Audio System";
+ case 6:
+ return "Tuner 2";
+ case 7:
+ return "Tuner 3";
+ case 8:
+ return "Playback Device 2";
+ case 9:
+ return "Playback Device 3";
+ case 10:
+ return "Tuner 4";
+ case 11:
+ return "Playback Device 3";
+ case 12:
+ return "Reserved 1";
+ case 13:
+ return "Reserved 2";
+ case 14:
+ return "Specific";
+ case 15:
+ default:
+ return "Unregistered";
+ }
+}
+
+static std::string tx_status2s(const struct cec_msg &msg)
+{
+ std::string s;
+ char num[4];
+ unsigned stat = msg.tx_status;
+
+ if (stat)
+ s += "Tx";
+ if (stat & CEC_TX_STATUS_OK)
+ s += ", OK";
+ if (stat & CEC_TX_STATUS_ARB_LOST) {
+ sprintf(num, "%u", msg.tx_arb_lost_cnt);
+ s += ", Arbitration Lost";
+ if (msg.tx_arb_lost_cnt)
+ s += " (" + std::string(num) + ")";
+ }
+ if (stat & CEC_TX_STATUS_NACK) {
+ sprintf(num, "%u", msg.tx_nack_cnt);
+ s += ", Not Acknowledged";
+ if (msg.tx_nack_cnt)
+ s += " (" + std::string(num) + ")";
+ }
+ if (stat & CEC_TX_STATUS_LOW_DRIVE) {
+ sprintf(num, "%u", msg.tx_low_drive_cnt);
+ s += ", Low Drive";
+ if (msg.tx_low_drive_cnt)
+ s += " (" + std::string(num) + ")";
+ }
+ if (stat & CEC_TX_STATUS_ERROR) {
+ sprintf(num, "%u", msg.tx_error_cnt);
+ s += ", Error";
+ if (msg.tx_error_cnt)
+ s += " (" + std::string(num) + ")";
+ }
+ if (stat & CEC_TX_STATUS_MAX_RETRIES)
+ s += ", Max Retries";
+ return s;
+}
+
+static std::string rx_status2s(unsigned stat)
+{
+ std::string s;
+
+ if (stat)
+ s += "Rx";
+ if (stat & CEC_RX_STATUS_OK)
+ s += ", OK";
+ if (stat & CEC_RX_STATUS_TIMEOUT)
+ s += ", Timeout";
+ if (stat & CEC_RX_STATUS_FEATURE_ABORT)
+ s += ", Feature Abort";
+ return s;
+}
+
+std::string status2s(const struct cec_msg &msg)
+{
+ std::string s;
+
+ if (msg.tx_status)
+ s = tx_status2s(msg);
+ if (msg.rx_status) {
+ if (!s.empty())
+ s += ", ";
+ s += rx_status2s(msg.rx_status);
+ }
+ return s;
+}
+
+std::string all_dev_types2s(unsigned types)
+{
+ std::string s;
+
+ if (types & CEC_OP_ALL_DEVTYPE_TV)
+ s += "TV, ";
+ if (types & CEC_OP_ALL_DEVTYPE_RECORD)
+ s += "Record, ";
+ if (types & CEC_OP_ALL_DEVTYPE_TUNER)
+ s += "Tuner, ";
+ if (types & CEC_OP_ALL_DEVTYPE_PLAYBACK)
+ s += "Playback, ";
+ if (types & CEC_OP_ALL_DEVTYPE_AUDIOSYSTEM)
+ s += "Audio System, ";
+ if (types & CEC_OP_ALL_DEVTYPE_SWITCH)
+ s += "Switch, ";
+ return s.erase(s.length() - 2, 2);
+}
+
+std::string rc_src_prof2s(unsigned prof)
+{
+ std::string s;
+
+ prof &= 0x1f;
+ if (prof == 0)
+ return "\t\tNone\n";
+ if (prof & CEC_OP_FEAT_RC_SRC_HAS_DEV_ROOT_MENU)
+ s += "\t\tSource Has Device Root Menu\n";
+ if (prof & CEC_OP_FEAT_RC_SRC_HAS_DEV_SETUP_MENU)
+ s += "\t\tSource Has Device Setup Menu\n";
+ if (prof & CEC_OP_FEAT_RC_SRC_HAS_MEDIA_CONTEXT_MENU)
+ s += "\t\tSource Has Contents Menu\n";
+ if (prof & CEC_OP_FEAT_RC_SRC_HAS_MEDIA_TOP_MENU)
+ s += "\t\tSource Has Media Top Menu\n";
+ if (prof & CEC_OP_FEAT_RC_SRC_HAS_MEDIA_CONTEXT_MENU)
+ s += "\t\tSource Has Media Context-Sensitive Menu\n";
+ return s;
+}
+
+std::string dev_feat2s(unsigned feat)
+{
+ std::string s;
+
+ feat &= 0x7e;
+ if (feat == 0)
+ return "\t\tNone\n";
+ if (feat & CEC_OP_FEAT_DEV_HAS_RECORD_TV_SCREEN)
+ s += "\t\tTV Supports <Record TV Screen>\n";
+ if (feat & CEC_OP_FEAT_DEV_HAS_SET_OSD_STRING)
+ s += "\t\tTV Supports <Set OSD String>\n";
+ if (feat & CEC_OP_FEAT_DEV_HAS_DECK_CONTROL)
+ s += "\t\tSupports Deck Control\n";
+ if (feat & CEC_OP_FEAT_DEV_HAS_SET_AUDIO_RATE)
+ s += "\t\tSource Supports <Set Audio Rate>\n";
+ if (feat & CEC_OP_FEAT_DEV_SINK_HAS_ARC_TX)
+ s += "\t\tSink Supports ARC Tx\n";
+ if (feat & CEC_OP_FEAT_DEV_SOURCE_HAS_ARC_RX)
+ s += "\t\tSource Supports ARC Rx\n";
+ return s;
+}
+
+const char *power_status2s(__u8 power_status)
+{
+ switch (power_status) {
+ case CEC_OP_POWER_STATUS_ON:
+ return "On";
+ case CEC_OP_POWER_STATUS_STANDBY:
+ return "Standby";
+ case CEC_OP_POWER_STATUS_TO_ON:
+ return "In transition Standby to On";
+ case CEC_OP_POWER_STATUS_TO_STANDBY:
+ return "In transition On to Standby";
+ default:
+ return "Unknown";
+ }
+}
+
+static std::string audio_format_code2s(__u8 format_code)
+{
+ switch (format_code) {
+ case 0:
+ return "Reserved";
+ case SAD_FMT_CODE_LPCM:
+ return "L-PCM";
+ case SAD_FMT_CODE_AC3:
+ return "AC-3";
+ case SAD_FMT_CODE_MPEG1:
+ return "MPEG-1";
+ case SAD_FMT_CODE_MP3:
+ return "MP3";
+ case SAD_FMT_CODE_MPEG2:
+ return "MPEG2";
+ case SAD_FMT_CODE_AAC_LC:
+ return "AAC LC";
+ case SAD_FMT_CODE_DTS:
+ return "DTS";
+ case SAD_FMT_CODE_ATRAC:
+ return "ATRAC";
+ case SAD_FMT_CODE_ONE_BIT_AUDIO:
+ return "One Bit Audio";
+ case SAD_FMT_CODE_ENHANCED_AC3:
+ return "Enhanced AC-3";
+ case SAD_FMT_CODE_DTS_HD:
+ return "DTS-HD";
+ case SAD_FMT_CODE_MAT:
+ return "MAT";
+ case SAD_FMT_CODE_DST:
+ return "DST";
+ case SAD_FMT_CODE_WMA_PRO:
+ return "WMA Pro";
+ case SAD_FMT_CODE_EXTENDED:
+ return "Extended";
+ default:
+ return "Illegal";
+ };
+}
+
+std::string extension_type_code2s(__u8 type_code)
+{
+ switch (type_code) {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ return "Not in use";
+ case SAD_EXT_TYPE_MPEG4_HE_AAC:
+ return "MPEG-4 HE AAC";
+ case SAD_EXT_TYPE_MPEG4_HE_AACv2:
+ return "MPEG-4 HE AAC v2";
+ case SAD_EXT_TYPE_MPEG4_AAC_LC:
+ return "MPEG-4 AAC LC";
+ case SAD_EXT_TYPE_DRA:
+ return "DRA";
+ case SAD_EXT_TYPE_MPEG4_HE_AAC_SURROUND:
+ return "MPEG-4 HE AAC + MPEG Surround";
+ case SAD_EXT_TYPE_MPEG4_AAC_LC_SURROUND:
+ return "MPEG-4 AAC LC + MPEG Surround";
+ case SAD_EXT_TYPE_MPEG_H_3D_AUDIO:
+ return "MPEG-H 3D Audio";
+ case SAD_EXT_TYPE_AC_4:
+ return "AC-4";
+ case SAD_EXT_TYPE_LPCM_3D_AUDIO:
+ return "L-PCM 3D Audio";
+ default:
+ return "Reserved";
+ }
+}
+
+std::string short_audio_desc2s(const struct short_audio_desc &sad)
+{
+ std::stringstream oss;
+
+ if (sad.format_code != SAD_FMT_CODE_EXTENDED)
+ oss << audio_format_code2s(sad.format_code);
+ else
+ oss << extension_type_code2s(sad.extension_type_code);
+ oss << ", " << (int)sad.num_channels << " channels";
+
+ oss << ", sampling rates (kHz): ";
+ if (sad.sample_freq_mask & SAD_SAMPLE_FREQ_MASK_32)
+ oss << "32,";
+ if (sad.sample_freq_mask & SAD_SAMPLE_FREQ_MASK_44_1)
+ oss << "44.1,";
+ if (sad.sample_freq_mask & SAD_SAMPLE_FREQ_MASK_48)
+ oss << "48,";
+ if (sad.sample_freq_mask & SAD_SAMPLE_FREQ_MASK_88_2)
+ oss << "88.2,";
+ if (sad.sample_freq_mask & SAD_SAMPLE_FREQ_MASK_96)
+ oss << "96,";
+ if (sad.sample_freq_mask & SAD_SAMPLE_FREQ_MASK_176_4)
+ oss << "176.4,";
+ if (sad.sample_freq_mask & SAD_SAMPLE_FREQ_MASK_192)
+ oss << "192,";
+ if (sad.sample_freq_mask & (1 << 7))
+ oss << "Reserved,";
+ oss << "\b \b";
+
+ if (sad.format_code == SAD_FMT_CODE_LPCM ||
+ (sad.format_code == SAD_FMT_CODE_EXTENDED &&
+ sad.extension_type_code == SAD_EXT_TYPE_LPCM_3D_AUDIO)) {
+ oss << ", bit depth: ";
+ if (sad.bit_depth_mask & SAD_BIT_DEPTH_MASK_16)
+ oss << "16,";
+ if (sad.bit_depth_mask & SAD_BIT_DEPTH_MASK_20)
+ oss << "20,";
+ if (sad.bit_depth_mask & SAD_BIT_DEPTH_MASK_24)
+ oss << "24,";
+ oss << "\b \b";
+ } else if (sad.format_code >= 2 && sad.format_code <= 8)
+ oss << " max bitrate (kbit/s): " << 8 * sad.max_bitrate;
+
+ if (sad.format_code == SAD_FMT_CODE_EXTENDED) {
+ switch (sad.extension_type_code) {
+ case 4:
+ case 5:
+ case 6:
+ case 8:
+ case 10:
+ oss << ", frame length: ";
+ if (sad.frame_length_mask & SAD_FRAME_LENGTH_MASK_960)
+ oss << "960,";
+ if (sad.frame_length_mask & SAD_FRAME_LENGTH_MASK_1024)
+ oss << "1024,";
+ oss << "\b";
+ break;
+ }
+
+ if (sad.extension_type_code == 8 || sad.extension_type_code == 10)
+ oss << ", MPS";
+ }
+
+ return oss.str();
+}
+
+void sad_decode(struct short_audio_desc *sad, __u32 descriptor)
+{
+ __u8 b1 = (descriptor >> 16) & 0xff;
+ __u8 b2 = (descriptor >> 8) & 0xff;
+ __u8 b3 = descriptor & 0xff;
+
+ sad->num_channels = (b1 & 0x07) + 1;
+ sad->format_code = (b1 >> 3) & 0x0f;
+ sad->sample_freq_mask = b2;
+
+ switch (sad->format_code) {
+ case SAD_FMT_CODE_LPCM:
+ sad->bit_depth_mask = b3 & 0x07;
+ break;
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ sad->max_bitrate = b3;
+ break;
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ sad->format_dependent = b3;
+ break;
+ case SAD_FMT_CODE_WMA_PRO:
+ sad->wma_profile = b3 & 0x03;
+ break;
+ case SAD_FMT_CODE_EXTENDED:
+ sad->extension_type_code = (b3 >> 3) & 0x1f;
+
+ switch (sad->extension_type_code) {
+ case 4:
+ case 5:
+ case 6:
+ sad->frame_length_mask = (b3 >> 1) & 0x03;
+ break;
+ case 8:
+ case 10:
+ sad->frame_length_mask = (b3 >> 1) & 0x03;
+ sad->mps = b3 & 1;
+ break;
+ case SAD_EXT_TYPE_MPEG_H_3D_AUDIO:
+ case SAD_EXT_TYPE_AC_4:
+ sad->format_dependent = b3 & 0x07;
+ case SAD_EXT_TYPE_LPCM_3D_AUDIO:
+ sad->bit_depth_mask = b3 & 0x07;
+ break;
+ }
+ break;
+ }
+}
+
+const char *bcast_system2s(__u8 bcast_system)
+{
+ switch (bcast_system) {
+ case CEC_OP_BCAST_SYSTEM_PAL_BG:
+ return "PAL B/G";
+ case CEC_OP_BCAST_SYSTEM_SECAM_LQ:
+ return "SECAM L'";
+ case CEC_OP_BCAST_SYSTEM_PAL_M:
+ return "PAL M";
+ case CEC_OP_BCAST_SYSTEM_NTSC_M:
+ return "NTSC M";
+ case CEC_OP_BCAST_SYSTEM_PAL_I:
+ return "PAL I";
+ case CEC_OP_BCAST_SYSTEM_SECAM_DK:
+ return "SECAM DK";
+ case CEC_OP_BCAST_SYSTEM_SECAM_BG:
+ return "SECAM B/G";
+ case CEC_OP_BCAST_SYSTEM_SECAM_L:
+ return "SECAM L";
+ case CEC_OP_BCAST_SYSTEM_PAL_DK:
+ return "PAL DK";
+ case 31:
+ return "Other System";
+ default:
+ return "Future use";
+ }
+}
+
+const char *dig_bcast_system2s(__u8 bcast_system)
+{
+ switch (bcast_system) {
+ case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ARIB_GEN:
+ return "ARIB generic";
+ case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_GEN:
+ return "ATSC generic";
+ case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_DVB_GEN:
+ return "DVB generic";
+ case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ARIB_BS:
+ return "ARIB-BS";
+ case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ARIB_CS:
+ return "ARIB-CS";
+ case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ARIB_T:
+ return "ARIB-T";
+ case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_CABLE:
+ return "ATSC Cable";
+ case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_SAT:
+ return "ATSC Satellite";
+ case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_T:
+ return "ATSC Terrestrial";
+ case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_DVB_C:
+ return "DVB-C";
+ case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_DVB_S:
+ return "DVB-S";
+ case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_DVB_S2:
+ return "DVB S2";
+ case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_DVB_T:
+ return "DVB-T";
+ default:
+ return "Invalid";
+ }
+}
+
+const char *hec_func_state2s(__u8 hfs)
+{
+ switch (hfs) {
+ case CEC_OP_HEC_FUNC_STATE_NOT_SUPPORTED:
+ return "HEC Not Supported";
+ case CEC_OP_HEC_FUNC_STATE_INACTIVE:
+ return "HEC Inactive";
+ case CEC_OP_HEC_FUNC_STATE_ACTIVE:
+ return "HEC Active";
+ case CEC_OP_HEC_FUNC_STATE_ACTIVATION_FIELD:
+ return "HEC Activation Field";
+ default:
+ return "Unknown";
+ }
+}
+
+const char *host_func_state2s(__u8 hfs)
+{
+ switch (hfs) {
+ case CEC_OP_HOST_FUNC_STATE_NOT_SUPPORTED:
+ return "Host Not Supported";
+ case CEC_OP_HOST_FUNC_STATE_INACTIVE:
+ return "Host Inactive";
+ case CEC_OP_HOST_FUNC_STATE_ACTIVE:
+ return "Host Active";
+ default:
+ return "Unknown";
+ }
+}
+
+const char *enc_func_state2s(__u8 efs)
+{
+ switch (efs) {
+ case CEC_OP_ENC_FUNC_STATE_EXT_CON_NOT_SUPPORTED:
+ return "Ext Con Not Supported";
+ case CEC_OP_ENC_FUNC_STATE_EXT_CON_INACTIVE:
+ return "Ext Con Inactive";
+ case CEC_OP_ENC_FUNC_STATE_EXT_CON_ACTIVE:
+ return "Ext Con Active";
+ default:
+ return "Unknown";
+ }
+}
+
+const char *cdc_errcode2s(__u8 cdc_errcode)
+{
+ switch (cdc_errcode) {
+ case CEC_OP_CDC_ERROR_CODE_NONE:
+ return "No error";
+ case CEC_OP_CDC_ERROR_CODE_CAP_UNSUPPORTED:
+ return "Initiator does not have requested capability";
+ case CEC_OP_CDC_ERROR_CODE_WRONG_STATE:
+ return "Initiator is in wrong state";
+ case CEC_OP_CDC_ERROR_CODE_OTHER:
+ return "Other error";
+ default:
+ return "Unknown";
+ }
+}
+
+std::string opcode2s(const struct cec_msg *msg)
+{
+ std::stringstream oss;
+ __u8 opcode = msg->msg[2];
+
+ if (opcode == CEC_MSG_CDC_MESSAGE) {
+ __u8 cdc_opcode = msg->msg[4];
+
+ for (unsigned i = 0; i < ARRAY_SIZE(cdcmsgtable); i++) {
+ if (cdcmsgtable[i].opcode == cdc_opcode)
+ return cdcmsgtable[i].name;
+ }
+ oss << "CDC: 0x" << std::hex << cdc_opcode;
+ return oss.str();
+ }
+
+ for (unsigned i = 0; i < ARRAY_SIZE(msgtable); i++) {
+ if (msgtable[i].opcode == opcode)
+ return msgtable[i].name;
+ }
+ oss << "0x" << std::hex << opcode;
+ return oss.str();
+}
+
+int cec_named_ioctl(struct node *node, const char *name,
+ unsigned long int request, void *parm)
+{
+ int retval;
+ int e;
+ struct cec_msg *msg = (struct cec_msg *)parm;
+ __u8 opcode = 0;
+
+ if (request == CEC_TRANSMIT)
+ opcode = cec_msg_opcode(msg);
+
+ retval = ioctl(node->fd, request, parm);
+
+ e = retval == 0 ? 0 : errno;
+ if (options[OptTrace])
+ printf("\t\t%s returned %d (%s)\n",
+ name, retval, strerror(e));
+
+ if (retval < 0)
+ app_result = -1;
+
+ if (!retval) {
+ /* TODO: The logic here might need to be re-evaluated.
+
+ Currently a message is registered as recognized if
+ - We receive a reply that is not Feature Abort with
+ [Unrecognized Opcode] or [Undetermined]
+ - We did not receive a reply, but also did not
+ expect one (other than Feature Abort)
+ - We manually receive (CEC_RECEIVE) and get a Feature Abort
+ with reason different than [Unrecognized Opcode] or
+ [Undetermined] */
+ if (request == CEC_TRANSMIT && msg->timeout > 0 &&
+ cec_msg_initiator(msg) != CEC_LOG_ADDR_UNREGISTERED &&
+ cec_msg_destination(msg) != CEC_LOG_ADDR_BROADCAST &&
+ (msg->tx_status & CEC_TX_STATUS_OK)) {
+ if (msg->rx_status & CEC_RX_STATUS_OK) {
+ __u8 la = cec_msg_initiator(msg);
+
+ if (cec_msg_status_is_abort(msg) &&
+ (abort_reason(msg) == CEC_OP_ABORT_UNRECOGNIZED_OP ||
+ abort_reason(msg) == CEC_OP_ABORT_UNDETERMINED))
+ node->remote[la].unrecognized_op[opcode] = true;
+ else
+ node->remote[la].recognized_op[opcode] = true;
+ }
+ else {
+ __u8 la = cec_msg_destination(msg);
+
+ if (msg->reply > 0)
+ node->remote[la].unrecognized_op[opcode] = true;
+ else
+ node->remote[la].recognized_op[opcode] = true;
+ }
+ }
+
+ if (request == CEC_RECEIVE &&
+ cec_msg_initiator(msg) != CEC_LOG_ADDR_UNREGISTERED &&
+ cec_msg_status_is_abort(msg)) {
+ __u8 la = cec_msg_initiator(msg);
+ __u8 abort_msg = msg->msg[2];
+
+ if (abort_reason(msg) == CEC_OP_ABORT_UNRECOGNIZED_OP ||
+ abort_reason(msg) == CEC_OP_ABORT_UNDETERMINED)
+ node->remote[la].unrecognized_op[abort_msg] = true;
+ else
+ node->remote[la].recognized_op[abort_msg] = true;
+ }
+ }
+
+ return retval == -1 ? e : (retval ? -1 : 0);
+}
+
+const char *ok(int res)
+{
+ static char buf[100];
+
+ if (res == NOTSUPPORTED) {
+ strcpy(buf, "OK (Not Supported)");
+ res = 0;
+ } else {
+ strcpy(buf, "OK");
+ }
+ tests_total++;
+ if (res) {
+ app_result = res;
+ sprintf(buf, "FAIL");
+ } else {
+ tests_ok++;
+ }
+ return buf;
+}
+
+int check_0(const void *p, int len)
+{
+ const __u8 *q = (const __u8 *)p;
+
+ while (len--)
+ if (*q++)
+ return 1;
+ return 0;
+}
+
+static int poll_remote_devs(struct node *node)
+{
+ node->remote_la_mask = 0;
+ if (!(node->caps & CEC_CAP_TRANSMIT))
+ return 0;
+
+ for (unsigned i = 0; i < 15; i++) {
+ struct cec_msg msg;
+
+ cec_msg_init(&msg, 0xf, i);
+
+ fail_on_test(doioctl(node, CEC_TRANSMIT, &msg));
+ if (msg.tx_status & CEC_TX_STATUS_OK)
+ node->remote_la_mask |= 1 << i;
+ }
+ return 0;
+}
+
+static void topology_probe_device(struct node *node, unsigned i, unsigned la)
+{
+ struct cec_msg msg = { };
+
+ printf("\tSystem Information for device %d (%s) from device %d (%s):\n",
+ i, la2s(i), la, la2s(la));
+
+ cec_msg_init(&msg, la, i);
+ cec_msg_get_cec_version(&msg, true);
+ printf("\t\tCEC Version : ");
+ if (!transmit_timeout(node, &msg) || timed_out_or_abort(&msg)) {
+ printf("%s\n", status2s(msg).c_str());
+ node->remote[i].cec_version = CEC_OP_CEC_VERSION_1_4;
+ }
+ /* This needs to be kept in sync with newer CEC versions */
+ else {
+ node->remote[i].cec_version = msg.msg[2];
+ if (msg.msg[2] < CEC_OP_CEC_VERSION_1_3A) {
+ printf("< 1.3a (%x)\n", msg.msg[2]);
+ warn("The reported CEC version is less than 1.3a. The device will be tested as a CEC 1.3a compliant device.\n");
+ }
+ else if (msg.msg[2] > CEC_OP_CEC_VERSION_2_0) {
+ printf("> 2.0 (%x)\n", msg.msg[2]);
+ warn("The reported CEC version is greater than 2.0. The device will be tested as a CEC 2.0 compliant device.\n");
+ }
+ else
+ printf("%s\n", version2s(msg.msg[2]));
+ }
+
+ cec_msg_init(&msg, la, i);
+ cec_msg_give_physical_addr(&msg, true);
+ printf("\t\tPhysical Address : ");
+ if (!transmit_timeout(node, &msg) || timed_out_or_abort(&msg)) {
+ printf("%s\n", status2s(msg).c_str());
+ node->remote[i].phys_addr = CEC_PHYS_ADDR_INVALID;
+ }
+ else {
+ node->remote[i].phys_addr = (msg.msg[2] << 8) | msg.msg[3];
+ printf("%x.%x.%x.%x\n",
+ cec_phys_addr_exp(node->remote[i].phys_addr));
+ node->remote[i].prim_type = msg.msg[4];
+ printf("\t\tPrimary Device Type : %s\n",
+ prim_type2s(node->remote[i].prim_type));
+ }
+
+ cec_msg_init(&msg, la, i);
+ cec_msg_give_device_vendor_id(&msg, true);
+ printf("\t\tVendor ID : ");
+ if (!transmit_timeout(node, &msg) || timed_out_or_abort(&msg))
+ printf("%s\n", status2s(msg).c_str());
+ else
+ printf("0x%02x%02x%02x\n",
+ msg.msg[2], msg.msg[3], msg.msg[4]);
+
+ cec_msg_init(&msg, la, i);
+ cec_msg_give_osd_name(&msg, true);
+ printf("\t\tOSD Name : ");
+ if (!transmit_timeout(node, &msg) || timed_out_or_abort(&msg))
+ printf("%s\n", status2s(msg).c_str());
+ else {
+ char osd_name[15];
+
+ cec_ops_set_osd_name(&msg, osd_name);
+ printf("'%s'\n", osd_name);
+ }
+
+ cec_msg_init(&msg, la, i);
+ cec_msg_get_menu_language(&msg, true);
+ if (transmit_timeout(node, &msg) && !timed_out_or_abort(&msg)) {
+ char language[4];
+
+ cec_ops_set_menu_language(&msg, language);
+ language[3] = 0;
+ printf("\t\tMenu Language : %s\n", language);
+ }
+
+ cec_msg_init(&msg, la, i);
+ cec_msg_give_device_power_status(&msg, true);
+ printf("\t\tPower Status : ");
+ if (!transmit_timeout(node, &msg) || timed_out_or_abort(&msg))
+ printf("%s\n", status2s(msg).c_str());
+ else {
+ __u8 pwr;
+
+ cec_ops_report_power_status(&msg, &pwr);
+ if (pwr >= 4)
+ printf("Invalid\n");
+ else {
+ node->remote[i].has_power_status = true;
+ node->remote[i].in_standby = pwr != CEC_OP_POWER_STATUS_ON;
+ printf("%s\n", power_status2s(pwr));
+ }
+ }
+
+ if (node->remote[i].cec_version < CEC_OP_CEC_VERSION_2_0)
+ return;
+ cec_msg_init(&msg, la, i);
+ cec_msg_give_features(&msg, true);
+ if (transmit_timeout(node, &msg) && !timed_out_or_abort(&msg)) {
+ /* RC Profile and Device Features are assumed to be 1 byte. As of CEC 2.0 only
+ 1 byte is used, but this might be extended in future versions. */
+ __u8 cec_version, all_device_types;
+ const __u8 *rc_profile, *dev_features;
+
+ cec_ops_report_features(&msg, &cec_version, &all_device_types,
+ &rc_profile, &dev_features);
+ if (rc_profile == NULL || dev_features == NULL)
+ return;
+ node->remote[i].rc_profile = *rc_profile;
+ node->remote[i].dev_features = *dev_features;
+ node->remote[i].has_arc_rx =
+ (*dev_features & CEC_OP_FEAT_DEV_SOURCE_HAS_ARC_RX) != 0;
+ node->remote[i].has_arc_tx =
+ (*dev_features & CEC_OP_FEAT_DEV_SINK_HAS_ARC_TX) != 0;
+ node->remote[i].has_aud_rate =
+ (*dev_features & CEC_OP_FEAT_DEV_HAS_SET_AUDIO_RATE) != 0;
+ node->remote[i].has_deck_ctl =
+ (*dev_features & CEC_OP_FEAT_DEV_HAS_DECK_CONTROL) != 0;
+ node->remote[i].has_rec_tv =
+ (*dev_features & CEC_OP_FEAT_DEV_HAS_RECORD_TV_SCREEN) != 0;
+ }
+}
+
+int main(int argc, char **argv)
+{
+ const char *device = "/dev/cec0"; /* -d device */
+ char short_options[26 * 2 * 2 + 1];
+ int remote_la = -1;
+ bool test_remote = false;
+ unsigned test_tags = 0;
+ int idx = 0;
+ int fd = -1;
+ int ch;
+ int i;
+
+ for (i = 0; long_options[i].name; i++) {
+ if (!isalpha(long_options[i].val))
+ continue;
+ short_options[idx++] = long_options[i].val;
+ if (long_options[i].has_arg == required_argument) {
+ short_options[idx++] = ':';
+ } else if (long_options[i].has_arg == optional_argument) {
+ short_options[idx++] = ':';
+ short_options[idx++] = ':';
+ }
+ }
+ while (1) {
+ int option_index = 0;
+
+ short_options[idx] = 0;
+ ch = getopt_long(argc, argv, short_options,
+ long_options, &option_index);
+ if (ch == -1)
+ break;
+
+ options[(int)ch] = 1;
+ switch (ch) {
+ case OptHelp:
+ usage();
+ return 0;
+ case OptSetDevice:
+ device = optarg;
+ if (device[0] >= '0' && device[0] <= '9' && strlen(device) <= 3) {
+ static char newdev[20];
+
+ sprintf(newdev, "/dev/cec%s", device);
+ device = newdev;
+ }
+ break;
+ case OptNoWarnings:
+ show_warnings = false;
+ break;
+ case OptRemote:
+ if (optarg) {
+ remote_la = strtoul(optarg, NULL, 0);
+ if (remote_la < 0 || remote_la > 15) {
+ fprintf(stderr, "--test: invalid remote logical address\n");
+ usage();
+ return 1;
+ }
+ }
+ test_remote = true;
+ break;
+ case OptVerbose:
+ show_info = true;
+ break;
+ case ':':
+ fprintf(stderr, "Option '%s' requires a value\n",
+ argv[optind]);
+ usage();
+ return 1;
+ case '?':
+ if (argv[optind])
+ fprintf(stderr, "Unknown argument '%s'\n", argv[optind]);
+ usage();
+ return 1;
+ }
+ }
+ if (optind < argc) {
+ printf("unknown arguments: ");
+ while (optind < argc)
+ printf("%s ", argv[optind++]);
+ printf("\n");
+ usage();
+ return 1;
+ }
+
+ if ((fd = open(device, O_RDWR)) < 0) {
+ fprintf(stderr, "Failed to open %s: %s\n", device,
+ strerror(errno));
+ exit(1);
+ }
+
+ struct node node = { };
+ struct cec_caps caps = { };
+
+ node.fd = fd;
+ node.device = device;
+ doioctl(&node, CEC_ADAP_G_CAPS, &caps);
+ node.caps = caps.capabilities;
+ node.available_log_addrs = caps.available_log_addrs;
+
+ if (options[OptTestAudioRateControl])
+ test_tags |= TAG_AUDIO_RATE_CONTROL;
+ if (options[OptTestARCControl])
+ test_tags |= TAG_ARC_CONTROL;
+ if (options[OptTestCapDiscoveryControl])
+ test_tags |= TAG_CAP_DISCOVERY_CONTROL;
+ if (options[OptTestDeckControl])
+ test_tags |= TAG_DECK_CONTROL;
+ if (options[OptTestDeviceMenuControl])
+ test_tags |= TAG_DEVICE_MENU_CONTROL;
+ if (options[OptTestDeviceOSDTransfer])
+ test_tags |= TAG_DEVICE_OSD_TRANSFER;
+ if (options[OptTestDynamicAutoLipsync])
+ test_tags |= TAG_DYNAMIC_AUTO_LIPSYNC;
+ if (options[OptTestOSDDisplay])
+ test_tags |= TAG_OSD_DISPLAY;
+ if (options[OptTestOneTouchPlay])
+ test_tags |= TAG_ONE_TOUCH_PLAY;
+ if (options[OptTestOneTouchRecord])
+ test_tags |= TAG_ONE_TOUCH_RECORD;
+ if (options[OptTestPowerStatus])
+ test_tags |= TAG_POWER_STATUS;
+ if (options[OptTestRemoteControlPassthrough])
+ test_tags |= TAG_REMOTE_CONTROL_PASSTHROUGH;
+ if (options[OptTestRoutingControl])
+ test_tags |= TAG_ROUTING_CONTROL;
+ if (options[OptTestSystemAudioControl])
+ test_tags |= TAG_SYSTEM_AUDIO_CONTROL;
+ if (options[OptTestSystemInformation])
+ test_tags |= TAG_SYSTEM_INFORMATION;
+ if (options[OptTestTimerProgramming])
+ test_tags |= TAG_TIMER_PROGRAMMING;
+ if (options[OptTestTunerControl])
+ test_tags |= TAG_TUNER_CONTROL;
+ if (options[OptTestVendorSpecificCommands])
+ test_tags |= TAG_VENDOR_SPECIFIC_COMMANDS;
+ /* When code is added to the Standby/Resume test for waking up
+ other devices than TVs, the necessary tags should be added
+ here (probably Routing Control and/or RC Passthrough) */
+ if (options[OptTestStandbyResume])
+ test_tags |= TAG_POWER_STATUS | TAG_STANDBY_RESUME;
+
+ if (!test_tags && !options[OptTestCore])
+ test_tags = TAG_ALL;
+
+ if (options[OptInteractive])
+ test_tags |= TAG_INTERACTIVE;
+
+ printf("Driver Info:\n");
+ printf("\tDriver Name : %s\n", caps.driver);
+ printf("\tAdapter Name : %s\n", caps.name);
+ printf("\tCapabilities : 0x%08x\n", caps.capabilities);
+ printf("%s", caps2s(caps.capabilities).c_str());
+ printf("\tDriver version : %d.%d.%d\n",
+ caps.version >> 16,
+ (caps.version >> 8) & 0xff,
+ caps.version & 0xff);
+ printf("\tAvailable Logical Addresses: %u\n",
+ caps.available_log_addrs);
+
+ node.phys_addr = CEC_PHYS_ADDR_INVALID;
+ doioctl(&node, CEC_ADAP_G_PHYS_ADDR, &node.phys_addr);
+
+ printf("\tPhysical Address : %x.%x.%x.%x\n",
+ cec_phys_addr_exp(node.phys_addr));
+
+ struct cec_log_addrs laddrs = { };
+ doioctl(&node, CEC_ADAP_G_LOG_ADDRS, &laddrs);
+
+ printf("\tLogical Address Mask : 0x%04x\n", laddrs.log_addr_mask);
+ printf("\tCEC Version : %s\n", version2s(laddrs.cec_version));
+ if (laddrs.vendor_id != CEC_VENDOR_ID_NONE)
+ printf("\tVendor ID : 0x%06x\n", laddrs.vendor_id);
+ printf("\tLogical Addresses : %u\n", laddrs.num_log_addrs);
+ for (unsigned i = 0; i < laddrs.num_log_addrs; i++) {
+ if (laddrs.log_addr[i] == CEC_LOG_ADDR_INVALID)
+ printf("\n\t Logical Address : Not Allocated\n");
+ else
+ printf("\n\t Logical Address : %d\n",
+ laddrs.log_addr[i]);
+ printf("\t Primary Device Type : %s\n",
+ prim_type2s(laddrs.primary_device_type[i]));
+ printf("\t Logical Address Type : %s\n",
+ la_type2s(laddrs.log_addr_type[i]));
+ if (laddrs.cec_version < CEC_OP_CEC_VERSION_2_0)
+ continue;
+ printf("\t All Device Types : %s\n",
+ all_dev_types2s(laddrs.all_device_types[i]).c_str());
+
+ bool is_dev_feat = false;
+ for (unsigned idx = 0; idx < sizeof(laddrs.features[0]); idx++) {
+ __u8 byte = laddrs.features[i][idx];
+
+ if (!is_dev_feat) {
+ if (byte & 0x40) {
+ printf("\t RC Source Profile :\n%s",
+ rc_src_prof2s(byte).c_str());
+ } else {
+ const char *s = "Reserved";
+
+ switch (byte & 0xf) {
+ case 0:
+ s = "None";
+ break;
+ case 2:
+ s = "RC Profile 1";
+ break;
+ case 6:
+ s = "RC Profile 2";
+ break;
+ case 10:
+ s = "RC Profile 3";
+ break;
+ case 14:
+ s = "RC Profile 4";
+ break;
+ }
+ printf("\t RC TV Profile : %s\n", s);
+ }
+ } else {
+ printf("\t Device Features :\n%s",
+ dev_feat2s(byte).c_str());
+ }
+ if (byte & CEC_OP_FEAT_EXT)
+ continue;
+ if (!is_dev_feat)
+ is_dev_feat = true;
+ else
+ break;
+ }
+ }
+
+ bool missing_pa = node.phys_addr == CEC_PHYS_ADDR_INVALID && (node.caps & CEC_CAP_PHYS_ADDR);
+ bool missing_la = laddrs.num_log_addrs == 0 && (node.caps & CEC_CAP_LOG_ADDRS);
+
+ if (missing_la || missing_pa)
+ printf("\n");
+ if (missing_pa)
+ fprintf(stderr, "FAIL: missing physical address, use cec-ctl to configure this\n");
+ if (missing_la)
+ fprintf(stderr, "FAIL: missing logical address(es), use cec-ctl to configure this\n");
+ if (missing_la || missing_pa)
+ exit(-1);
+
+ printf("\nCompliance test for device %s:\n\n", device);
+ printf(" The test results mean the following:\n"
+ " OK Supported correctly by the device.\n"
+ " OK (Not Supported) Not supported and not mandatory for the device.\n"
+ " OK (Presumed) Presumably supported. Manually check to confirm.\n"
+ " OK (Unexpected) Supported correctly but is not expected to be supported for this device.\n"
+ " OK (Refused) Supported by the device, but was refused.\n"
+ " FAIL Failed and was expected to be supported by this device.\n\n");
+
+ node.has_cec20 = laddrs.cec_version >= CEC_OP_CEC_VERSION_2_0;
+ node.num_log_addrs = laddrs.num_log_addrs;
+ memcpy(node.log_addr, laddrs.log_addr, laddrs.num_log_addrs);
+ node.adap_la_mask = laddrs.log_addr_mask;
+
+ printf("Find remote devices:\n");
+ printf("\tPolling: %s\n", ok(poll_remote_devs(&node)));
+
+ if (options[OptTestAdapter]) {
+ /* Required ioctls */
+
+ printf("\nCEC API:\n");
+ printf("\tCEC_ADAP_G_CAPS: %s\n", ok(testCap(&node)));
+ printf("\tCEC_DQEVENT: %s\n", ok(testDQEvent(&node)));
+ printf("\tCEC_ADAP_G/S_PHYS_ADDR: %s\n", ok(testAdapPhysAddr(&node)));
+ if (node.caps & CEC_CAP_PHYS_ADDR)
+ doioctl(&node, CEC_ADAP_S_PHYS_ADDR, &node.phys_addr);
+ printf("\tCEC_ADAP_G/S_LOG_ADDRS: %s\n", ok(testAdapLogAddrs(&node)));
+ fcntl(node.fd, F_SETFL, fcntl(node.fd, F_GETFL) & ~O_NONBLOCK);
+ if (node.caps & CEC_CAP_LOG_ADDRS) {
+ struct cec_log_addrs clear = { };
+
+ doioctl(&node, CEC_ADAP_S_LOG_ADDRS, &clear);
+ doioctl(&node, CEC_ADAP_S_LOG_ADDRS, &laddrs);
+ }
+ doioctl(&node, CEC_ADAP_G_LOG_ADDRS, &laddrs);
+ if (laddrs.log_addr_mask != node.adap_la_mask)
+ printf("\tNew Logical Address Mask : 0x%04x\n", laddrs.log_addr_mask);
+ // The LAs may have changed after these tests, so update these node fields
+ node.num_log_addrs = laddrs.num_log_addrs;
+ memcpy(node.log_addr, laddrs.log_addr, laddrs.num_log_addrs);
+ node.adap_la_mask = laddrs.log_addr_mask;
+
+ printf("\tCEC_TRANSMIT: %s\n", ok(testTransmit(&node)));
+ printf("\tCEC_RECEIVE: %s\n", ok(testReceive(&node)));
+ __u32 mode = CEC_MODE_INITIATOR;
+ doioctl(&node, CEC_S_MODE, &mode);
+ printf("\tCEC_TRANSMIT/RECEIVE (non-blocking): %s\n", ok(testNonBlocking(&node)));
+ fcntl(node.fd, F_SETFL, fcntl(node.fd, F_GETFL) & ~O_NONBLOCK);
+ doioctl(&node, CEC_S_MODE, &mode);
+
+ struct node node2 = node;
+
+ if ((node2.fd = open(device, O_RDWR)) < 0) {
+ fprintf(stderr, "Failed to open %s: %s\n", device,
+ strerror(errno));
+ exit(1);
+ }
+
+ printf("\tCEC_G/S_MODE: %s\n", ok(testModes(&node, &node2)));
+ close(node2.fd);
+ doioctl(&node, CEC_S_MODE, &mode);
+ printf("\tCEC_EVENT_LOST_MSGS: %s\n", ok(testLostMsgs(&node)));
+ fcntl(node.fd, F_SETFL, fcntl(node.fd, F_GETFL) & ~O_NONBLOCK);
+ }
+ printf("\n");
+
+ printf("Network topology:\n");
+ for (unsigned i = 0; i < 15; i++)
+ if (node.remote_la_mask & (1 << i))
+ topology_probe_device(&node, i, node.log_addr[0]);
+ printf("\n");
+
+ unsigned remote_la_mask = node.remote_la_mask;
+
+ if (remote_la >= 0)
+ remote_la_mask = 1 << remote_la;
+
+ if (test_remote) {
+ for (unsigned from = 0; from <= 15; from++) {
+ if (!(node.adap_la_mask & (1 << from)))
+ continue;
+ for (unsigned to = 0; to <= 15; to++)
+ if ((remote_la_mask & (1 << to)) &&
+ node.remote[to].phys_addr != CEC_PHYS_ADDR_INVALID)
+ testRemote(&node, from, to, test_tags, options[OptInteractive]);
+ }
+ }
+
+ /* Final test report */
+
+ close(fd);
+
+ printf("Total: %d, Succeeded: %d, Failed: %d, Warnings: %d\n",
+ tests_total, tests_ok, tests_total - tests_ok, warnings);
+ exit(app_result);
+}
diff --git a/utils/cec-compliance/cec-compliance.h b/utils/cec-compliance/cec-compliance.h
new file mode 100644
index 00000000..6ee74029
--- /dev/null
+++ b/utils/cec-compliance/cec-compliance.h
@@ -0,0 +1,459 @@
+/*
+ * CEC API compliance test tool.
+ *
+ * Copyright 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
+ *
+ * This program is free software; you may redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef _CEC_COMPLIANCE_H_
+#define _CEC_COMPLIANCE_H_
+
+#include <stdarg.h>
+#include <cerrno>
+#include <string>
+#include <linux/cec-funcs.h>
+
+#ifdef ANDROID
+#include <android-config.h>
+#else
+#include <config.h>
+#endif
+
+#define TAG_AUDIO_RATE_CONTROL 1
+#define TAG_ARC_CONTROL (1 << 1)
+#define TAG_CAP_DISCOVERY_CONTROL (1 << 2)
+#define TAG_DECK_CONTROL (1 << 3)
+#define TAG_DEVICE_MENU_CONTROL (1 << 4)
+#define TAG_DEVICE_OSD_TRANSFER (1 << 5)
+#define TAG_DYNAMIC_AUTO_LIPSYNC (1 << 6)
+#define TAG_OSD_DISPLAY (1 << 7)
+#define TAG_ONE_TOUCH_PLAY (1 << 8)
+#define TAG_ONE_TOUCH_RECORD (1 << 9)
+#define TAG_POWER_STATUS (1 << 10)
+#define TAG_REMOTE_CONTROL_PASSTHROUGH (1 << 11)
+#define TAG_ROUTING_CONTROL (1 << 12)
+#define TAG_SYSTEM_AUDIO_CONTROL (1 << 13)
+#define TAG_SYSTEM_INFORMATION (1 << 14)
+#define TAG_TIMER_PROGRAMMING (1 << 15)
+#define TAG_TUNER_CONTROL (1 << 16)
+#define TAG_VENDOR_SPECIFIC_COMMANDS (1 << 17)
+#define TAG_STANDBY_RESUME (1 << 18)
+
+#define TAG_INTERACTIVE (1 << 19)
+#define TAG_CORE 0
+#define TAG_ALL (~TAG_INTERACTIVE)
+
+#define NUM_TAGS 21
+
+struct short_audio_desc {
+ /* Byte 1 */
+ __u8 num_channels;
+ __u8 format_code;
+
+ /* Byte 2 */
+ __u8 sample_freq_mask;
+
+ /* Byte 3 */
+ union {
+ __u8 bit_depth_mask; // LPCM
+ __u8 max_bitrate; // Format codes 2-8
+ __u8 format_dependent; // Format codes 9-13
+ __u8 wma_profile; // WMA Pro
+ __u8 frame_length_mask; // Extension type codes 4-6, 8-10
+ };
+ __u8 mps; // Format codes 8-10
+ __u8 extension_type_code;
+};
+
+#define SAD_FMT_CODE_LPCM 1
+#define SAD_FMT_CODE_AC3 2
+#define SAD_FMT_CODE_MPEG1 3
+#define SAD_FMT_CODE_MP3 4
+#define SAD_FMT_CODE_MPEG2 5
+#define SAD_FMT_CODE_AAC_LC 6
+#define SAD_FMT_CODE_DTS 7
+#define SAD_FMT_CODE_ATRAC 8
+#define SAD_FMT_CODE_ONE_BIT_AUDIO 9
+#define SAD_FMT_CODE_ENHANCED_AC3 10
+#define SAD_FMT_CODE_DTS_HD 11
+#define SAD_FMT_CODE_MAT 12
+#define SAD_FMT_CODE_DST 13
+#define SAD_FMT_CODE_WMA_PRO 14
+#define SAD_FMT_CODE_EXTENDED 15
+
+#define SAD_BIT_DEPTH_MASK_16 1
+#define SAD_BIT_DEPTH_MASK_20 (1 << 1)
+#define SAD_BIT_DEPTH_MASK_24 (1 << 2)
+
+#define SAD_SAMPLE_FREQ_MASK_32 1
+#define SAD_SAMPLE_FREQ_MASK_44_1 (1 << 1)
+#define SAD_SAMPLE_FREQ_MASK_48 (1 << 2)
+#define SAD_SAMPLE_FREQ_MASK_88_2 (1 << 3)
+#define SAD_SAMPLE_FREQ_MASK_96 (1 << 4)
+#define SAD_SAMPLE_FREQ_MASK_176_4 (1 << 5)
+#define SAD_SAMPLE_FREQ_MASK_192 (1 << 6)
+
+#define SAD_FRAME_LENGTH_MASK_960 1
+#define SAD_FRAME_LENGTH_MASK_1024 (1 << 1)
+
+#define SAD_EXT_TYPE_MPEG4_HE_AAC 4
+#define SAD_EXT_TYPE_MPEG4_HE_AACv2 5
+#define SAD_EXT_TYPE_MPEG4_AAC_LC 6
+#define SAD_EXT_TYPE_DRA 7
+#define SAD_EXT_TYPE_MPEG4_HE_AAC_SURROUND 8
+#define SAD_EXT_TYPE_MPEG4_AAC_LC_SURROUND 10
+#define SAD_EXT_TYPE_MPEG_H_3D_AUDIO 11
+#define SAD_EXT_TYPE_AC_4 12
+#define SAD_EXT_TYPE_LPCM_3D_AUDIO 13
+
+extern bool show_info;
+extern bool show_warnings;
+extern unsigned warnings;
+
+struct remote {
+ bool recognized_op[256];
+ bool unrecognized_op[256];
+ unsigned prim_type;
+ __u16 phys_addr;
+ __u8 cec_version;
+ __u8 rc_profile;
+ __u8 dev_features;
+ bool has_osd;
+ bool has_power_status;
+ bool has_image_view_on;
+ bool has_text_view_on;
+ bool in_standby;
+ bool has_remote_control_passthrough;
+ bool has_arc_rx;
+ bool has_arc_tx;
+ bool arc_initiated;
+ bool has_sys_audio_mode_req;
+ bool has_set_sys_audio_mode;
+ bool has_sad;
+ __u8 supp_format_id;
+ __u8 supp_format_code;
+ __u8 volume;
+ __u8 mute;
+ bool has_aud_rate;
+ bool has_deck_ctl;
+ __u8 bcast_sys;
+ __u8 dig_bcast_sys;
+ bool has_rec_tv;
+ bool has_cdc;
+};
+
+struct node {
+ int fd;
+ const char *device;
+ bool has_cec20;
+ bool test_cec20;
+ unsigned caps;
+ unsigned available_log_addrs;
+ unsigned num_log_addrs;
+ unsigned adap_la_mask;
+ __u8 log_addr[CEC_MAX_LOG_ADDRS];
+ unsigned remote_la_mask;
+ struct remote remote[16];
+ __u16 phys_addr;
+};
+
+struct remote_subtest {
+ const char *name;
+ const __u16 la_mask;
+ int (*const test_fn)(struct node *node, unsigned me, unsigned la, bool interactive);
+};
+
+#define PRESUMED_OK 1
+#define FAIL 2
+#define FAIL_CRITICAL 3
+#define NOTSUPPORTED 4
+#define NOTAPPLICABLE 5
+#define REFUSED 6
+
+#define CEC_LOG_ADDR_MASK_ALL 0xffff
+
+#define ARRAY_SIZE(a) \
+ (sizeof(a) / sizeof(*a))
+
+#define cec_phys_addr_exp(pa) \
+ ((pa) >> 12), ((pa) >> 8) & 0xf, ((pa) >> 4) & 0xf, (pa) & 0xf
+
+#define info(fmt, args...) \
+ do { \
+ if (show_info) \
+ printf("\t\tinfo: " fmt, ##args); \
+ } while (0)
+
+#define announce(fmt, args...) \
+ do { \
+ printf("\t\t>>> " fmt "\n", ##args); \
+ } while (0)
+
+#define interactive_info(block, fmt, args...) \
+ do { \
+ if (interactive) { \
+ printf("\t\t>>> " fmt "\n", ##args); \
+ if (block) { \
+ printf("\t\t>>> Press ENTER to proceed.\n"); \
+ getchar(); \
+ } \
+ } \
+ } while(0)
+
+#define warn(fmt, args...) \
+ do { \
+ warnings++; \
+ if (show_warnings) \
+ printf("\t\twarn: %s(%d): " fmt, __FILE__, __LINE__, ##args); \
+ } while (0)
+
+#define warn_once(fmt, args...) \
+ do { \
+ static bool show; \
+ \
+ if (!show) { \
+ show = true; \
+ warnings++; \
+ if (show_warnings) \
+ printf("\t\twarn: %s(%d): " fmt, \
+ __FILE__, __LINE__, ##args); \
+ } \
+ } while (0)
+
+#define fail(fmt, args...) \
+({ \
+ printf("\t\tfail: %s(%d): " fmt, __FILE__, __LINE__, ##args); \
+ FAIL; \
+})
+
+#define fail_on_test(test) \
+ do { \
+ if (test) \
+ return fail("%s\n", #test); \
+ } while (0)
+
+#define fail_on_test_v2(version, test) fail_on_test(version >= CEC_OP_CEC_VERSION_2_0 && (test))
+
+#define fail_on_test_v2_warn(version, test) \
+ do { \
+ if (test) { \
+ if (version >= CEC_OP_CEC_VERSION_2_0) \
+ return fail("%s\n", #test); \
+ else \
+ warn("fails in CEC 2.0: %s\n", #test); \
+ } \
+ } while(0)
+
+static inline char get_yn()
+{
+ char c;
+
+ while ((c = tolower(getchar())) != 'y' && c != 'n');
+ getchar();
+ return c;
+}
+
+static inline bool question(const char* prompt)
+{
+ printf("\t\t>>> %s (y/n) ", prompt);
+ return get_yn() == 'y';
+}
+
+int cec_named_ioctl(struct node *node, const char *name,
+ unsigned long int request, void *parm);
+
+#define doioctl(n, r, p) cec_named_ioctl(n, #r, r, p)
+
+#define cec_phys_addr_exp(pa) \
+ ((pa) >> 12), ((pa) >> 8) & 0xf, ((pa) >> 4) & 0xf, (pa) & 0xf
+
+std::string opcode2s(const struct cec_msg *msg);
+
+static inline bool is_tv(unsigned la, unsigned prim_type)
+{
+ return cec_has_tv(1 << la) ||
+ (cec_has_specific(1 << la) && prim_type == CEC_OP_PRIM_DEVTYPE_TV);
+}
+
+static inline bool cec_msg_status_is_abort(const struct cec_msg *msg)
+{
+ return msg->rx_status & CEC_RX_STATUS_FEATURE_ABORT;
+}
+
+static inline __u8 abort_reason(const struct cec_msg *msg)
+{
+ return msg->msg[3];
+}
+
+static inline bool unrecognized_op(const struct cec_msg *msg)
+{
+ if (!cec_msg_status_is_abort(msg))
+ return false;
+ if (abort_reason(msg) == CEC_OP_ABORT_UNRECOGNIZED_OP)
+ return true;
+ if (abort_reason(msg) == CEC_OP_ABORT_UNDETERMINED) {
+ warn("Opcode %x was undetermined and is treated as not supported.\n", msg->msg[2]);
+ return true;
+ }
+ return false;
+}
+
+static inline bool refused(const struct cec_msg *msg)
+{
+ return cec_msg_status_is_abort(msg) && abort_reason(msg) == CEC_OP_ABORT_REFUSED;
+}
+
+static inline bool timed_out(const struct cec_msg *msg)
+{
+ return msg->rx_status & CEC_RX_STATUS_TIMEOUT;
+}
+
+static inline bool timed_out_or_abort(const struct cec_msg *msg)
+{
+ return timed_out(msg) || cec_msg_status_is_abort(msg);
+}
+
+static inline unsigned response_time_ms(const struct cec_msg *msg)
+{
+ return (msg->rx_ts - msg->tx_ts) / 1000000;
+}
+
+static inline bool transmit_timeout(struct node *node, struct cec_msg *msg,
+ unsigned timeout = 2000)
+{
+ bool broadcast = cec_msg_is_broadcast(msg);
+
+ msg->timeout = timeout;
+ if (doioctl(node, CEC_TRANSMIT, msg) ||
+ !(msg->tx_status & CEC_TX_STATUS_OK))
+ return false;
+
+ if (((msg->rx_status & CEC_RX_STATUS_OK) || (msg->rx_status & CEC_RX_STATUS_FEATURE_ABORT))
+ && response_time_ms(msg) > 1000)
+ warn("Waited %4ums for reply.\n", response_time_ms(msg));
+
+ if (!cec_msg_status_is_abort(msg))
+ return true;
+
+ if (broadcast) {
+ fail("Received Feature Abort in reply to broadcast message\n");
+ return false;
+ }
+
+ const char *reason;
+
+ switch (abort_reason(msg)) {
+ case CEC_OP_ABORT_UNRECOGNIZED_OP:
+ case CEC_OP_ABORT_UNDETERMINED:
+ return true;
+ case CEC_OP_ABORT_INVALID_OP:
+ reason = "Invalid operand";
+ break;
+ case CEC_OP_ABORT_NO_SOURCE:
+ reason = "Cannot provide source";
+ break;
+ case CEC_OP_ABORT_REFUSED:
+ reason = "Refused";
+ break;
+ case CEC_OP_ABORT_INCORRECT_MODE:
+ reason = "Incorrect mode";
+ break;
+ default:
+ reason = "Unknown";
+ break;
+ }
+ info("Opcode %s was replied to with Feature Abort [%s]\n",
+ opcode2s(msg).c_str(), reason);
+
+ return true;
+}
+
+static inline bool transmit(struct node *node, struct cec_msg *msg)
+{
+ return transmit_timeout(node, msg, 0);
+}
+
+static inline unsigned get_ts_ms()
+{
+ struct timespec timespec;
+
+ clock_gettime(CLOCK_MONOTONIC, &timespec);
+ return timespec.tv_sec * 1000ull + timespec.tv_nsec / 1000000;
+}
+
+const char *ok(int res);
+const char *la2s(unsigned la);
+const char *la_type2s(unsigned type);
+const char *prim_type2s(unsigned type);
+const char *version2s(unsigned version);
+std::string status2s(const struct cec_msg &msg);
+std::string all_dev_types2s(unsigned types);
+std::string rc_src_prof2s(unsigned prof);
+std::string dev_feat2s(unsigned feat);
+const char *power_status2s(__u8 power_status);
+std::string short_audio_desc2s(const struct short_audio_desc &sad);
+void sad_decode(struct short_audio_desc *sad, __u32 descriptor);
+const char *bcast_system2s(__u8 bcast_system);
+const char *dig_bcast_system2s(__u8 bcast_system);
+const char *hec_func_state2s(__u8 hfs);
+const char *host_func_state2s(__u8 hfs);
+const char *enc_func_state2s(__u8 efs);
+const char *cdc_errcode2s(__u8 cdc_errcode);
+int check_0(const void *p, int len);
+
+// CEC adapter tests
+int testCap(struct node *node);
+int testDQEvent(struct node *node);
+int testAdapPhysAddr(struct node *node);
+int testAdapLogAddrs(struct node *node);
+int testTransmit(struct node *node);
+int testReceive(struct node *node);
+int testNonBlocking(struct node *node);
+int testModes(struct node *node, struct node *node2);
+int testLostMsgs(struct node *node);
+
+// CEC core tests
+int testCore(struct node *node);
+
+// CEC processing
+int testProcessing(struct node *node, unsigned me);
+
+// CEC testing
+void testRemote(struct node *node, unsigned me, unsigned la, unsigned test_tags,
+ bool interactive);
+
+// cec-audio.cpp
+extern struct remote_subtest sac_subtests[];
+extern const unsigned sac_subtests_size;
+extern struct remote_subtest dal_subtests[];
+extern const unsigned dal_subtests_size;
+extern struct remote_subtest arc_subtests[];
+extern const unsigned arc_subtests_size;
+extern struct remote_subtest audio_rate_ctl_subtests[];
+extern const unsigned audio_rate_ctl_subtests_size;
+
+// cec-power.cpp
+bool util_interactive_ensure_power_state(struct node *node, unsigned me, unsigned la, bool interactive,
+ __u8 target_pwr);
+extern struct remote_subtest standby_subtests[];
+extern const unsigned standby_subtests_size;
+extern struct remote_subtest one_touch_play_subtests[];
+extern const unsigned one_touch_play_subtests_size;
+extern struct remote_subtest power_status_subtests[];
+extern const unsigned power_status_subtests_size;
+extern struct remote_subtest standby_resume_subtests[];
+extern const unsigned standby_resume_subtests_size;
+
+#endif
diff --git a/utils/cec-compliance/cec-test-adapter.cpp b/utils/cec-compliance/cec-test-adapter.cpp
new file mode 100644
index 00000000..58bbf462
--- /dev/null
+++ b/utils/cec-compliance/cec-test-adapter.cpp
@@ -0,0 +1,916 @@
+/*
+ * Copyright 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
+ *
+ * This program is free software; you may redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <inttypes.h>
+#include <getopt.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+#include <sys/wait.h>
+#include <sstream>
+
+#include "cec-compliance.h"
+
+static const __u8 tx_ok_retry_mask = CEC_TX_STATUS_OK | CEC_TX_STATUS_MAX_RETRIES;
+
+// Flush any pending messages
+static int flush_pending_msgs(struct node *node)
+{
+ int res;
+
+ do {
+ struct cec_msg msg;
+
+ memset(&msg, 0xff, sizeof(msg));
+ msg.timeout = 1;
+ res = doioctl(node, CEC_RECEIVE, &msg);
+ fail_on_test(res && res != ETIMEDOUT);
+ } while (res == 0);
+ return 0;
+}
+
+int testCap(struct node *node)
+{
+ struct cec_caps caps;
+
+ memset(&caps, 0xff, sizeof(caps));
+ // Must always be there
+ fail_on_test(doioctl(node, CEC_ADAP_G_CAPS, &caps));
+ fail_on_test(caps.available_log_addrs == 0 ||
+ caps.available_log_addrs > CEC_MAX_LOG_ADDRS);
+ fail_on_test((caps.capabilities & CEC_CAP_PASSTHROUGH) &&
+ !(caps.capabilities & CEC_CAP_TRANSMIT));
+ return 0;
+}
+
+int testDQEvent(struct node *node)
+{
+ struct cec_event ev;
+
+ memset(&ev, 0xff, sizeof(ev));
+ fail_on_test(doioctl(node, CEC_DQEVENT, &ev));
+ fail_on_test(!(ev.flags & CEC_EVENT_FL_INITIAL_STATE));
+ fail_on_test(ev.flags & ~CEC_EVENT_FL_INITIAL_STATE);
+ fail_on_test(ev.ts == 0 || ev.ts == ~0ULL);
+ fail_on_test(ev.event != CEC_EVENT_STATE_CHANGE);
+ fail_on_test(ev.state_change.log_addr_mask == 0xffff);
+ memset(&ev.state_change, 0, sizeof(ev.state_change));
+ fail_on_test(check_0(ev.raw, sizeof(ev.raw)));
+ return 0;
+}
+
+int testAdapPhysAddr(struct node *node)
+{
+ __u16 old_pa = 0xefff;
+ __u16 pa = 0x1000;
+
+ fail_on_test(doioctl(node, CEC_ADAP_G_PHYS_ADDR, &old_pa));
+ fail_on_test(old_pa == 0xefff);
+ if (node->caps & CEC_CAP_PHYS_ADDR) {
+ fail_on_test(doioctl(node, CEC_ADAP_S_PHYS_ADDR, &pa));
+ fail_on_test(doioctl(node, CEC_ADAP_G_PHYS_ADDR, &pa));
+ fail_on_test(pa != 0x1000);
+
+ fail_on_test(doioctl(node, CEC_ADAP_S_PHYS_ADDR, &old_pa));
+ fail_on_test(doioctl(node, CEC_ADAP_G_PHYS_ADDR, &pa));
+ fail_on_test(pa != old_pa);
+ } else {
+ fail_on_test(doioctl(node, CEC_ADAP_S_PHYS_ADDR, &pa) != ENOTTY);
+ }
+ return 0;
+}
+
+int testAdapLogAddrs(struct node *node)
+{
+ static const __u8 la_types[] = {
+ CEC_LOG_ADDR_TYPE_TV,
+ CEC_LOG_ADDR_TYPE_RECORD,
+ CEC_LOG_ADDR_TYPE_TUNER,
+ CEC_LOG_ADDR_TYPE_PLAYBACK
+ };
+ static const __u8 prim_dev_types[] = {
+ CEC_OP_PRIM_DEVTYPE_TV,
+ CEC_OP_PRIM_DEVTYPE_RECORD,
+ CEC_OP_PRIM_DEVTYPE_TUNER,
+ CEC_OP_PRIM_DEVTYPE_PLAYBACK
+ };
+ static const __u8 all_dev_types =
+ CEC_OP_ALL_DEVTYPE_TV | CEC_OP_ALL_DEVTYPE_RECORD;
+ static const __u8 features[12] = {
+ 0x90, 0x00, 0x8e, 0x00,
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff
+ };
+ struct cec_log_addrs clear = { };
+ struct cec_log_addrs laddrs;
+ struct cec_event ev;
+ int res;
+
+ memset(&laddrs, 0xff, sizeof(laddrs));
+ fail_on_test(doioctl(node, CEC_ADAP_G_LOG_ADDRS, &laddrs));
+ fail_on_test(laddrs.vendor_id != CEC_VENDOR_ID_NONE &&
+ (laddrs.vendor_id & 0xff000000));
+ fail_on_test(laddrs.cec_version != CEC_OP_CEC_VERSION_1_4 &&
+ laddrs.cec_version != CEC_OP_CEC_VERSION_2_0);
+ fail_on_test(laddrs.num_log_addrs > CEC_MAX_LOG_ADDRS);
+
+ if (node->phys_addr == CEC_PHYS_ADDR_INVALID || laddrs.num_log_addrs == 0)
+ fail_on_test(laddrs.log_addr_mask);
+ else
+ fail_on_test(!laddrs.log_addr_mask);
+
+ if (!(node->caps & CEC_CAP_LOG_ADDRS)) {
+ fail_on_test(!laddrs.num_log_addrs);
+ fail_on_test(doioctl(node, CEC_ADAP_S_LOG_ADDRS, &laddrs) != ENOTTY);
+ return 0;
+ }
+
+ fail_on_test(doioctl(node, CEC_ADAP_S_LOG_ADDRS, &clear));
+ fail_on_test(clear.num_log_addrs != 0);
+ fail_on_test(clear.log_addr_mask != 0);
+ fail_on_test(doioctl(node, CEC_ADAP_G_LOG_ADDRS, &laddrs));
+ fail_on_test(laddrs.num_log_addrs != 0);
+ fail_on_test(laddrs.log_addr_mask != 0);
+
+ __u16 pa;
+
+ fail_on_test(doioctl(node, CEC_ADAP_G_PHYS_ADDR, &pa));
+ fail_on_test(pa != node->phys_addr);
+
+ memset(&laddrs, 0, sizeof(laddrs));
+ strcpy(laddrs.osd_name, "Compliance");
+ laddrs.num_log_addrs = node->available_log_addrs;
+ laddrs.cec_version = laddrs.num_log_addrs > 2 ?
+ CEC_OP_CEC_VERSION_1_4: CEC_OP_CEC_VERSION_2_0;
+ for (unsigned i = 0; i < CEC_MAX_LOG_ADDRS; i++) {
+ laddrs.log_addr_type[i] = la_types[i];
+ laddrs.primary_device_type[i] = prim_dev_types[i];
+ laddrs.all_device_types[i] = all_dev_types;
+ memcpy(laddrs.features[i], features, sizeof(features));
+ }
+
+ fail_on_test(doioctl(node, CEC_ADAP_S_LOG_ADDRS, &laddrs));
+ fail_on_test(laddrs.num_log_addrs != node->available_log_addrs);
+ fail_on_test(laddrs.log_addr_mask == 0);
+ for (unsigned i = 0; i < laddrs.num_log_addrs; i++) {
+ fail_on_test(laddrs.log_addr[i] == CEC_LOG_ADDR_INVALID);
+ fail_on_test(memcmp(laddrs.features[i], features, 4));
+ fail_on_test(check_0(laddrs.features[i] + 4, 8));
+ }
+ for (unsigned i = laddrs.num_log_addrs; i < CEC_MAX_LOG_ADDRS; i++) {
+ fail_on_test(laddrs.log_addr_type[i] ||
+ laddrs.primary_device_type[i] ||
+ laddrs.all_device_types[i]);
+ fail_on_test(check_0(laddrs.features[i], sizeof(laddrs.features[i])));
+ }
+ fail_on_test(doioctl(node, CEC_ADAP_S_LOG_ADDRS, &laddrs) != EBUSY);
+
+ fcntl(node->fd, F_SETFL, fcntl(node->fd, F_GETFL) | O_NONBLOCK);
+ do {
+ res = doioctl(node, CEC_DQEVENT, &ev);
+ fail_on_test(res && res != EAGAIN);
+ if (!res) {
+ struct timeval tv = { 0, 10000 }; // 10 ms
+
+ fail_on_test(ev.flags & CEC_EVENT_FL_INITIAL_STATE);
+ select(0, NULL, NULL, NULL, &tv);
+ }
+ } while (!res);
+ fail_on_test(doioctl(node, CEC_ADAP_S_LOG_ADDRS, &clear));
+ do {
+ struct timeval tv = { 0, 10000 }; // 10 ms
+
+ res = doioctl(node, CEC_DQEVENT, &ev);
+ fail_on_test(res && res != EAGAIN);
+ if (res)
+ select(0, NULL, NULL, NULL, &tv);
+ } while (res);
+ fail_on_test(ev.flags & CEC_EVENT_FL_INITIAL_STATE);
+ fail_on_test(ev.ts == 0);
+ fail_on_test(ev.event != CEC_EVENT_STATE_CHANGE);
+ fail_on_test(ev.state_change.phys_addr != node->phys_addr);
+ fail_on_test(ev.state_change.log_addr_mask);
+
+ fail_on_test(doioctl(node, CEC_ADAP_S_LOG_ADDRS, &laddrs));
+ do {
+ struct timeval tv = { 0, 10000 }; // 10 ms
+
+ res = doioctl(node, CEC_DQEVENT, &ev);
+ fail_on_test(res && res != EAGAIN);
+ if (res)
+ select(0, NULL, NULL, NULL, &tv);
+ } while (res);
+ fail_on_test(ev.flags & CEC_EVENT_FL_INITIAL_STATE);
+ fail_on_test(ev.ts == 0);
+ fail_on_test(ev.event != CEC_EVENT_STATE_CHANGE);
+ fail_on_test(ev.state_change.phys_addr != node->phys_addr);
+ fail_on_test(ev.state_change.log_addr_mask == 0);
+ return 0;
+}
+
+int testTransmit(struct node *node)
+{
+ struct cec_msg msg = { };
+ unsigned i, la = node->log_addr[0];
+ bool tested_self = false;
+ bool tested_valid_la = false;
+ bool tested_invalid_la = false;
+
+ if (!(node->caps & CEC_CAP_TRANSMIT)) {
+ cec_msg_init(&msg, 0xf, 0);
+ fail_on_test(doioctl(node, CEC_TRANSMIT, &msg) != ENOTTY);
+ return NOTSUPPORTED;
+ }
+
+ /* Check invalid messages */
+ cec_msg_init(&msg, 0xf, 0xf);
+ fail_on_test(doioctl(node, CEC_TRANSMIT, &msg) != EINVAL);
+
+ cec_msg_init(&msg, 0xf, 0);
+ msg.timeout = 1000;
+ fail_on_test(doioctl(node, CEC_TRANSMIT, &msg) != EINVAL);
+
+ cec_msg_init(&msg, 0xf, 0);
+ msg.len = 0;
+ fail_on_test(doioctl(node, CEC_TRANSMIT, &msg) != EINVAL);
+
+ cec_msg_init(&msg, 0xf, 0);
+ msg.len = CEC_MAX_MSG_SIZE + 1;
+ fail_on_test(doioctl(node, CEC_TRANSMIT, &msg) != EINVAL);
+
+ cec_msg_init(&msg, 0xf, 0);
+ msg.reply = CEC_MSG_CEC_VERSION;
+ fail_on_test(doioctl(node, CEC_TRANSMIT, &msg) != EINVAL);
+
+ for (i = 0; i < 15; i++) {
+ if (tested_self && (node->adap_la_mask & (1 << i)))
+ continue;
+
+ memset(&msg, 0xff, sizeof(msg));
+ msg.msg[0] = 0xf0 | i;
+ msg.len = 1;
+ msg.timeout = 0;
+ msg.reply = 0;
+
+ fail_on_test(doioctl(node, CEC_TRANSMIT, &msg));
+
+ fail_on_test(msg.len != 1);
+ fail_on_test(check_0(msg.msg + msg.len, sizeof(msg.msg) - msg.len));
+ fail_on_test(msg.timeout || msg.reply);
+ fail_on_test(msg.flags || msg.rx_status || msg.rx_ts);
+ fail_on_test(msg.tx_status == 0 || msg.tx_ts == 0 || msg.tx_ts == ~0ULL);
+ fail_on_test(msg.msg[0] != (0xf0 | i));
+ fail_on_test(msg.sequence == 0 || msg.sequence == ~0U);
+ fail_on_test(msg.tx_status & ~0x3f);
+ fail_on_test((msg.tx_status & tx_ok_retry_mask) == tx_ok_retry_mask);
+ fail_on_test(!(msg.tx_status & tx_ok_retry_mask));
+
+ if (node->adap_la_mask & (1 << i)) {
+ // Send message to yourself
+ fail_on_test(msg.tx_status !=
+ (CEC_TX_STATUS_NACK | CEC_TX_STATUS_MAX_RETRIES));
+ fail_on_test(msg.tx_nack_cnt != 1);
+ fail_on_test(msg.tx_arb_lost_cnt);
+ fail_on_test(msg.tx_low_drive_cnt);
+ fail_on_test(msg.tx_error_cnt);
+
+ cec_msg_init(&msg, i, i);
+ cec_msg_give_physical_addr(&msg, true);
+ fail_on_test(doioctl(node, CEC_TRANSMIT, &msg) != EINVAL);
+ } else if (msg.tx_status & CEC_TX_STATUS_OK) {
+ if (tested_valid_la)
+ continue;
+ tested_valid_la = true;
+ // Send message to a remote LA
+ memset(&msg, 0xff, sizeof(msg));
+ msg.msg[0] = (la << 4) | i;
+ msg.timeout = 1001;
+ cec_msg_give_physical_addr(&msg, true);
+ fail_on_test(doioctl(node, CEC_TRANSMIT, &msg));
+ fail_on_test(msg.len != 5);
+ fail_on_test(check_0(msg.msg + msg.len, sizeof(msg.msg) - msg.len));
+ fail_on_test(msg.timeout != 1001);
+ fail_on_test(msg.sequence == 0 || msg.sequence == ~0U);
+ fail_on_test(msg.reply != CEC_MSG_REPORT_PHYSICAL_ADDR);
+ fail_on_test(msg.rx_ts == 0 || msg.rx_ts == ~0ULL);
+ fail_on_test(msg.rx_status == 0 || msg.flags);
+ fail_on_test(msg.rx_status & ~0x07);
+ fail_on_test(msg.tx_ts == 0 || msg.tx_ts == ~0ULL);
+ fail_on_test(msg.rx_ts <= msg.tx_ts);
+ fail_on_test(!(msg.tx_status & CEC_TX_STATUS_OK));
+ fail_on_test((msg.tx_status & tx_ok_retry_mask) == tx_ok_retry_mask);
+ fail_on_test(msg.tx_nack_cnt == 0xff);
+ fail_on_test(msg.tx_arb_lost_cnt == 0xff);
+ fail_on_test(msg.tx_low_drive_cnt == 0xff);
+ fail_on_test(msg.tx_error_cnt == 0xff);
+
+ memset(&msg, 0xff, sizeof(msg));
+ msg.msg[0] = (la << 4) | i;
+ msg.timeout = 0;
+ cec_msg_give_physical_addr(&msg, false);
+ fail_on_test(doioctl(node, CEC_TRANSMIT, &msg));
+ fail_on_test(msg.timeout);
+ fail_on_test(msg.sequence == 0 || msg.sequence == ~0U);
+ fail_on_test(msg.reply);
+ fail_on_test(check_0(msg.msg + msg.len, sizeof(msg.msg) - msg.len));
+ fail_on_test(msg.rx_status || msg.rx_ts || msg.flags);
+ fail_on_test(msg.tx_ts == 0 || msg.tx_ts == ~0ULL);
+ fail_on_test(!(msg.tx_status & CEC_TX_STATUS_OK));
+ fail_on_test((msg.tx_status & tx_ok_retry_mask) == tx_ok_retry_mask);
+ fail_on_test(msg.tx_nack_cnt == 0xff);
+ fail_on_test(msg.tx_arb_lost_cnt == 0xff);
+ fail_on_test(msg.tx_low_drive_cnt == 0xff);
+ fail_on_test(msg.tx_error_cnt == 0xff);
+ } else {
+ if (tested_invalid_la)
+ continue;
+ tested_invalid_la = true;
+ // Send message to a remote non-existent LA
+ memset(&msg, 0xff, sizeof(msg));
+ msg.msg[0] = (la << 4) | i;
+ msg.timeout = 1002;
+ cec_msg_give_physical_addr(&msg, true);
+ fail_on_test(doioctl(node, CEC_TRANSMIT, &msg));
+ fail_on_test(msg.timeout != 1002);
+ fail_on_test(msg.sequence == 0 || msg.sequence == ~0U);
+ fail_on_test(msg.len != 2);
+ fail_on_test(check_0(msg.msg + msg.len, sizeof(msg.msg) - msg.len));
+ fail_on_test(msg.flags);
+ fail_on_test(msg.reply != CEC_MSG_REPORT_PHYSICAL_ADDR);
+ fail_on_test(!(msg.tx_status & CEC_TX_STATUS_MAX_RETRIES));
+ fail_on_test((msg.tx_status & tx_ok_retry_mask) == tx_ok_retry_mask);
+ fail_on_test(msg.rx_ts || msg.rx_status);
+ fail_on_test(msg.tx_ts == 0 || msg.tx_ts == ~0ULL);
+ fail_on_test(msg.tx_nack_cnt == 0xff);
+ fail_on_test(msg.tx_arb_lost_cnt == 0xff);
+ fail_on_test(msg.tx_low_drive_cnt == 0xff);
+ fail_on_test(msg.tx_error_cnt == 0xff);
+
+ memset(&msg, 0xff, sizeof(msg));
+ msg.msg[0] = (la << 4) | i;
+ msg.timeout = 0;
+ cec_msg_give_physical_addr(&msg, false);
+ fail_on_test(doioctl(node, CEC_TRANSMIT, &msg));
+ fail_on_test(msg.timeout);
+ fail_on_test(msg.sequence == 0 || msg.sequence == ~0U);
+ fail_on_test(msg.reply);
+ fail_on_test(msg.len != 2);
+ fail_on_test(check_0(msg.msg + msg.len, sizeof(msg.msg) - msg.len));
+ fail_on_test(msg.rx_status || msg.rx_ts || msg.flags);
+ fail_on_test(msg.tx_ts == 0 || msg.tx_ts == ~0ULL);
+ fail_on_test(!(msg.tx_status & CEC_TX_STATUS_MAX_RETRIES));
+ fail_on_test((msg.tx_status & tx_ok_retry_mask) == tx_ok_retry_mask);
+ fail_on_test(msg.tx_nack_cnt == 0xff);
+ fail_on_test(msg.tx_arb_lost_cnt == 0xff);
+ fail_on_test(msg.tx_low_drive_cnt == 0xff);
+ fail_on_test(msg.tx_error_cnt == 0xff);
+ }
+ }
+
+ return 0;
+}
+
+int testReceive(struct node *node)
+{
+ unsigned la = node->log_addr[0], remote_la = 0;
+ struct cec_msg msg;
+
+ if (!(node->caps & CEC_CAP_TRANSMIT)) {
+ fail_on_test(doioctl(node, CEC_RECEIVE, &msg) != ENOTTY);
+ return NOTSUPPORTED;
+ }
+
+ for (unsigned i = 0; i < 15; i++) {
+ if (node->remote_la_mask & (1 << i))
+ break;
+ remote_la++;
+ }
+
+ fail_on_test(flush_pending_msgs(node));
+
+ if (remote_la == 15)
+ return PRESUMED_OK;
+
+ cec_msg_init(&msg, la, remote_la);
+ cec_msg_give_physical_addr(&msg, false);
+ fail_on_test(doioctl(node, CEC_TRANSMIT, &msg));
+
+ msg.timeout = 1500;
+ fail_on_test(doioctl(node, CEC_RECEIVE, &msg) != ETIMEDOUT);
+ fail_on_test(msg.timeout != 1500);
+
+ __u32 mode = CEC_MODE_INITIATOR | CEC_MODE_FOLLOWER;
+
+ fail_on_test(doioctl(node, CEC_S_MODE, &mode));
+
+ cec_msg_init(&msg, la, remote_la);
+ cec_msg_give_physical_addr(&msg, false);
+ fail_on_test(doioctl(node, CEC_TRANSMIT, &msg));
+
+ memset(&msg, 0xff, sizeof(msg));
+ msg.timeout = 1500;
+ fail_on_test(doioctl(node, CEC_RECEIVE, &msg));
+
+ fail_on_test(msg.tx_ts || msg.tx_status || msg.tx_arb_lost_cnt ||
+ msg.tx_nack_cnt || msg.tx_low_drive_cnt || msg.tx_error_cnt);
+ fail_on_test(msg.timeout != 1500);
+ fail_on_test(msg.len != 5 || msg.msg[1] != CEC_MSG_REPORT_PHYSICAL_ADDR);
+ fail_on_test(check_0(msg.msg + msg.len, sizeof(msg.msg) - msg.len));
+ fail_on_test(msg.msg[0] != ((remote_la << 4) | 0xf));
+ fail_on_test(msg.sequence);
+ fail_on_test(msg.rx_ts == 0);
+ fail_on_test(msg.flags || msg.reply);
+ fail_on_test(msg.rx_status != CEC_RX_STATUS_OK);
+
+ return 0;
+}
+
+int testNonBlocking(struct node *node)
+{
+ unsigned la = node->log_addr[0], remote_la = 0, invalid_remote = 0xf;
+ struct cec_msg msg;
+
+ if (!(node->caps & CEC_CAP_TRANSMIT))
+ return NOTSUPPORTED;
+
+ for (unsigned i = 0; i < 15; i++) {
+ if (node->remote_la_mask & (1 << i))
+ break;
+ if (invalid_remote == 0xf && !(node->adap_la_mask & (1 << i)))
+ invalid_remote = i;
+ remote_la++;
+ }
+
+ fail_on_test(flush_pending_msgs(node));
+
+ fcntl(node->fd, F_SETFL, fcntl(node->fd, F_GETFL) | O_NONBLOCK);
+
+ fail_on_test(doioctl(node, CEC_RECEIVE, &msg) != EAGAIN);
+
+ cec_msg_init(&msg, 0xf, la);
+ fail_on_test(doioctl(node, CEC_TRANSMIT, &msg));
+ fail_on_test(msg.tx_status != (CEC_TX_STATUS_NACK | CEC_TX_STATUS_MAX_RETRIES));
+
+ if (invalid_remote < 15) {
+ __u32 seq;
+
+ // Send non-blocking non-reply message to invalid remote LA
+ memset(&msg, 0xff, sizeof(msg));
+ msg.msg[0] = (la << 4) | invalid_remote;
+ msg.timeout = 0;
+ cec_msg_give_physical_addr(&msg, false);
+ fail_on_test(doioctl(node, CEC_TRANSMIT, &msg));
+ fail_on_test(msg.len != 2);
+ fail_on_test(check_0(msg.msg + msg.len, sizeof(msg.msg) - msg.len));
+ fail_on_test(msg.msg[0] != ((la << 4) | invalid_remote));
+ fail_on_test(msg.msg[1] != CEC_MSG_GIVE_PHYSICAL_ADDR);
+ fail_on_test(msg.timeout || msg.flags);
+ fail_on_test(msg.sequence == 0 || msg.sequence == ~0U);
+ fail_on_test(msg.reply);
+ fail_on_test(msg.tx_status);
+ fail_on_test(msg.rx_ts || msg.rx_status);
+ fail_on_test(msg.tx_ts || msg.tx_nack_cnt || msg.tx_arb_lost_cnt ||
+ msg.tx_low_drive_cnt || msg.tx_error_cnt);
+ seq = msg.sequence;
+
+ sleep(1);
+ while (true) {
+ memset(&msg, 0xff, sizeof(msg));
+ msg.timeout = 1500;
+ fail_on_test(doioctl(node, CEC_RECEIVE, &msg));
+ if (msg.sequence == 0)
+ continue;
+ fail_on_test(msg.sequence != seq);
+ fail_on_test(msg.len != 2);
+ fail_on_test(check_0(msg.msg + msg.len, sizeof(msg.msg) - msg.len));
+ fail_on_test(msg.msg[0] != ((la << 4) | invalid_remote));
+ fail_on_test(msg.msg[1] != CEC_MSG_GIVE_PHYSICAL_ADDR);
+ fail_on_test(msg.timeout != 1500 || msg.flags);
+ fail_on_test(msg.reply);
+ fail_on_test(!(msg.tx_status & CEC_TX_STATUS_MAX_RETRIES));
+ fail_on_test((msg.tx_status & tx_ok_retry_mask) == tx_ok_retry_mask);
+ fail_on_test(msg.tx_nack_cnt == 0xff);
+ fail_on_test(msg.tx_arb_lost_cnt == 0xff);
+ fail_on_test(msg.tx_low_drive_cnt == 0xff);
+ fail_on_test(msg.tx_error_cnt == 0xff);
+ fail_on_test(msg.rx_ts || msg.rx_status);
+ break;
+ }
+
+ // Send non-blocking reply message to invalid remote LA
+ memset(&msg, 0xff, sizeof(msg));
+ msg.msg[0] = (la << 4) | invalid_remote;
+ msg.timeout = 0;
+ cec_msg_give_physical_addr(&msg, true);
+ fail_on_test(doioctl(node, CEC_TRANSMIT, &msg));
+ fail_on_test(msg.len != 2);
+ fail_on_test(check_0(msg.msg + msg.len, sizeof(msg.msg) - msg.len));
+ fail_on_test(msg.msg[0] != ((la << 4) | invalid_remote));
+ fail_on_test(msg.msg[1] != CEC_MSG_GIVE_PHYSICAL_ADDR);
+ fail_on_test(msg.timeout != 1000 || msg.flags);
+ fail_on_test(msg.sequence == 0 || msg.sequence == ~0U);
+ fail_on_test(msg.reply != CEC_MSG_REPORT_PHYSICAL_ADDR);
+ fail_on_test(msg.tx_status);
+ fail_on_test(msg.rx_ts || msg.rx_status);
+ fail_on_test(msg.tx_ts || msg.tx_nack_cnt || msg.tx_arb_lost_cnt ||
+ msg.tx_low_drive_cnt || msg.tx_error_cnt);
+ seq = msg.sequence;
+
+ sleep(1);
+ while (true) {
+ memset(&msg, 0xff, sizeof(msg));
+ msg.timeout = 1500;
+ fail_on_test(doioctl(node, CEC_RECEIVE, &msg));
+ if (msg.sequence == 0)
+ continue;
+ fail_on_test(msg.sequence != seq);
+ fail_on_test(msg.len != 2);
+ fail_on_test(check_0(msg.msg + msg.len, sizeof(msg.msg) - msg.len));
+ fail_on_test(msg.msg[0] != ((la << 4) | invalid_remote));
+ fail_on_test(msg.msg[1] != CEC_MSG_GIVE_PHYSICAL_ADDR);
+ fail_on_test(msg.timeout != 1500 || msg.flags);
+ fail_on_test(msg.reply != CEC_MSG_REPORT_PHYSICAL_ADDR);
+ fail_on_test(!(msg.tx_status & CEC_TX_STATUS_MAX_RETRIES));
+ fail_on_test((msg.tx_status & tx_ok_retry_mask) == tx_ok_retry_mask);
+ fail_on_test(msg.tx_nack_cnt == 0xff);
+ fail_on_test(msg.tx_arb_lost_cnt == 0xff);
+ fail_on_test(msg.tx_low_drive_cnt == 0xff);
+ fail_on_test(msg.tx_error_cnt == 0xff);
+ fail_on_test(msg.rx_ts || msg.rx_status);
+ break;
+ }
+
+ __u32 mode = CEC_MODE_INITIATOR | CEC_MODE_FOLLOWER;
+
+ fail_on_test(doioctl(node, CEC_S_MODE, &mode));
+ }
+ if (remote_la < 15) {
+ __u32 seq;
+
+ // Send non-blocking non-reply message to valid remote LA
+ memset(&msg, 0xff, sizeof(msg));
+ msg.msg[0] = (la << 4) | remote_la;
+ msg.timeout = 0;
+ cec_msg_give_physical_addr(&msg, false);
+ fail_on_test(doioctl(node, CEC_TRANSMIT, &msg));
+ fail_on_test(msg.len != 2);
+ fail_on_test(check_0(msg.msg + msg.len, sizeof(msg.msg) - msg.len));
+ fail_on_test(msg.msg[0] != ((la << 4) | remote_la));
+ fail_on_test(msg.msg[1] != CEC_MSG_GIVE_PHYSICAL_ADDR);
+ fail_on_test(msg.timeout || msg.flags);
+ fail_on_test(msg.sequence == 0 || msg.sequence == ~0U);
+ fail_on_test(msg.reply);
+ fail_on_test(msg.tx_status);
+ fail_on_test(msg.rx_ts || msg.rx_status);
+ fail_on_test(msg.tx_ts || msg.tx_nack_cnt || msg.tx_arb_lost_cnt ||
+ msg.tx_low_drive_cnt || msg.tx_error_cnt);
+ seq = msg.sequence;
+
+ sleep(1);
+ while (true) {
+ memset(&msg, 0xff, sizeof(msg));
+ msg.timeout = 1500;
+ fail_on_test(doioctl(node, CEC_RECEIVE, &msg));
+ if (msg.sequence == 0)
+ continue;
+ fail_on_test(msg.sequence != seq);
+ fail_on_test(msg.len != 2);
+ fail_on_test(check_0(msg.msg + msg.len, sizeof(msg.msg) - msg.len));
+ fail_on_test(msg.msg[0] != ((la << 4) | remote_la));
+ fail_on_test(msg.msg[1] != CEC_MSG_GIVE_PHYSICAL_ADDR);
+ fail_on_test(msg.timeout != 1500 || msg.flags);
+ fail_on_test(msg.reply);
+ fail_on_test(!(msg.tx_status & CEC_TX_STATUS_OK));
+ fail_on_test((msg.tx_status & tx_ok_retry_mask) == tx_ok_retry_mask);
+ fail_on_test(msg.tx_nack_cnt == 0xff);
+ fail_on_test(msg.tx_arb_lost_cnt == 0xff);
+ fail_on_test(msg.tx_low_drive_cnt == 0xff);
+ fail_on_test(msg.tx_error_cnt == 0xff);
+ fail_on_test(msg.rx_ts || msg.rx_status);
+ break;
+ }
+
+ // Send non-blocking reply message to valid remote LA
+ memset(&msg, 0xff, sizeof(msg));
+ msg.msg[0] = (la << 4) | remote_la;
+ msg.timeout = 0;
+ cec_msg_give_physical_addr(&msg, true);
+ fail_on_test(doioctl(node, CEC_TRANSMIT, &msg));
+ fail_on_test(msg.len != 2);
+ fail_on_test(check_0(msg.msg + msg.len, sizeof(msg.msg) - msg.len));
+ fail_on_test(msg.msg[0] != ((la << 4) | remote_la));
+ fail_on_test(msg.msg[1] != CEC_MSG_GIVE_PHYSICAL_ADDR);
+ fail_on_test(msg.timeout != 1000 || msg.flags);
+ fail_on_test(msg.sequence == 0 || msg.sequence == ~0U);
+ fail_on_test(msg.reply != CEC_MSG_REPORT_PHYSICAL_ADDR);
+ fail_on_test(msg.tx_status);
+ fail_on_test(msg.rx_ts || msg.rx_status);
+ fail_on_test(msg.tx_ts || msg.tx_nack_cnt || msg.tx_arb_lost_cnt ||
+ msg.tx_low_drive_cnt || msg.tx_error_cnt);
+ seq = msg.sequence;
+
+ sleep(1);
+ while (true) {
+ memset(&msg, 0xff, sizeof(msg));
+ msg.timeout = 1500;
+ fail_on_test(doioctl(node, CEC_RECEIVE, &msg));
+ if (msg.sequence == 0)
+ continue;
+ fail_on_test(msg.sequence != seq);
+ fail_on_test(msg.len != 5);
+ fail_on_test(check_0(msg.msg + msg.len, sizeof(msg.msg) - msg.len));
+ fail_on_test(msg.msg[0] != ((remote_la << 4) | 0xf));
+ fail_on_test(msg.msg[1] != CEC_MSG_REPORT_PHYSICAL_ADDR);
+ fail_on_test(msg.timeout != 1500 || msg.flags);
+ fail_on_test(msg.reply != CEC_MSG_REPORT_PHYSICAL_ADDR);
+ fail_on_test(!(msg.tx_status & CEC_TX_STATUS_OK));
+ fail_on_test((msg.tx_status & tx_ok_retry_mask) == tx_ok_retry_mask);
+ fail_on_test(msg.tx_nack_cnt == 0xff);
+ fail_on_test(msg.tx_arb_lost_cnt == 0xff);
+ fail_on_test(msg.tx_low_drive_cnt == 0xff);
+ fail_on_test(msg.tx_error_cnt == 0xff);
+ fail_on_test(msg.rx_ts == 0 || msg.rx_ts == ~0ULL || msg.rx_status != CEC_RX_STATUS_OK);
+ fail_on_test(msg.rx_ts < msg.tx_ts);
+ break;
+ }
+
+ __u32 mode = CEC_MODE_INITIATOR | CEC_MODE_FOLLOWER;
+
+ fail_on_test(doioctl(node, CEC_S_MODE, &mode));
+ }
+ if (remote_la == 15 || invalid_remote == 15)
+ return PRESUMED_OK;
+ return 0;
+}
+
+int testModes(struct node *node, struct node *node2)
+{
+ struct cec_msg msg;
+ __u8 me = node->log_addr[0];
+ __u8 remote = CEC_LOG_ADDR_INVALID;
+ __u32 mode = 0, m;
+
+ for (unsigned i = 0; i < 15; i++) {
+ if (node->remote_la_mask & (1 << i)) {
+ remote = i;
+ break;
+ }
+ }
+
+ cec_msg_init(&msg, 0xf, 0);
+ fail_on_test(doioctl(node, CEC_TRANSMIT, &msg));
+ fail_on_test(doioctl(node2, CEC_TRANSMIT, &msg));
+
+ fail_on_test(doioctl(node, CEC_G_MODE, &m));
+ fail_on_test(m != CEC_MODE_INITIATOR);
+ fail_on_test(doioctl(node2, CEC_G_MODE, &m));
+ fail_on_test(m != CEC_MODE_INITIATOR);
+
+ fail_on_test(doioctl(node, CEC_S_MODE, &mode));
+ fail_on_test(doioctl(node, CEC_G_MODE, &m));
+ fail_on_test(m != mode);
+ fail_on_test(doioctl(node, CEC_TRANSMIT, &msg) != EBUSY);
+ fail_on_test(doioctl(node2, CEC_TRANSMIT, &msg));
+
+ mode = CEC_MODE_INITIATOR;
+ fail_on_test(doioctl(node, CEC_S_MODE, &mode));
+ fail_on_test(doioctl(node, CEC_G_MODE, &m));
+ fail_on_test(m != mode);
+ fail_on_test(doioctl(node, CEC_TRANSMIT, &msg));
+
+ mode = CEC_MODE_EXCL_INITIATOR;
+ fail_on_test(doioctl(node, CEC_S_MODE, &mode));
+ fail_on_test(doioctl(node, CEC_G_MODE, &m));
+ fail_on_test(m != mode);
+ fail_on_test(doioctl(node, CEC_TRANSMIT, &msg));
+ fail_on_test(doioctl(node2, CEC_TRANSMIT, &msg) != EBUSY);
+
+ mode = CEC_MODE_INITIATOR;
+ fail_on_test(doioctl(node, CEC_S_MODE, &mode));
+ fail_on_test(doioctl(node, CEC_G_MODE, &m));
+ fail_on_test(m != mode);
+
+ if (remote == CEC_LOG_ADDR_INVALID)
+ return 0;
+
+ fail_on_test(flush_pending_msgs(node));
+ cec_msg_init(&msg, me, remote);
+ cec_msg_give_physical_addr(&msg, false);
+ fail_on_test(doioctl(node, CEC_TRANSMIT, &msg));
+ msg.timeout = 1200;
+ fail_on_test(doioctl(node, CEC_RECEIVE, &msg) != ETIMEDOUT);
+
+ mode = CEC_MODE_INITIATOR | CEC_MODE_FOLLOWER;
+ fail_on_test(doioctl(node, CEC_S_MODE, &mode));
+ fail_on_test(doioctl(node, CEC_G_MODE, &m));
+ fail_on_test(m != mode);
+
+ fail_on_test(flush_pending_msgs(node));
+ cec_msg_init(&msg, me, remote);
+ cec_msg_give_physical_addr(&msg, false);
+ fail_on_test(doioctl(node, CEC_TRANSMIT, &msg));
+ msg.timeout = 1200;
+ fail_on_test(doioctl(node, CEC_RECEIVE, &msg));
+ fail_on_test(msg.msg[1] != CEC_MSG_REPORT_PHYSICAL_ADDR);
+
+ mode = CEC_MODE_INITIATOR | CEC_MODE_FOLLOWER;
+ fail_on_test(doioctl(node2, CEC_S_MODE, &mode));
+ fail_on_test(doioctl(node2, CEC_G_MODE, &m));
+ fail_on_test(m != mode);
+
+ fail_on_test(flush_pending_msgs(node));
+ fail_on_test(flush_pending_msgs(node2));
+ cec_msg_init(&msg, me, remote);
+ cec_msg_give_physical_addr(&msg, false);
+ fail_on_test(doioctl(node, CEC_TRANSMIT, &msg));
+ msg.timeout = 1200;
+ fail_on_test(doioctl(node, CEC_RECEIVE, &msg));
+ fail_on_test(msg.msg[1] != CEC_MSG_REPORT_PHYSICAL_ADDR);
+ msg.timeout = 1;
+ fail_on_test(doioctl(node2, CEC_RECEIVE, &msg));
+ fail_on_test(msg.msg[1] != CEC_MSG_REPORT_PHYSICAL_ADDR);
+
+ mode = CEC_MODE_INITIATOR | CEC_MODE_EXCL_FOLLOWER;
+ fail_on_test(doioctl(node, CEC_S_MODE, &mode));
+ fail_on_test(doioctl(node, CEC_G_MODE, &m));
+ fail_on_test(m != mode);
+
+ fail_on_test(flush_pending_msgs(node));
+ fail_on_test(flush_pending_msgs(node2));
+ cec_msg_init(&msg, me, remote);
+ cec_msg_give_physical_addr(&msg, false);
+ fail_on_test(doioctl(node, CEC_TRANSMIT, &msg));
+ msg.timeout = 1200;
+ fail_on_test(doioctl(node, CEC_RECEIVE, &msg));
+ fail_on_test(msg.msg[1] != CEC_MSG_REPORT_PHYSICAL_ADDR);
+ msg.timeout = 1;
+ fail_on_test(doioctl(node2, CEC_RECEIVE, &msg) != ETIMEDOUT);
+
+ mode = CEC_MODE_INITIATOR | CEC_MODE_EXCL_FOLLOWER_PASSTHRU;
+ fail_on_test(doioctl(node, CEC_S_MODE, &mode));
+ fail_on_test(doioctl(node, CEC_G_MODE, &m));
+ fail_on_test(m != mode);
+
+ // Note: this test is the same as for CEC_MODE_EXCL_FOLLOWER.
+ // Ideally the remote device would send us a passthrough message,
+ // but there is no way to trigger that.
+ fail_on_test(flush_pending_msgs(node));
+ fail_on_test(flush_pending_msgs(node2));
+ cec_msg_init(&msg, me, remote);
+ cec_msg_give_physical_addr(&msg, false);
+ fail_on_test(doioctl(node, CEC_TRANSMIT, &msg));
+ msg.timeout = 1200;
+ fail_on_test(doioctl(node, CEC_RECEIVE, &msg));
+ fail_on_test(msg.msg[1] != CEC_MSG_REPORT_PHYSICAL_ADDR);
+ msg.timeout = 1;
+ fail_on_test(doioctl(node2, CEC_RECEIVE, &msg) != ETIMEDOUT);
+
+ mode = CEC_MODE_INITIATOR;
+ fail_on_test(doioctl(node, CEC_S_MODE, &mode));
+ fail_on_test(doioctl(node, CEC_G_MODE, &m));
+ fail_on_test(m != mode);
+
+ mode = CEC_MODE_INITIATOR | CEC_MODE_MONITOR;
+ fail_on_test(doioctl(node2, CEC_S_MODE, &mode) != EINVAL);
+ fail_on_test(doioctl(node2, CEC_G_MODE, &m));
+ fail_on_test(m != (CEC_MODE_INITIATOR | CEC_MODE_FOLLOWER));
+
+ bool is_root = getuid() == 0;
+
+ mode = CEC_MODE_MONITOR;
+ fail_on_test(doioctl(node2, CEC_S_MODE, &mode) != (is_root ? 0 : EPERM));
+
+ if (is_root) {
+ fail_on_test(doioctl(node2, CEC_G_MODE, &m));
+ fail_on_test(m != mode);
+ fail_on_test(flush_pending_msgs(node2));
+ cec_msg_init(&msg, me, remote);
+ cec_msg_give_physical_addr(&msg, false);
+ fail_on_test(doioctl(node, CEC_TRANSMIT, &msg));
+ msg.timeout = 1200;
+ fail_on_test(doioctl(node2, CEC_RECEIVE, &msg));
+ fail_on_test(msg.msg[1] != CEC_MSG_GIVE_PHYSICAL_ADDR);
+ while (true) {
+ fail_on_test(doioctl(node2, CEC_RECEIVE, &msg));
+ if (msg.msg[1] == CEC_MSG_REPORT_PHYSICAL_ADDR)
+ break;
+ fail_on_test(!cec_msg_is_broadcast(&msg) &&
+ !(node2->adap_la_mask & (1 << cec_msg_destination(&msg))));
+ }
+ }
+
+ mode = CEC_MODE_MONITOR_ALL;
+ int res = doioctl(node2, CEC_S_MODE, &mode);
+ if (node2->caps & CEC_CAP_MONITOR_ALL)
+ fail_on_test(res != (is_root ? 0 : EPERM));
+ else
+ fail_on_test(res != EINVAL);
+
+ if (is_root) {
+ fail_on_test(doioctl(node2, CEC_G_MODE, &m));
+ fail_on_test(m != mode);
+ fail_on_test(flush_pending_msgs(node2));
+ cec_msg_init(&msg, me, remote);
+ cec_msg_give_physical_addr(&msg, false);
+ fail_on_test(doioctl(node, CEC_TRANSMIT, &msg));
+ msg.timeout = 1200;
+ fail_on_test(doioctl(node2, CEC_RECEIVE, &msg));
+ fail_on_test(msg.msg[1] != CEC_MSG_GIVE_PHYSICAL_ADDR);
+ while (true) {
+ fail_on_test(doioctl(node2, CEC_RECEIVE, &msg));
+ if (msg.msg[1] == CEC_MSG_REPORT_PHYSICAL_ADDR)
+ break;
+ }
+ }
+ return 0;
+}
+
+int testLostMsgs(struct node *node)
+{
+ struct cec_msg msg;
+ struct cec_event ev;
+ __u8 me = node->log_addr[0];
+ __u8 remote = CEC_LOG_ADDR_INVALID;
+
+ for (unsigned i = 0; i < 15; i++) {
+ if (node->remote_la_mask & (1 << i)) {
+ remote = i;
+ break;
+ }
+ }
+
+ fail_on_test(flush_pending_msgs(node));
+ fcntl(node->fd, F_SETFL, fcntl(node->fd, F_GETFL) | O_NONBLOCK);
+ cec_msg_init(&msg, me, remote);
+ cec_msg_get_cec_version(&msg, true);
+
+ // flush pending events
+ while (doioctl(node, CEC_DQEVENT, &ev) == 0) { }
+
+ bool got_busy = false;
+ unsigned xfer_cnt = 0;
+
+ do {
+ int res;
+
+ do {
+ res = doioctl(node, CEC_TRANSMIT, &msg);
+
+ fail_on_test(res && res != EBUSY);
+ if (res == EBUSY) {
+ struct timeval tv = { 0, 10000 }; // 10 ms
+
+ select(0, NULL, NULL, NULL, &tv);
+ got_busy = true;
+ } else if (!got_busy) {
+ xfer_cnt++;
+ }
+ } while (res == EBUSY);
+ // Alternate between wait for reply and just transmit
+ msg.timeout = msg.timeout ? 0 : 1000;
+ msg.reply = CEC_MSG_CEC_VERSION;
+ } while (doioctl(node, CEC_DQEVENT, &ev));
+ fail_on_test(ev.event != CEC_EVENT_LOST_MSGS);
+ fail_on_test(!got_busy);
+
+ /*
+ * No more than max 18 transmits can be queued, but one message
+ * might be finished transmitting before the queue fills up, so
+ * check for 19 instead.
+ */
+ fail_on_test(xfer_cnt == 0 || xfer_cnt > 19);
+
+ unsigned pending_msgs = 0;
+
+ fcntl(node->fd, F_SETFL, fcntl(node->fd, F_GETFL) & ~O_NONBLOCK);
+ msg.timeout = 1000;
+
+ while (!doioctl(node, CEC_RECEIVE, &msg))
+ pending_msgs++;
+
+ /* Should be at least the size of the internal message queue */
+ fail_on_test(pending_msgs < 18 * 3);
+
+ return 0;
+}
diff --git a/utils/cec-compliance/cec-test-audio.cpp b/utils/cec-compliance/cec-test-audio.cpp
new file mode 100644
index 00000000..42aa2e47
--- /dev/null
+++ b/utils/cec-compliance/cec-test-audio.cpp
@@ -0,0 +1,831 @@
+/*
+ * Copyright 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
+ *
+ * This program is free software; you may redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <inttypes.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+#include <config.h>
+
+#include "cec-compliance.h"
+
+
+/* Dynamic Auto Lipsync */
+
+static int dal_request_current_latency(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ struct cec_msg msg = {};
+
+ cec_msg_init(&msg, me, la);
+ cec_msg_request_current_latency(&msg, true, node->remote[la].phys_addr);
+ fail_on_test(!transmit_timeout(node, &msg));
+ fail_on_test_v2(node->remote[la].cec_version,
+ timed_out(&msg) && is_tv(la, node->remote[la].prim_type));
+ if (timed_out(&msg))
+ return NOTSUPPORTED;
+
+ /* When the device supports Dynamic Auto Lipsync but does not implement
+ CEC 1.4b, or 2.0, a very strict subset of CEC can be supported. If we
+ get here and the version is < 1.4, we know that the device is not
+ complying to this specification. */
+ if (node->remote[la].cec_version < CEC_OP_CEC_VERSION_1_4) {
+ warn("CEC 2.0 specifies that devices with CEC version < 1.4 which implement\n");
+ warn("Dynamic Auto Lipsync shall only implement a very strict subset of CEC.\n");
+ }
+
+ __u16 phys_addr;
+ __u8 video_latency, low_latency_mode, audio_out_compensated, audio_out_delay;
+
+ cec_ops_report_current_latency(&msg, &phys_addr, &video_latency, &low_latency_mode,
+ &audio_out_compensated, &audio_out_delay);
+ fail_on_test(phys_addr != node->remote[la].phys_addr);
+ info("Video latency: %d\n", video_latency);
+ info("Low latency mode: %d\n", low_latency_mode);
+ info("Audio output compensation: %d\n", audio_out_compensated);
+ info("Audio out delay: %d\n", audio_out_delay);
+
+ return 0;
+}
+
+static int dal_req_current_latency_invalid(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ struct cec_msg msg = {};
+
+ /* Test that there is no reply when the physical address operand is not the
+ physical address of the remote device. */
+ cec_msg_init(&msg, me, la);
+ cec_msg_request_current_latency(&msg, true, CEC_PHYS_ADDR_INVALID);
+ fail_on_test(!transmit_timeout(node, &msg));
+ fail_on_test(!timed_out(&msg));
+
+ return 0;
+}
+
+struct remote_subtest dal_subtests[] = {
+ { "Request Current Latency", CEC_LOG_ADDR_MASK_ALL, dal_request_current_latency },
+ { "Request Current Latency with invalid PA", CEC_LOG_ADDR_MASK_ALL, dal_req_current_latency_invalid },
+};
+
+const unsigned dal_subtests_size = ARRAY_SIZE(dal_subtests);
+
+
+/* Audio Return Channel Control */
+
+static __u16 pa_common_mask(__u16 pa1, __u16 pa2)
+{
+ __u16 mask = 0xf000;
+
+ for (int i = 0; i < 3; i++) {
+ if ((pa1 & mask) != (pa2 & mask))
+ break;
+ mask = (mask >> 4) | 0xf000;
+ }
+ return mask << 4;
+}
+static bool pa_are_adjacent(__u16 pa1, __u16 pa2)
+{
+ const __u16 mask = pa_common_mask(pa1, pa2);
+ const __u16 trail_mask = ((~mask) & 0xffff) >> 4;
+
+ if (pa1 == CEC_PHYS_ADDR_INVALID || pa2 == CEC_PHYS_ADDR_INVALID || pa1 == pa2)
+ return false;
+ if ((pa1 & trail_mask) || (pa2 & trail_mask))
+ return false;
+ if (!((pa1 & ~mask) && (pa2 & ~mask)))
+ return true;
+ return false;
+}
+
+static bool pa_is_upstream_from(__u16 pa1, __u16 pa2)
+{
+ const __u16 mask = pa_common_mask(pa1, pa2);
+
+ if (pa1 == CEC_PHYS_ADDR_INVALID || pa2 == CEC_PHYS_ADDR_INVALID)
+ return false;
+ if (!(pa1 & ~mask) && (pa2 & ~mask))
+ return true;
+ return false;
+}
+
+static bool util_receive(struct node *node, unsigned la, struct cec_msg *msg,
+ __u8 sent_msg, __u8 reply1, __u8 reply2 = 0)
+{
+ while (1) {
+ memset(msg, 0, sizeof(*msg));
+ msg->timeout = 1;
+ if (doioctl(node, CEC_RECEIVE, msg))
+ break;
+ if (cec_msg_initiator(msg) != la)
+ continue;
+
+ if (msg->msg[1] == CEC_MSG_FEATURE_ABORT) {
+ __u8 reason, abort_msg;
+
+ cec_ops_feature_abort(msg, &abort_msg, &reason);
+ if (abort_msg != sent_msg)
+ continue;
+ return true;
+ }
+
+ if (msg->msg[1] == reply1 || (reply2 && msg->msg[1] == reply2))
+ return true;
+ }
+
+ return false;
+}
+
+static int arc_initiate_tx(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ /* Check if we are upstream from the device. If we are, then the device is
+ an HDMI source, which means that it is an ARC receiver, not a transmitter. */
+ if (pa_is_upstream_from(node->phys_addr, node->remote[la].phys_addr))
+ return NOTAPPLICABLE;
+
+ struct cec_msg msg = {};
+ bool got_response = false;
+
+ /* TODO: CEC framework should perhaps support more than one possible
+ reply opcode for a message.
+
+ An ARC Tx device can reply to the Initiate ARC message with either
+ Report ARC Initiated or Report ARC Terminated, in addition to Feature
+ Abort. The CEC framework currently doesn't handle several possible
+ reply messages, so when setting it to expect a reply, only Report ARC
+ Initiated will be recognized as a reply, and it will time out when
+ receiving Report ARC Terminated.
+
+ For now, this is handled manually by looping over the received messages
+ after sending Initiate ARC. */
+ __u32 mode = CEC_MODE_INITIATOR | CEC_MODE_FOLLOWER;
+
+ doioctl(node, CEC_S_MODE, &mode);
+ cec_msg_init(&msg, me, la);
+ cec_msg_initiate_arc(&msg, false);
+ fail_on_test(!transmit_timeout(node, &msg));
+ got_response = util_receive(node, la, &msg, CEC_MSG_INITIATE_ARC,
+ CEC_MSG_REPORT_ARC_INITIATED, CEC_MSG_REPORT_ARC_TERMINATED);
+ mode = CEC_MODE_INITIATOR;
+ doioctl(node, CEC_S_MODE, &mode);
+
+ if (!got_response || (got_response && unrecognized_op(&msg))) {
+ fail_on_test_v2(node->remote[la].cec_version, node->remote[la].has_arc_tx);
+ return NOTSUPPORTED;
+ }
+ if (cec_msg_opcode(&msg) == CEC_MSG_REPORT_ARC_INITIATED) {
+ fail_on_test(!pa_are_adjacent(node->phys_addr, node->remote[la].phys_addr));
+ fail_on_test_v2(node->remote[la].cec_version, !node->remote[la].has_arc_tx);
+ node->remote[la].arc_initiated = true;
+ }
+ else if (cec_msg_opcode(&msg) == CEC_MSG_REPORT_ARC_TERMINATED)
+ announce("Device supports ARC but is not ready to initiate.");
+ else if (refused(&msg))
+ return REFUSED;
+ else if (cec_msg_status_is_abort(&msg))
+ return PRESUMED_OK;
+
+ return 0;
+}
+
+static int arc_terminate_tx(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ /* Check if we are upstream from the device. If we are, then the device is
+ an HDMI source, which means that it is an ARC receiver, not a transmitter. */
+ if (pa_is_upstream_from(node->phys_addr, node->remote[la].phys_addr))
+ return NOTAPPLICABLE;
+ if (!node->remote[la].arc_initiated)
+ return NOTAPPLICABLE;
+
+ struct cec_msg msg = {};
+
+ cec_msg_init(&msg, me, la);
+ cec_msg_terminate_arc(&msg, true);
+ fail_on_test(!transmit_timeout(node, &msg));
+ if (timed_out(&msg)) {
+ warn("Timed out waiting for Report ARC Terminated.\n");
+ return PRESUMED_OK;
+ }
+ fail_on_test(unrecognized_op(&msg));
+ if (cec_msg_status_is_abort(&msg)) {
+ warn("Received Feature Abort for Terminate ARC (but the message was recognized).");
+ if (refused(&msg))
+ return REFUSED;
+ return PRESUMED_OK;
+ }
+
+ return 0;
+}
+
+static int arc_initiate_rx(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ /* Check if the DUT is upstream from us. If it is, then it is an
+ HDMI sink, which means that it is an ARC transmitter, not receiver. */
+ if (pa_is_upstream_from(node->remote[la].phys_addr, node->phys_addr))
+ return NOTAPPLICABLE;
+
+ struct cec_msg msg = {};
+
+ cec_msg_init(&msg, me, la);
+ cec_msg_request_arc_initiation(&msg, true);
+
+ bool unsupported = false;
+
+ fail_on_test(!transmit_timeout(node, &msg));
+ if (timed_out(&msg) || unrecognized_op(&msg))
+ unsupported = true;
+ else if (cec_msg_status_is_abort(&msg)) {
+ __u8 abort_msg, reason;
+
+ cec_ops_feature_abort(&msg, &abort_msg, &reason);
+ if (reason == CEC_OP_ABORT_INCORRECT_MODE) {
+ announce("The device supports ARC but is not ready to initiate.");
+ return 0;
+ }
+ else {
+ warn("Device responded Feature Abort with unexpected abort reason. Assuming no ARC support.\n");
+ unsupported = true;
+ }
+ }
+ if (unsupported) {
+ fail_on_test_v2(node->remote[la].cec_version, node->remote[la].has_arc_rx);
+ return NOTSUPPORTED;
+ }
+ fail_on_test(!pa_are_adjacent(node->phys_addr, node->remote[la].phys_addr));
+ fail_on_test_v2(node->remote[la].cec_version, !node->remote[la].has_arc_rx);
+
+ cec_msg_init(&msg, me, la);
+ cec_msg_report_arc_initiated(&msg);
+ fail_on_test(!transmit_timeout(node, &msg));
+ fail_on_test(unrecognized_op(&msg));
+ node->remote[la].arc_initiated = true;
+
+ return 0;
+}
+
+static int arc_terminate_rx(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ /* Check if the DUT is upstream from us. If it is, then it is an
+ HDMI sink, which means that it is an ARC transmitter, not receiver. */
+ if (pa_is_upstream_from(node->remote[la].phys_addr, node->phys_addr))
+ return NOTAPPLICABLE;
+ if (!node->remote[la].arc_initiated)
+ return NOTAPPLICABLE;
+
+ struct cec_msg msg = {};
+
+ cec_msg_init(&msg, me, la);
+ cec_msg_request_arc_termination(&msg, true);
+ fail_on_test(!transmit_timeout(node, &msg));
+ if (timed_out(&msg)) {
+ warn("Timed out waiting for Terminate ARC.\n");
+ return PRESUMED_OK;
+ }
+ fail_on_test(unrecognized_op(&msg));
+ if (cec_msg_status_is_abort(&msg)) {
+ warn("Received Feature Abort for Request ARC Termination (but the message was recognized).");
+ if (refused(&msg))
+ return REFUSED;
+ return PRESUMED_OK;
+ }
+
+ cec_msg_init(&msg, me, la);
+ cec_msg_report_arc_terminated(&msg);
+ fail_on_test(!transmit_timeout(node, &msg));
+ fail_on_test(unrecognized_op(&msg));
+
+ return 0;
+}
+
+struct remote_subtest arc_subtests[] = {
+ { "Initiate ARC (RX)", CEC_LOG_ADDR_MASK_ALL, arc_initiate_rx },
+ { "Terminate ARC (RX)", CEC_LOG_ADDR_MASK_ALL, arc_terminate_rx },
+ { "Initiate ARC (TX)", CEC_LOG_ADDR_MASK_ALL, arc_initiate_tx },
+ { "Terminate ARC (TX)", CEC_LOG_ADDR_MASK_ALL, arc_terminate_tx },
+};
+
+const unsigned arc_subtests_size = ARRAY_SIZE(arc_subtests);
+
+
+/* System Audio Control */
+
+static int sac_request_sad_probe(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ struct cec_msg msg = {};
+ __u8 audio_format_id = 0;
+ __u8 audio_format_code = 1;
+
+ cec_msg_init(&msg, me, la);
+ cec_msg_request_short_audio_descriptor(&msg, true, 1, &audio_format_id, &audio_format_code);
+ fail_on_test(!transmit_timeout(node, &msg));
+ fail_on_test(timed_out(&msg));
+ if (unrecognized_op(&msg))
+ return NOTSUPPORTED;
+ if (refused(&msg))
+ return REFUSED;
+ if (cec_msg_status_is_abort(&msg))
+ return PRESUMED_OK;
+ node->remote[la].has_sad = true;
+
+ return 0;
+}
+
+static int sac_request_sad_invalid(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ if (!node->remote[la].has_sad)
+ return NOTAPPLICABLE;
+
+ struct cec_msg msg = {};
+ __u8 audio_format_id = CEC_OP_AUD_FMT_ID_CEA861;
+ __u8 audio_format_code = 63; // This is outside the range of CEA861-F
+
+ cec_msg_init(&msg, me, la);
+ cec_msg_request_short_audio_descriptor(&msg, true, 1, &audio_format_id, &audio_format_code);
+ fail_on_test(!transmit_timeout(node, &msg));
+ fail_on_test(timed_out(&msg));
+ fail_on_test(!cec_msg_status_is_abort(&msg));
+ if (abort_reason(&msg) != CEC_OP_ABORT_INVALID_OP) {
+ warn("Expected Feature Abort [Invalid operand] in reply to Request Short\n");
+ warn("Audio Descriptor with invalid audio format as operand.\n");
+ }
+
+ return 0;
+}
+
+static int sac_sad_format_check(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ if (!node->remote[la].has_sad)
+ return NOTAPPLICABLE;
+
+ struct cec_msg msg = {};
+ __u8 audio_format_id;
+ __u8 audio_format_code;
+
+ for (unsigned int id = 0; id <= 1; id++) {
+ audio_format_id = id;
+ for (unsigned int fmt_code = 1; fmt_code <= 14; fmt_code++) {
+ audio_format_code = fmt_code;
+
+ cec_msg_init(&msg, me, la);
+ cec_msg_request_short_audio_descriptor(&msg, true, 1, &audio_format_id, &audio_format_code);
+ fail_on_test(!transmit_timeout(node, &msg));
+ fail_on_test(timed_out(&msg));
+ fail_on_test(unrecognized_op(&msg) || refused(&msg));
+
+ if (cec_msg_status_is_abort(&msg) &&
+ abort_reason(&msg) == CEC_OP_ABORT_INVALID_OP)
+ continue;
+
+ __u8 num_descriptors;
+ __u32 descriptors[4] = {};
+
+ cec_ops_report_short_audio_descriptor(&msg, &num_descriptors, descriptors);
+ fail_on_test(num_descriptors == 0);
+ if (id == 1 && node->remote[la].cec_version < CEC_OP_CEC_VERSION_2_0)
+ warn("The device has CEC version < 2.0 but reports audio format(s) introduced in CEC 2.0.\n");
+
+ for (int j = 0; j < num_descriptors; j++) {
+ struct short_audio_desc sad;
+
+ sad_decode(&sad, descriptors[j]);
+ if ((id == 0 && sad.format_code != fmt_code) ||
+ (id == 1 && sad.extension_type_code != fmt_code))
+ return fail("Different audio format code reported than requested.\n");
+ info("Supports format %s\n", short_audio_desc2s(sad).c_str());
+
+ /* We need to store the ID and Code for one of the audio formats found,
+ for use in later test(s) */
+ node->remote[la].supp_format_id = audio_format_id;
+ node->remote[la].supp_format_code = audio_format_code;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int sac_sad_req_multiple(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ if (!node->remote[la].has_sad || node->remote[la].supp_format_code == 0)
+ return NOTAPPLICABLE;
+
+ /* Check that if we got a response to a Request Short Audio Descriptor
+ with a single format, we also get a response when the same audio format
+ occurs in a request together with other formats. */
+ struct cec_msg msg = {};
+ __u8 audio_format_id[4];
+ __u8 audio_format_code[4];
+
+ for (int i = 0; i < 4; i++) {
+ if (node->remote[la].supp_format_code <= 12)
+ audio_format_code[i] = node->remote[la].supp_format_code - 1 + i;
+ else
+ audio_format_code[i] = node->remote[la].supp_format_code - 1 - i;
+ }
+ cec_msg_init(&msg, me, la);
+ cec_msg_request_short_audio_descriptor(&msg, true, 4, audio_format_id, audio_format_code);
+ fail_on_test(!transmit_timeout(node, &msg));
+ fail_on_test(timed_out(&msg));
+ fail_on_test(cec_msg_status_is_abort(&msg));
+
+ return 0;
+}
+
+static int sac_set_system_audio_mode_direct(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ struct cec_msg msg = {};
+
+ cec_msg_init(&msg, me, la);
+ cec_msg_set_system_audio_mode(&msg, CEC_OP_SYS_AUD_STATUS_ON);
+ fail_on_test(!transmit_timeout(node, &msg));
+ fail_on_test_v2(node->remote[la].cec_version,
+ unrecognized_op(&msg) && is_tv(la, node->remote[la].prim_type));
+ if (unrecognized_op(&msg))
+ return NOTSUPPORTED;
+ if (refused(&msg))
+ return REFUSED;
+ node->remote[la].has_set_sys_audio_mode = true;
+
+ return PRESUMED_OK;
+}
+
+static int sac_set_system_audio_mode_broadcast_on(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ struct cec_msg msg = {};
+
+ cec_msg_init(&msg, me, CEC_LOG_ADDR_BROADCAST);
+ cec_msg_set_system_audio_mode(&msg, CEC_OP_SYS_AUD_STATUS_ON);
+ fail_on_test(!transmit_timeout(node, &msg));
+
+ return PRESUMED_OK;
+}
+
+static int sac_system_audio_mode_status(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ struct cec_msg msg = {};
+
+ /* The device shall not feature abort System Audio Status if it did not
+ feature abort Set System Audio Mode.
+
+ The message is mandatory for TVs in CEC 2.0. */
+ cec_msg_init(&msg, me, la);
+ cec_msg_system_audio_mode_status(&msg, CEC_OP_SYS_AUD_STATUS_ON);
+ fail_on_test(!transmit_timeout(node, &msg));
+ fail_on_test_v2(node->remote[la].cec_version,
+ is_tv(la, node->remote[la].prim_type) && unrecognized_op(&msg));
+ if (unrecognized_op(&msg) && !node->remote[la].has_set_sys_audio_mode)
+ return NOTSUPPORTED;
+ fail_on_test(unrecognized_op(&msg));
+ if (refused(&msg))
+ return REFUSED;
+ if (cec_msg_status_is_abort(&msg))
+ return PRESUMED_OK;
+
+ return 0;
+}
+
+static int sac_set_system_audio_mode_broadcast_off(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ struct cec_msg msg = {};
+
+ cec_msg_init(&msg, me, CEC_LOG_ADDR_BROADCAST);
+ cec_msg_set_system_audio_mode(&msg, CEC_OP_SYS_AUD_STATUS_OFF);
+ fail_on_test(!transmit_timeout(node, &msg));
+
+ return PRESUMED_OK;
+}
+
+static int sac_system_audio_mode_req_on(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ struct cec_msg msg = {};
+ __u8 status;
+
+ /* Send a System Audio Mode Request to the audio system. This notifies the
+ audio system that our device has SAC capabilities, so it should enable
+ the feature right away by sending Set System Audio Mode with On as status.
+
+ The message is mandatory for audio systems in CEC 2.0. */
+ cec_msg_init(&msg, me, la);
+ cec_msg_system_audio_mode_request(&msg, true, node->phys_addr);
+ fail_on_test(!transmit_timeout(node, &msg));
+ fail_on_test(timed_out(&msg));
+ fail_on_test_v2(node->remote[la].cec_version,
+ cec_has_audiosystem(1 << la) && unrecognized_op(&msg));
+ if (unrecognized_op(&msg))
+ return NOTSUPPORTED;
+ if (refused(&msg))
+ return REFUSED;
+ if (cec_msg_status_is_abort(&msg))
+ return PRESUMED_OK;
+ node->remote[la].has_sys_audio_mode_req = true;
+ cec_ops_set_system_audio_mode(&msg, &status);
+ fail_on_test(status != CEC_OP_SYS_AUD_STATUS_ON);
+
+ return 0;
+}
+
+static int sac_give_system_audio_mode_status(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ struct cec_msg msg = {};
+ __u8 system_audio_status;
+
+ /* The device shall not feature abort Give System Audio Mode Status if it did not
+ feature abort System Audio Mode Request.
+
+ The message is mandatory for audio systems in CEC 2.0. */
+ cec_msg_init(&msg, me, la);
+ cec_msg_give_system_audio_mode_status(&msg, true);
+ fail_on_test(!transmit_timeout(node, &msg));
+ fail_on_test(timed_out(&msg));
+ fail_on_test_v2(node->remote[la].cec_version,
+ cec_has_audiosystem(1 << la) && unrecognized_op(&msg));
+ if (unrecognized_op(&msg) && !node->remote[la].has_sys_audio_mode_req)
+ return NOTSUPPORTED;
+ fail_on_test(unrecognized_op(&msg));
+ if (refused(&msg))
+ return REFUSED;
+ if (cec_msg_status_is_abort(&msg))
+ return PRESUMED_OK;
+ cec_ops_system_audio_mode_status(&msg, &system_audio_status);
+ fail_on_test(system_audio_status != CEC_OP_SYS_AUD_STATUS_ON);
+
+ return 0;
+}
+
+static int sac_give_audio_status(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ struct cec_msg msg = {};
+
+ /* Give Audio Status is mandatory for audio systems in CEC 2.0, except
+ for systems that lack external controls for volume/mute status. */
+ cec_msg_init(&msg, me, la);
+ cec_msg_give_audio_status(&msg, true);
+ fail_on_test(!transmit_timeout(node, &msg));
+ fail_on_test(timed_out(&msg));
+ fail_on_test_v2(node->remote[la].cec_version,
+ cec_has_audiosystem(1 << la) && unrecognized_op(&msg));
+ if (unrecognized_op(&msg))
+ return NOTSUPPORTED;
+ if (refused(&msg))
+ return REFUSED;
+ if (cec_msg_status_is_abort(&msg))
+ return PRESUMED_OK;
+
+ cec_ops_report_audio_status(&msg, &node->remote[la].mute, &node->remote[la].volume);
+ fail_on_test(node->remote[la].volume > 100);
+ info("Volume: %d %s\n", node->remote[la].volume, node->remote[la].mute ? "(muted)" : "");
+
+ return 0;
+}
+
+static int sac_util_send_user_control_press(struct node *node, unsigned me, unsigned la, __u8 ui_cmd)
+{
+ struct cec_msg msg = {};
+ struct cec_op_ui_command rc_press = {};
+
+ /* The device shall not feature abort
+ - User Control Pressed ["Volume Up"]
+ - User Control Pressed ["Volume Down"]
+ - User Control Pressed ["Mute"]
+ if it did not feature abort System Audio Mode Request.
+
+ The messages are mandatory for audio systems and TVs in CEC 2.0,
+ and it is mandatory for audio systems to send Report Audio Status
+ back to the TV in CEC 2.0.
+
+ It is recommended for devices to not send Report Audio Status back
+ more often than once every 500ms. We therefore sleep a second before
+ each User Control Pressed is sent. */
+ __u32 mode = CEC_MODE_INITIATOR | CEC_MODE_FOLLOWER;
+ bool got_response;
+
+ sleep(1);
+ doioctl(node, CEC_S_MODE, &mode);
+ cec_msg_init(&msg, me, la);
+ rc_press.ui_cmd = ui_cmd;
+ cec_msg_user_control_pressed(&msg, &rc_press);
+ fail_on_test(!transmit(node, &msg));
+ cec_msg_init(&msg, me, la);
+ cec_msg_user_control_released(&msg);
+ fail_on_test(!transmit_timeout(node, &msg));
+ got_response = util_receive(node, la, &msg, CEC_MSG_USER_CONTROL_PRESSED, CEC_MSG_REPORT_AUDIO_STATUS);
+ mode = CEC_MODE_INITIATOR;
+ doioctl(node, CEC_S_MODE, &mode);
+
+ fail_on_test_v2(node->remote[la].cec_version, !got_response &&
+ cec_has_audiosystem(1 << la));
+ fail_on_test_v2(node->remote[la].cec_version, unrecognized_op(&msg) &&
+ (is_tv(la, node->remote[la].prim_type) || cec_has_audiosystem(1 << la)));
+ if (unrecognized_op(&msg) && !node->remote[la].has_sys_audio_mode_req)
+ return NOTSUPPORTED;
+ fail_on_test(unrecognized_op(&msg));
+ if (refused(&msg))
+ return REFUSED;
+ if (cec_msg_status_is_abort(&msg))
+ return PRESUMED_OK;
+ if (got_response) {
+ cec_ops_report_audio_status(&msg, &node->remote[la].mute, &node->remote[la].volume);
+ return 0;
+ }
+
+ return PRESUMED_OK;
+}
+
+static int sac_user_control_press_vol_up(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ __u8 ret, old_volume = node->remote[la].volume;
+
+ if ((ret = sac_util_send_user_control_press(node, me, la, 0x41)))
+ return ret;
+ /* Check that if not already at the highest, the volume was increased. */
+ fail_on_test_v2(node->remote[la].cec_version,
+ la == CEC_LOG_ADDR_AUDIOSYSTEM &&
+ old_volume < 100 && node->remote[la].volume <= old_volume);
+
+ return 0;
+}
+
+static int sac_user_control_press_vol_down(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ __u8 ret, old_volume = node->remote[la].volume;
+
+ if ((ret = sac_util_send_user_control_press(node, me, la, 0x42)))
+ return ret;
+ /* Check that if not already at the lowest, the volume was lowered. */
+ fail_on_test_v2(node->remote[la].cec_version,
+ la == CEC_LOG_ADDR_AUDIOSYSTEM &&
+ old_volume > 0 && node->remote[la].volume >= old_volume);
+
+ return 0;
+}
+
+static int sac_user_control_press_mute(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ __u8 ret, old_mute = node->remote[la].mute;
+
+ if ((ret = sac_util_send_user_control_press(node, me, la, 0x43)))
+ return ret;
+ /* Check that mute has been toggled from what it was before. */
+ fail_on_test_v2(node->remote[la].cec_version,
+ la == CEC_LOG_ADDR_AUDIOSYSTEM &&
+ node->remote[la].mute == old_mute);
+
+ return 0;
+}
+
+static int sac_user_control_release(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ struct cec_msg msg = {};
+
+ /* The device shall not feature abort User Control Released if it did not
+ feature abort System Audio Mode Request
+
+ The message is mandatory for audio systems and TVs in CEC 2.0. */
+ cec_msg_init(&msg, me, la);
+ cec_msg_user_control_released(&msg);
+ fail_on_test(!transmit_timeout(node, &msg));
+ fail_on_test_v2(node->remote[la].cec_version, unrecognized_op(&msg) &&
+ (is_tv(la, node->remote[la].prim_type) || cec_has_audiosystem(1 << la)));
+ if (unrecognized_op(&msg) && !node->remote[la].has_sys_audio_mode_req)
+ return NOTSUPPORTED;
+ fail_on_test(unrecognized_op(&msg));
+ if (refused(&msg))
+ return REFUSED;
+ if (cec_msg_status_is_abort(&msg))
+ return PRESUMED_OK;
+
+ return PRESUMED_OK;
+}
+
+static int sac_system_audio_mode_req_off(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ if (!node->remote[la].has_sys_audio_mode_req)
+ return NOTAPPLICABLE;
+
+ struct cec_msg msg = {};
+ __u8 status;
+
+ cec_msg_init(&msg, me, la);
+ cec_msg_system_audio_mode_request(&msg, true, CEC_PHYS_ADDR_INVALID);
+ fail_on_test(!transmit_timeout(node, &msg));
+ fail_on_test(timed_out(&msg));
+ fail_on_test_v2(node->remote[la].cec_version,
+ cec_has_audiosystem(1 << la) && unrecognized_op(&msg));
+ if (unrecognized_op(&msg))
+ return NOTSUPPORTED;
+ if (refused(&msg))
+ return REFUSED;
+ if (cec_msg_status_is_abort(&msg))
+ return PRESUMED_OK;
+ cec_ops_set_system_audio_mode(&msg, &status);
+ fail_on_test(status != CEC_OP_SYS_AUD_STATUS_OFF);
+
+ return 0;
+}
+
+struct remote_subtest sac_subtests[] = {
+ { "Request Short Audio Descriptor",
+ CEC_LOG_ADDR_MASK_AUDIOSYSTEM,
+ sac_request_sad_probe },
+ { "Request Short Audio Descriptor, invalid",
+ CEC_LOG_ADDR_MASK_AUDIOSYSTEM,
+ sac_request_sad_invalid },
+ { "Report Short Audio Descriptor consistency",
+ CEC_LOG_ADDR_MASK_AUDIOSYSTEM,
+ sac_sad_format_check },
+ { "Report Short Audio Descriptor, multiple requests in one",
+ CEC_LOG_ADDR_MASK_AUDIOSYSTEM,
+ sac_sad_req_multiple },
+ { "Set System Audio Mode (directly addressed)",
+ CEC_LOG_ADDR_MASK_TV,
+ sac_set_system_audio_mode_direct },
+ { "Set System Audio Mode (broadcast on)",
+ CEC_LOG_ADDR_MASK_TV,
+ sac_set_system_audio_mode_broadcast_on },
+ { "System Audio Mode Status",
+ CEC_LOG_ADDR_MASK_TV,
+ sac_system_audio_mode_status },
+ { "System Audio Mode Request (on)",
+ CEC_LOG_ADDR_MASK_AUDIOSYSTEM,
+ sac_system_audio_mode_req_on },
+ { "Give System Audio Mode Status",
+ CEC_LOG_ADDR_MASK_AUDIOSYSTEM,
+ sac_give_system_audio_mode_status },
+ { "Give Audio Status",
+ CEC_LOG_ADDR_MASK_AUDIOSYSTEM,
+ sac_give_audio_status },
+ { "User Control Pressed (Volume Up)",
+ CEC_LOG_ADDR_MASK_AUDIOSYSTEM | CEC_LOG_ADDR_MASK_TV,
+ sac_user_control_press_vol_up },
+ { "User Control Pressed (Volume Down)",
+ CEC_LOG_ADDR_MASK_AUDIOSYSTEM | CEC_LOG_ADDR_MASK_TV,
+ sac_user_control_press_vol_down },
+ { "User Control Pressed (Mute)",
+ CEC_LOG_ADDR_MASK_AUDIOSYSTEM | CEC_LOG_ADDR_MASK_TV,
+ sac_user_control_press_mute },
+ { "User Control Released",
+ CEC_LOG_ADDR_MASK_AUDIOSYSTEM | CEC_LOG_ADDR_MASK_TV,
+ sac_user_control_release },
+ { "Set System Audio Mode (broadcast off)",
+ CEC_LOG_ADDR_MASK_TV,
+ sac_set_system_audio_mode_broadcast_off },
+ { "System Audio Mode Request (off)",
+ CEC_LOG_ADDR_MASK_AUDIOSYSTEM,
+ sac_system_audio_mode_req_off },
+};
+
+const unsigned sac_subtests_size = ARRAY_SIZE(sac_subtests);
+
+
+/* Audio Rate Control */
+
+/*
+ TODO: These are very rudimentary tests which should be expanded.
+ */
+
+static int audio_rate_ctl_set_audio_rate(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ struct cec_msg msg = {};
+
+ cec_msg_init(&msg, me, la);
+ cec_msg_set_audio_rate(&msg, CEC_OP_AUD_RATE_WIDE_STD);
+ fail_on_test(!transmit_timeout(node, &msg));
+ /* CEC 2.0: Devices shall use the device feature bit to indicate support. */
+ fail_on_test_v2(node->remote[la].cec_version,
+ node->remote[la].has_aud_rate && unrecognized_op(&msg));
+ fail_on_test_v2(node->remote[la].cec_version,
+ !node->remote[la].has_aud_rate && !unrecognized_op(&msg));
+ if (unrecognized_op(&msg))
+ return NOTSUPPORTED;
+ if (refused(&msg))
+ return REFUSED;
+
+ return PRESUMED_OK;
+}
+
+struct remote_subtest audio_rate_ctl_subtests[] = {
+ { "Set Audio Rate",
+ CEC_LOG_ADDR_MASK_PLAYBACK | CEC_LOG_ADDR_MASK_RECORD | CEC_LOG_ADDR_MASK_TUNER,
+ audio_rate_ctl_set_audio_rate },
+};
+
+const unsigned audio_rate_ctl_subtests_size = ARRAY_SIZE(audio_rate_ctl_subtests);
diff --git a/utils/cec-compliance/cec-test-power.cpp b/utils/cec-compliance/cec-test-power.cpp
new file mode 100644
index 00000000..e88e3387
--- /dev/null
+++ b/utils/cec-compliance/cec-test-power.cpp
@@ -0,0 +1,441 @@
+/*
+ * Copyright 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
+ *
+ * This program is free software; you may redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <inttypes.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+#include <config.h>
+
+#include "cec-compliance.h"
+
+
+static bool get_power_status(struct node *node, unsigned me, unsigned la, __u8 &power_status)
+{
+ struct cec_msg msg = {};
+
+ cec_msg_init(&msg, me, la);
+ cec_msg_give_device_power_status(&msg, true);
+ if (!transmit_timeout(node, &msg) || timed_out_or_abort(&msg))
+ return false;
+ cec_ops_report_power_status(&msg, &power_status);
+ return true;
+}
+
+bool util_interactive_ensure_power_state(struct node *node, unsigned me, unsigned la, bool interactive,
+ __u8 target_pwr)
+{
+ interactive_info(true, "Please ensure that the device is in state %s.",
+ power_status2s(target_pwr));
+
+ if (!node->remote[la].has_power_status)
+ return true;
+
+ while (interactive) {
+ __u8 pwr;
+
+ if (!get_power_status(node, me, la, pwr))
+ announce("Failed to retrieve power status.");
+ else if (pwr == target_pwr)
+ return true;
+ else
+ announce("The device reported power status %s.", power_status2s(pwr));
+ fail_on_test(!question("Retry?"));
+ }
+
+ return true;
+}
+
+
+/* Give Device Power Status */
+
+static int power_status_give(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ struct cec_msg msg = { };
+
+ cec_msg_init(&msg, me, la);
+ cec_msg_give_device_power_status(&msg, true);
+ fail_on_test(!transmit_timeout(node, &msg));
+ fail_on_test(timed_out(&msg));
+ fail_on_test(unrecognized_op(&msg));
+ if (refused(&msg))
+ return REFUSED;
+ if (cec_msg_status_is_abort(&msg))
+ return PRESUMED_OK;
+
+ __u8 power_status;
+ cec_ops_report_power_status(&msg, &power_status);
+ fail_on_test(power_status >= 4);
+
+ return 0;
+}
+
+static int power_status_report(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ struct cec_msg msg = {};
+
+ cec_msg_init(&msg, me, la);
+ cec_msg_report_power_status(&msg, CEC_OP_POWER_STATUS_ON);
+ fail_on_test(!transmit_timeout(node, &msg));
+ if (unrecognized_op(&msg))
+ return NOTSUPPORTED;
+ if (refused(&msg))
+ return REFUSED;
+
+ return PRESUMED_OK;
+}
+
+struct remote_subtest power_status_subtests[] = {
+ { "Give Device Power Status", CEC_LOG_ADDR_MASK_ALL, power_status_give },
+ { "Report Device Power Status", CEC_LOG_ADDR_MASK_ALL, power_status_report },
+};
+
+const unsigned power_status_subtests_size = ARRAY_SIZE(power_status_subtests);
+
+
+/* One Touch Play */
+
+static int one_touch_play_image_view_on(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ struct cec_msg msg = {};
+
+ fail_on_test(!util_interactive_ensure_power_state(node, me, la, interactive, CEC_OP_POWER_STATUS_STANDBY));
+
+ cec_msg_init(&msg, me, la);
+ cec_msg_image_view_on(&msg);
+ fail_on_test(!transmit_timeout(node, &msg));
+ fail_on_test(is_tv(la, node->remote[la].prim_type) && unrecognized_op(&msg));
+ if (refused(&msg))
+ return REFUSED;
+ if (cec_msg_status_is_abort(&msg))
+ return PRESUMED_OK;
+ fail_on_test(interactive && !question("Did the TV turn on?"));
+ node->remote[la].has_image_view_on = true;
+
+ if (interactive)
+ return 0;
+ else
+ return PRESUMED_OK;
+}
+
+static int one_touch_play_text_view_on(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ struct cec_msg msg = {};
+
+ fail_on_test(!util_interactive_ensure_power_state(node, me, la, interactive, CEC_OP_POWER_STATUS_STANDBY));
+
+ cec_msg_init(&msg, me, la);
+ cec_msg_text_view_on(&msg);
+ fail_on_test(!transmit_timeout(node, &msg));
+ fail_on_test(is_tv(la, node->remote[la].prim_type) && unrecognized_op(&msg));
+ if (refused(&msg))
+ return REFUSED;
+ if (cec_msg_status_is_abort(&msg))
+ return PRESUMED_OK;
+ fail_on_test(interactive && !question("Did the TV turn on?"));
+ node->remote[la].has_text_view_on = true;
+
+ if (interactive)
+ return 0;
+ else
+ return PRESUMED_OK;
+}
+
+static int one_touch_play_active_source(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ struct cec_msg msg = {};
+
+ while (interactive) {
+ __u8 pwr;
+
+ interactive_info(true, "Please switch the TV to another source.");
+ fail_on_test(!get_power_status(node, me, la, pwr));
+ if (pwr == CEC_OP_POWER_STATUS_ON)
+ break;
+ announce("The device reported power status %s.", power_status2s(pwr));
+ fail_on_test(!question("Retry?"));
+ }
+
+ cec_msg_init(&msg, me, la);
+ cec_msg_active_source(&msg, node->phys_addr);
+ fail_on_test(!transmit_timeout(node, &msg));
+ fail_on_test(interactive && !question("Did the TV switch to this source?"));
+
+ if (interactive)
+ return 0;
+ else
+ return PRESUMED_OK;
+}
+
+static int one_touch_play_req_active_source(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ struct cec_msg msg = {};
+
+ /* We have now said that we are active source, so receiving a reply to
+ Request Active Source should fail the test. */
+ cec_msg_init(&msg, me, la);
+ cec_msg_request_active_source(&msg, true);
+ fail_on_test(!transmit_timeout(node, &msg));
+ fail_on_test(!timed_out(&msg));
+
+ return 0;
+}
+
+struct remote_subtest one_touch_play_subtests[] = {
+ { "Image View On", CEC_LOG_ADDR_MASK_TV, one_touch_play_image_view_on },
+ { "Text View On", CEC_LOG_ADDR_MASK_TV, one_touch_play_text_view_on },
+ { "Active Source", CEC_LOG_ADDR_MASK_TV | CEC_LOG_ADDR_MASK_UNREGISTERED, one_touch_play_active_source },
+ { "Request Active Source", (__u16)~CEC_LOG_ADDR_MASK_TV, one_touch_play_req_active_source },
+};
+
+const unsigned one_touch_play_subtests_size = ARRAY_SIZE(one_touch_play_subtests);
+
+
+/* Standby / Resume */
+
+/* The default sleep time between power status requests. */
+#define SLEEP_POLL_POWER_STATUS 2
+
+static bool wait_changing_power_status(struct node *node, unsigned me, unsigned la, __u8 &new_status,
+ unsigned &unresponsive_time)
+{
+ __u8 old_status;
+
+ announce("Checking for power status change. This may take up to 60 s.");
+ if (!get_power_status(node, me, la, old_status))
+ return false;
+ for (unsigned i = 0; i < 60 / SLEEP_POLL_POWER_STATUS; i++) {
+ __u8 power_status;
+
+ if (!get_power_status(node, me, la, power_status)) {
+ /* Some TVs become completely unresponsive when transitioning
+ between power modes. Register that this happens, but continue
+ the test. */
+ unresponsive_time = i * SLEEP_POLL_POWER_STATUS;
+ } else if (old_status != power_status) {
+ new_status = power_status;
+ return true;
+ }
+ sleep(SLEEP_POLL_POWER_STATUS);
+ }
+ new_status = old_status;
+ return false;
+}
+
+static bool poll_stable_power_status(struct node *node, unsigned me, unsigned la,
+ __u8 expected_status, unsigned &unresponsive_time)
+{
+ bool transient = false;
+ unsigned time_to_transient = 0;
+
+ /* Some devices can use several seconds to transition from one power
+ state to another, so the power state must be repeatedly polled */
+ announce("Waiting for new stable power status. This may take up to 60 s.");
+ for (unsigned tries = 0; tries < 60 / SLEEP_POLL_POWER_STATUS; tries++) {
+ __u8 power_status;
+
+ if (!get_power_status(node, me, la, power_status)) {
+ /* Some TVs become completely unresponsive when transitioning
+ between power modes. Register that this happens, but continue
+ the test. */
+ unresponsive_time = tries * SLEEP_POLL_POWER_STATUS;
+ }
+ if (!transient && (power_status == CEC_OP_POWER_STATUS_TO_ON ||
+ power_status == CEC_OP_POWER_STATUS_TO_STANDBY)) {
+ time_to_transient = tries * SLEEP_POLL_POWER_STATUS;
+ transient = true;
+ }
+ if (power_status == expected_status) {
+ announce("Transient state after %d s, stable state %s after %d s",
+ time_to_transient, power_status2s(power_status), tries * SLEEP_POLL_POWER_STATUS);
+ return true;
+ }
+ sleep(SLEEP_POLL_POWER_STATUS);
+ }
+ return false;
+}
+
+static int standby_resume_standby(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ if (!node->remote[la].has_power_status)
+ return NOTAPPLICABLE;
+
+ struct cec_msg msg = {};
+ unsigned unresponsive_time = 0;
+
+ fail_on_test(!util_interactive_ensure_power_state(node, me, la, interactive, CEC_OP_POWER_STATUS_ON));
+
+ announce("Sending Standby message.");
+ cec_msg_init(&msg, me, la);
+ cec_msg_standby(&msg);
+ fail_on_test(!transmit_timeout(node, &msg));
+ fail_on_test(cec_msg_status_is_abort(&msg));
+ fail_on_test(!poll_stable_power_status(node, me, la, CEC_OP_POWER_STATUS_STANDBY, unresponsive_time));
+ fail_on_test(interactive && !question("Is the device in standby?"));
+ node->remote[la].in_standby = true;
+
+ if (unresponsive_time > 0)
+ return fail("The device went correctly into standby, but became unresponsive for %d s during the transition.\n",
+ unresponsive_time);
+
+ return 0;
+}
+
+static int standby_resume_standby_toggle(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ if (!node->remote[la].in_standby)
+ return NOTAPPLICABLE;
+
+ struct cec_msg msg = {};
+ unsigned unresponsive_time = 0;
+ __u8 new_status;
+
+ node->remote[la].in_standby = false;
+
+ /* Send Standby again to test that it is not acting like a toggle */
+ announce("Sending Standby message.");
+ cec_msg_init(&msg, me, la);
+ cec_msg_standby(&msg);
+ fail_on_test(!transmit_timeout(node, &msg));
+ fail_on_test(cec_msg_status_is_abort(&msg));
+ fail_on_test(wait_changing_power_status(node, me, la, new_status, unresponsive_time));
+ fail_on_test(new_status != CEC_OP_POWER_STATUS_STANDBY);
+ fail_on_test(interactive && !question("Is the device still in standby?"));
+ node->remote[la].in_standby = true;
+ if (unresponsive_time > 0)
+ return fail("The device went correctly into standby, but became unresponsive for %d s during the transition.\n",
+ unresponsive_time);
+
+ return 0;
+}
+
+static int standby_resume_active_source_nowake(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ if (!node->remote[la].in_standby)
+ return NOTAPPLICABLE;
+
+ struct cec_msg msg = {};
+ unsigned unresponsive_time = 0;
+ __u8 new_status;
+
+ node->remote[la].in_standby = false;
+
+ /* In CEC 2.0 it is specified that a device shall not go out of standby
+ if an Active Source message is received. */
+ announce("Sending Active Source message.");
+ cec_msg_init(&msg, me, la);
+ cec_msg_active_source(&msg, node->phys_addr);
+ fail_on_test(!transmit_timeout(node, &msg));
+ fail_on_test(wait_changing_power_status(node, me, la, new_status, unresponsive_time));
+ fail_on_test_v2_warn(node->remote[la].cec_version, new_status != CEC_OP_POWER_STATUS_STANDBY);
+ node->remote[la].in_standby = true;
+ if (unresponsive_time > 0)
+ return fail("The device stayed correctly in standby, but became unresponsive for %d s.\n",
+ unresponsive_time);
+
+ return 0;
+}
+
+static int wakeup_rc(struct node *node, unsigned me, unsigned la)
+{
+ struct cec_msg msg = {};
+ struct cec_op_ui_command rc_press = {};
+
+ /* Todo: A release should be sent after this */
+ cec_msg_init(&msg, me, la);
+ rc_press.ui_cmd = 0x6D; /* Power On Function */
+ cec_msg_user_control_pressed(&msg, &rc_press);
+ fail_on_test(!transmit_timeout(node, &msg));
+ fail_on_test(cec_msg_status_is_abort(&msg));
+
+ return 0;
+}
+
+static int wakeup_tv(struct node *node, unsigned me, unsigned la)
+{
+ struct cec_msg msg = {};
+
+ cec_msg_init(&msg, me, la);
+ cec_msg_image_view_on(&msg);
+ fail_on_test(!transmit_timeout(node, &msg));
+ if (!cec_msg_status_is_abort(&msg))
+ return 0;
+
+ cec_msg_init(&msg, me, la);
+ cec_msg_text_view_on(&msg);
+ fail_on_test(!transmit_timeout(node, &msg));
+ if (!cec_msg_status_is_abort(&msg))
+ return 0;
+
+ return wakeup_rc(node, me, la);
+}
+
+static int wakeup_source(struct node *node, unsigned me, unsigned la)
+{
+ struct cec_msg msg = {};
+
+ cec_msg_init(&msg, me, la);
+ cec_msg_set_stream_path(&msg, node->remote[la].phys_addr);
+ fail_on_test(!transmit_timeout(node, &msg));
+ if (!cec_msg_status_is_abort(&msg))
+ return 0;
+
+ return wakeup_rc(node, me, la);
+}
+
+static int standby_resume_wakeup(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ if (!node->remote[la].in_standby)
+ return NOTAPPLICABLE;
+
+ int ret;
+
+ if (is_tv(la, node->remote[la].prim_type))
+ ret = wakeup_tv(node, me, la);
+ else
+ ret = wakeup_source(node, me, la);
+ if (ret)
+ return ret;
+
+ unsigned unresponsive_time = 0;
+
+ announce("Device is woken up");
+ fail_on_test(!poll_stable_power_status(node, me, la, CEC_OP_POWER_STATUS_ON, unresponsive_time));
+ fail_on_test(interactive && !question("Is the device in On state?"));
+
+ if (unresponsive_time > 0)
+ return fail("The device went correctly out of standby, but became unresponsive for %d s during the transition.",
+ unresponsive_time);
+
+ return 0;
+}
+
+struct remote_subtest standby_resume_subtests[] = {
+ { "Standby", CEC_LOG_ADDR_MASK_ALL, standby_resume_standby },
+ { "Repeated Standby message does not wake up", CEC_LOG_ADDR_MASK_ALL, standby_resume_standby_toggle },
+ { "No wakeup on Active Source", CEC_LOG_ADDR_MASK_ALL, standby_resume_active_source_nowake },
+ { "Wake up", CEC_LOG_ADDR_MASK_ALL, standby_resume_wakeup },
+};
+
+const unsigned standby_resume_subtests_size = ARRAY_SIZE(standby_resume_subtests);
diff --git a/utils/cec-compliance/cec-test.cpp b/utils/cec-compliance/cec-test.cpp
new file mode 100644
index 00000000..5fac04a9
--- /dev/null
+++ b/utils/cec-compliance/cec-test.cpp
@@ -0,0 +1,1493 @@
+/*
+ * Copyright 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
+ *
+ * This program is free software; you may redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <inttypes.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+#include <config.h>
+#include <sstream>
+
+#include "cec-compliance.h"
+
+/*
+ * cec-funcs.h doesn't have the reply argument for these CEC messages.
+ * A fix is pending but not yet merged. Once it is merged this workaround
+ * can be removed.
+ */
+static inline void cec_msg_record_on_reply(struct cec_msg *msg,
+ bool reply,
+ const struct cec_op_record_src *rec_src)
+{
+ switch (rec_src->type) {
+ case CEC_OP_RECORD_SRC_OWN:
+ cec_msg_record_on_own(msg);
+ break;
+ case CEC_OP_RECORD_SRC_DIGITAL:
+ cec_msg_record_on_digital(msg, &rec_src->digital);
+ break;
+ case CEC_OP_RECORD_SRC_ANALOG:
+ cec_msg_record_on_analog(msg,
+ rec_src->analog.ana_bcast_type,
+ rec_src->analog.ana_freq,
+ rec_src->analog.bcast_system);
+ break;
+ case CEC_OP_RECORD_SRC_EXT_PLUG:
+ cec_msg_record_on_plug(msg, rec_src->ext_plug.plug);
+ break;
+ case CEC_OP_RECORD_SRC_EXT_PHYS_ADDR:
+ cec_msg_record_on_phys_addr(msg,
+ rec_src->ext_phys_addr.phys_addr);
+ break;
+ }
+ msg->reply = reply ? CEC_MSG_RECORD_STATUS : 0;
+}
+#define cec_msg_record_on cec_msg_record_on_reply
+
+static inline void cec_msg_record_off_reply(struct cec_msg *msg, bool reply)
+{
+ msg->len = 2;
+ msg->msg[1] = CEC_MSG_RECORD_OFF;
+ msg->reply = reply ? CEC_MSG_RECORD_STATUS : 0;
+}
+#define cec_msg_record_off cec_msg_record_off_reply
+
+#define test_case(name, tags, subtests) {name, tags, subtests, ARRAY_SIZE(subtests)}
+#define test_case_ext(name, tags, subtests) {name, tags, subtests, subtests##_size}
+
+struct remote_test {
+ const char *name;
+ const unsigned tags;
+ struct remote_subtest *subtests;
+ unsigned num_subtests;
+};
+
+
+/* System Information */
+
+static int system_info_polling(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ struct cec_msg msg = { };
+
+ cec_msg_init(&msg, 0xf, la);
+ fail_on_test(doioctl(node, CEC_TRANSMIT, &msg));
+ if (node->remote_la_mask & (1 << la)) {
+ if (!cec_msg_status_is_ok(&msg))
+ return FAIL_CRITICAL;
+ } else {
+ if (cec_msg_status_is_ok(&msg))
+ return FAIL_CRITICAL;
+ return NOTSUPPORTED;
+ }
+
+ return 0;
+}
+
+static int system_info_phys_addr(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ struct cec_msg msg = { };
+
+ cec_msg_init(&msg, me, la);
+ cec_msg_give_physical_addr(&msg, true);
+ if (!transmit_timeout(node, &msg) || timed_out_or_abort(&msg))
+ return FAIL_CRITICAL;
+
+ return 0;
+}
+
+static int system_info_version(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ struct cec_msg msg = {};
+
+ cec_msg_init(&msg, me, la);
+ cec_msg_get_cec_version(&msg, true);
+ fail_on_test(!transmit_timeout(node, &msg));
+ fail_on_test(timed_out(&msg));
+ if (unrecognized_op(&msg))
+ return NOTSUPPORTED;
+ if (refused(&msg))
+ return REFUSED;
+
+ /* This needs to be kept in sync with newer CEC versions */
+ fail_on_test(msg.msg[2] < CEC_OP_CEC_VERSION_1_3A ||
+ msg.msg[2] > CEC_OP_CEC_VERSION_2_0);
+
+ return 0;
+}
+
+static int system_info_get_menu_lang(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ struct cec_msg msg = {};
+ char language[4];
+
+ cec_msg_init(&msg, me, la);
+ cec_msg_get_menu_language(&msg, true);
+ fail_on_test(!transmit_timeout(node, &msg));
+ fail_on_test(timed_out(&msg));
+
+ /* Devices other than TVs shall send Feature Abort [Unregcognized Opcode]
+ in reply to Get Menu Language. */
+ fail_on_test(!is_tv(la, node->remote[la].prim_type) && !unrecognized_op(&msg));
+
+ if (unrecognized_op(&msg)) {
+ if (is_tv(la, node->remote[la].prim_type))
+ warn("TV did not respond to Get Menu Language.\n");
+ return NOTSUPPORTED;
+ }
+ if (refused(&msg))
+ return REFUSED;
+ if (cec_msg_status_is_abort(&msg))
+ return PRESUMED_OK;
+ cec_ops_set_menu_language(&msg, language);
+ language[3] = 0;
+
+ return 0;
+}
+
+static int system_info_set_menu_lang(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ struct cec_msg msg = {};
+
+ cec_msg_init(&msg, me, la);
+ cec_msg_set_menu_language(&msg, "eng");
+ fail_on_test(!transmit_timeout(node, &msg));
+ if (unrecognized_op(&msg))
+ return NOTSUPPORTED;
+ if (refused(&msg))
+ return REFUSED;
+
+ return PRESUMED_OK;
+}
+
+static int system_info_give_features(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ struct cec_msg msg = { };
+
+ cec_msg_init(&msg, me, la);
+ cec_msg_give_features(&msg, true);
+ fail_on_test(!transmit_timeout(node, &msg));
+ fail_on_test(timed_out(&msg));
+ if (unrecognized_op(&msg)) {
+ if (node->remote[la].cec_version < CEC_OP_CEC_VERSION_2_0)
+ return NOTSUPPORTED;
+ fail_on_test_v2(node->remote[la].cec_version, true);
+ }
+ if (refused(&msg))
+ return REFUSED;
+ if (node->remote[la].cec_version < CEC_OP_CEC_VERSION_2_0)
+ info("Device has CEC Version < 2.0 but supports Give Features.\n");
+
+ /* RC Profile and Device Features are assumed to be 1 byte. As of CEC 2.0 only
+ 1 byte is used, but this might be extended in future versions. */
+ __u8 cec_version, all_device_types;
+ const __u8 *rc_profile, *dev_features;
+
+ cec_ops_report_features(&msg, &cec_version, &all_device_types, &rc_profile, &dev_features);
+ fail_on_test(cec_version != node->remote[la].cec_version);
+ fail_on_test(rc_profile == NULL || dev_features == NULL);
+ info("All Device Types: \t\t%s\n", all_dev_types2s(all_device_types).c_str());
+ info("RC Profile: \t%s", rc_src_prof2s(*rc_profile).c_str());
+ info("Device Features: \t%s", dev_feat2s(*dev_features).c_str());
+
+ if (!(cec_has_playback(1 << la) || cec_has_record(1 << la) || cec_has_tuner(1 << la)) &&
+ node->remote[la].has_aud_rate) {
+ return fail("Only Playback, Recording or Tuner devices shall set the Set Audio Rate bit");
+ }
+ if (!(cec_has_playback(1 << la) || cec_has_record(1 << la)) && node->remote[la].has_deck_ctl)
+ return fail("Only Playback and Recording devices shall set the Supports Deck Control bit");
+ if (!cec_has_tv(1 << la) && node->remote[la].has_rec_tv)
+ return fail("Only TVs shall set the Record TV Screen bit");
+
+ return 0;
+}
+
+static struct remote_subtest system_info_subtests[] = {
+ { "Polling Message", CEC_LOG_ADDR_MASK_ALL, system_info_polling },
+ { "Give Physical Address", CEC_LOG_ADDR_MASK_ALL, system_info_phys_addr },
+ { "Give CEC Version", CEC_LOG_ADDR_MASK_ALL, system_info_version },
+ { "Get Menu Language", CEC_LOG_ADDR_MASK_ALL, system_info_get_menu_lang },
+ { "Set Menu Language", CEC_LOG_ADDR_MASK_ALL, system_info_set_menu_lang },
+ { "Give Device Features", CEC_LOG_ADDR_MASK_ALL, system_info_give_features },
+};
+
+
+/* Core behavior */
+
+static int core_unknown(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ struct cec_msg msg = { };
+
+ /* Unknown opcodes should be responded to with Feature Abort, with abort
+ reason Unknown Opcode.
+
+ For CEC 2.0 and before, 0xfe is an unused opcode. The test possibly
+ needs to be updated for future CEC versions. */
+ cec_msg_init(&msg, me, la);
+ msg.len = 2;
+ msg.msg[1] = 0xfe;
+ fail_on_test(!transmit_timeout(node, &msg));
+ fail_on_test(timed_out(&msg));
+ fail_on_test(!cec_msg_status_is_abort(&msg));
+
+ __u8 abort_msg, reason;
+
+ cec_ops_feature_abort(&msg, &abort_msg, &reason);
+ fail_on_test(reason != CEC_OP_ABORT_UNRECOGNIZED_OP);
+ fail_on_test(abort_msg != 0xfe);
+ return 0;
+}
+
+static int core_abort(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ struct cec_msg msg = {};
+
+ /* The Abort message should always be responded to with Feature Abort
+ (with any abort reason) */
+ cec_msg_init(&msg, me, la);
+ cec_msg_abort(&msg);
+ fail_on_test(!transmit_timeout(node, &msg));
+ fail_on_test(timed_out(&msg));
+ fail_on_test(!cec_msg_status_is_abort(&msg));
+ return 0;
+}
+
+static struct remote_subtest core_subtests[] = {
+ { "Feature aborts unknown messages", CEC_LOG_ADDR_MASK_ALL, core_unknown },
+ { "Feature aborts Abort message", CEC_LOG_ADDR_MASK_ALL, core_abort },
+};
+
+
+/* Vendor Specific Commands */
+
+static int vendor_specific_commands_id(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ struct cec_msg msg = {};
+
+ cec_msg_init(&msg, me, la);
+ cec_msg_give_device_vendor_id(&msg, true);
+ fail_on_test(!transmit(node, &msg));
+ if (unrecognized_op(&msg))
+ return NOTSUPPORTED;
+ if (refused(&msg))
+ return REFUSED;
+ if (cec_msg_status_is_abort(&msg))
+ return PRESUMED_OK;
+
+ return 0;
+}
+
+static struct remote_subtest vendor_specific_subtests[] = {
+ { "Give Device Vendor ID", CEC_LOG_ADDR_MASK_ALL, vendor_specific_commands_id },
+};
+
+
+/* Device OSD Transfer */
+
+static int device_osd_transfer_set(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ struct cec_msg msg = { };
+
+ cec_msg_init(&msg, me, la);
+ cec_msg_set_osd_name(&msg, "Whatever");
+ fail_on_test(!transmit_timeout(node, &msg));
+ if (unrecognized_op(&msg)) {
+ if (is_tv(la, node->remote[la].prim_type) &&
+ node->remote[la].cec_version >= CEC_OP_CEC_VERSION_2_0)
+ warn("TV feature aborted Set OSD Name\n");
+ return NOTSUPPORTED;
+ }
+ if (refused(&msg))
+ return REFUSED;
+
+ return PRESUMED_OK;
+}
+
+static int device_osd_transfer_give(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ struct cec_msg msg = { };
+
+ /* Todo: CEC 2.0: devices with several logical addresses shall report
+ the same for each logical address. */
+ cec_msg_init(&msg, me, la);
+ cec_msg_give_osd_name(&msg, true);
+ fail_on_test(!transmit(node, &msg));
+ fail_on_test(timed_out(&msg));
+ fail_on_test(!is_tv(la, node->remote[la].prim_type) && unrecognized_op(&msg));
+ if (unrecognized_op(&msg))
+ return NOTSUPPORTED;
+ if (refused(&msg))
+ return REFUSED;
+ if (cec_msg_status_is_abort(&msg))
+ return PRESUMED_OK;
+
+ return 0;
+}
+
+static struct remote_subtest device_osd_transfer_subtests[] = {
+ { "Set OSD Name", CEC_LOG_ADDR_MASK_TV, device_osd_transfer_set },
+ { "Give OSD Name", CEC_LOG_ADDR_MASK_ALL, device_osd_transfer_give },
+};
+
+
+/* OSD Display */
+
+static int osd_string_set_default(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ if (!node->remote[la].has_osd)
+ return NOTAPPLICABLE;
+
+ struct cec_msg msg = { };
+ char osd[14];
+ bool unsuitable = false;
+
+ sprintf(osd, "Rept %x from %x", la, me);
+
+ interactive_info(true, "You should see \"%s\" appear on the screen for approximately one second.", osd);
+ cec_msg_init(&msg, me, la);
+ cec_msg_set_osd_string(&msg, CEC_OP_DISP_CTL_DEFAULT, osd);
+ fail_on_test(!transmit_timeout(node, &msg));
+ /* In CEC 2.0 it is mandatory for a TV to support this if it reports so
+ in its Device Features. */
+ fail_on_test_v2(node->remote[la].cec_version,
+ unrecognized_op(&msg) &&
+ (node->remote[la].dev_features & CEC_OP_FEAT_DEV_HAS_SET_OSD_STRING));
+ if (unrecognized_op(&msg))
+ return NOTSUPPORTED;
+ else if (refused(&msg))
+ return REFUSED;
+ else {
+ warn("The device is in an unsuitable state or cannot display the complete message.\n");
+ unsuitable = true;
+ }
+
+ cec_msg_init(&msg, me, la);
+ cec_msg_set_osd_string(&msg, CEC_OP_DISP_CTL_CLEAR, "");
+ fail_on_test(!transmit_timeout(node, &msg, 250));
+ fail_on_test(cec_msg_status_is_abort(&msg));
+ fail_on_test(!unsuitable && interactive && !question("Did the string appear?"));
+
+ node->remote[la].has_osd = true;
+ if (interactive)
+ return 0;
+ else
+ return PRESUMED_OK;
+}
+
+static int osd_string_set_until_clear(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ if (!node->remote[la].has_osd)
+ return NOTAPPLICABLE;
+
+ struct cec_msg msg = { };
+ char osd[14];
+ bool unsuitable = false;
+
+ strcpy(osd, "Appears 1 sec");
+ // Make sure the string is the maximum possible length
+ fail_on_test(strlen(osd) != 13);
+
+ interactive_info(true, "You should see \"%s\" appear on the screen for approximately three seconds.", osd);
+ cec_msg_init(&msg, me, la);
+ cec_msg_set_osd_string(&msg, CEC_OP_DISP_CTL_UNTIL_CLEARED, osd);
+ fail_on_test(!transmit(node, &msg));
+ if (cec_msg_status_is_abort(&msg) && abort_reason(&msg) != CEC_OP_ABORT_UNRECOGNIZED_OP) {
+ warn("The device is in an unsuitable state or cannot display the complete message.\n");
+ unsuitable = true;
+ }
+ sleep(3);
+
+ cec_msg_init(&msg, me, la);
+ cec_msg_set_osd_string(&msg, CEC_OP_DISP_CTL_CLEAR, "");
+ fail_on_test(!transmit_timeout(node, &msg, 250));
+ fail_on_test(cec_msg_status_is_abort(&msg));
+ fail_on_test(!unsuitable && interactive && !question("Did the string appear?"));
+
+ if (interactive)
+ return 0;
+ else
+ return PRESUMED_OK;
+}
+
+static int osd_string_invalid(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ if (!node->remote[la].has_osd)
+ return NOTAPPLICABLE;
+
+ struct cec_msg msg = { };
+
+ /* Send Set OSD String with an Display Control operand. A Feature Abort is
+ expected in reply. */
+ interactive_info(true, "You should observe no change on the on screen display");
+ cec_msg_init(&msg, me, la);
+ cec_msg_set_osd_string(&msg, 0xff, "");
+ fail_on_test(!transmit_timeout(node, &msg));
+ fail_on_test(timed_out(&msg));
+ fail_on_test(!cec_msg_status_is_abort(&msg));
+ fail_on_test(interactive && question("Did the display change?"));
+
+ return 0;
+}
+
+static struct remote_subtest osd_string_subtests[] = {
+ { "Set OSD String with default timeout", CEC_LOG_ADDR_MASK_TV, osd_string_set_default },
+ { "Set OSD String with no timeout", CEC_LOG_ADDR_MASK_TV, osd_string_set_until_clear },
+ { "Set OSD String with invalid operand", CEC_LOG_ADDR_MASK_TV, osd_string_invalid },
+};
+
+
+/* Routing Control */
+
+static int routing_control_inactive_source(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ struct cec_msg msg = {};
+
+ interactive_info(true, "Please make sure that the TV is currently viewing this source.");
+ cec_msg_init(&msg, me, la);
+ cec_msg_inactive_source(&msg, node->phys_addr);
+ fail_on_test(!transmit_timeout(node, &msg));
+ if (unrecognized_op(&msg))
+ return NOTSUPPORTED;
+ if (refused(&msg))
+ return REFUSED;
+ fail_on_test(interactive && !question("Did the TV switch away from or stop showing this source?"));
+
+ if (interactive)
+ return 0;
+ else
+ return PRESUMED_OK;
+}
+
+static int routing_control_active_source(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ struct cec_msg msg = {};
+
+ interactive_info(true, "Please switch the TV to another source.");
+ cec_msg_init(&msg, me, la);
+ cec_msg_active_source(&msg, node->phys_addr);
+ fail_on_test(!transmit_timeout(node, &msg));
+ fail_on_test(interactive && !question("Did the TV switch to this source?"));
+
+ if (interactive)
+ return 0;
+ else
+ return PRESUMED_OK;
+}
+
+static int routing_control_req_active_source(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ struct cec_msg msg = {};
+
+ /* We have now said that we are active source, so receiving a reply to
+ Request Active Source should fail the test. */
+ cec_msg_init(&msg, me, la);
+ cec_msg_request_active_source(&msg, true);
+ fail_on_test(!transmit_timeout(node, &msg));
+ fail_on_test(!timed_out(&msg));
+
+ return 0;
+}
+
+static int routing_control_set_stream_path(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ struct cec_msg msg = {};
+ __u16 phys_addr;
+
+ /* Send Set Stream Path with the remote physical address. We expect the
+ source to eventually send Active Source. The timeout of 60 seconds is
+ necessary because the device might have to wake up from standby.
+
+ In CEC 2.0 it is mandatory for sources to send Active Source. */
+ if (is_tv(la, node->remote[la].prim_type))
+ interactive_info(true, "Please ensure that the device is in standby.");
+ announce("Sending Set Stream Path and waiting for reply. This may take up to 60 s.");
+ cec_msg_init(&msg, me, la);
+ cec_msg_set_stream_path(&msg, node->remote[la].phys_addr);
+ msg.reply = CEC_MSG_ACTIVE_SOURCE;
+ fail_on_test(!transmit_timeout(node, &msg, 60000));
+ if (timed_out(&msg) && is_tv(la, node->remote[la].prim_type))
+ return NOTSUPPORTED;
+ if (timed_out(&msg) && node->remote[la].cec_version < CEC_OP_CEC_VERSION_2_0) {
+ warn("Device did not respond to Set Stream Path.\n");
+ return NOTSUPPORTED;
+ }
+ fail_on_test_v2(node->remote[la].cec_version, timed_out(&msg));
+ cec_ops_active_source(&msg, &phys_addr);
+ fail_on_test(phys_addr != node->remote[la].phys_addr);
+ if (is_tv(la, node->remote[la].prim_type))
+ fail_on_test(interactive && !question("Did the device go out of standby?"));
+
+ if (interactive || node->remote[la].cec_version >= CEC_OP_CEC_VERSION_2_0)
+ return 0;
+ else
+ return PRESUMED_OK;
+ return 0;
+}
+
+static struct remote_subtest routing_control_subtests[] = {
+ { "Inactive Source", CEC_LOG_ADDR_MASK_TV, routing_control_inactive_source },
+ { "Active Source", CEC_LOG_ADDR_MASK_TV, routing_control_active_source },
+ { "Request Active Source", CEC_LOG_ADDR_MASK_ALL, routing_control_req_active_source },
+ { "Set Stream Path", CEC_LOG_ADDR_MASK_ALL, routing_control_set_stream_path },
+};
+
+
+/* Remote Control Passthrough */
+
+static int rc_passthrough_user_ctrl_pressed(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ struct cec_msg msg = {};
+ struct cec_op_ui_command rc_press;
+
+ cec_msg_init(&msg, me, la);
+ rc_press.ui_cmd = 0x41; // Volume up key (the key is not crucial here)
+ cec_msg_user_control_pressed(&msg, &rc_press);
+ fail_on_test(!transmit_timeout(node, &msg));
+ /* Mandatory for all except devices which have taken logical address 15 */
+ fail_on_test_v2(node->remote[la].cec_version,
+ unrecognized_op(&msg) && !(cec_is_unregistered(1 << la)));
+ if (unrecognized_op(&msg))
+ return NOTSUPPORTED;
+ if (refused(&msg))
+ return REFUSED;
+
+ return PRESUMED_OK;
+}
+
+static int rc_passthrough_user_ctrl_released(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ struct cec_msg msg = {};
+
+ cec_msg_init(&msg, me, la);
+ cec_msg_user_control_released(&msg);
+ fail_on_test(!transmit_timeout(node, &msg));
+ fail_on_test_v2(node->remote[la].cec_version,
+ cec_msg_status_is_abort(&msg) && !(la & CEC_LOG_ADDR_MASK_UNREGISTERED));
+ if (unrecognized_op(&msg))
+ return NOTSUPPORTED;
+ if (refused(&msg))
+ return REFUSED;
+ node->remote[la].has_remote_control_passthrough = true;
+
+ return PRESUMED_OK;
+}
+
+static struct remote_subtest rc_passthrough_subtests[] = {
+ { "User Control Pressed", CEC_LOG_ADDR_MASK_ALL, rc_passthrough_user_ctrl_pressed },
+ { "User Control Released", CEC_LOG_ADDR_MASK_ALL, rc_passthrough_user_ctrl_released },
+};
+
+
+/* Device Menu Control */
+
+/*
+ TODO: These are very rudimentary tests which should be expanded.
+ */
+
+static int dev_menu_ctl_request(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ struct cec_msg msg = {};
+
+ cec_msg_init(&msg, me, la);
+ cec_msg_menu_request(&msg, true, CEC_OP_MENU_REQUEST_QUERY);
+ fail_on_test(!transmit_timeout(node, &msg));
+ if (unrecognized_op(&msg))
+ return NOTSUPPORTED;
+ if (refused(&msg))
+ return REFUSED;
+ if (cec_msg_status_is_abort(&msg))
+ return PRESUMED_OK;
+ if (node->remote[la].cec_version >= CEC_OP_CEC_VERSION_2_0)
+ warn("The Device Menu Control feature is deprecated in CEC 2.0");
+
+ return 0;
+}
+
+static int dev_menu_ctl_status(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ struct cec_msg msg = {};
+
+ cec_msg_init(&msg, me, la);
+ cec_msg_menu_status(&msg, CEC_OP_MENU_STATE_ACTIVATED);
+ fail_on_test(!transmit_timeout(node, &msg));
+ if (unrecognized_op(&msg))
+ return NOTSUPPORTED;
+ if (refused(&msg))
+ return REFUSED;
+ if (cec_msg_status_is_abort(&msg))
+ return PRESUMED_OK;
+ if (node->remote[la].cec_version >= CEC_OP_CEC_VERSION_2_0)
+ warn("The Device Menu Control feature is deprecated in CEC 2.0");
+
+ return 0;
+}
+static struct remote_subtest dev_menu_ctl_subtests[] = {
+ { "Menu Request", (__u16)~CEC_LOG_ADDR_MASK_TV, dev_menu_ctl_request },
+ { "Menu Status", CEC_LOG_ADDR_MASK_TV, dev_menu_ctl_status },
+ { "User Control Pressed", CEC_LOG_ADDR_MASK_ALL, rc_passthrough_user_ctrl_pressed },
+ { "User Control Released", CEC_LOG_ADDR_MASK_ALL, rc_passthrough_user_ctrl_released },
+};
+
+
+/* Deck Control */
+
+/*
+ TODO: These are very rudimentary tests which should be expanded.
+ */
+
+static int deck_ctl_give_status(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ struct cec_msg msg = {};
+
+ cec_msg_init(&msg, me, la);
+ cec_msg_give_deck_status(&msg, true, CEC_OP_STATUS_REQ_ONCE);
+ fail_on_test(!transmit_timeout(node, &msg));
+ fail_on_test(timed_out(&msg));
+ fail_on_test_v2(node->remote[la].cec_version,
+ node->remote[la].has_deck_ctl && cec_msg_status_is_abort(&msg));
+ fail_on_test_v2(node->remote[la].cec_version,
+ !node->remote[la].has_deck_ctl && !unrecognized_op(&msg));
+ if (unrecognized_op(&msg))
+ return NOTSUPPORTED;
+ if (refused(&msg))
+ return REFUSED;
+ if (cec_msg_status_is_abort(&msg))
+ return PRESUMED_OK;
+
+ return 0;
+}
+
+static int deck_ctl_deck_status(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ struct cec_msg msg = {};
+
+ cec_msg_init(&msg, me, la);
+ cec_msg_deck_status(&msg, CEC_OP_DECK_INFO_STOP);
+ fail_on_test(!transmit_timeout(node, &msg));
+ if (unrecognized_op(&msg))
+ return NOTSUPPORTED;
+ if (refused(&msg))
+ return REFUSED;
+ if (cec_msg_status_is_abort(&msg))
+ return PRESUMED_OK;
+
+ return 0;
+}
+
+static int deck_ctl_deck_ctl(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ struct cec_msg msg = {};
+
+ cec_msg_init(&msg, me, la);
+ cec_msg_deck_control(&msg, CEC_OP_DECK_CTL_MODE_STOP);
+ fail_on_test(!transmit_timeout(node, &msg));
+ fail_on_test_v2(node->remote[la].cec_version,
+ node->remote[la].has_deck_ctl && unrecognized_op(&msg));
+ fail_on_test_v2(node->remote[la].cec_version,
+ !node->remote[la].has_deck_ctl && !unrecognized_op(&msg));
+ if (unrecognized_op(&msg))
+ return NOTSUPPORTED;
+ if (refused(&msg))
+ return REFUSED;
+ if (cec_msg_status_is_abort(&msg))
+ return PRESUMED_OK;
+
+ return PRESUMED_OK;
+}
+
+static int deck_ctl_play(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ struct cec_msg msg = {};
+
+ cec_msg_init(&msg, me, la);
+ cec_msg_play(&msg, CEC_OP_PLAY_MODE_PLAY_STILL);
+ fail_on_test(!transmit_timeout(node, &msg));
+ fail_on_test_v2(node->remote[la].cec_version,
+ node->remote[la].has_deck_ctl && unrecognized_op(&msg));
+ fail_on_test_v2(node->remote[la].cec_version,
+ !node->remote[la].has_deck_ctl && !unrecognized_op(&msg));
+ if (unrecognized_op(&msg))
+ return NOTSUPPORTED;
+ if (refused(&msg))
+ return REFUSED;
+ if (cec_msg_status_is_abort(&msg))
+ return PRESUMED_OK;
+
+ return PRESUMED_OK;
+}
+
+static struct remote_subtest deck_ctl_subtests[] = {
+ { "Give Deck Status",
+ CEC_LOG_ADDR_MASK_PLAYBACK | CEC_LOG_ADDR_MASK_RECORD,
+ deck_ctl_give_status },
+ { "Deck Status",
+ CEC_LOG_ADDR_MASK_ALL,
+ deck_ctl_deck_status },
+ { "Deck Control",
+ CEC_LOG_ADDR_MASK_PLAYBACK | CEC_LOG_ADDR_MASK_RECORD,
+ deck_ctl_deck_ctl },
+ { "Play",
+ CEC_LOG_ADDR_MASK_PLAYBACK | CEC_LOG_ADDR_MASK_RECORD,
+ deck_ctl_play },
+};
+
+
+/* Tuner Control */
+
+/*
+ TODO: These are very rudimentary tests which should be expanded.
+ */
+
+static int tuner_ctl_give_status(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ struct cec_msg msg = {};
+
+ cec_msg_init(&msg, me, la);
+ cec_msg_give_tuner_device_status(&msg, true, CEC_OP_STATUS_REQ_ONCE);
+ fail_on_test(!transmit_timeout(node, &msg));
+ fail_on_test(timed_out(&msg));
+ if (unrecognized_op(&msg))
+ return NOTSUPPORTED;
+ if (refused(&msg))
+ return REFUSED;
+ if (cec_msg_status_is_abort(&msg))
+ return PRESUMED_OK;
+
+ return 0;
+}
+
+static int tuner_ctl_sel_analog_service(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ struct cec_msg msg = {};
+
+ node->remote[la].bcast_sys = ~0;
+ for (unsigned sys = 0; sys <= 8; sys++) {
+ cec_msg_init(&msg, me, la);
+ cec_msg_select_analogue_service(&msg, CEC_OP_ANA_BCAST_TYPE_CABLE,
+ 7668, sys); // 479.25 MHz analog frequency
+ fail_on_test(!transmit_timeout(node, &msg));
+ if (unrecognized_op(&msg))
+ return NOTSUPPORTED;
+ if (abort_reason(&msg) == CEC_OP_ABORT_INVALID_OP) {
+ info("Tuner supports %s, but cannot select that service.\n",
+ bcast_system2s(sys));
+ node->remote[la].bcast_sys = sys;
+ continue;
+ }
+ if (cec_msg_status_is_abort(&msg))
+ continue;
+ info("Tuner supports %s\n", bcast_system2s(sys));
+ node->remote[la].bcast_sys = sys;
+ }
+
+ if (node->remote[la].bcast_sys == (__u8)~0)
+ warn("No analog broadcast format supported\n");
+ else
+ return 0;
+
+ return PRESUMED_OK;
+}
+
+static int tuner_ctl_sel_digital_service(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ struct cec_msg msg = {};
+ struct cec_op_digital_service_id digital_service_id = {};
+
+ digital_service_id.service_id_method = CEC_OP_SERVICE_ID_METHOD_BY_CHANNEL;
+ digital_service_id.channel.channel_number_fmt = CEC_OP_CHANNEL_NUMBER_FMT_1_PART;
+ digital_service_id.channel.minor = 1;
+
+ __u8 bcast_systems[] = {
+ CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ARIB_GEN,
+ CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_GEN,
+ CEC_OP_DIG_SERVICE_BCAST_SYSTEM_DVB_GEN,
+ CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ARIB_BS,
+ CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ARIB_CS,
+ CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ARIB_T,
+ CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_CABLE,
+ CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_SAT,
+ CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_T,
+ CEC_OP_DIG_SERVICE_BCAST_SYSTEM_DVB_C,
+ CEC_OP_DIG_SERVICE_BCAST_SYSTEM_DVB_S,
+ CEC_OP_DIG_SERVICE_BCAST_SYSTEM_DVB_S2,
+ CEC_OP_DIG_SERVICE_BCAST_SYSTEM_DVB_T,
+ };
+
+ node->remote[la].dig_bcast_sys = ~0;
+ for (unsigned i = 0; i < ARRAY_SIZE(bcast_systems); i++) {
+ __u8 sys = bcast_systems[i];
+
+ digital_service_id.dig_bcast_system = sys;
+ cec_msg_init(&msg, me, la);
+ cec_msg_select_digital_service(&msg, &digital_service_id);
+ fail_on_test(!transmit_timeout(node, &msg));
+ if (unrecognized_op(&msg))
+ return NOTSUPPORTED;
+ if (abort_reason(&msg) == CEC_OP_ABORT_INVALID_OP) {
+ info("Tuner supports %s, but cannot select that service.\n",
+ dig_bcast_system2s(sys));
+ node->remote[la].dig_bcast_sys = sys;
+ continue;
+ }
+ if (cec_msg_status_is_abort(&msg))
+ continue;
+ info("Tuner supports %s\n", dig_bcast_system2s(sys));
+ node->remote[la].dig_bcast_sys = sys;
+ }
+
+ if (node->remote[la].dig_bcast_sys == (__u8)~0)
+ warn("No digital broadcast system supported\n");
+ else
+ return 0;
+
+ return PRESUMED_OK;
+}
+
+static int tuner_ctl_tuner_dev_status(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ struct cec_msg msg = {};
+ struct cec_op_tuner_device_info tuner_dev_info = {};
+
+ tuner_dev_info.rec_flag = CEC_OP_REC_FLAG_NOT_USED;
+ tuner_dev_info.tuner_display_info = CEC_OP_TUNER_DISPLAY_INFO_NONE;
+ tuner_dev_info.is_analog = false;
+ tuner_dev_info.digital.service_id_method = CEC_OP_SERVICE_ID_METHOD_BY_CHANNEL;
+ tuner_dev_info.digital.dig_bcast_system = CEC_OP_DIG_SERVICE_BCAST_SYSTEM_DVB_C;
+ tuner_dev_info.digital.channel.channel_number_fmt = CEC_OP_CHANNEL_NUMBER_FMT_1_PART;
+ tuner_dev_info.digital.channel.minor = 1;
+
+ cec_msg_init(&msg, me, la);
+
+ cec_msg_tuner_device_status(&msg, &tuner_dev_info);
+ fail_on_test(!transmit_timeout(node, &msg));
+ if (unrecognized_op(&msg))
+ return NOTSUPPORTED;
+ if (refused(&msg))
+ return REFUSED;
+ if (cec_msg_status_is_abort(&msg))
+ return PRESUMED_OK;
+
+ return 0;
+}
+
+static int tuner_ctl_step_dec(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ struct cec_msg msg = {};
+
+ cec_msg_init(&msg, me, la);
+ cec_msg_tuner_step_decrement(&msg);
+ fail_on_test(!transmit_timeout(node, &msg));
+ if (unrecognized_op(&msg))
+ return NOTSUPPORTED;
+ if (refused(&msg))
+ return REFUSED;
+
+ return PRESUMED_OK;
+}
+
+static int tuner_ctl_step_inc(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ struct cec_msg msg = {};
+
+ cec_msg_init(&msg, me, la);
+ cec_msg_tuner_step_increment(&msg);
+ fail_on_test(!transmit_timeout(node, &msg));
+ if (unrecognized_op(&msg))
+ return NOTSUPPORTED;
+ if (refused(&msg))
+ return REFUSED;
+
+ return PRESUMED_OK;
+}
+
+static struct remote_subtest tuner_ctl_subtests[] = {
+ { "Give Tuner Device Status", CEC_LOG_ADDR_MASK_TUNER, tuner_ctl_give_status },
+ { "Select Analogue Service", CEC_LOG_ADDR_MASK_TUNER, tuner_ctl_sel_analog_service },
+ { "Select Digital Service", CEC_LOG_ADDR_MASK_TUNER, tuner_ctl_sel_digital_service },
+ { "Tuner Device Status", CEC_LOG_ADDR_MASK_ALL, tuner_ctl_tuner_dev_status },
+ { "Tuner Step Decrement", CEC_LOG_ADDR_MASK_TUNER, tuner_ctl_step_dec },
+ { "Tuner Step Increment", CEC_LOG_ADDR_MASK_TUNER, tuner_ctl_step_inc },
+};
+
+
+/* One Touch Record */
+
+/*
+ TODO: These are very rudimentary tests which should be expanded.
+
+ - The HDMI CEC 1.4b spec details that Standby shall not be acted upon while the
+ device is recording, but it should remember that it received Standby.
+ */
+
+static int one_touch_rec_tv_screen(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ /*
+ TODO:
+ - Page 36 in HDMI CEC 1.4b spec lists additional behaviors that should be
+ checked for.
+ - The TV should ignore this message when received from other LA than Recording or
+ Reserved.
+ */
+ struct cec_msg msg = {};
+
+ cec_msg_init(&msg, me, la);
+ cec_msg_record_tv_screen(&msg, true);
+ fail_on_test(!transmit_timeout(node, &msg));
+ fail_on_test_v2(node->remote[la].cec_version,
+ node->remote[la].has_rec_tv && unrecognized_op(&msg));
+ fail_on_test_v2(node->remote[la].cec_version,
+ !node->remote[la].has_rec_tv && !unrecognized_op(&msg));
+ if (unrecognized_op(&msg))
+ return NOTSUPPORTED;
+ if (refused(&msg))
+ return REFUSED;
+ if (cec_msg_status_is_abort(&msg))
+ return PRESUMED_OK;
+
+ return 0;
+}
+
+static int one_touch_rec_on(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ /*
+ TODO: Page 36 in HDMI CEC 1.4b spec lists additional behaviors that should be
+ checked for.
+ */
+ struct cec_msg msg = {};
+ struct cec_op_record_src rec_src = {};
+
+ rec_src.type = CEC_OP_RECORD_SRC_OWN;
+ cec_msg_init(&msg, me, la);
+ cec_msg_record_on(&msg, true, &rec_src);
+ fail_on_test(!transmit_timeout(node, &msg));
+ fail_on_test(timed_out(&msg));
+ fail_on_test(cec_has_record(1 << la) && unrecognized_op(&msg));
+ if (unrecognized_op(&msg))
+ return NOTSUPPORTED;
+ if (refused(&msg))
+ return REFUSED;
+ if (cec_msg_status_is_abort(&msg))
+ return PRESUMED_OK;
+
+ return 0;
+}
+
+static int one_touch_rec_off(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ struct cec_msg msg = {};
+
+ cec_msg_init(&msg, me, la);
+ cec_msg_record_off(&msg, false);
+ fail_on_test(!transmit_timeout(node, &msg));
+ fail_on_test(cec_has_record(1 << la) && unrecognized_op(&msg));
+ if (unrecognized_op(&msg))
+ return NOTSUPPORTED;
+ if (refused(&msg))
+ return REFUSED;
+ if (cec_msg_status_is_abort(&msg))
+ return PRESUMED_OK;
+ if (timed_out(&msg))
+ return PRESUMED_OK;
+ else
+ return 0;
+}
+
+static int one_touch_rec_status(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ struct cec_msg msg = {};
+
+ cec_msg_init(&msg, me, la);
+ cec_msg_record_status(&msg, CEC_OP_RECORD_STATUS_DIG_SERVICE);
+ fail_on_test(!transmit_timeout(node, &msg));
+ if (unrecognized_op(&msg))
+ return NOTSUPPORTED;
+ if (refused(&msg))
+ return REFUSED;
+ if (cec_msg_status_is_abort(&msg))
+ return PRESUMED_OK;
+
+ return 0;
+}
+
+static struct remote_subtest one_touch_rec_subtests[] = {
+ { "Record TV Screen", CEC_LOG_ADDR_MASK_TV, one_touch_rec_tv_screen },
+ { "Record On", CEC_LOG_ADDR_MASK_RECORD, one_touch_rec_on },
+ { "Record Off", CEC_LOG_ADDR_MASK_RECORD, one_touch_rec_off },
+ { "Record Status", CEC_LOG_ADDR_MASK_ALL, one_touch_rec_status },
+};
+
+
+/* Timer Programming */
+
+/*
+ TODO: These are very rudimentary tests which should be expanded.
+ */
+
+static int timer_prog_set_analog_timer(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ /* TODO: Check the timer status for possible errors, etc. */
+
+ struct cec_msg msg = {};
+
+ cec_msg_init(&msg, me, la);
+ cec_msg_set_analogue_timer(&msg, true, 1, 1, 0, 0, 1, 0, CEC_OP_REC_SEQ_ONCE_ONLY,
+ CEC_OP_ANA_BCAST_TYPE_CABLE,
+ 7668, // 479.25 MHz
+ node->remote[la].bcast_sys);
+ fail_on_test(!transmit_timeout(node, &msg, 10000));
+ if (timed_out(&msg)) {
+ warn("Timed out waiting for Timer Status. Assuming timer was set.\n");
+ return PRESUMED_OK;
+ }
+ if (unrecognized_op(&msg))
+ return NOTSUPPORTED;
+ if (refused(&msg))
+ return REFUSED;
+ if (cec_msg_status_is_abort(&msg))
+ return PRESUMED_OK;
+
+ return 0;
+}
+
+static int timer_prog_set_digital_timer(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ /* TODO: Check the timer status for possible errors, etc. */
+
+ struct cec_msg msg = {};
+ struct cec_op_digital_service_id digital_service_id = {};
+
+ digital_service_id.service_id_method = CEC_OP_SERVICE_ID_METHOD_BY_CHANNEL;
+ digital_service_id.channel.channel_number_fmt = CEC_OP_CHANNEL_NUMBER_FMT_1_PART;
+ digital_service_id.channel.minor = 1;
+ digital_service_id.dig_bcast_system = node->remote[la].dig_bcast_sys;
+ cec_msg_init(&msg, me, la);
+ cec_msg_set_digital_timer(&msg, true, 1, 1, 0, 0, 1, 0, CEC_OP_REC_SEQ_ONCE_ONLY,
+ &digital_service_id);
+ fail_on_test(!transmit_timeout(node, &msg, 10000));
+ if (timed_out(&msg)) {
+ warn("Timed out waiting for Timer Status. Assuming timer was set.\n");
+ return PRESUMED_OK;
+ }
+ if (unrecognized_op(&msg))
+ return NOTSUPPORTED;
+ if (refused(&msg))
+ return REFUSED;
+ if (cec_msg_status_is_abort(&msg))
+ return PRESUMED_OK;
+
+ return 0;
+}
+
+static int timer_prog_set_ext_timer(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ /* TODO: Check the timer status. */
+
+ struct cec_msg msg = {};
+
+ cec_msg_init(&msg, me, la);
+ cec_msg_set_ext_timer(&msg, true, 1, 1, 0, 0, 1, 0, CEC_OP_REC_SEQ_ONCE_ONLY,
+ CEC_OP_EXT_SRC_PHYS_ADDR, 0, node->phys_addr);
+ fail_on_test(!transmit_timeout(node, &msg, 10000));
+ if (timed_out(&msg)) {
+ warn("Timed out waiting for Timer Status. Assuming timer was set.\n");
+ return PRESUMED_OK;
+ }
+ if (unrecognized_op(&msg))
+ return NOTSUPPORTED;
+ if (refused(&msg))
+ return REFUSED;
+ if (cec_msg_status_is_abort(&msg))
+ return PRESUMED_OK;
+
+ return 0;
+}
+
+static int timer_prog_clear_analog_timer(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ /* TODO: Check the timer cleared status. */
+
+ struct cec_msg msg = {};
+
+ cec_msg_init(&msg, me, la);
+ cec_msg_clear_analogue_timer(&msg, true, 1, 1, 0, 0, 1, 0, CEC_OP_REC_SEQ_ONCE_ONLY,
+ CEC_OP_ANA_BCAST_TYPE_CABLE,
+ 7668, // 479.25 MHz
+ node->remote[la].bcast_sys);
+ fail_on_test(!transmit_timeout(node, &msg, 10000));
+ if (timed_out(&msg)) {
+ warn("Timed out waiting for Timer Cleared Status. Assuming timer was cleared.\n");
+ return PRESUMED_OK;
+ }
+ if (unrecognized_op(&msg))
+ return NOTSUPPORTED;
+ if (refused(&msg))
+ return REFUSED;
+ if (cec_msg_status_is_abort(&msg))
+ return PRESUMED_OK;
+
+ return 0;
+}
+
+static int timer_prog_clear_digital_timer(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ /* TODO: Check the timer cleared status. */
+
+ struct cec_msg msg = {};
+ struct cec_op_digital_service_id digital_service_id = {};
+
+ digital_service_id.service_id_method = CEC_OP_SERVICE_ID_METHOD_BY_CHANNEL;
+ digital_service_id.channel.channel_number_fmt = CEC_OP_CHANNEL_NUMBER_FMT_1_PART;
+ digital_service_id.channel.minor = 1;
+ digital_service_id.dig_bcast_system = node->remote[la].dig_bcast_sys;
+ cec_msg_init(&msg, me, la);
+ cec_msg_clear_digital_timer(&msg, true, 1, 1, 0, 0, 1, 0, CEC_OP_REC_SEQ_ONCE_ONLY,
+ &digital_service_id);
+ fail_on_test(!transmit_timeout(node, &msg, 10000));
+ if (timed_out(&msg)) {
+ warn("Timed out waiting for Timer Cleared Status. Assuming timer was cleared.\n");
+ return PRESUMED_OK;
+ }
+ if (unrecognized_op(&msg))
+ return NOTSUPPORTED;
+ if (refused(&msg))
+ return REFUSED;
+ if (cec_msg_status_is_abort(&msg))
+ return PRESUMED_OK;
+
+ return 0;
+}
+
+static int timer_prog_clear_ext_timer(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ /* TODO: Check the timer cleared status. */
+
+ struct cec_msg msg = {};
+
+ cec_msg_init(&msg, me, la);
+ cec_msg_clear_ext_timer(&msg, true, 1, 1, 0, 0, 1, 0, CEC_OP_REC_SEQ_ONCE_ONLY,
+ CEC_OP_EXT_SRC_PHYS_ADDR, 0, node->phys_addr);
+ fail_on_test(!transmit_timeout(node, &msg, 10000));
+ if (timed_out(&msg)) {
+ warn("Timed out waiting for Timer Cleared Status. Assuming timer was cleared.\n");
+ return PRESUMED_OK;
+ }
+ if (unrecognized_op(&msg))
+ return NOTSUPPORTED;
+ if (refused(&msg))
+ return REFUSED;
+ if (cec_msg_status_is_abort(&msg))
+ return PRESUMED_OK;
+
+ return 0;
+}
+
+static int timer_prog_set_prog_title(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ struct cec_msg msg = {};
+
+ cec_msg_init(&msg, me, la);
+ cec_msg_set_timer_program_title(&msg, "Super-Hans II");
+ fail_on_test(!transmit_timeout(node, &msg));
+ if (unrecognized_op(&msg))
+ return NOTSUPPORTED;
+ if (refused(&msg))
+ return REFUSED;
+
+ return PRESUMED_OK;
+}
+
+static int timer_prog_timer_status(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ struct cec_msg msg = {};
+
+ cec_msg_init(&msg, me, la);
+ cec_msg_timer_status(&msg, CEC_OP_TIMER_OVERLAP_WARNING_NO_OVERLAP,
+ CEC_OP_MEDIA_INFO_NO_MEDIA,
+ CEC_OP_PROG_INFO_ENOUGH_SPACE,
+ 0, 0, 0);
+ fail_on_test(!transmit_timeout(node, &msg));
+ if (unrecognized_op(&msg))
+ return NOTSUPPORTED;
+ if (refused(&msg))
+ return REFUSED;
+
+ return PRESUMED_OK;
+}
+
+static int timer_prog_timer_clear_status(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ struct cec_msg msg = {};
+
+ cec_msg_init(&msg, me, la);
+ cec_msg_timer_cleared_status(&msg, CEC_OP_TIMER_CLR_STAT_CLEARED);
+ fail_on_test(!transmit_timeout(node, &msg));
+ if (unrecognized_op(&msg))
+ return NOTSUPPORTED;
+ if (refused(&msg))
+ return REFUSED;
+
+ return PRESUMED_OK;
+}
+
+static struct remote_subtest timer_prog_subtests[] = {
+ { "Set Analogue Timer", CEC_LOG_ADDR_MASK_RECORD, timer_prog_set_analog_timer },
+ { "Set Digital Timer", CEC_LOG_ADDR_MASK_RECORD, timer_prog_set_digital_timer },
+ { "Set Timer Program Title", CEC_LOG_ADDR_MASK_RECORD, timer_prog_set_prog_title },
+ { "Set External Timer", CEC_LOG_ADDR_MASK_RECORD, timer_prog_set_ext_timer },
+ { "Clear Analogue Timer", CEC_LOG_ADDR_MASK_RECORD, timer_prog_clear_analog_timer },
+ { "Clear Digital Timer", CEC_LOG_ADDR_MASK_RECORD, timer_prog_clear_digital_timer },
+ { "Clear External Timer", CEC_LOG_ADDR_MASK_RECORD, timer_prog_clear_ext_timer },
+ { "Timer Status", CEC_LOG_ADDR_MASK_RECORD, timer_prog_timer_status },
+ { "Timer Cleared Status", CEC_LOG_ADDR_MASK_RECORD, timer_prog_timer_clear_status },
+};
+
+static int cdc_hec_discover(struct node *node, unsigned me, unsigned la, bool print)
+{
+ /* TODO: For future use cases, it might be necessary to store the results
+ from the HEC discovery to know which HECs are possible to form, etc. */
+ struct cec_msg msg = {};
+ __u32 mode = CEC_MODE_INITIATOR | CEC_MODE_FOLLOWER;
+ bool has_cdc = false;
+
+ doioctl(node, CEC_S_MODE, &mode);
+ cec_msg_init(&msg, me, la);
+ cec_msg_cdc_hec_discover(&msg);
+ fail_on_test(!transmit(node, &msg));
+
+ /* The spec describes that we shall wait for messages
+ up to 1 second, and extend the deadline for every received
+ message. The maximum time to wait for incoming state reports
+ is 5 seconds. */
+ unsigned ts_start = get_ts_ms();
+ while (get_ts_ms() - ts_start < 5000) {
+ __u8 from;
+
+ memset(&msg, 0, sizeof(msg));
+ msg.timeout = 1000;
+ if (doioctl(node, CEC_RECEIVE, &msg))
+ break;
+ from = cec_msg_initiator(&msg);
+ if (msg.msg[1] == CEC_MSG_FEATURE_ABORT) {
+ if (from == la)
+ return fail("Device replied Feature Abort to broadcast message\n");
+ else
+ warn("Device %d replied Feature Abort to broadcast message\n", cec_msg_initiator(&msg));
+ }
+ if (msg.msg[1] != CEC_MSG_CDC_MESSAGE)
+ continue;
+ if (msg.msg[4] != CEC_MSG_CDC_HEC_REPORT_STATE)
+ continue;
+
+ __u16 phys_addr, target_phys_addr, hec_field;
+ __u8 hec_func_state, host_func_state, enc_func_state, cdc_errcode, has_field;
+
+ cec_ops_cdc_hec_report_state(&msg, &phys_addr, &target_phys_addr,
+ &hec_func_state, &host_func_state,
+ &enc_func_state, &cdc_errcode,
+ &has_field, &hec_field);
+
+ if (target_phys_addr != node->phys_addr)
+ continue;
+ if (phys_addr == node->remote[la].phys_addr)
+ has_cdc = true;
+ if (!print)
+ continue;
+
+ from = cec_msg_initiator(&msg);
+ info("Received CDC HEC State report from device %d (%s):\n", from, la2s(from));
+ info("Physical address : %x.%x.%x.%x\n",
+ cec_phys_addr_exp(phys_addr));
+ info("Target physical address : %x.%x.%x.%x\n",
+ cec_phys_addr_exp(target_phys_addr));
+ info("HEC Functionality State : %s\n", hec_func_state2s(hec_func_state));
+ info("Host Functionality State : %s\n", host_func_state2s(host_func_state));
+ info("ENC Functionality State : %s\n", enc_func_state2s(enc_func_state));
+ info("CDC Error Code : %s\n", cdc_errcode2s(cdc_errcode));
+
+ if (has_field) {
+ std::ostringstream oss;
+
+ /* Bit 14 indicates whether or not the device's HDMI
+ output has HEC support/is active. */
+ if (!hec_field)
+ oss << "None";
+ else {
+ if (hec_field & (1 << 14))
+ oss << "out, ";
+ for (int i = 13; i >= 0; i--) {
+ if (hec_field & (1 << i))
+ oss << "in" << (14 - i) << ", ";
+ }
+ oss << "\b\b ";
+ }
+ info("HEC Suppport Field : %s\n", oss.str().c_str());
+ }
+ }
+
+ mode = CEC_MODE_INITIATOR;
+ doioctl(node, CEC_S_MODE, &mode);
+
+ if (has_cdc)
+ return 0;
+ return NOTSUPPORTED;
+}
+
+static struct remote_subtest cdc_subtests[] = {
+ { "CDC_HEC_Discover", CEC_LOG_ADDR_MASK_ALL, cdc_hec_discover },
+};
+
+
+/* Post-test checks */
+
+static int post_test_check_recognized(struct node *node, unsigned me, unsigned la, bool interactive)
+{
+ bool fail = false;
+
+ for (unsigned i = 0; i < 256; i++) {
+ if (node->remote[la].recognized_op[i] && node->remote[la].unrecognized_op[i]) {
+ struct cec_msg msg = {};
+ msg.msg[1] = i;
+ fail("Opcode %s has been both recognized by and has been replied\n", opcode2s(&msg).c_str());
+ fail("Feature Abort [Unrecognized Opcode] to by the device.\n");
+ fail = true;
+ }
+ }
+ fail_on_test(fail);
+
+ return 0;
+}
+
+static struct remote_subtest post_test_subtests[] = {
+ { "Recognized/unrecognized message consistency", CEC_LOG_ADDR_MASK_ALL, post_test_check_recognized },
+};
+
+
+static struct remote_test tests[] = {
+ test_case("Core",
+ TAG_CORE,
+ core_subtests),
+ test_case_ext("Give Device Power Status feature",
+ TAG_POWER_STATUS,
+ power_status_subtests),
+ test_case("System Information feature",
+ TAG_SYSTEM_INFORMATION,
+ system_info_subtests),
+ test_case("Vendor Specific Commands feature",
+ TAG_VENDOR_SPECIFIC_COMMANDS,
+ vendor_specific_subtests),
+ test_case("Device OSD Transfer feature",
+ TAG_DEVICE_OSD_TRANSFER,
+ device_osd_transfer_subtests),
+ test_case("OSD String feature",
+ TAG_OSD_DISPLAY,
+ osd_string_subtests),
+ test_case("Remote Control Passthrough feature",
+ TAG_REMOTE_CONTROL_PASSTHROUGH,
+ rc_passthrough_subtests),
+ test_case("Device Menu Control feature",
+ TAG_DEVICE_MENU_CONTROL,
+ dev_menu_ctl_subtests),
+ test_case("Deck Control feature",
+ TAG_DECK_CONTROL,
+ deck_ctl_subtests),
+ test_case("Tuner Control feature",
+ TAG_TUNER_CONTROL,
+ tuner_ctl_subtests),
+ test_case("One Touch Record feature",
+ TAG_ONE_TOUCH_RECORD,
+ one_touch_rec_subtests),
+ test_case("Timer Progrmaming feature",
+ TAG_TIMER_PROGRAMMING,
+ timer_prog_subtests),
+ test_case("Capability Discovery and Control feature",
+ TAG_CAP_DISCOVERY_CONTROL,
+ cdc_subtests),
+ test_case_ext("Dynamic Auto Lipsync feature",
+ TAG_DYNAMIC_AUTO_LIPSYNC,
+ dal_subtests),
+ test_case_ext("Audio Return Channel feature",
+ TAG_ARC_CONTROL,
+ arc_subtests),
+ test_case_ext("System Audio Control feature",
+ TAG_SYSTEM_AUDIO_CONTROL,
+ sac_subtests),
+ test_case_ext("Audio Rate Control feature",
+ TAG_AUDIO_RATE_CONTROL,
+ audio_rate_ctl_subtests),
+ test_case_ext("One Touch Play feature",
+ TAG_ONE_TOUCH_PLAY,
+ one_touch_play_subtests),
+ test_case("Routing Control feature",
+ TAG_ROUTING_CONTROL,
+ routing_control_subtests),
+ test_case_ext("Standby/Resume and Power Status",
+ TAG_POWER_STATUS | TAG_STANDBY_RESUME,
+ standby_resume_subtests),
+ test_case("Post-test checks",
+ TAG_CORE,
+ post_test_subtests),
+};
+
+static const unsigned num_tests = sizeof(tests) / sizeof(struct remote_test);
+
+void testRemote(struct node *node, unsigned me, unsigned la, unsigned test_tags,
+ bool interactive)
+{
+ printf("testing CEC local LA %d (%s) to remote LA %d (%s):\n",
+ me, la2s(me), la, la2s(la));
+
+ if (!util_interactive_ensure_power_state(node, me, la, interactive, CEC_OP_POWER_STATUS_ON))
+ return;
+ if (node->remote[la].in_standby && !interactive) {
+ announce("The remote device is in standby. It should be powered on when testing. Aborting.");
+ return;
+ }
+ if (!node->remote[la].has_power_status) {
+ announce("The device didn't support Give Device Power Status.");
+ announce("Assuming that the device is powered on.");
+ }
+
+ int ret = 0;
+
+ for (unsigned i = 0; i < num_tests; i++) {
+ if ((tests[i].tags & test_tags) != tests[i].tags)
+ continue;
+ printf("\t%s:\n", tests[i].name);
+ for (unsigned j = 0; j < tests[i].num_subtests; j++) {
+ ret = tests[i].subtests[j].test_fn(node, me, la, interactive);
+ if (!(tests[i].subtests[j].la_mask & (1 << la)) && !ret) {
+ printf("\t %s: OK (Unexpected)\n",
+ tests[i].subtests[j].name);
+ }
+ else if (ret == PRESUMED_OK)
+ printf("\t %s: OK (Presumed)\n", tests[i].subtests[j].name);
+ else if (ret == REFUSED)
+ printf("\t %s: OK (Refused)\n", tests[i].subtests[j].name);
+ else if (ret != NOTAPPLICABLE)
+ printf("\t %s: %s\n", tests[i].subtests[j].name, ok(ret));
+ if (ret == FAIL_CRITICAL)
+ return;
+ }
+ printf("\n");
+ }
+}

Privacy Policy