diff options
author | Hans Verkuil <hans.verkuil@cisco.com> | 2016-07-10 11:26:24 +0200 |
---|---|---|
committer | Hans Verkuil <hans.verkuil@cisco.com> | 2016-07-10 11:26:24 +0200 |
commit | b3d044ecadb020a02e14b75ab07582541e03440a (patch) | |
tree | 715dfe06bfed42aad9101dee9ef88bdc1ff0c446 /utils | |
parent | 5e74f6a15aa14c01d8319e086d98f33d96a6a04d (diff) |
cec-ctl: add CEC control utility
This utility can be used to receive and send CEC messages over
/dev/cecX device nodes.
Signed-off-by: Hans Verkuil <hans.verkuil@cisco.com>
Diffstat (limited to 'utils')
-rw-r--r-- | utils/Makefile.am | 1 | ||||
-rw-r--r-- | utils/cec-ctl/.gitignore | 2 | ||||
-rw-r--r-- | utils/cec-ctl/Makefile.am | 11 | ||||
-rw-r--r-- | utils/cec-ctl/cec-ctl.cpp | 1518 | ||||
-rwxr-xr-x | utils/cec-ctl/msg2ctl.pl | 464 |
5 files changed, 1996 insertions, 0 deletions
diff --git a/utils/Makefile.am b/utils/Makefile.am index 5674c16b..2cb56f0d 100644 --- a/utils/Makefile.am +++ b/utils/Makefile.am @@ -11,6 +11,7 @@ SUBDIRS = \ v4l2-ctl \ v4l2-dbg \ v4l2-sysfs-path \ + cec-ctl \ rds-ctl if LINUX_OS diff --git a/utils/cec-ctl/.gitignore b/utils/cec-ctl/.gitignore new file mode 100644 index 00000000..91cfc1a7 --- /dev/null +++ b/utils/cec-ctl/.gitignore @@ -0,0 +1,2 @@ +cec-ctl +cec-ctl-gen.h diff --git a/utils/cec-ctl/Makefile.am b/utils/cec-ctl/Makefile.am new file mode 100644 index 00000000..a959acc2 --- /dev/null +++ b/utils/cec-ctl/Makefile.am @@ -0,0 +1,11 @@ +bin_PROGRAMS = cec-ctl + +cec_ctl_SOURCES = cec-ctl.cpp + +cec-ctl.cpp: cec-ctl-gen.h + +cec-ctl-gen.h: msg2ctl.pl ../../include/linux/cec.h ../../include/linux/cec-funcs.h + ./msg2ctl.pl 0 ../../include/linux/cec.h ../../include/linux/cec-funcs.h >$@ + +clean-local: + -rm -vf cec-ctl-gen.h diff --git a/utils/cec-ctl/cec-ctl.cpp b/utils/cec-ctl/cec-ctl.cpp new file mode 100644 index 00000000..d55030b1 --- /dev/null +++ b/utils/cec-ctl/cec-ctl.cpp @@ -0,0 +1,1518 @@ +/* + * 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 <stdarg.h> +#include <cerrno> +#include <string> +#include <vector> +#include <linux/cec-funcs.h> + +#ifdef __ANDROID__ +#include <android-config.h> +#else +#include <config.h> +#endif + +#define CEC_MAX_ARGS 16 + +#define xstr(s) str(s) +#define str(s) #s + +struct cec_enum_values { + const char *type_name; + __u8 value; +}; + +enum cec_types { + CEC_TYPE_U8, + CEC_TYPE_U16, + CEC_TYPE_U32, + CEC_TYPE_STRING, + CEC_TYPE_ENUM, +}; + +struct arg { + enum cec_types type; + __u8 num_enum_values; + const struct cec_enum_values *values; +}; + +static const struct arg arg_u8 = { + CEC_TYPE_U8, +}; + +static const struct arg arg_u16 = { + CEC_TYPE_U16, +}; + +static const struct arg arg_u32 = { + CEC_TYPE_U32, +}; + +static const struct arg arg_string = { + CEC_TYPE_STRING, +}; + +static const struct cec_enum_values type_ui_cmd[] = { + { "Select", 0x00 }, + { "Up", 0x01 }, + { "Down", 0x02 }, + { "Left", 0x03 }, + { "Right", 0x04 }, + { "Right-Up", 0x05 }, + { "Right-Down", 0x06 }, + { "Left-Up", 0x07 }, + { "Left-Down", 0x08 }, + { "Device Root Menu", 0x09 }, + { "Device Setup Menu", 0x0a }, + { "Contents Menu", 0x0b }, + { "Favorite Menu", 0x0c }, + { "Back", 0x0d }, + { "Media Top Menu", 0x10 }, + { "Media Context-sensitive Menu", 0x11 }, + { "Number Entry Mode", 0x1d }, + { "Number 11", 0x1e }, + { "Number 12", 0x1f }, + { "Number 0 or Number 10", 0x20 }, + { "Number 1", 0x21 }, + { "Number 2", 0x22 }, + { "Number 3", 0x23 }, + { "Number 4", 0x24 }, + { "Number 5", 0x25 }, + { "Number 6", 0x26 }, + { "Number 7", 0x27 }, + { "Number 8", 0x28 }, + { "Number 9", 0x29 }, + { "Dot", 0x2a }, + { "Enter", 0x2b }, + { "Clear", 0x2c }, + { "Next Favorite", 0x2f }, + { "Channel Up", 0x30 }, + { "Channel Down", 0x31 }, + { "Previous Channel", 0x32 }, + { "Sound Select", 0x33 }, + { "Input Select", 0x34 }, + { "Display Information", 0x35 }, + { "Help", 0x36 }, + { "Page Up", 0x37 }, + { "Page Down", 0x38 }, + { "Power", 0x40 }, + { "Volume Up", 0x41 }, + { "Volume Down", 0x42 }, + { "Mute", 0x43 }, + { "Play", 0x44 }, + { "Stop", 0x45 }, + { "Pause", 0x46 }, + { "Record", 0x47 }, + { "Rewind", 0x48 }, + { "Fast forward", 0x49 }, + { "Eject", 0x4a }, + { "Skip Forward", 0x4b }, + { "Skip Backward", 0x4c }, + { "Stop-Record", 0x4d }, + { "Pause-Record", 0x4e }, + { "Angle", 0x50 }, + { "Sub picture", 0x51 }, + { "Video on Demand", 0x52 }, + { "Electronic Program Guide", 0x53 }, + { "Timer Programming", 0x54 }, + { "Initial Configuration", 0x55 }, + { "Select Broadcast Type", 0x56 }, + { "Select Sound Presentation", 0x57 }, + { "Audio Description", 0x58 }, + { "Internet", 0x59 }, + { "3D Mode", 0x5a }, + { "Play Function", 0x60 }, + { "Pause-Play Function", 0x61 }, + { "Record Function", 0x62 }, + { "Pause-Record Function", 0x63 }, + { "Stop Function", 0x64 }, + { "Mute Function", 0x65 }, + { "Restore Volume Function", 0x66 }, + { "Tune Function", 0x67 }, + { "Select Media Function", 0x68 }, + { "Select A/V Input Function", 0x69 }, + { "Select Audio Input Function", 0x6a }, + { "Power Toggle Function", 0x6b }, + { "Power Off Function", 0x6c }, + { "Power On Function", 0x6d }, + { "F1 (Blue)", 0x71 }, + { "F2 (Red)", 0x72 }, + { "F3 (Green)", 0x73 }, + { "F4 (Yellow)", 0x74 }, + { "F5", 0x75 }, + { "Data", 0x76 }, +}; + +static const struct arg arg_rc_ui_cmd = { + CEC_TYPE_ENUM, sizeof(type_ui_cmd) / sizeof(type_ui_cmd[0]), type_ui_cmd +}; + +struct message { + __u8 msg; + unsigned option; + __u8 num_args; + const char *arg_names[CEC_MAX_ARGS+1]; + const struct arg *args[CEC_MAX_ARGS]; + const char *msg_name; +}; + +static struct cec_op_digital_service_id *args2digital_service_id(__u8 service_id_method, + __u8 dig_bcast_system, + __u16 transport_id, + __u16 service_id, + __u16 orig_network_id, + __u16 program_number, + __u8 channel_number_fmt, + __u16 major, + __u16 minor) +{ + static struct cec_op_digital_service_id dsid; + + dsid.service_id_method = service_id_method; + dsid.dig_bcast_system = dig_bcast_system; + if (service_id_method == CEC_OP_SERVICE_ID_METHOD_BY_CHANNEL) { + dsid.channel.channel_number_fmt = channel_number_fmt; + dsid.channel.major = major; + dsid.channel.minor = minor; + return &dsid; + } + switch (dig_bcast_system) { + case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_GEN: + case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_CABLE: + case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_SAT: + case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_T: + dsid.atsc.transport_id = transport_id; + dsid.atsc.program_number = program_number; + break; + default: + dsid.dvb.transport_id = transport_id; + dsid.dvb.service_id = service_id; + dsid.dvb.orig_network_id = orig_network_id; + break; + } + return &dsid; +} + +static struct cec_op_ui_command *args2ui_command(__u8 ui_cmd, + __u8 has_opt_arg, + __u8 play_mode, + __u8 ui_function_media, + __u8 ui_function_select_av_input, + __u8 ui_function_select_audio_input, + __u8 ui_bcast_type, + __u8 ui_snd_pres_ctl, + __u8 channel_number_fmt, + __u16 major, + __u16 minor) +{ + static struct cec_op_ui_command ui_command; + + ui_command.ui_cmd = ui_cmd; + ui_command.has_opt_arg = has_opt_arg; + if (!has_opt_arg) + return &ui_command; + switch (ui_cmd) { + case 0x56: + ui_command.ui_broadcast_type = ui_bcast_type; + break; + case 0x57: + ui_command.ui_sound_presentation_control = ui_snd_pres_ctl; + break; + case 0x60: + ui_command.play_mode = play_mode; + break; + case 0x68: + ui_command.ui_function_media = ui_function_media; + break; + case 0x69: + ui_command.ui_function_select_av_input = ui_function_select_av_input; + break; + case 0x6a: + ui_command.ui_function_select_audio_input = ui_function_select_audio_input; + break; + case 0x67: + ui_command.channel_identifier.channel_number_fmt = channel_number_fmt; + ui_command.channel_identifier.major = major; + ui_command.channel_identifier.minor = minor; + break; + default: + ui_command.has_opt_arg = false; + break; + } + return &ui_command; +} + +static __u32 *args2short_descrs(__u32 descriptor1, + __u32 descriptor2, + __u32 descriptor3, + __u32 descriptor4) +{ + static __u32 descriptors[4]; + + descriptors[0] = descriptor1; + descriptors[1] = descriptor2; + descriptors[2] = descriptor3; + descriptors[3] = descriptor4; + return descriptors; +} + +static __u8 *args2short_aud_fmt_ids(__u8 audio_format_id1, + __u8 audio_format_id2, + __u8 audio_format_id3, + __u8 audio_format_id4) +{ + static __u8 audio_format_ids[4]; + + audio_format_ids[0] = audio_format_id1; + audio_format_ids[1] = audio_format_id2; + audio_format_ids[2] = audio_format_id3; + audio_format_ids[3] = audio_format_id4; + return audio_format_ids; +} + +static __u8 *args2short_aud_fmt_codes(__u8 audio_format_code1, + __u8 audio_format_code2, + __u8 audio_format_code3, + __u8 audio_format_code4) +{ + static __u8 audio_format_codes[4]; + + audio_format_codes[0] = audio_format_code1; + audio_format_codes[1] = audio_format_code2; + audio_format_codes[2] = audio_format_code3; + audio_format_codes[3] = audio_format_code4; + return audio_format_codes; +} + +static int parse_subopt(char **subs, const char * const *subopts, char **value) +{ + int opt = getsubopt(subs, (char * const *)subopts, value); + + if (opt == -1) { + fprintf(stderr, "Invalid suboptions specified\n"); + return -1; + } + if (*value == NULL) { + fprintf(stderr, "No value given to suboption <%s>\n", + subopts[opt]); + return -1; + } + return opt; +} + +static unsigned parse_enum(const char *value, const struct arg *a) +{ + if (isdigit(*value)) + return strtoul(value, NULL, 0); + for (int i = 0; i < a->num_enum_values; i++) { + if (!strcmp(value, a->values[i].type_name)) + return a->values[i].value; + } + return 0; +} + +static unsigned parse_phys_addr(const char *value) +{ + unsigned p1, p2, p3, p4; + + if (!strchr(value, '.')) + return strtoul(value, NULL, 0); + if (sscanf(value, "%x.%x.%x.%x", &p1, &p2, &p3, &p4) != 4) { + fprintf(stderr, "Expected a physical address of the form x.x.x.x\n"); + return 0; + } + if (p1 > 0xf || p2 > 0xf || p3 > 0xf || p4 > 0xf) { + fprintf(stderr, "Physical address components should never be larger than 0xf\n"); + return 0; + } + return (p1 << 12) | (p2 << 8) | (p3 << 4) | p4; +} + +static char options[512]; + +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; +} + +static 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; +} + +static void log_arg(const struct arg *arg, const char *arg_name, __u32 val) +{ + unsigned i; + + switch (arg->type) { + case CEC_TYPE_ENUM: + for (i = 0; i < arg->num_enum_values; i++) { + if (arg->values[i].value == val) { + printf("\t%s: %s (0x%02x)\n", arg_name, + arg->values[i].type_name, val); + return; + } + } + /* fall through */ + case CEC_TYPE_U8: + printf("\t%s: %u (0x%02x)\n", arg_name, val, val); + return; + case CEC_TYPE_U16: + if (strstr(arg_name, "phys-addr")) + printf("\t%s: %x.%x.%x.%x\n", arg_name, + val >> 12, (val >> 8) & 0xf, (val >> 4) & 0xf, val & 0xf); + else + printf("\t%s: %u (0x%04x)\n", arg_name, val, val); + return; + case CEC_TYPE_U32: + printf("\t%s: %u (0x%08x)\n", arg_name, val, val); + return; + default: + break; + } + printf("\t%s: unknown type\n", arg_name); +} + +static void log_arg(const struct arg *arg, const char *arg_name, + const char *s) +{ + switch (arg->type) { + case CEC_TYPE_STRING: + printf("\t%s: %s\n", arg_name, s); + return; + default: + break; + } + printf("\t%s: unknown type\n", arg_name); +} + +static const struct cec_enum_values type_rec_src_type[] = { + { "own", CEC_OP_RECORD_SRC_OWN }, + { "digital", CEC_OP_RECORD_SRC_DIGITAL }, + { "analog", CEC_OP_RECORD_SRC_ANALOG }, + { "ext-plug", CEC_OP_RECORD_SRC_EXT_PLUG }, + { "ext-phys-addr", CEC_OP_RECORD_SRC_EXT_PHYS_ADDR }, +}; + +static const struct arg arg_rec_src_type = { + CEC_TYPE_ENUM, 5, type_rec_src_type +}; + +static void log_digital(const char *arg_name, const struct cec_op_digital_service_id *digital); +static void log_rec_src(const char *arg_name, const struct cec_op_record_src *rec_src); +static void log_tuner_dev_info(const char *arg_name, const struct cec_op_tuner_device_info *tuner_dev_info); +static void log_features(const struct arg *arg, const char *arg_name, const __u8 *p); +static void log_ui_command(const char *arg_name, const struct cec_op_ui_command *ui_cmd); +static void log_descriptors(const char *arg_name, unsigned num, const __u32 *descriptors); +static void log_u8_array(const char *arg_name, unsigned num, const __u8 *vals); +static void log_unknown_msg(const struct cec_msg *msg); + +#include "cec-ctl-gen.h" + +static void log_digital(const char *arg_name, const struct cec_op_digital_service_id *digital) +{ + log_arg(&arg_service_id_method, "service-id-method", digital->service_id_method); + log_arg(&arg_dig_bcast_system, "dig-bcast-system", digital->dig_bcast_system); + if (digital->service_id_method == CEC_OP_SERVICE_ID_METHOD_BY_CHANNEL) { + log_arg(&arg_channel_number_fmt, "channel-number-fmt", digital->channel.channel_number_fmt); + log_arg(&arg_u16, "major", digital->channel.major); + log_arg(&arg_u16, "minor", digital->channel.minor); + return; + } + + switch (digital->dig_bcast_system) { + case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_GEN: + case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_CABLE: + case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_SAT: + case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_T: + log_arg(&arg_u16, "transport-id", digital->atsc.transport_id); + log_arg(&arg_u16, "program-number", digital->atsc.program_number); + break; + default: + log_arg(&arg_u16, "transport-id", digital->dvb.transport_id); + log_arg(&arg_u16, "service-id", digital->dvb.service_id); + log_arg(&arg_u16, "orig-network-id", digital->dvb.orig_network_id); + break; + } +} + +static void log_rec_src(const char *arg_name, const struct cec_op_record_src *rec_src) +{ + log_arg(&arg_rec_src_type, "rec-src-type", rec_src->type); + switch (rec_src->type) { + case CEC_OP_RECORD_SRC_OWN: + default: + break; + case CEC_OP_RECORD_SRC_DIGITAL: + log_digital(arg_name, &rec_src->digital); + break; + case CEC_OP_RECORD_SRC_ANALOG: + log_arg(&arg_ana_bcast_type, "ana-bcast-type", rec_src->analog.ana_bcast_type); + log_arg(&arg_u16, "ana-freq", rec_src->analog.ana_freq); + log_arg(&arg_bcast_system, "bcast-system", rec_src->analog.bcast_system); + break; + case CEC_OP_RECORD_SRC_EXT_PLUG: + log_arg(&arg_u8, "plug", rec_src->ext_plug.plug); + break; + case CEC_OP_RECORD_SRC_EXT_PHYS_ADDR: + log_arg(&arg_u16, "phys-addr", rec_src->ext_phys_addr.phys_addr); + break; + } +} + +static void log_tuner_dev_info(const char *arg_name, const struct cec_op_tuner_device_info *tuner_dev_info) +{ + log_arg(&arg_rec_flag, "rec-flag", tuner_dev_info->rec_flag); + log_arg(&arg_tuner_display_info, "tuner-display-info", tuner_dev_info->tuner_display_info); + if (tuner_dev_info->is_analog) { + log_arg(&arg_ana_bcast_type, "ana-bcast-type", tuner_dev_info->analog.ana_bcast_type); + log_arg(&arg_u16, "ana-freq", tuner_dev_info->analog.ana_freq); + log_arg(&arg_bcast_system, "bcast-system", tuner_dev_info->analog.bcast_system); + } else { + log_digital(arg_name, &tuner_dev_info->digital); + } +} + +static void log_features(const struct arg *arg, + const char *arg_name, const __u8 *p) +{ + do { + log_arg(arg, arg_name, (__u32)((*p) & ~CEC_OP_FEAT_EXT)); + } while ((*p++) & CEC_OP_FEAT_EXT); +} + +static void log_ui_command(const char *arg_name, + const struct cec_op_ui_command *ui_cmd) +{ + log_arg(&arg_rc_ui_cmd, arg_name, ui_cmd->ui_cmd); + if (!ui_cmd->has_opt_arg) + return; + switch (ui_cmd->ui_cmd) { + case 0x56: + log_arg(&arg_ui_bcast_type, "ui-broadcast-type", + ui_cmd->ui_broadcast_type); + break; + case 0x57: + log_arg(&arg_ui_snd_pres_ctl, "ui-sound-presentation-control", + ui_cmd->ui_sound_presentation_control); + break; + case 0x60: + log_arg(&arg_u8, "play-mode", ui_cmd->play_mode); + break; + case 0x68: + log_arg(&arg_u8, "ui-function-media", ui_cmd->ui_function_media); + break; + case 0x69: + log_arg(&arg_u8, "ui-function-select-av-input", ui_cmd->ui_function_select_av_input); + break; + case 0x6a: + log_arg(&arg_u8, "ui-function-select-audio-input", ui_cmd->ui_function_select_audio_input); + break; + case 0x67: + log_arg(&arg_channel_number_fmt, "channel-number-fmt", + ui_cmd->channel_identifier.channel_number_fmt); + log_arg(&arg_u16, "major", ui_cmd->channel_identifier.major); + log_arg(&arg_u16, "minor", ui_cmd->channel_identifier.minor); + break; + } +} + +static void log_descriptors(const char *arg_name, unsigned num, const __u32 *descriptors) +{ + for (unsigned i = 0; i < num; i++) + log_arg(&arg_u32, arg_name, descriptors[i]); +} + +static void log_u8_array(const char *arg_name, unsigned num, const __u8 *vals) +{ + for (unsigned i = 0; i < num; i++) + log_arg(&arg_u8, arg_name, vals[i]); +} + +/* 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 { + OptClear = 'C', + OptSetDevice = 'd', + OptFrom = 'f', + OptHelp = 'h', + OptMonitor = 'm', + OptMonitorAll = 'M', + OptNoReply = 'n', + OptPhysAddr = 'p', + OptShowTopology = 'S', + OptTo = 't', + OptTrace = 'T', + OptVerbose = 'v', + OptVendorID = 'V', + + OptTV = 128, + OptRecord, + OptTuner, + OptPlayback, + OptAudio, + OptProcessor, + OptSwitch, + OptCDCOnly, + OptUnregistered, + OptCECVersion1_4, + OptListUICommands, + CEC_FEATURE_OPTIONS +}; + +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 doioctl(n, r, p) cec_named_ioctl((n)->fd, #r, r, p) + +bool show_info; + +typedef std::vector<cec_msg> msg_vec; + +static const struct message *opt2message[OptLast - OptMessages]; + +static void init_messages() +{ + for (unsigned i = 0; messages[i].msg_name; i++) + opt2message[messages[i].option - OptMessages] = &messages[i]; +} + +static struct option long_options[] = { + { "device", required_argument, 0, OptSetDevice }, + { "help", no_argument, 0, OptHelp }, + { "trace", no_argument, 0, OptTrace }, + { "verbose", no_argument, 0, OptVerbose }, + { "phys-addr", required_argument, 0, OptPhysAddr }, + { "vendor-id", required_argument, 0, OptVendorID }, + { "cec-version-1.4", no_argument, 0, OptCECVersion1_4 }, + { "clear", no_argument, 0, OptClear }, + { "monitor", no_argument, 0, OptMonitor }, + { "monitor-all", no_argument, 0, OptMonitorAll }, + { "no-reply", no_argument, 0, OptNoReply }, + { "to", required_argument, 0, OptTo }, + { "from", required_argument, 0, OptFrom }, + { "show-topology", no_argument, 0, OptShowTopology }, + { "list-ui-commands", no_argument, 0, OptListUICommands }, + + { "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 }, + { "help-all", no_argument, 0, OptHelpAll }, + + CEC_LONG_OPTS + + { 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" + " -p, --phys-addr=<addr>\n" + " Use this physical address\n" + " -V, --vendor-id=<id>\n" + " Use this vendor ID\n" + " -C, --clear Clear all logical addresses\n" + " -m, --monitor Monitor CEC traffic\n" + " -M, --monitor-all Monitor all CEC traffic\n" + " -n, --no-reply Don't wait for a reply\n" + " -t, --to=<la> Send message to the given logical address\n" + " -f, --from=<la> Send message from the given logical address\n" + " -S, --show-topology Show the CEC topology\n" + " By default use the first assigned logical address\n" + " --cec-version-1.4 Use CEC Version 1.4 instead of 2.0\n" + " --list-ui-commands List all UI commands that can be used with --user-control-pressed\n" + " --log-status Log the CEC adapter status to the kernel log\n" + "\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" + "\n" + " -h, --help Display this help message\n" + " --help-all Show all messages\n" + " -T, --trace Trace all called ioctls\n" + " -v, --verbose Turn on verbose reporting\n" + CEC_USAGE + ); +} + +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; +} + +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 *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 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; +} + +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"; + 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)); + + return retval == -1 ? e : (retval ? -1 : 0); +} + +static void log_unknown_msg(const struct cec_msg *msg) +{ + __u32 vendor_id; + __u16 phys_addr; + unsigned i; + + switch (msg->msg[1]) { + case CEC_MSG_VENDOR_COMMAND: + printf("CEC_MSG_VENDOR_COMMAND:\n"); + printf("\tvendor-specific-data:"); + for (i = 2; i < msg->len; i++) + printf(" 0x%02x", msg->msg[i]); + printf("\n"); + break; + case CEC_MSG_VENDOR_COMMAND_WITH_ID: + printf("CEC_MSG_VENDOR_COMMAND_WITH_ID:\n"); + cec_ops_device_vendor_id(msg, &vendor_id); + log_arg(&arg_vendor_id, "vendor-id", vendor_id); + printf("\tvendor-specific-data:"); + for (i = 5; i < msg->len; i++) + printf(" 0x%02x", msg->msg[i]); + printf("\n"); + break; + case CEC_MSG_VENDOR_REMOTE_BUTTON_DOWN: + printf("CEC_MSG_VENDOR_REMOTE_BUTTON_DOWN:\n"); + printf("\tvendor-specific-rc-code:"); + for (i = 2; i < msg->len; i++) + printf(" 0x%02x", msg->msg[i]); + printf("\n"); + break; + case CEC_MSG_CDC_MESSAGE: + phys_addr = (msg->msg[2] << 8) | msg->msg[3]; + + printf("CEC_MSG_CDC 0x%02x:\n", msg->msg[4]); + log_arg(&arg_u16, "phys-addr", phys_addr); + printf("\tpayload:"); + for (i = 5; i < msg->len; i++) + printf(" 0x%02x", msg->msg[i]); + printf("\n"); + break; + default: + printf("CEC_MSG 0x%02x:\n", msg->msg[1]); + printf("\tpayload:"); + for (i = 2; i < msg->len; i++) + printf(" 0x%02x", msg->msg[i]); + printf("\n"); + break; + } +} + +static void log_event(struct cec_event &ev) +{ + __u16 pa; + + printf("\n"); + if (ev.flags & CEC_EVENT_FL_INITIAL_STATE) + printf("Initial "); + switch (ev.event) { + case CEC_EVENT_STATE_CHANGE: + pa = ev.state_change.phys_addr; + printf("Event: State Change: PA: %x.%x.%x.%x, LA mask: 0x%04x\n", + pa >> 12, (pa >> 8) & 0xf, + (pa >> 4) & 0xf, pa & 0xf, + ev.state_change.log_addr_mask); + break; + case CEC_EVENT_LOST_MSGS: + printf("Event: Lost Messages\n"); + break; + default: + printf("Event: Unknown (0x%x)\n", ev.event); + break; + } + printf("\tTimestamp: %llu.%09llus\n", ev.ts / 1000000000, ev.ts % 1000000000); +} + +static int showTopologyDevice(struct node *node, unsigned i, unsigned la) +{ + struct cec_msg msg; + char osd_name[15]; + + 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); + doioctl(node, CEC_TRANSMIT, &msg); + printf("\t\tCEC Version : %s\n", + (!cec_msg_status_is_ok(&msg)) ? status2s(msg).c_str() : version2s(msg.msg[2])); + + cec_msg_init(&msg, la, i); + cec_msg_give_physical_addr(&msg, true); + doioctl(node, CEC_TRANSMIT, &msg); + printf("\t\tPhysical Address : "); + if (!cec_msg_status_is_ok(&msg)) { + printf("%s\n", status2s(msg).c_str()); + } else { + __u16 phys_addr = (msg.msg[2] << 8) | msg.msg[3]; + + printf("%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])); + } + + cec_msg_init(&msg, la, i); + cec_msg_give_device_vendor_id(&msg, true); + doioctl(node, CEC_TRANSMIT, &msg); + printf("\t\tVendor ID : "); + if (!cec_msg_status_is_ok(&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); + doioctl(node, CEC_TRANSMIT, &msg); + cec_ops_set_osd_name(&msg, osd_name); + printf("\t\tOSD Name : %s\n", + (!cec_msg_status_is_ok(&msg)) ? status2s(msg).c_str() : osd_name); + return 0; +} + +static int showTopology(struct node *node) +{ + struct cec_msg msg = { }; + struct cec_log_addrs laddrs = { }; + + if (!(node->caps & CEC_CAP_TRANSMIT)) + return -ENOTTY; + + doioctl(node, CEC_ADAP_G_LOG_ADDRS, &laddrs); + + for (unsigned i = 0; i < 15; i++) { + int ret; + + cec_msg_init(&msg, 0xf, i); + ret = doioctl(node, CEC_TRANSMIT, &msg); + + if (ret) + continue; + + if (msg.tx_status & CEC_TX_STATUS_OK) + showTopologyDevice(node, i, laddrs.log_addr[0]); + else if (show_info && !(msg.tx_status & CEC_TX_STATUS_MAX_RETRIES)) + printf("\t\t%s for addr %d\n", status2s(msg).c_str(), i); + } + return 0; +} + +int main(int argc, char **argv) +{ + const char *device = "/dev/cec0"; /* -d device */ + const message *opt; + msg_vec msgs; + char short_options[26 * 2 * 2 + 1]; + __u32 vendor_id = 0x000c03; /* HDMI LLC vendor ID */ + __u16 phys_addr; + __u8 from = 0, to = 0; + bool reply = true; + int idx = 0; + int fd = -1; + int ch; + int i; + + init_messages(); + + 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; + struct cec_msg msg; + + short_options[idx] = 0; + ch = getopt_long(argc, argv, short_options, + long_options, &option_index); + if (ch == -1) + break; + + if (ch > OptMessages) + cec_msg_init(&msg, 0, 0); + 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 OptVerbose: + show_info = true; + break; + case OptFrom: + from = strtoul(optarg, NULL, 0) & 0xf; + break; + case OptTo: + to = strtoul(optarg, NULL, 0) & 0xf; + break; + case OptNoReply: + reply = false; + break; + case OptPhysAddr: + phys_addr = parse_phys_addr(optarg); + break; + case OptVendorID: + vendor_id = strtoul(optarg, NULL, 0) & 0x00ffffff; + break; + case OptListUICommands: + printf("'ui-cmd' can have these values:\n"); + for (unsigned i = 0; i < sizeof(type_ui_cmd) / sizeof(type_ui_cmd[0]); i++) + printf("\t%s (%u)\n", + type_ui_cmd[i].type_name, type_ui_cmd[i].value); + printf("\n"); + 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; + default: + if (ch >= OptHelpAll) { + usage_options(ch); + exit(0); + } + if (ch < OptMessages) + break; + opt = opt2message[ch - OptMessages]; + parse_msg_args(msg, reply, opt, ch); + msgs.push_back(msg); + break; + } + } + 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; + + 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; + + 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); + + if ((node.caps & CEC_CAP_LOG_ADDRS) && options[OptClear]) { + struct cec_log_addrs laddrs = { }; + + doioctl(&node, CEC_ADAP_S_LOG_ADDRS, &laddrs); + } + + if ((node.caps & CEC_CAP_PHYS_ADDR) && options[OptPhysAddr]) + doioctl(&node, CEC_ADAP_S_PHYS_ADDR, &phys_addr); + doioctl(&node, CEC_ADAP_G_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)) + printf("Perhaps you should use option --phys-addr?\n"); + + if ((node.caps & CEC_CAP_LOG_ADDRS) && flags) { + struct cec_log_addrs laddrs = {}; + + doioctl(&node, CEC_ADAP_S_LOG_ADDRS, &laddrs); + memset(&laddrs, 0, sizeof(laddrs)); + + laddrs.cec_version = options[OptCECVersion1_4] ? + CEC_OP_CEC_VERSION_1_4 : CEC_OP_CEC_VERSION_2_0; + strcpy(laddrs.osd_name, osd_name); + laddrs.vendor_id = vendor_id; + + for (unsigned i = 0; i < 8; i++) { + unsigned la_type; + unsigned all_dev_type; + + if (!(flags & (1 << i))) + continue; + if (laddrs.num_log_addrs == node.available_log_addrs) { + fprintf(stderr, "Attempt to define too many logical addresses\n"); + exit(-1); + } + switch (i) { + case CEC_OP_PRIM_DEVTYPE_TV: + la_type = CEC_LOG_ADDR_TYPE_TV; + all_dev_type = CEC_OP_ALL_DEVTYPE_TV; + break; + case CEC_OP_PRIM_DEVTYPE_RECORD: + la_type = CEC_LOG_ADDR_TYPE_RECORD; + all_dev_type = CEC_OP_ALL_DEVTYPE_RECORD; + break; + case CEC_OP_PRIM_DEVTYPE_TUNER: + la_type = CEC_LOG_ADDR_TYPE_TUNER; + all_dev_type = CEC_OP_ALL_DEVTYPE_TUNER; + break; + case CEC_OP_PRIM_DEVTYPE_PLAYBACK: + la_type = CEC_LOG_ADDR_TYPE_PLAYBACK; + all_dev_type = CEC_OP_ALL_DEVTYPE_PLAYBACK; + break; + case CEC_OP_PRIM_DEVTYPE_AUDIOSYSTEM: + la_type = CEC_LOG_ADDR_TYPE_AUDIOSYSTEM; + all_dev_type = CEC_OP_ALL_DEVTYPE_AUDIOSYSTEM; + break; + case CEC_OP_PRIM_DEVTYPE_PROCESSOR: + la_type = CEC_LOG_ADDR_TYPE_SPECIFIC; + all_dev_type = CEC_OP_ALL_DEVTYPE_SWITCH; + break; + case CEC_OP_PRIM_DEVTYPE_SWITCH: + default: + la_type = CEC_LOG_ADDR_TYPE_UNREGISTERED; + all_dev_type = CEC_OP_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; + } + + doioctl(&node, CEC_ADAP_S_LOG_ADDRS, &laddrs); + } + + struct cec_log_addrs laddrs = { }; + doioctl(&node, CEC_ADAP_G_LOG_ADDRS, &laddrs); + node.num_log_addrs = laddrs.num_log_addrs; + 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 (%s)\n", + laddrs.log_addr[i], la2s(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\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", + dev_feat2s(byte).c_str()); + } + if (byte & CEC_OP_FEAT_EXT) + continue; + if (!is_dev_feat) + is_dev_feat = true; + else + break; + } + } + if (node.num_log_addrs == 0) { + if (options[OptMonitor]) + goto skip_la; + return 0; + } + printf("\n"); + + if (!options[OptFrom]) + from = laddrs.log_addr[0]; + + if (options[OptShowTopology]) + showTopology(&node); + + for (msg_vec::iterator iter = msgs.begin(); iter != msgs.end(); ++iter) { + struct cec_msg msg = *iter; + + if (!cec_msg_is_broadcast(&msg) && !options[OptTo]) { + fprintf(stderr, "attempting to send message without --to\n"); + exit(1); + } + printf("\nTransmit from %s to %s (%d to %d):\n", la2s(from), + (cec_msg_is_broadcast(&msg) || to == 0xf) ? "all" : la2s(to), + from, to); + msg.msg[0] |= (from << 4) | (cec_msg_is_broadcast(&msg) ? 0xf : to); + log_msg(&msg); + if (doioctl(&node, CEC_TRANSMIT, &msg)) + continue; + if (msg.reply) { + printf(" Received from %s (%d):\n ", la2s(cec_msg_initiator(&msg)), + cec_msg_initiator(&msg)); + log_msg(&msg); + } + printf("\tSequence: %u Timestamp: %llu.%09llus\n", + msg.sequence, msg.ts / 1000000000, msg.ts % 1000000000); + if (!cec_msg_status_is_ok(&msg)) + printf("\t%s\n", status2s(msg).c_str()); + } + +skip_la: + if (options[OptMonitor] || options[OptMonitorAll]) { + __u32 monitor = options[OptMonitorAll] ? + CEC_MODE_MONITOR_ALL : CEC_MODE_MONITOR; + fd_set rd_fds; + fd_set ex_fds; + int fd = node.fd; + + printf("\n"); + if (!(node.caps & CEC_CAP_MONITOR_ALL) && + monitor == CEC_MODE_MONITOR_ALL) { + printf("Monitor All mode is not supported, falling back to regular monitoring\n"); + monitor = CEC_MODE_MONITOR; + } + if (doioctl(&node, CEC_S_MODE, &monitor)) { + printf("Selecting monitor mode failed, you may have to run this as root.\n"); + goto skip_mon; + } + + fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK); + while (1) { + int res; + + FD_ZERO(&rd_fds); + FD_ZERO(&ex_fds); + FD_SET(fd, &rd_fds); + FD_SET(fd, &ex_fds); + res = select(fd + 1, &rd_fds, NULL, &ex_fds, NULL); + if (res <= 0) + break; + if (FD_ISSET(fd, &rd_fds)) { + struct cec_msg msg = { }; + __u8 from, to; + + if (doioctl(&node, CEC_RECEIVE, &msg)) + continue; + + from = cec_msg_initiator(&msg); + to = cec_msg_destination(&msg); + bool transmitted = msg.tx_status != 0; + printf("\n%s %s to %s (%d to %d):\n", + transmitted ? "Transmitted by" : "Received from", + la2s(from), to == 0xf ? "all" : la2s(to), from, to); + log_msg(&msg); + printf("\tSequence: %u Timestamp: %llu.%09llus\n", + msg.sequence, msg.ts / 1000000000, msg.ts % 1000000000); + } + if (FD_ISSET(fd, &ex_fds)) { + struct cec_event ev; + + if (doioctl(&node, CEC_DQEVENT, &ev)) + continue; + log_event(ev); + } + } + } + +skip_mon: + close(fd); + return 0; +} diff --git a/utils/cec-ctl/msg2ctl.pl b/utils/cec-ctl/msg2ctl.pl new file mode 100755 index 00000000..8bbd369f --- /dev/null +++ b/utils/cec-ctl/msg2ctl.pl @@ -0,0 +1,464 @@ +#!/usr/bin/perl + +sub maxprefix { + my $p = shift(@_); + for (@_) { + chop $p until /^\Q$p/; + } + $p =~ s/_[^_]*$/_/; + $p = "CEC_OP_CEC_" if ($p =~ /CEC_OP_CEC_VERSION_/); + return $p; +} + +sub process_func +{ + my $feature = shift; + my $func = shift; + my $func_args = $func; + $func =~ s/\(.*//; + my $msg = $func; + $msg =~ s/([a-z])/\U\1/g; + $func =~ s/cec_msg//; + my $opt = $func; + $opt =~ s/_([a-z])/\U\1/g; + $func_args =~ s/.*\((.*)\).*/\1/; + my $has_reply = $func_args =~ /^bool reply/; + $func_args =~ s/^bool reply,? ?//; + my $arg_names; + my $arg_ptrs; + my $name, $type, $size; + my $msg_dash_name, $msg_lc_name; + my @enum, $val; + my $usage; + my $has_digital = $func_args =~ /cec_op_digital_service_id/; + my $has_ui_command = $func_args =~ /cec_op_ui_command/; + my $has_short_aud_descr = $func_args =~ /num_descriptors/; + + my @ops_args = split(/, */, $func_args); + if ($has_digital) { + $func_args =~ s/const struct cec_op_digital_service_id \*digital/__u8 service_id_method, __u8 dig_bcast_system, __u16 transport_id, __u16 service_id, __u16 orig_network_id, __u16 program_number, __u8 channel_number_fmt, __u16 major, __u16 minor/; + } + if ($has_ui_command) { + $func_args =~ s/const struct cec_op_ui_command \*ui_cmd/__u8 ui_cmd, __u8 has_opt_arg, __u8 play_mode, __u8 ui_function_media, __u8 ui_function_select_av_input, __u8 ui_function_select_audio_input, __u8 ui_bcast_type, __u8 ui_snd_pres_ctl, __u8 channel_number_fmt, __u16 major, __u16 minor/; + } + if ($has_short_aud_descr) { + $func_args =~ s/const __u32 \*descriptors/__u8 descriptor1, __u8 descriptor2, __u8 descriptor3, __u8 descriptor4/; + $func_args =~ s/const __u8 \*audio_format_id, const __u8 \*audio_format_code/__u8 audio_format_id1, __u8 audio_format_code1, __u8 audio_format_id2, __u8 audio_format_code2, __u8 audio_format_id3, __u8 audio_format_code3, __u8 audio_format_id4, __u8 audio_format_code4/; + } + my @args = split(/, */, $func_args); + my $has_struct = $func_args =~ /struct/; + return if ($func_args =~ /__u\d+\s*\*/); + + my $cec_msg = $msg; + while ($cec_msg ne "" && !exists($msgs{$cec_msg})) { + $cec_msg =~ s/_[^_]*$//; + } + return if ($cec_msg eq ""); + + my $msg_name = $cec_msg; + $msg_name =~ s/CEC_MSG_//; + $msg_dash_name = $msg; + $msg_dash_name =~ s/CEC_MSG_//; + $msg_dash_name =~ s/([A-Z])/\l\1/g; + $msg_dash_name =~ s/_/-/g; + $msg_lc_name = $msg; + $msg_lc_name =~ s/([A-Z])/\l\1/g; + + if ($cec_msg eq $msg) { + if ($cec_msg =~ /_CDC_/ && !$cdc_case) { + $cdc_case = 1; + $logswitch .= "\tcase CEC_MSG_CDC_MESSAGE:\n"; + $logswitch .= "\tswitch (msg->msg[4]) {\n"; + } + if (@args == 0) { + $logswitch .= "\tcase $cec_msg:\n"; + $logswitch .= "\t\tprintf(\"$cec_msg (0x%02x):\\n\", $cec_msg);\n"; + $logswitch .= "\t\tbreak;\n\n"; + } else { + $logswitch .= "\tcase $cec_msg: {\n"; + foreach (@ops_args) { + ($type, $name) = /(.*?) ?([a-zA-Z_]\w+)$/; + if ($type =~ /struct .*\*/) { + $type =~ s/ \*//; + $type =~ s/const //; + } + if ($name eq "rc_profile" || $name eq "dev_features") { + $logswitch .= "\t\tconst __u8 *$name = NULL;\n"; + } elsif ($type eq "const char *") { + $logswitch .= "\t\tchar $name\[16\];\n"; + } elsif ($type eq "const __u32 *") { + $logswitch .= "\t\t__u32 $name\[4\];\n"; + } elsif ($type eq "const __u8 *") { + $logswitch .= "\t\t__u8 $name\[4\];\n"; + } elsif ($type =~ /struct/) { + $logswitch .= "\t\t$type $name = {};\n"; + } else { + $logswitch .= "\t\t$type $name;\n"; + } + } + if ($cdc_case) { + $logswitch .= "\t\t__u16 phys_addr;\n"; + } + my $ops_lc_name = $msg_lc_name; + $ops_lc_name =~ s/^cec_msg/cec_ops/; + $logswitch .= "\n\t\t$ops_lc_name(msg"; + if ($cdc_case) { + $logswitch .= ", &phys_addr"; + } + foreach (@ops_args) { + ($type, $name) = /(.*?) ?([a-zA-Z_]\w+)$/; + if ($type eq "const char *" || + $type eq "const __u8 *" || + $type eq "const __u32 *") { + $logswitch .= ", $name"; + } else { + $logswitch .= ", &$name"; + } + } + $logswitch .= ");\n"; + $logswitch .= "\t\tprintf(\"$cec_msg (0x%02x):\\n\", $cec_msg);\n"; + if ($cdc_case) { + $logswitch .= "\t\tlog_arg(&arg_phys_addr, \"phys-addr\", phys_addr);\n"; + } + foreach (@ops_args) { + ($type, $name) = /(.*?) ?([a-zA-Z_]\w+)$/; + my $dash_name = $name; + $dash_name =~ s/_/-/g; + if ($name eq "rc_profile" || $name eq "dev_features") { + $logswitch .= "\t\tlog_features(&arg_$name, \"$dash_name\", $name);\n"; + } elsif ($name eq "digital") { + $logswitch .= "\t\tlog_digital(\"$dash_name\", &$name);\n"; + } elsif ($name eq "ui_cmd") { + $logswitch .= "\t\tlog_ui_command(\"$dash_name\", &$name);\n"; + } elsif ($name eq "rec_src") { + $logswitch .= "\t\tlog_rec_src(\"$dash_name\", &$name);\n"; + } elsif ($name eq "tuner_dev_info") { + $logswitch .= "\t\tlog_tuner_dev_info(\"$dash_name\", &$name);\n"; + } elsif ($name eq "descriptors") { + $logswitch .= "\t\tlog_descriptors(\"$dash_name\", num_descriptors, $name);\n"; + } elsif ($name eq "audio_format_id" || $name eq "audio_format_code") { + $logswitch .= "\t\tlog_u8_array(\"$dash_name\", num_descriptors, $name);\n"; + } else { + $logswitch .= "\t\tlog_arg(&arg_$name, \"$dash_name\", $name);\n"; + } + } + $logswitch .= "\t\tbreak;\n\t}\n"; + } + } + return if $has_struct; + + $options .= "\tOpt$opt,\n"; + $messages .= "\t\t$cec_msg,\n"; + $messages .= "\t\tOpt$opt,\n" unless $is_log; + if (@args == 0) { + $messages .= "\t\t0, { }, { },\n"; + $long_opts .= "\t{ \"$msg_dash_name\", no_argument, 0, Opt$opt }, \\\n"; + $usage .= "\t\" --" . sprintf("%-30s", $msg_dash_name) . "Send $msg_name message (\" xstr($cec_msg) \")\\n\"\n"; + $usage_msg{$msg} = $usage; + $switch .= "\tcase Opt$opt: {\n"; + $switch .= "\t\t$msg_lc_name(&msg"; + $switch .= ", reply" if $has_reply; + $switch .= ");\n\t\tbreak;\n\t}\n\n"; + } else { + $long_opts .= "\t{ \"$msg_dash_name\", required_argument, 0, Opt$opt }, \\\n"; + $usage .= "\t\" --$msg_dash_name"; + my $prefix = "\t\" " . sprintf("%-30s", " "); + my $sep = "="; + foreach (@args) { + ($type, $name) = /(.*?) ?([a-zA-Z_]\w+)$/; + $name =~ s/_/-/g; + $usage .= "$sep$name=<val>"; + $sep = ","; + } + $usage .= "\\n\"\n"; + foreach (@args) { + ($type, $name) = /(.*?) ?([a-zA-Z_]\w+)$/; + @enum = @{$types{$name}}; + next if !scalar(@enum); + $name =~ s/_/-/g; + $usage .= $prefix . "'$name' can have these values:\\n\"\n"; + my $common_prefix = maxprefix(@enum); + foreach (@enum) { + my $e = $_; + s/^$common_prefix//; + s/([A-Z])/\l\1/g; + s/_/-/g; + $usage .= $prefix . " $_ (\" xstr($e) \")\\n\"\n"; + } + } + $usage .= $prefix . "Send $msg_name message (\" xstr($cec_msg) \")\\n\"\n"; + $usage_msg{$msg} = $usage; + $switch .= "\tcase Opt$opt: {\n"; + foreach (@args) { + ($type, $name) = /(.*?) ?([a-zA-Z_]\w+)$/; + if ($type =~ /char/) { + $switch .= "\t\tconst char *$name = \"\";\n"; + } else { + $switch .= "\t\t$type $name = 0;\n"; + } + } + $switch .= "\n\t\twhile (*subs != '\\0') {\n"; + $switch .= "\t\t\tswitch (parse_subopt(&subs, opt->arg_names, &value)) {\n"; + my $cnt = 0; + foreach (@args) { + ($type, $name) = /(.*?) ?([a-zA-Z_]\w+)$/; + @enum = @{$types{$name}}; + $switch .= "\t\t\tcase $cnt:\n"; + if ($type =~ /char/) { + $switch .= "\t\t\t\t$name = value;\n"; + } elsif (scalar(@enum) || $name eq "ui_cmd") { + $switch .= "\t\t\t\t$name = parse_enum(value, opt->args\[$cnt\]);\n"; + } elsif ($name =~ /phys_addr/) { + $switch .= "\t\t\t\t$name = parse_phys_addr(value);\n"; + } else { + $switch .= "\t\t\t\t$name = strtol(value, 0L, 0);\n"; + } + $switch .= "\t\t\t\tbreak;\n"; + $cnt++; + } + $switch .= "\t\t\tdefault:\n"; + $switch .= "\t\t\t\texit(1);\n"; + $switch .= "\t\t\t}\n\t\t}\n"; + $switch .= "\t\t$msg_lc_name(&msg"; + $switch .= ", reply" if $has_reply; + foreach (@args) { + ($type, $name) = /(.*?) ?([a-zA-Z_]\w+)$/; + $switch .= ", $name"; + } + $switch .= ");\n\t\tbreak;\n\t}\n\n"; + + foreach (@args) { + ($type, $name) = /(.*?) ?([a-zA-Z_]\w+)$/; + if ($arg_names ne "") { + $arg_names .= ", "; + $arg_ptrs .= ", "; + } + if ($name eq "ui_cmd") { + $arg_ptrs .= "&arg_rc_$name"; + } else { + $arg_ptrs .= "&arg_$name"; + } + $name =~ s/_/-/g; + $arg_names .= '"' . $name . '"'; + } + $size = $#args + 1; + $messages .= "\t\t$size, { $arg_names },\n"; + $messages .= "\t\t{ $arg_ptrs },\n"; + foreach (@args) { + ($type, $name) = /(.*?) ?([a-zA-Z_]\w+)$/; + @enum = @{$types{$name}}; + $size = scalar(@enum); + + if ($size && !defined($created_enum{$name})) { + $created_enum{$name} = 1; + $enums .= "static const struct cec_enum_values type_$name\[\] = {\n"; + my $common_prefix = maxprefix(@enum); + foreach (@enum) { + $val = $_; + s/^$common_prefix//; + s/([A-Z])/\l\1/g; + s/_/-/g; + $enums .= "\t{ \"$_\", $val },\n"; + } + $enums .= "};\n\n"; + } + if (!defined($created_arg{$name})) { + $created_arg{$name} = 1; + if ($type eq "__u8" && $size) { + $arg_structs .= "static const struct arg arg_$name = {\n"; + $arg_structs .= "\tCEC_TYPE_ENUM, $size, type_$name\n};\n\n"; + } elsif ($type eq "__u8") { + $arg_structs .= "#define arg_$name arg_u8\n"; + } elsif ($type eq "__u16") { + $arg_structs .= "#define arg_$name arg_u16\n"; + } elsif ($type eq "__u32") { + $arg_structs .= "#define arg_$name arg_u32\n"; + } elsif ($type eq "const char *") { + $arg_structs .= "#define arg_$name arg_string\n"; + } + } + } + } + $messages .= "\t\t\"$msg_name\"\n"; + $messages .= "\t}, {\n"; + $feature_usage{$feature} .= $usage; +} + +$is_log = shift; + +while (<>) { + last if /\/\* Messages \*\//; +} + +$comment = 0; +$has_also = 0; +$operand_name = ""; +$feature = ""; + +while (<>) { + chomp; + last if /_CEC_UAPI_FUNCS_H/; + if (/^\/\*.*Feature \*\/$/) { + ($feature) = /^\/\* (.*) Feature/; + $feature_usage{$feature} = ""; + } + if ($operand_name ne "" && !/^#define/) { + @{$types{$operand_name}} = @ops; + undef @ops; + $operand_name = ""; + } + if (/\/\*.*Operand \((.*)\)/) { + $operand_name = $1; + next; + } + s/\/\*.*\*\///; + if ($comment) { + if ($has_also) { + if (/CEC_MSG/) { + ($also_msg) = /(CEC_MSG\S+)/; + push @{$feature_also{$feature}}, $also_msg; + } + } elsif (/^ \* Has also:$/) { + $has_also = 1; + } + $has_also = 0 if (/\*\//); + next unless /\*\//; + $comment = 0; + s/^.*\*\///; + } + if (/\/\*/) { + $comment = 1; + $has_also = 0; + next; + } + next if /^\s*$/; + if (/^\#define/) { + ($name, $val) = /define (\S+)\s+(\S+)/; + if ($name =~ /^CEC_MSG/) { + $msgs{$name} = 1; + } elsif ($operand_name ne "" && $name =~ /^CEC_OP/) { + push @ops, $name; + } + next; + } +} + +while (<>) { + chomp; + if (/^\/\*.*Feature \*\/$/) { + ($feature) = /^\/\* (.*) Feature/; + } + s/\/\*.*\*\///; + if ($comment) { + next unless /\*\//; + $comment = 0; + s/^.*\*\///; + } + if (/\/\*/) { + $comment = 1; + next; + } + next if /^\s*$/; + next if /cec_msg_reply_feature_abort/; + if (/^static inline void cec_msg.*\(.*\)/) { + s/static\sinline\svoid\s//; + s/struct cec_msg \*msg, //; + s/struct cec_msg \*msg//; + process_func($feature, $_); + next; + } + if (/^static inline void cec_msg/) { + $func = $_; + next; + } + if ($func ne "") { + $func .= $_; + next unless /\)$/; + $func =~ s/\s+/ /g; + $func =~ s/static\sinline\svoid\s//; + $func =~ s/struct cec_msg \*msg, //; + $func =~ s/struct cec_msg \*msg//; + process_func($feature, $func); + $func = ""; + } +} + +$options .= "\tOptHelpAll,\n"; + +unless ($is_log) { + foreach (sort keys %feature_usage) { + $name = $_; + s/ /_/g; + s/([A-Z])/\l\1/g; + $usage_var = $_ . "_usage"; + printf "static const char *$usage_var =\n"; + $usage = $feature_usage{$name}; + foreach (@{$feature_also{$name}}) { + $usage .= $usage_msg{$_}; + } + chop $usage; + printf "%s;\n\n", $usage; + s/_/-/g; + $help_features .= sprintf("\t\" --help-%-28s Show messages for the $name feature\\n\" \\\n", $_); + $opt = "OptHelp" . $name; + $opt =~ s/ //g; + $help .= "\tif (options[OptHelpAll] || options\[$opt\]) {\n"; + $help .= "\t\tprintf(\"$name Feature:\\n\");\n"; + $help .= "\t\tprintf(\"\%s\\n\", $usage_var);\n\t}\n"; + $options .= "\t$opt,\n"; + $long_opts .= "\t{ \"help-$_\", no_argument, 0, $opt }, \\\n"; + } + + print "enum {\n\tOptMessages = 255,\n"; + printf "%s\n\tOptLast = 512\n};\n\n", $options; + + printf "#define CEC_LONG_OPTS \\\n%s\n\n", $long_opts; + printf "#define CEC_USAGE \\\n%s\n\n", $help_features; +} + +printf "%s%s\n", $enums, $arg_structs; +printf "static const struct message messages[] = {\n\t{\n"; +printf "%s\t}\n};\n\n", $messages; + +unless ($is_log) { + printf "static void usage_options(int ch)\n{\n"; + printf "%s}\n\n", $help; + printf "static void parse_msg_args(struct cec_msg &msg, bool reply, const message *opt, int ch)\n{\n"; + printf "\tchar *value, *subs = optarg;\n\n"; + printf "\tswitch (ch) {\n"; + $switch =~ s/(service_id_method, dig_bcast_system, transport_id, service_id, orig_network_id, program_number, channel_number_fmt, major, minor)/args2digital_service_id(\1)/g; + $switch =~ s/(ui_cmd, has_opt_arg, play_mode, ui_function_media, ui_function_select_av_input, ui_function_select_audio_input, ui_bcast_type, ui_snd_pres_ctl, channel_number_fmt, major, minor)/args2ui_command(\1)/g; + $switch =~ s/(descriptor1, descriptor2, descriptor3, descriptor4)/args2short_descrs(\1)/g; + $switch =~ s/(audio_format_id1, audio_format_code1, audio_format_id2, audio_format_code2, audio_format_id3, audio_format_code3, audio_format_id4, audio_format_code4)/args2short_aud_fmt_ids(audio_format_id1, audio_format_id2, audio_format_id3, audio_format_id4), args2short_aud_fmt_codes(audio_format_code1, audio_format_code2, audio_format_code3, audio_format_code4)/g; + printf "%s", $switch; + printf "\t}\n};\n\n"; +} + +print <<'EOF'; +void log_msg(const struct cec_msg *msg) +{ + if (msg->len == 1) + printf("CEC_MSG_POLL:\n"); + if ((msg->tx_status && !(msg->tx_status & CEC_TX_STATUS_OK)) || + (msg->rx_status && !(msg->rx_status & (CEC_RX_STATUS_OK | CEC_RX_STATUS_FEATURE_ABORT)))) + printf("\t%s\n", status2s(*msg).c_str()); + + if (msg->len == 1) + return; + + switch (msg->msg[1]) { +EOF +printf "%s", $logswitch; +print <<'EOF'; + default: + log_unknown_msg(msg); + break; + } + break; + + default: + log_unknown_msg(msg); + break; + } +} +EOF |