@@ -26,6 +26,7 @@ AC_CONFIG_FILES([Makefile
utils/keytable/Makefile
utils/media-ctl/Makefile
utils/rds/Makefile
+ utils/cec-compliance/Makefile
utils/v4l2-compliance/Makefile
utils/v4l2-ctl/Makefile
utils/v4l2-dbg/Makefile
@@ -5,6 +5,7 @@ SUBDIRS = \
decode_tm6000 \
keytable \
media-ctl \
+ cec-compliance \
v4l2-compliance \
v4l2-ctl \
v4l2-dbg \
new file mode 100644
@@ -0,0 +1,3 @@
+bin_PROGRAMS = cec-compliance
+
+cec_compliance_SOURCES = cec-compliance.cpp
new file mode 100644
@@ -0,0 +1,943 @@
+/*
+ Copyright 2015 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
+ Author: Hans Verkuil <hans.verkuil@cisco.com>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+ */
+
+#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 <config.h>
+
+#include "cec-compliance.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 {
+ OptPhysAddr = 'a',
+ OptSetDevice = 'd',
+ OptHelp = 'h',
+ OptNoWarnings = 'n',
+ OptTrace = 'T',
+ OptVerbose = 'v',
+ OptVendorID = 'V',
+
+ OptTV,
+ OptRecord,
+ OptTuner,
+ OptPlayback,
+ OptAudio,
+ OptProcessor,
+ OptSwitch,
+ OptCDCOnly,
+ OptUnregistered,
+ 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},
+ {"trace", no_argument, 0, OptTrace},
+ {"verbose", no_argument, 0, OptVerbose},
+ {"phys-addr", required_argument, 0, OptPhysAddr},
+ {"vendor-id", required_argument, 0, OptVendorID},
+
+ {"tv", no_argument, 0, OptTV},
+ {"record", no_argument, 0, OptRecord},
+ {"tuner", no_argument, 0, OptTuner},
+ {"playback", no_argument, 0, OptPlayback},
+ {"audio", no_argument, 0, OptAudio},
+ {"processor", no_argument, 0, OptProcessor},
+ {"switch", no_argument, 0, OptSwitch},
+ {"cdc-only", no_argument, 0, OptCDCOnly},
+ {"unregistered", no_argument, 0, OptUnregistered},
+ {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"
+ " -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"
+ " -a, --phys-addr=<addr>\n"
+ " Use this physical address.\n"
+ " -V, --vendor-id=<id>\n"
+ " Use this vendor ID.\n"
+ " --tv This is a TV\n"
+ " --record This is a recording device\n"
+ " --tuner This is a tuner device\n"
+ " --playback This is a playback device\n"
+ " --audio This is an audio system device\n"
+ " --processor This is a processor device\n"
+ " --switch This is a pure CEC switch\n"
+ " --cdc-only This is a CDC-only device\n"
+ " --unregistered This is an unregistered device\n"
+ );
+}
+
+static std::string caps2s(unsigned caps)
+{
+ std::string s;
+
+ if (caps & CEC_CAP_STATE)
+ s += "\t\tState\n";
+ 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_RECEIVE)
+ s += "\t\tReceive\n";
+ if (caps & CEC_CAP_VENDOR_ID)
+ s += "\t\tVendor ID\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_ARC)
+ s += "\t\tAudio Return Channel\n";
+ if (caps & CEC_CAP_CDC)
+ s += "\t\tCapability Discovery and Control\n";
+ return s;
+}
+
+static 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";
+ }
+}
+
+static const char *power_status2s(unsigned status)
+{
+ switch (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 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";
+ }
+}
+
+static 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";
+ }
+}
+
+static 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 la_flags2s(unsigned flags)
+{
+ std::string s;
+
+ if (flags & CEC_LOG_ADDRS_FL_HANDLE_MSGS)
+ s += "Userspace Handles Messages";
+ return s;
+}
+
+static std::string status2s(unsigned stat)
+{
+ std::string s;
+
+ if (stat & CEC_TX_STATUS_ARB_LOST)
+ s += "ArbitrationLost ";
+ if (stat & CEC_TX_STATUS_REPLY_TIMEOUT)
+ s += "ReplyTimeout ";
+ if (stat & CEC_TX_STATUS_RETRY_TIMEOUT)
+ s += "RetryTimeout ";
+ if (stat & CEC_TX_STATUS_FEATURE_ABORT)
+ s += "FeatureAbort ";
+ return s;
+}
+
+static std::string all_dev_types2s(unsigned types)
+{
+ std::string s;
+
+ if (types & CEC_FL_ALL_DEVTYPE_TV)
+ s += "TV, ";
+ if (types & CEC_FL_ALL_DEVTYPE_RECORD)
+ s += "Record, ";
+ if (types & CEC_FL_ALL_DEVTYPE_TUNER)
+ s += "Tuner, ";
+ if (types & CEC_FL_ALL_DEVTYPE_PLAYBACK)
+ s += "Playback, ";
+ if (types & CEC_FL_ALL_DEVTYPE_AUDIOSYSTEM)
+ s += "Audio System, ";
+ if (types & CEC_FL_ALL_DEVTYPE_SWITCH)
+ s += "Switch, ";
+ return s.erase(s.length() - 2, 2);
+}
+
+static 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;
+}
+
+static std::string dev_feat2s(unsigned feat)
+{
+ std::string s;
+
+ feat &= 0x3e;
+ 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;
+}
+
+int cec_named_ioctl(int fd, const char *name,
+ unsigned long int request, void *parm)
+{
+ int retval = ioctl(fd, request, parm);
+ int e;
+
+ 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;
+
+ return retval == -1 ? e : (retval ? -1 : 0);
+}
+
+const char *ok(int res)
+{
+ static char buf[100];
+
+ if (res == ENOTTY) {
+ 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 testCap(struct node *node)
+{
+ struct cec_caps caps;
+
+ memset(&caps, 0xff, sizeof(caps));
+ // Must always be there
+ fail_on_test(doioctl(node, CEC_G_CAPS, &caps));
+ fail_on_test(check_0(caps.reserved, sizeof(caps.reserved)));
+ 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_RECEIVE));
+ return 0;
+}
+
+static int testAdapPhysAddr(struct node *node, __u16 set_phys_addr)
+{
+ __u16 pa = 0xefff;
+
+ fail_on_test(doioctl(node, CEC_G_ADAP_PHYS_ADDR, &pa));
+ fail_on_test(pa == 0xefff);
+ if (node->caps & CEC_CAP_PHYS_ADDR) {
+ fail_on_test(doioctl(node, CEC_S_ADAP_PHYS_ADDR, &set_phys_addr));
+ fail_on_test(doioctl(node, CEC_G_ADAP_PHYS_ADDR, &pa));
+ fail_on_test(pa != set_phys_addr);
+ } else {
+ fail_on_test(doioctl(node, CEC_S_ADAP_PHYS_ADDR, &pa) != ENOTTY);
+ }
+ return 0;
+}
+
+static int testVendorID(struct node *node, __u32 set_vendor_id)
+{
+ __u32 vendor_id = 0xeeeeeeee;
+
+ fail_on_test(doioctl(node, CEC_G_VENDOR_ID, &vendor_id));
+ fail_on_test(vendor_id == 0xeeeeeeee);
+ fail_on_test(vendor_id != CEC_VENDOR_ID_NONE &&
+ (vendor_id & 0xff000000));
+ if (node->caps & CEC_CAP_VENDOR_ID) {
+ vendor_id = 0x000c03; /* HDMI LLC vendor ID */
+ fail_on_test(doioctl(node, CEC_S_VENDOR_ID, &vendor_id) != EINVAL);
+ fail_on_test(doioctl(node, CEC_S_VENDOR_ID, &set_vendor_id));
+ fail_on_test(doioctl(node, CEC_G_VENDOR_ID, &vendor_id));
+ fail_on_test(vendor_id != set_vendor_id);
+ } else {
+ fail_on_test(doioctl(node, CEC_S_VENDOR_ID, &vendor_id) != ENOTTY);
+ }
+ return 0;
+}
+
+static int testAdapState(struct node *node)
+{
+ __u32 state = 0xffffffff;
+
+ fail_on_test(doioctl(node, CEC_G_ADAP_STATE, &state));
+ fail_on_test(state > CEC_STATE_ENABLED);
+ if (node->caps & CEC_CAP_STATE) {
+ state = CEC_STATE_DISABLED;
+ fail_on_test(doioctl(node, CEC_S_ADAP_STATE, &state));
+ fail_on_test(doioctl(node, CEC_G_ADAP_STATE, &state));
+ fail_on_test(state != CEC_STATE_DISABLED);
+ state = CEC_STATE_ENABLED;
+ fail_on_test(doioctl(node, CEC_S_ADAP_STATE, &state));
+ fail_on_test(doioctl(node, CEC_G_ADAP_STATE, &state));
+ fail_on_test(state != CEC_STATE_ENABLED);
+
+ /*
+ * Do this again, thus guaranteeing that there is always
+ * a disabled -> enabled and an enabled -> disabled state
+ * transition tested.
+ */
+ state = CEC_STATE_DISABLED;
+ fail_on_test(doioctl(node, CEC_S_ADAP_STATE, &state));
+ fail_on_test(doioctl(node, CEC_G_ADAP_STATE, &state));
+ fail_on_test(state != CEC_STATE_DISABLED);
+ state = CEC_STATE_ENABLED;
+ fail_on_test(doioctl(node, CEC_S_ADAP_STATE, &state));
+ fail_on_test(doioctl(node, CEC_G_ADAP_STATE, &state));
+ fail_on_test(state != CEC_STATE_ENABLED);
+ } else {
+ fail_on_test(doioctl(node, CEC_S_ADAP_STATE, &state) != ENOTTY);
+ }
+ return 0;
+}
+
+static int testAdapLogAddrs(struct node *node, unsigned flags,
+ const char *osd_name)
+{
+ struct cec_log_addrs laddrs;
+
+ memset(&laddrs, 0xff, sizeof(laddrs));
+ fail_on_test(doioctl(node, CEC_G_ADAP_LOG_ADDRS, &laddrs));
+ fail_on_test(check_0(laddrs.reserved, sizeof(laddrs.reserved)));
+ 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->caps & CEC_CAP_LOG_ADDRS) {
+ memset(&laddrs, 0, sizeof(laddrs));
+ fail_on_test(doioctl(node, CEC_S_ADAP_LOG_ADDRS, &laddrs));
+ fail_on_test(laddrs.num_log_addrs != 0);
+ fail_on_test(doioctl(node, CEC_G_ADAP_LOG_ADDRS, &laddrs));
+ fail_on_test(laddrs.num_log_addrs != 0);
+
+ memset(&laddrs, 0, sizeof(laddrs));
+ laddrs.cec_version = CEC_OP_CEC_VERSION_2_0;
+ strcpy(laddrs.osd_name, osd_name);
+ for (unsigned i = 0; i < 8; i++) {
+ unsigned la_type;
+ unsigned all_dev_type;
+
+ if (!(flags & (1 << i)))
+ continue;
+ fail_on_test(laddrs.num_log_addrs == node->available_log_addrs);
+ switch (i) {
+ case CEC_OP_PRIM_DEVTYPE_TV:
+ la_type = CEC_LOG_ADDR_TYPE_TV;
+ all_dev_type = CEC_FL_ALL_DEVTYPE_TV;
+ break;
+ case CEC_OP_PRIM_DEVTYPE_RECORD:
+ la_type = CEC_LOG_ADDR_TYPE_RECORD;
+ all_dev_type = CEC_FL_ALL_DEVTYPE_RECORD;
+ break;
+ case CEC_OP_PRIM_DEVTYPE_TUNER:
+ la_type = CEC_LOG_ADDR_TYPE_TUNER;
+ all_dev_type = CEC_FL_ALL_DEVTYPE_TUNER;
+ break;
+ case CEC_OP_PRIM_DEVTYPE_PLAYBACK:
+ la_type = CEC_LOG_ADDR_TYPE_PLAYBACK;
+ all_dev_type = CEC_FL_ALL_DEVTYPE_PLAYBACK;
+ break;
+ case CEC_OP_PRIM_DEVTYPE_AUDIOSYSTEM:
+ la_type = CEC_LOG_ADDR_TYPE_AUDIOSYSTEM;
+ all_dev_type = CEC_FL_ALL_DEVTYPE_AUDIOSYSTEM;
+ break;
+ case CEC_OP_PRIM_DEVTYPE_PROCESSOR:
+ la_type = CEC_LOG_ADDR_TYPE_SPECIFIC;
+ all_dev_type = CEC_FL_ALL_DEVTYPE_SWITCH;
+ break;
+ case CEC_OP_PRIM_DEVTYPE_SWITCH:
+ default:
+ la_type = CEC_LOG_ADDR_TYPE_UNREGISTERED;
+ all_dev_type = CEC_FL_ALL_DEVTYPE_SWITCH;
+ break;
+ }
+ laddrs.log_addr_type[laddrs.num_log_addrs] = la_type;
+ laddrs.all_device_types[laddrs.num_log_addrs] = all_dev_type;
+ laddrs.primary_device_type[laddrs.num_log_addrs++] = i;
+ }
+
+ fail_on_test(doioctl(node, CEC_S_ADAP_LOG_ADDRS, &laddrs));
+ fail_on_test(laddrs.num_log_addrs == 0 ||
+ laddrs.num_log_addrs > CEC_MAX_LOG_ADDRS);
+ node->num_log_addrs = laddrs.num_log_addrs;
+ memcpy(node->log_addr, laddrs.log_addr, laddrs.num_log_addrs);
+ fail_on_test(doioctl(node, CEC_S_ADAP_LOG_ADDRS, &laddrs) != EBUSY);
+ } else {
+ node->num_log_addrs = laddrs.num_log_addrs;
+ memcpy(node->log_addr, laddrs.log_addr, laddrs.num_log_addrs);
+ fail_on_test(doioctl(node, CEC_S_ADAP_LOG_ADDRS, &laddrs) != ENOTTY);
+ }
+ return 0;
+}
+
+static bool validMsgStatus(const struct cec_msg &msg)
+{
+ if (msg.status == 0)
+ return true;
+ std::string s = status2s(msg.status);
+ warn("%s\n", s.c_str());
+ return false;
+}
+
+static int testTopologyDevice(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));
+
+ msg.len = 2;
+ msg.msg[0] = (la << 4) | (i & 0xf);
+ msg.msg[1] = CEC_MSG_GET_CEC_VERSION;
+ msg.reply = CEC_MSG_CEC_VERSION;
+ fail_on_test(doioctl(node, CEC_TRANSMIT, &msg));
+ if (validMsgStatus(msg))
+ printf("\t\tCEC Version : %s\n", version2s(msg.msg[2]));
+
+ msg.len = 2;
+ msg.msg[0] = (la << 4) | (i & 0xf);
+ msg.msg[1] = CEC_MSG_GIVE_PHYSICAL_ADDR;
+ msg.reply = CEC_MSG_REPORT_PHYSICAL_ADDR;
+ fail_on_test(doioctl(node, CEC_TRANSMIT, &msg));
+ //fail_on_test(msg.status);
+ validMsgStatus(msg);
+ __u16 phys_addr = (msg.msg[2] << 8) | msg.msg[3];
+ printf("\t\tPhysical Address : %x.%x.%x.%x\n",
+ phys_addr >> 12, (phys_addr >> 8) & 0xf,
+ (phys_addr >> 4) & 0xf, phys_addr & 0xf);
+ printf("\t\tPrimary Device Type : %s\n",
+ prim_type2s(msg.msg[4]));
+
+ msg.len = 2;
+ msg.msg[0] = (la << 4) | (i & 0xf);
+ msg.msg[1] = CEC_MSG_GIVE_DEVICE_VENDOR_ID;
+ msg.reply = CEC_MSG_DEVICE_VENDOR_ID;
+ fail_on_test(doioctl(node, CEC_TRANSMIT, &msg));
+ if (validMsgStatus(msg))
+ printf("\t\tVendor ID : 0x%02x%02x%02x\n",
+ msg.msg[2], msg.msg[3], msg.msg[4]);
+
+ msg.len = 2;
+ msg.msg[0] = (la << 4) | (i & 0xf);
+ msg.msg[1] = CEC_MSG_GIVE_DEVICE_POWER_STATUS;
+ msg.reply = CEC_MSG_REPORT_POWER_STATUS;
+ fail_on_test(doioctl(node, CEC_TRANSMIT, &msg));
+ if (validMsgStatus(msg))
+ printf("\t\tPower Status : %s\n",
+ power_status2s(msg.msg[2]));
+
+ msg.len = 2;
+ msg.msg[0] = (la << 4) | (i & 0xf);
+ msg.msg[1] = CEC_MSG_GIVE_OSD_NAME;
+ msg.reply = CEC_MSG_SET_OSD_NAME;
+ fail_on_test(doioctl(node, CEC_TRANSMIT, &msg));
+ if (validMsgStatus(msg)) {
+ char s[15];
+
+ memset(s, 0, sizeof(s));
+ memcpy(s, msg.msg + 2, msg.len - 2);
+ fail_on_test(msg.status);
+ printf("\t\tOSD Name : %s\n", s);
+ }
+ return 0;
+}
+
+static int testTopology(struct node *node)
+{
+ struct cec_msg msg = { };
+ struct cec_log_addrs laddrs = { };
+
+ fail_on_test(doioctl(node, CEC_G_ADAP_LOG_ADDRS, &laddrs));
+
+ if (!(node->caps & CEC_CAP_TRANSMIT)) {
+ msg.len = 1;
+ msg.msg[0] = 15 << 4;
+ fail_on_test(doioctl(node, CEC_TRANSMIT, &msg) != ENOTTY);
+ return -ENOTTY;
+ }
+
+ for (unsigned i = 0; i < 15; i++) {
+ int ret;
+
+ msg.len = 1;
+ msg.msg[0] = (15 << 4) | (i & 0xf);
+ ret = doioctl(node, CEC_TRANSMIT, &msg);
+
+ switch (msg.status) {
+ case CEC_TX_STATUS_OK:
+ fail_on_test(testTopologyDevice(node, i, laddrs.log_addr[0]));
+ break;
+ case CEC_TX_STATUS_ARB_LOST:
+ warn("tx arbitration lost for addr %d\n", i);
+ break;
+ case CEC_TX_STATUS_RETRY_TIMEOUT:
+ break;
+ default:
+ return fail("ret ? %d\n", ret);
+ }
+ }
+ return 0;
+}
+
+static int testARC(struct node *node)
+{
+ struct cec_msg msg = { };
+ struct cec_log_addrs laddrs = { };
+ unsigned la;
+ unsigned i;
+
+ fail_on_test(doioctl(node, CEC_G_ADAP_LOG_ADDRS, &laddrs));
+ la = laddrs.log_addr[0];
+
+ for (i = 0; i < 15; i++) {
+ if (i == la)
+ continue;
+ msg.len = 2;
+ msg.msg[0] = (la << 4) | (i & 0xf);
+ msg.msg[1] = CEC_MSG_INITIATE_ARC;
+ msg.reply = CEC_MSG_REPORT_ARC_INITIATED;
+ fail_on_test(doioctl(node, CEC_TRANSMIT, &msg));
+ if (msg.status == 0) {
+ msg.len = 2;
+ msg.msg[0] = (la << 4) | (i & 0xf);
+ msg.msg[1] = CEC_MSG_TERMINATE_ARC;
+ msg.reply = CEC_MSG_REPORT_ARC_TERMINATED;
+ fail_on_test(doioctl(node, CEC_TRANSMIT, &msg));
+ fail_on_test(msg.status);
+ printf("logical address %d supports ARC\n", i);
+ } else {
+ printf("logical address %d doesn't support ARC\n", i);
+ }
+ }
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ const char *device = "/dev/cec0"; /* -d device */
+ char short_options[26 * 2 * 2 + 1];
+ __u32 vendor_id;
+ __u16 phys_addr;
+ 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++] = ':';
+ }
+ 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 OptVerbose:
+ show_info = true;
+ break;
+ case OptPhysAddr:
+ phys_addr = strtoul(optarg, NULL, 0);
+ break;
+ case OptVendorID:
+ vendor_id = strtoul(optarg, NULL, 0) & 0x00ffffff;
+ break;
+ case OptSwitch:
+ if (options[OptCDCOnly] || options[OptUnregistered]) {
+ fprintf(stderr, "--switch cannot be combined with --cdc-only or --unregistered.\n");
+ usage();
+ return 1;
+ }
+ break;
+ case OptCDCOnly:
+ if (options[OptSwitch] || options[OptUnregistered]) {
+ fprintf(stderr, "--cdc-only cannot be combined with --switch or --unregistered.\n");
+ usage();
+ return 1;
+ }
+ break;
+ case OptUnregistered:
+ if (options[OptCDCOnly] || options[OptSwitch]) {
+ fprintf(stderr, "--unregistered cannot be combined with --cdc-only or --switch.\n");
+ usage();
+ return 1;
+ }
+ 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_G_CAPS, &caps);
+ node.caps = caps.capabilities;
+ node.available_log_addrs = caps.available_log_addrs;
+
+ unsigned flags = 0;
+ const char *osd_name;
+
+ if (options[OptTV])
+ osd_name = "TV";
+ else if (options[OptRecord])
+ osd_name = "Record";
+ else if (options[OptPlayback])
+ osd_name = "Playback";
+ else if (options[OptTuner])
+ osd_name = "Tuner";
+ else if (options[OptAudio])
+ osd_name = "Audio System";
+ else if (options[OptProcessor])
+ osd_name = "Processor";
+ else if (options[OptSwitch] || options[OptCDCOnly] || options[OptUnregistered])
+ osd_name = "";
+ else
+ osd_name = "TV";
+
+ if (options[OptTV])
+ flags |= 1 << CEC_OP_PRIM_DEVTYPE_TV;
+ if (options[OptRecord])
+ flags |= 1 << CEC_OP_PRIM_DEVTYPE_RECORD;
+ if (options[OptTuner])
+ flags |= 1 << CEC_OP_PRIM_DEVTYPE_TUNER;
+ if (options[OptPlayback])
+ flags |= 1 << CEC_OP_PRIM_DEVTYPE_PLAYBACK;
+ if (options[OptAudio])
+ flags |= 1 << CEC_OP_PRIM_DEVTYPE_AUDIOSYSTEM;
+ if (options[OptProcessor])
+ flags |= 1 << CEC_OP_PRIM_DEVTYPE_PROCESSOR;
+ if (options[OptSwitch] || options[OptCDCOnly] || options[OptUnregistered])
+ flags |= 1 << CEC_OP_PRIM_DEVTYPE_SWITCH;
+ if (flags == 0)
+ flags |= 1 << CEC_OP_PRIM_DEVTYPE_TV;
+
+ printf("Driver Info:\n");
+ printf("\tCapabilities : 0x%08x\n", caps.capabilities);
+ printf("%s", caps2s(caps.capabilities).c_str());
+ printf("\tAvailable Logical Addresses: %u\n",
+ caps.available_log_addrs);
+
+ doioctl(&node, CEC_G_ADAP_PHYS_ADDR, &phys_addr);
+ printf("\tPhysical Address : %x.%x.%x.%x\n",
+ phys_addr >> 12, (phys_addr >> 8) & 0xf,
+ (phys_addr >> 4) & 0xf, phys_addr & 0xf);
+ if (!options[OptPhysAddr] && phys_addr == 0xffff &&
+ (node.caps & CEC_CAP_PHYS_ADDR))
+ warn("Perhaps you should use option --phys-addr?\n");
+
+ doioctl(&node, CEC_G_VENDOR_ID, &vendor_id);
+ if (vendor_id != CEC_VENDOR_ID_NONE)
+ printf("\tVendor ID : 0x%06x\n", vendor_id);
+
+ __u32 adap_state;
+ doioctl(&node, CEC_G_ADAP_STATE, &adap_state);
+ printf("\tAdapter State : %s\n", adap_state ? "Enabled" : "Disabled");
+
+ struct cec_log_addrs laddrs = { };
+ doioctl(&node, CEC_G_ADAP_LOG_ADDRS, &laddrs);
+ printf("\tCEC Version : %s\n", version2s(laddrs.cec_version));
+ printf("\tLogical Addresses : %u\n", laddrs.num_log_addrs);
+ for (unsigned i = 0; i < laddrs.num_log_addrs; i++) {
+ printf("\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]));
+ printf("\t Flags : %s\n",
+ la_flags2s(laddrs.flags[i]).c_str());
+ 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\n",
+ 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\n",
+ dev_feat2s(byte).c_str());
+ }
+ if (byte & CEC_OP_FEAT_EXT)
+ continue;
+ if (!is_dev_feat)
+ is_dev_feat = true;
+ else
+ break;
+ }
+ }
+
+ printf("\nCompliance test for device %s:\n\n", device);
+
+ /* Required ioctls */
+
+ printf("Required ioctls:\n");
+ printf("\ttest CEC_G_CAPS: %s\n", ok(testCap(&node)));
+ printf("\ttest CEC_G/S_ADAP_PHYS_ADDR: %s\n", ok(testAdapPhysAddr(&node, phys_addr)));
+ printf("\ttest CEC_G/S_VENDOR_ID: %s\n", ok(testVendorID(&node, vendor_id)));
+ printf("\ttest CEC_G/S_ADAP_STATE: %s\n", ok(testAdapState(&node)));
+ printf("\ttest CEC_G/S_ADAP_LOG_ADDRS: %s\n", ok(testAdapLogAddrs(&node, flags, osd_name)));
+ printf("\ttest CEC topology discovery: %s\n", ok(testTopology(&node)));
+ printf("\ttest CEC ARC: %s\n", ok(testARC(&node)));
+ printf("\n");
+
+ /* 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);
+}
new file mode 100644
@@ -0,0 +1,87 @@
+/*
+ CEC API compliance test tool.
+
+ Copyright 2015 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
+ Author: Hans Verkuil <hans.verkuil@cisco.com>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+ */
+
+#ifndef _CEC_COMPLIANCE_H_
+#define _CEC_COMPLIANCE_H_
+
+#include <stdarg.h>
+#include <cerrno>
+#include <string>
+#include <linux/cec.h>
+
+extern bool show_info;
+extern bool show_warnings;
+extern unsigned warnings;
+
+struct node {
+ int fd;
+ const char *device;
+ unsigned caps;
+ unsigned available_log_addrs;
+ unsigned num_log_addrs;
+ __u8 log_addr[CEC_MAX_LOG_ADDRS];
+};
+
+#define info(fmt, args...) \
+ do { \
+ if (show_info) \
+ printf("\t\tinfo: " fmt, ##args); \
+ } 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); \
+ 1; \
+})
+
+#define fail_on_test(test) \
+ do { \
+ if (test) \
+ return fail("%s\n", #test); \
+ } while (0)
+
+int cec_named_ioctl(int fd, const char *name,
+ unsigned long int request, void *parm);
+
+#define doioctl(n, r, p) cec_named_ioctl((n)->fd, #r, r, p)
+
+const char *ok(int res);
+
+// CEC core tests
+int testCore(struct node *node);
+
+#endif