aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHans Verkuil <hans.verkuil@cisco.com>2017-08-01 14:59:59 +0200
committerHans Verkuil <hans.verkuil@cisco.com>2017-08-01 14:59:59 +0200
commit98c162029eb1a21b39028180ac5d36b544a9493c (patch)
tree34b15ae97c7213e75d9df1c0d20015e04187e3e2
parentcd573ec9f41bbeb6c2e78bfa55187f1d418d8be4 (diff)
cec-ctl: add --record-pin and --playback-pin options
With these option you can record and playback CEC pin events. This is only possible for CEC adapters that support CEC_CAP_MONITOR_PIN. These options make it easy to record the CEC bus traffic at the lowest level and play it back and analyze it later. Signed-off-by: Hans Verkuil <hans.verkuil@cisco.com>
-rw-r--r--utils/cec-ctl/cec-ctl.1.in15
-rw-r--r--utils/cec-ctl/cec-ctl.cpp173
2 files changed, 169 insertions, 19 deletions
diff --git a/utils/cec-ctl/cec-ctl.1.in b/utils/cec-ctl/cec-ctl.1.in
index d92d4d13..702a6801 100644
--- a/utils/cec-ctl/cec-ctl.1.in
+++ b/utils/cec-ctl/cec-ctl.1.in
@@ -3,7 +3,7 @@
cec-ctl - An application to control cec devices
.SH SYNOPSIS
.B cec-ctl
-[\fI-h\fR] [\fI-d <dev>\fR] [many other options]
+[\fI\-h\fR] [\fI\-d <dev>\fR] [many other options]
.SH DESCRIPTION
The cec-ctl tool is used to control cec devices. It is able to control almost
any aspect of such devices covering the full CEC API.
@@ -51,10 +51,17 @@ possible if the device has the CEC_CAP_MONITOR_PIN capability. This option requi
When in pin monitoring mode all 0->1 and 1->0 CEC pin transitions are monitored and
analysed. This is effectively a cheap CEC bus analyzer.
.TP
-\fB\--monitor-time\fR=\fI<secs>\fR
+\fB\-\-monitor\-time\fR=\fI<secs>\fR
Monitor for the given number of seconds, then exit. The default (0) is to monitor
forever.
.TP
+\fB\-\-record\-pin\fR=\fI<to>\fR
+Record the CEC pin events to the given file. This can be played back for analysis later
+via the \fB\-\-playback\-pin\fR option.
+.TP
+\fB\-\-playback\-pin\fR=\fI<from>\fR
+Read and playback the CEC pin events from the given file.
+.TP
\fB\-n\fR, \fB\-\-no\-reply\fR
By default when sending a CEC message that expects a reply this utility will
wait for that reply. With this option it will just send it without waiting
@@ -107,7 +114,7 @@ the reply.
Set the reply timeout in milliseconds (default is 1000 ms).
.TP
\fB\-\-list\-ui\-commands\fR
-List all UI commands that can be used with option --user-control-pressed.
+List all UI commands that can be used with option \fB\-\-control\-pressed\fR.
.TP
\fB\-\-tv\fR
Configure the CEC adapter as a TV.
@@ -130,7 +137,7 @@ Configure the CEC adapter as a processor device.
\fB\-\-switch\fR
Configure the CEC adapter as a pure CEC switch.
.TP
-\fB\-\-cdc-only\fR
+\fB\-\-cdc\-only\fR
Configure the CEC adapter as a CDC-only device.
.TP
\fB\-\-unregistered\fR
diff --git a/utils/cec-ctl/cec-ctl.cpp b/utils/cec-ctl/cec-ctl.cpp
index 21c98f65..282d2f64 100644
--- a/utils/cec-ctl/cec-ctl.cpp
+++ b/utils/cec-ctl/cec-ctl.cpp
@@ -674,6 +674,8 @@ enum Option {
OptTimeout,
OptMonitorTime,
OptMonitorPin,
+ OptRecordPin,
+ OptPlaybackPin,
OptListUICommands,
OptRcTVProfile1,
OptRcTVProfile2,
@@ -702,6 +704,8 @@ struct node {
unsigned caps;
unsigned available_log_addrs;
unsigned num_log_addrs;
+ __u16 log_addr_mask;
+ __u16 phys_addr;
__u8 log_addr[CEC_MAX_LOG_ADDRS];
};
@@ -738,6 +742,8 @@ static struct option long_options[] = {
{ "monitor-all", no_argument, 0, OptMonitorAll },
{ "monitor-pin", no_argument, 0, OptMonitorPin },
{ "monitor-time", required_argument, 0, OptMonitorTime },
+ { "record-pin", required_argument, 0, OptRecordPin },
+ { "playback-pin", required_argument, 0, OptPlaybackPin },
{ "no-reply", no_argument, 0, OptNoReply },
{ "to", required_argument, 0, OptTo },
{ "from", required_argument, 0, OptFrom },
@@ -795,6 +801,8 @@ static void usage(void)
" -M, --monitor-all Monitor all CEC traffic\n"
" --monitor-pin Monitor low-level CEC pin\n"
" --monitor-time=<secs> Monitor for <secs> seconds (default is forever)\n"
+ " --record-pin=<to> Record low-level CEC pin changes to the file <to>\n"
+ " --playback-pin=<from> Playback low-level CEC pin changes from the file <from>\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"
@@ -1486,32 +1494,55 @@ static inline unsigned response_time_ms(const struct cec_msg &msg)
return 0;
}
-static void monitor(struct node &node, __u32 monitor_time)
+static void monitor(struct node &node, __u32 monitor_time, const char *record_pin)
{
- __u32 monitor = options[OptMonitorAll] ?
- CEC_MODE_MONITOR_ALL : (options[OptMonitorPin] ? CEC_MODE_MONITOR_PIN :
- CEC_MODE_MONITOR);
+ __u32 monitor = CEC_MODE_MONITOR;
fd_set rd_fds;
fd_set ex_fds;
int fd = node.fd;
+ FILE *frecord = NULL;
time_t t;
+
+ if (options[OptMonitorAll])
+ monitor = CEC_MODE_MONITOR_ALL;
+ else if (options[OptMonitorPin] || options[OptRecordPin])
+ monitor = CEC_MODE_MONITOR_PIN;
printf("\n");
- if (!(node.caps & CEC_CAP_MONITOR_ALL) &&
- monitor == CEC_MODE_MONITOR_ALL) {
+ 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 (!(node.caps & CEC_CAP_MONITOR_PIN) &&
- monitor == CEC_MODE_MONITOR_PIN) {
- printf("Monitor Pin mode is not supported, falling back to regular monitoring\n");
- monitor = CEC_MODE_MONITOR;
+ if (!(node.caps & CEC_CAP_MONITOR_PIN) && monitor == CEC_MODE_MONITOR_PIN) {
+ fprintf(stderr, "Monitor Pin mode is not supported\n");
+ usage();
+ exit(1);
}
+
if (doioctl(&node, CEC_S_MODE, &monitor)) {
printf("Selecting monitor mode failed, you may have to run this as root.\n");
return;
}
+ if (record_pin) {
+ frecord = fopen(record_pin, "w+");
+ if (frecord == NULL) {
+ fprintf(stderr, "Failed to open %s: %s\n", record_pin,
+ strerror(errno));
+ exit(1);
+ }
+ fprintf(frecord, "# cec-ctl --record-pin\n");
+ fprintf(frecord, "# version 1\n");
+ fprintf(frecord, "# start_monotonic %llu\n",
+ start_monotonic.tv_sec * 1000000000ULL + start_monotonic.tv_nsec);
+ fprintf(frecord, "# start_timeofday %llu\n",
+ start_timeofday.tv_sec * 1000000ULL + start_timeofday.tv_usec);
+ fprintf(frecord, "# log_addr_mask 0x%04x\n", node.log_addr_mask);
+ fprintf(frecord, "# phys_addr %x.%x.%x.%x\n",
+ node.phys_addr >> 12, (node.phys_addr >> 8) & 0xf,
+ (node.phys_addr >> 4) & 0xf, node.phys_addr & 0xf);
+ }
+
fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
t = time(NULL) + monitor_time;
@@ -1564,7 +1595,13 @@ static void monitor(struct node &node, __u32 monitor_time)
if (ev.event == CEC_EVENT_PIN_LOW ||
ev.event == CEC_EVENT_PIN_HIGH)
pin_event = true;
- log_event(ev);
+ if (pin_event && frecord) {
+ fprintf(frecord, "%llu %d\n",
+ ev.ts, ev.event == CEC_EVENT_PIN_HIGH);
+ fflush(frecord);
+ }
+ if (!pin_event || options[OptMonitorPin])
+ log_event(ev);
}
if (!pin_event && eob_ts) {
struct timespec ts;
@@ -1578,10 +1615,87 @@ static void monitor(struct node &node, __u32 monitor_time)
CEC_EVENT_PIN_HIGH
};
- log_event(ev);
+ if (frecord) {
+ fprintf(frecord, "%llu %d\n",
+ ev.ts, ev.event == CEC_EVENT_PIN_HIGH);
+ fflush(frecord);
+ }
+ if (options[OptMonitorPin])
+ log_event(ev);
}
}
}
+ if (frecord)
+ fclose(frecord);
+}
+
+static void playback(const char *playback_pin)
+{
+ FILE *fplayback = fopen(playback_pin, "r");
+ unsigned long long tv;
+ unsigned version;
+ unsigned log_addr_mask;
+ unsigned pa1, pa2, pa3, pa4;
+ unsigned line = 1;
+ char s[100];
+
+ if (fplayback == NULL) {
+ fprintf(stderr, "Failed to open %s: %s\n", playback_pin,
+ strerror(errno));
+ exit(1);
+ }
+ if (!fgets(s, sizeof(s), fplayback) ||
+ strcmp(s, "# cec-ctl --record-pin\n"))
+ goto err;
+ line++;
+ if (!fgets(s, sizeof(s), fplayback) ||
+ sscanf(s, "# version %u\n", &version) != 1 ||
+ version != 1)
+ goto err;
+ line++;
+ if (!fgets(s, sizeof(s), fplayback) ||
+ sscanf(s, "# start_monotonic %llu\n", &tv) != 1)
+ goto err;
+ start_monotonic.tv_sec = tv / 1000000000ULL;
+ start_monotonic.tv_nsec = tv % 1000000000ULL;
+ line++;
+ if (!fgets(s, sizeof(s), fplayback) ||
+ sscanf(s, "# start_timeofday %llu\n", &tv) != 1)
+ goto err;
+ start_timeofday.tv_sec = tv / 1000000ULL;
+ start_timeofday.tv_usec = tv % 1000000ULL;
+ line++;
+ if (!fgets(s, sizeof(s), fplayback) ||
+ sscanf(s, "# log_addr_mask 0x%04x\n", &log_addr_mask) != 1)
+ goto err;
+ line++;
+ if (!fgets(s, sizeof(s), fplayback) ||
+ sscanf(s, "# phys_addr %x.%x.%x.%x\n", &pa1, &pa2, &pa3, &pa4) != 4)
+ goto err;
+ line++;
+
+ fprintf(stderr, "Physical Address: %x.%x.%x.%x\n", pa1, pa2, pa3, pa4);
+ fprintf(stderr, "Logical Address Mask: 0x%04x\n\n", log_addr_mask);
+
+ while (fgets(s, sizeof(s), fplayback)) {
+ struct cec_event ev = { };
+ unsigned high;
+
+ if (sscanf(s, "%llu %d\n", &ev.ts, &high) != 2 || high > 1) {
+ fprintf(stderr, "malformed data at line %d\n", line);
+ break;
+ }
+ ev.event = high ? CEC_EVENT_PIN_HIGH : CEC_EVENT_PIN_LOW;
+ log_event(ev);
+ line++;
+ }
+
+ fclose(fplayback);
+ return;
+
+err:
+ fprintf(stderr, "Not a pin recording file: malformed data at line %d\n", line);
+ exit(1);
}
int main(int argc, char **argv)
@@ -1599,6 +1713,8 @@ int main(int argc, char **argv)
__u8 rc_tv = 0;
__u8 rc_src = 0;
const char *osd_name = "";
+ const char *record_pin = NULL;
+ const char *playback_pin = NULL;
bool reply = true;
int idx = 0;
int fd = -1;
@@ -1658,6 +1774,12 @@ int main(int argc, char **argv)
case OptMonitorTime:
monitor_time = strtoul(optarg, NULL, 0);
break;
+ case OptRecordPin:
+ record_pin = optarg;
+ break;
+ case OptPlaybackPin:
+ playback_pin = optarg;
+ break;
case OptNoReply:
reply = false;
break;
@@ -1924,6 +2046,23 @@ int main(int argc, char **argv)
return 1;
}
+ if (record_pin && playback_pin) {
+ fprintf(stderr, "--record-pin and --playback-pin options cannot be combined.\n\n");
+ usage();
+ return 1;
+ }
+
+ if (playback_pin && options[OptSetDevice]) {
+ fprintf(stderr, "--device and --playback-pin options cannot be combined.\n\n");
+ usage();
+ return 1;
+ }
+
+ if (playback_pin) {
+ playback(playback_pin);
+ return 0;
+ }
+
if (rc_tv && rc_src) {
fprintf(stderr, "--rc-tv- and --rc-src- options cannot be combined.\n\n");
usage();
@@ -2135,6 +2274,8 @@ int main(int argc, char **argv)
struct cec_log_addrs laddrs = { };
doioctl(&node, CEC_ADAP_G_LOG_ADDRS, &laddrs);
node.num_log_addrs = laddrs.num_log_addrs;
+ node.log_addr_mask = laddrs.log_addr_mask;
+ node.phys_addr = phys_addr;
if (!options[OptSkipInfo]) {
printf("\tLogical Address Mask : 0x%04x\n", laddrs.log_addr_mask);
printf("\tCEC Version : %s\n", version2s(laddrs.cec_version));
@@ -2205,7 +2346,8 @@ int main(int argc, char **argv)
}
}
if (node.num_log_addrs == 0) {
- if (options[OptMonitor] || options[OptMonitorAll] || options[OptMonitorPin])
+ if (options[OptMonitor] || options[OptMonitorAll] ||
+ options[OptMonitorPin] || options[OptRecordPin])
goto skip_la;
return 0;
}
@@ -2255,8 +2397,9 @@ int main(int argc, char **argv)
fflush(stdout);
skip_la:
- if (options[OptMonitor] || options[OptMonitorAll] || options[OptMonitorPin])
- monitor(node, monitor_time);
+ if (options[OptMonitor] || options[OptMonitorAll] ||
+ options[OptMonitorPin] || options[OptRecordPin])
+ monitor(node, monitor_time, record_pin);
fflush(stdout);
close(fd);
return 0;

Privacy Policy