aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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