blob: 87aa00a25c1ff7e06b587bc131f53abeebafe356 [file] [log] [blame]
// SPDX-License-Identifier: (LGPL-2.1-only OR BSD-3-Clause)
/*
* CEC common helper functions
*
* Copyright 2017 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
*/
#include <cctype>
#include <string>
#include <dirent.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <cec-info.h>
#include <cec-htng.h>
#include "cec-msgs-gen.h"
const char *cec_opcode2s(unsigned opcode)
{
for (auto i : msgtable)
if (i.opcode == opcode)
return i.name;
return nullptr;
}
const char *cec_cdc_opcode2s(unsigned cdc_opcode)
{
for (auto i : cdcmsgtable)
if (i.opcode == cdc_opcode)
return i.name;
return nullptr;
}
const char *cec_htng_opcode2s(unsigned htng_opcode)
{
for (auto i : htngmsgtable)
if (i.opcode == htng_opcode)
return i.name;
return nullptr;
}
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";
if (caps & CEC_CAP_NEEDS_HPD)
s += "\t\tNeeds HPD\n";
if (caps & CEC_CAP_MONITOR_PIN)
s += "\t\tMonitor Pin\n";
if (caps & CEC_CAP_CONNECTOR_INFO)
s += "\t\tConnector Info\n";
return s;
}
static std::string laflags2s(unsigned flags)
{
std::string s;
if (!flags)
return s;
s = "(";
if (flags & CEC_LOG_ADDRS_FL_ALLOW_UNREG_FALLBACK)
s += "Allow Fallback to Unregistered, ";
if (flags & CEC_LOG_ADDRS_FL_ALLOW_RC_PASSTHRU)
s += "Allow RC Passthrough, ";
if (flags & CEC_LOG_ADDRS_FL_CDC_ONLY)
s += "CDC-Only, ";
if (s.length())
s.erase(s.length() - 2, 2);
return s + ")";
}
const char *cec_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";
}
}
/*
* Most of these vendor IDs come from include/cectypes.h from libcec.
*/
const char *cec_vendor2s(unsigned vendor)
{
switch (vendor) {
case 0x000039:
case 0x000ce7:
return "Toshiba";
case 0x0000f0:
return "Samsung";
case 0x0005cd:
return "Denon";
case 0x000678:
return "Marantz";
case 0x000982:
return "Loewe";
case 0x0009b0:
return "Onkyo";
case 0x000c03:
return "HDMI";
case 0x001582:
return "Pulse-Eight";
case 0x001950:
case 0x9c645e:
return "Harman Kardon";
case 0x001a11:
return "Google";
case 0x0020c7:
return "Akai";
case 0x002467:
return "AOC";
case 0x005060:
return "Cisco";
case 0x008045:
return "Panasonic";
case 0x00903e:
return "Philips";
case 0x009053:
return "Daewoo";
case 0x00a0de:
return "Yamaha";
case 0x00d0d5:
return "Grundig";
case 0x00d38d:
return "Hospitality Profile";
case 0x00e036:
return "Pioneer";
case 0x00e091:
return "LG";
case 0x08001f:
case 0x534850:
return "Sharp";
case 0x080046:
return "Sony";
case 0x0acd8f:
return "CDDC";
case 0x18c086:
return "Broadcom";
case 0x5cad76:
return "TCL";
case 0x6b746d:
return "Vizio";
case 0x743a65:
return "NEC";
case 0x8065e9:
return "Benq";
default:
return nullptr;
}
}
const char *cec_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 *cec_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 *cec_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 "Recording Device 3";
case 10:
return "Tuner 4";
case 11:
return "Playback Device 3";
case 12:
return "Backup 1";
case 13:
return "Backup 2";
case 14:
return "Specific";
case 15:
default:
return "Unregistered";
}
}
std::string cec_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, ";
if (s.length())
return s.erase(s.length() - 2, 2);
return s;
}
std::string cec_rc_src_prof2s(unsigned prof, const std::string &prefix)
{
std::string s;
prof &= 0x1f;
if (prof == 0)
return prefix + "\t\tNone\n";
if (prof & CEC_OP_FEAT_RC_SRC_HAS_DEV_ROOT_MENU)
s += prefix + "\t\tSource Has Device Root Menu\n";
if (prof & CEC_OP_FEAT_RC_SRC_HAS_DEV_SETUP_MENU)
s += prefix + "\t\tSource Has Device Setup Menu\n";
if (prof & CEC_OP_FEAT_RC_SRC_HAS_MEDIA_CONTEXT_MENU)
s += prefix + "\t\tSource Has Contents Menu\n";
if (prof & CEC_OP_FEAT_RC_SRC_HAS_MEDIA_TOP_MENU)
s += prefix + "\t\tSource Has Media Top Menu\n";
if (prof & CEC_OP_FEAT_RC_SRC_HAS_MEDIA_CONTEXT_MENU)
s += prefix + "\t\tSource Has Media Context-Sensitive Menu\n";
return s;
}
std::string cec_dev_feat2s(unsigned feat, const std::string &prefix)
{
std::string s;
feat &= 0x7e;
if (feat == 0)
return prefix + "\t\tNone\n";
if (feat & CEC_OP_FEAT_DEV_HAS_RECORD_TV_SCREEN)
s += prefix + "\t\tTV Supports <Record TV Screen>\n";
if (feat & CEC_OP_FEAT_DEV_HAS_SET_OSD_STRING)
s += prefix + "\t\tTV Supports <Set OSD String>\n";
if (feat & CEC_OP_FEAT_DEV_HAS_DECK_CONTROL)
s += prefix + "\t\tSupports Deck Control\n";
if (feat & CEC_OP_FEAT_DEV_HAS_SET_AUDIO_RATE)
s += prefix + "\t\tSource Supports <Set Audio Rate>\n";
if (feat & CEC_OP_FEAT_DEV_SINK_HAS_ARC_TX)
s += prefix + "\t\tSink Supports ARC Tx\n";
if (feat & CEC_OP_FEAT_DEV_SOURCE_HAS_ARC_RX)
s += prefix + "\t\tSource Supports ARC Rx\n";
return s;
}
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_ABORTED)
s += ", Aborted";
if (stat & CEC_TX_STATUS_TIMEOUT)
s += ", Timeout";
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";
if (stat & CEC_RX_STATUS_ABORTED)
s += ", Aborted";
return s;
}
std::string cec_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;
}
void cec_driver_info(const struct cec_caps &caps,
const struct cec_log_addrs &laddrs, __u16 phys_addr,
const struct cec_connector_info &conn_info)
{
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);
switch (conn_info.type) {
case CEC_CONNECTOR_TYPE_NO_CONNECTOR:
printf("\tConnector Info : None\n");
break;
case CEC_CONNECTOR_TYPE_DRM:
printf("\tDRM Connector Info : card %u, connector %u\n",
conn_info.drm.card_no, conn_info.drm.connector_id);
break;
default:
printf("\tConnector Info : Type %u\n",
conn_info.type);
break;
}
printf("\tPhysical Address : %x.%x.%x.%x\n",
cec_phys_addr_exp(phys_addr));
printf("\tLogical Address Mask : 0x%04x\n", laddrs.log_addr_mask);
printf("\tCEC Version : %s\n", cec_version2s(laddrs.cec_version));
if (laddrs.vendor_id != CEC_VENDOR_ID_NONE) {
const char *vendor = cec_vendor2s(laddrs.vendor_id);
if (vendor)
printf("\tVendor ID : 0x%06x (%s)\n",
laddrs.vendor_id, vendor);
else
printf("\tVendor ID : 0x%06x, %u\n",
laddrs.vendor_id, laddrs.vendor_id);
}
printf("\tOSD Name : '%s'\n", laddrs.osd_name);
printf("\tLogical Addresses : %u %s\n",
laddrs.num_log_addrs, laflags2s(laddrs.flags).c_str());
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 (%s)\n",
laddrs.log_addr[i], cec_la2s(laddrs.log_addr[i]));
printf("\t Primary Device Type : %s\n",
cec_prim_type2s(laddrs.primary_device_type[i]));
printf("\t Logical Address Type : %s\n",
cec_la_type2s(laddrs.log_addr_type[i]));
printf("\t All Device Types : %s\n",
cec_all_dev_types2s(laddrs.all_device_types[i]).c_str());
bool is_dev_feat = false;
for (__u8 byte : laddrs.features[i]) {
if (!is_dev_feat) {
if (byte & 0x40) {
printf("\t RC Source Profile :\n%s",
cec_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",
cec_dev_feat2s(byte, "").c_str());
}
if (byte & CEC_OP_FEAT_EXT)
continue;
if (!is_dev_feat)
is_dev_feat = true;
else
break;
}
}
}
std::string cec_device_find(const char *driver, const char *adapter)
{
DIR *dp;
struct dirent *ep;
std::string name;
dp = opendir("/dev");
if (dp == nullptr) {
perror("Couldn't open the directory");
return name;
}
while ((ep = readdir(dp)))
if (!memcmp(ep->d_name, "cec", 3) && isdigit(ep->d_name[3])) {
std::string devname("/dev/");
struct cec_caps caps;
int fd;
devname += ep->d_name;
fd = open(devname.c_str(), O_RDWR);
if (fd < 0)
continue;
int err = ioctl(fd, CEC_ADAP_G_CAPS, &caps);
close(fd);
if (err)
continue;
if ((!driver || !strcmp(driver, caps.driver)) &&
(!adapter || !strcmp(adapter, caps.name))) {
name = devname;
break;
}
}
closedir(dp);
return name;
}