1 // SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
2 /*
3 * Copyright 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
4 */
5
6 #include <cstring>
7 #include <ctime>
8 #include <sstream>
9
10 #include <fcntl.h>
11 #include <getopt.h>
12 #include <sys/ioctl.h>
13
14 #include "cec-follower.h"
15 #include "compiler.h"
16
17 /* Short option list
18
19 Please keep in alphabetical order.
20 That makes it easier to see which short options are still free.
21
22 In general the lower case is used to set something and the upper
23 case is used to retrieve a setting. */
24 enum Option {
25 OptSetAdapter = 'a',
26 OptSetDevice = 'd',
27 OptSetDriver = 'D',
28 OptExclusive = 'e',
29 OptHelp = 'h',
30 OptIgnore = 'i',
31 OptNoWarnings = 'n',
32 OptTrace = 'T',
33 OptVerbose = 'v',
34 OptShowMsgs = 'm',
35 OptShowState = 's',
36 OptWallClock = 'w',
37 OptServiceByDigID = 128,
38 OptStandby,
39 OptTogglePowerStatus,
40 OptIgnoreStandby,
41 OptIgnoreViewOn,
42 OptVersion,
43 OptLast = 256
44 };
45
46 static char options[OptLast];
47
48 bool show_info;
49 bool show_msgs;
50 bool show_state;
51 bool show_warnings = true;
52 unsigned warnings;
53 std::set<struct Timer> programmed_timers;
54
55 static struct option long_options[] = {
56 { "device", required_argument, nullptr, OptSetDevice },
57 { "adapter", required_argument, nullptr, OptSetAdapter },
58 { "driver", required_argument, nullptr, OptSetDriver },
59 { "exclusive", no_argument, 0, OptExclusive },
60 { "help", no_argument, nullptr, OptHelp },
61 { "no-warnings", no_argument, nullptr, OptNoWarnings },
62 { "trace", no_argument, nullptr, OptTrace },
63 { "verbose", no_argument, nullptr, OptVerbose },
64 { "show-msgs", no_argument, nullptr, OptShowMsgs },
65 { "show-state", no_argument, nullptr, OptShowState },
66 { "wall-clock", no_argument, nullptr, OptWallClock },
67 { "service-by-dig-id", no_argument, nullptr, OptServiceByDigID },
68 { "standby", no_argument, nullptr, OptStandby },
69 { "toggle-power-status", required_argument, nullptr, OptTogglePowerStatus },
70 { "ignore-standby", required_argument, nullptr, OptIgnoreStandby },
71 { "ignore-view-on", required_argument, nullptr, OptIgnoreViewOn },
72 { "ignore", required_argument, nullptr, OptIgnore },
73 { "version", no_argument, nullptr, OptVersion },
74
75 { nullptr, 0, nullptr, 0 }
76 };
77
78 #define STR(x) #x
79 #define STRING(x) STR(x)
80
usage()81 static void usage()
82 {
83 printf("Usage:\n"
84 " -d, --device <dev> Use device <dev> instead of /dev/cec0\n"
85 " If <dev> starts with a digit, then /dev/cec<dev> is used.\n"
86 " -D, --driver <driver> Use a cec device with this driver name\n"
87 " -a, --adapter <adapter> Use a cec device with this adapter name\n"
88 " -h, --help Display this help message\n"
89 " -n, --no-warnings Turn off warning messages\n"
90 " -T, --trace Trace all called ioctls\n"
91 " -v, --verbose Turn on verbose reporting\n"
92 " -w, --wall-clock Show timestamps as wall-clock time (implies -v)\n"
93 " -e, --exclusive If specified, then the follower uses exclusive mode (CEC_MODE_EXCL_FOLLOWER)\n"
94 " -m, --show-msgs Show received messages\n"
95 " -s, --show-state Show state changes from the emulated device\n"
96 " --service-by-dig-id Report digital services by digital ID instead of by channel\n"
97 " --standby Start in Standby state\n"
98 " --toggle-power-status <secs>\n"
99 " Toggle the power status every <secs> seconds\n"
100 " --ignore-standby <n>\n"
101 " Ignore every <n>th received Standby message\n"
102 " --ignore-view-on <n>\n"
103 " Ignore every <n>th received Image/Text View On message\n"
104 " -i, --ignore <la>,<opcode>\n"
105 " Ignore messages from logical address <la> and opcode\n"
106 " <opcode>. 'all' can be used for <la> or <opcode> to match\n"
107 " all logical addresses or opcodes.\n"
108 " --version Show version information\n"
109 );
110 }
111
sad_encode(const struct short_audio_desc * sad,__u32 * descriptor)112 void sad_encode(const struct short_audio_desc *sad, __u32 *descriptor)
113 {
114 __u8 b1, b2, b3 = 0;
115
116 b1 = (sad->num_channels - 1) & 0x07;
117 b1 |= (sad->format_code & 0x0f) << 3;
118
119 b2 = sad->sample_freq_mask;
120
121 switch (sad->format_code) {
122 case SAD_FMT_CODE_LPCM:
123 b3 = sad->bit_depth_mask & 0x07;
124 break;
125 case 2:
126 case 3:
127 case 4:
128 case 5:
129 case 6:
130 case 7:
131 case 8:
132 b3 = sad->max_bitrate;
133 break;
134 case 9:
135 case 10:
136 case 11:
137 case 12:
138 case 13:
139 b3 = sad->format_dependent;
140 break;
141 case SAD_FMT_CODE_WMA_PRO:
142 b3 = sad->wma_profile & 0x03;
143 break;
144 case SAD_FMT_CODE_EXTENDED:
145 b3 = (sad->extension_type_code & 0x1f) << 3;
146
147 switch (sad->extension_type_code) {
148 case 4:
149 case 5:
150 case 6:
151 b3 |= (sad->frame_length_mask & 0x03) << 1;
152 break;
153 case 8:
154 case 10:
155 b3 |= (sad->frame_length_mask & 0x03) << 1;
156 b3 |= sad->mps & 1;
157 break;
158 case SAD_EXT_TYPE_MPEG_H_3D_AUDIO:
159 case SAD_EXT_TYPE_AC_4:
160 b3 |= sad->format_dependent & 0x07;
161 fallthrough;
162 case SAD_EXT_TYPE_LPCM_3D_AUDIO:
163 b3 |= sad->bit_depth_mask & 0x07;
164 break;
165 }
166 break;
167 }
168
169 *descriptor = (b1 << 16) | (b2 << 8) | b3;
170 }
171
audio_format_code2s(__u8 format_code)172 static std::string audio_format_code2s(__u8 format_code)
173 {
174 switch (format_code) {
175 case 0:
176 return "Reserved";
177 case SAD_FMT_CODE_LPCM:
178 return "L-PCM";
179 case SAD_FMT_CODE_AC3:
180 return "AC-3";
181 case SAD_FMT_CODE_MPEG1:
182 return "MPEG-1";
183 case SAD_FMT_CODE_MP3:
184 return "MP3";
185 case SAD_FMT_CODE_MPEG2:
186 return "MPEG2";
187 case SAD_FMT_CODE_AAC_LC:
188 return "AAC LC";
189 case SAD_FMT_CODE_DTS:
190 return "DTS";
191 case SAD_FMT_CODE_ATRAC:
192 return "ATRAC";
193 case SAD_FMT_CODE_ONE_BIT_AUDIO:
194 return "One Bit Audio";
195 case SAD_FMT_CODE_ENHANCED_AC3:
196 return "Enhanced AC-3";
197 case SAD_FMT_CODE_DTS_HD:
198 return "DTS-HD";
199 case SAD_FMT_CODE_MAT:
200 return "MAT";
201 case SAD_FMT_CODE_DST:
202 return "DST";
203 case SAD_FMT_CODE_WMA_PRO:
204 return "WMA Pro";
205 case SAD_FMT_CODE_EXTENDED:
206 return "Extended";
207 default:
208 return "Illegal";
209 }
210 }
211
extension_type_code2s(__u8 type_code)212 static std::string extension_type_code2s(__u8 type_code)
213 {
214 switch (type_code) {
215 case 0:
216 case 1:
217 case 2:
218 case 3:
219 return "Not in use";
220 case SAD_EXT_TYPE_MPEG4_HE_AAC:
221 return "MPEG-4 HE AAC";
222 case SAD_EXT_TYPE_MPEG4_HE_AACv2:
223 return "MPEG-4 HE AAC v2";
224 case SAD_EXT_TYPE_MPEG4_AAC_LC:
225 return "MPEG-4 AAC LC";
226 case SAD_EXT_TYPE_DRA:
227 return "DRA";
228 case SAD_EXT_TYPE_MPEG4_HE_AAC_SURROUND:
229 return "MPEG-4 HE AAC + MPEG Surround";
230 case SAD_EXT_TYPE_MPEG4_AAC_LC_SURROUND:
231 return "MPEG-4 AAC LC + MPEG Surround";
232 case SAD_EXT_TYPE_MPEG_H_3D_AUDIO:
233 return "MPEG-H 3D Audio";
234 case SAD_EXT_TYPE_AC_4:
235 return "AC-4";
236 case SAD_EXT_TYPE_LPCM_3D_AUDIO:
237 return "L-PCM 3D Audio";
238 default:
239 return "Reserved";
240 }
241 }
242
audio_format_id_code2s(__u8 audio_format_id,__u8 audio_format_code)243 std::string audio_format_id_code2s(__u8 audio_format_id, __u8 audio_format_code)
244 {
245 if (audio_format_id == 0)
246 return audio_format_code2s(audio_format_code);
247 if (audio_format_id == 1)
248 return extension_type_code2s(audio_format_code);
249 return "Invalid";
250 }
251
opcode2s(const struct cec_msg * msg)252 std::string opcode2s(const struct cec_msg *msg)
253 {
254 std::stringstream oss;
255 __u8 opcode = msg->msg[1];
256 const char *name;
257
258 if (opcode == CEC_MSG_CDC_MESSAGE) {
259 __u8 cdc_opcode = msg->msg[4];
260 name = cec_cdc_opcode2s(cdc_opcode);
261
262 if (name)
263 return name;
264 oss << "CDC: 0x" << std::hex << static_cast<unsigned>(cdc_opcode);
265 return oss.str();
266 }
267
268 name = cec_opcode2s(opcode);
269
270 if (name)
271 return name;
272 oss << "0x" << std::hex << static_cast<unsigned>(opcode);
273 return oss.str();
274 }
275
cec_named_ioctl(int fd,const char * name,unsigned long int request,void * parm)276 int cec_named_ioctl(int fd, const char *name,
277 unsigned long int request, void *parm)
278 {
279 int retval;
280 int e;
281
282 retval = ioctl(fd, request, parm);
283
284 e = retval == 0 ? 0 : errno;
285 if (options[OptTrace])
286 printf("\t\t%s returned %d (%s)\n",
287 name, retval, strerror(e));
288
289 if (!retval) {
290 const auto msg = static_cast<const struct cec_msg *>(parm);
291
292 /* Update the timestamp whenever we successfully transmit to an LA,
293 or whenever we receive something from the LA */
294 if (request == CEC_TRANSMIT && (msg->tx_status & CEC_TX_STATUS_OK) &&
295 !cec_msg_is_broadcast(msg)) {
296 if (msg->timeout) {
297 if (msg->rx_status & (CEC_RX_STATUS_OK | CEC_RX_STATUS_FEATURE_ABORT))
298 la_info[cec_msg_initiator(msg)].ts = msg->rx_ts;
299 } else
300 la_info[cec_msg_destination(msg)].ts = msg->tx_ts;
301 }
302 if (request == CEC_RECEIVE &&
303 cec_msg_initiator(msg) != CEC_LOG_ADDR_UNREGISTERED &&
304 (msg->rx_status & CEC_RX_STATUS_OK))
305 la_info[cec_msg_initiator(msg)].ts = msg->rx_ts;
306 }
307
308 return retval == -1 ? e : (retval ? -1 : 0);
309 }
310
print_timers(struct node * node)311 void print_timers(struct node *node)
312 {
313 if (show_info) {
314 printf("Timers Set:\n");
315 if (node->state.recording_controlled_by_timer)
316 printf("Deck is currently recording from the first timer.\n");
317 if (node->state.one_touch_record_on && !node->state.recording_controlled_by_timer)
318 printf("Deck is currently recording independent of timers.\n");
319 for (auto &t : programmed_timers) {
320 std::string start = ctime(&t.start_time);
321 time_t end_time = t.start_time + t.duration;
322 std::string end = ctime(&end_time);
323 /* Remove the seconds because timer is precise only to the minute. */
324 start.erase(16, 3);
325 end.erase(16, 3);
326 /* Remove the new line characters. */
327 start.erase(start.end() - 1);
328 end.erase(end.end() - 1);
329 /* Remove the start year if it is the same as the end year. */
330 if ((start.compare(start.size() - 4, 5, end, end.size() - 4, 5) == 0))
331 start.erase(start.size() - 5, 5);
332 printf("\t%s - %s, ", start.c_str(), end.c_str());
333 /* Find and print the source. */
334 std::string source;
335 switch (t.src.type) {
336 case CEC_OP_RECORD_SRC_OWN:
337 source = "own";
338 break;
339 case CEC_OP_RECORD_SRC_DIGITAL:
340 source = "digital";
341 break;
342 case CEC_OP_RECORD_SRC_ANALOG:
343 source = "analog";
344 break;
345 case CEC_OP_RECORD_SRC_EXT_PLUG:
346 source = "ext plug";
347 break;
348 case CEC_OP_RECORD_SRC_EXT_PHYS_ADDR:
349 source = "ext phy addr";
350 break;
351 default:
352 break;
353 }
354 printf("source: %s, ", source.c_str());
355 if (t.recording_seq)
356 printf("rec-seq: 0x%x, ", t.recording_seq);
357 printf("needs: %d %s\n", t.duration, "MB."); /* 1MB per second. */
358 }
359 printf("Total media space available for recording: ");
360 if (node->state.media_space_available >= 0)
361 printf("%d MB.\n\n", node->state.media_space_available);
362 else
363 printf("0 MB.\n\n");
364 }
365 }
366
state_init(struct node & node)367 void state_init(struct node &node)
368 {
369 if (options[OptStandby])
370 node.state.power_status = CEC_OP_POWER_STATUS_STANDBY;
371 else
372 node.state.power_status = CEC_OP_POWER_STATUS_ON;
373 node.state.old_power_status = CEC_OP_POWER_STATUS_ON;
374 node.state.power_status_changed_time = 0;
375 strcpy(node.state.menu_language, "eng");
376 node.state.video_latency = 10;
377 node.state.low_latency_mode = 1;
378 node.state.audio_out_compensated = 3;
379 node.state.audio_out_delay = 20;
380 node.state.arc_active = false;
381 node.state.sac_active = false;
382 node.state.volume = 50;
383 node.state.mute = false;
384 node.state.deck_report_changes = false;
385 node.state.deck_report_changes_to = 0;
386 node.state.deck_state = CEC_OP_DECK_INFO_STOP;
387 node.state.deck_skip_start = 0;
388 node.state.one_touch_record_on = false;
389 node.state.record_received_standby = false;
390 node.state.media_space_available = 36000; /* In MB; space for 10 hours @ 1MB/sec */
391 node.state.recording_controlled_by_timer = false;
392 tuner_dev_info_init(&node.state);
393 node.state.last_aud_rate_rx_ts = 0;
394 }
395
main(int argc,char ** argv)396 int main(int argc, char **argv)
397 {
398 std::string device;
399 struct node node = { };
400 const char *driver = nullptr;
401 const char *adapter = nullptr;
402 unsigned toggle_power_status = 0;
403 char short_options[26 * 2 * 2 + 1];
404 int idx = 0;
405 int fd = -1;
406 int ch;
407 int i;
408
409 for (i = 0; long_options[i].name; i++) {
410 if (!isalpha(long_options[i].val))
411 continue;
412 short_options[idx++] = long_options[i].val;
413 if (long_options[i].has_arg == required_argument) {
414 short_options[idx++] = ':';
415 } else if (long_options[i].has_arg == optional_argument) {
416 short_options[idx++] = ':';
417 short_options[idx++] = ':';
418 }
419 }
420 while (true) {
421 int option_index = 0;
422
423 short_options[idx] = 0;
424 ch = getopt_long(argc, argv, short_options,
425 long_options, &option_index);
426 if (ch == -1)
427 break;
428
429 options[ch] = 1;
430 if (!option_index) {
431 for (i = 0; long_options[i].val; i++) {
432 if (long_options[i].val == ch) {
433 option_index = i;
434 break;
435 }
436 }
437 }
438 if (long_options[option_index].has_arg == optional_argument &&
439 !optarg && argv[optind] && argv[optind][0] != '-')
440 optarg = argv[optind++];
441
442 switch (ch) {
443 case OptHelp:
444 usage();
445 return 0;
446 case OptSetDevice:
447 device = optarg;
448 if (device[0] >= '0' && device[0] <= '9' && device.length() <= 3) {
449 static char newdev[20];
450
451 sprintf(newdev, "/dev/cec%s", optarg);
452 device = newdev;
453 }
454 break;
455 case OptSetDriver:
456 driver = optarg;
457 break;
458 case OptSetAdapter:
459 adapter = optarg;
460 break;
461 case OptNoWarnings:
462 show_warnings = false;
463 break;
464 case OptShowMsgs:
465 show_msgs = true;
466 break;
467 case OptShowState:
468 show_state = true;
469 break;
470 case OptTogglePowerStatus:
471 toggle_power_status = strtoul(optarg, nullptr, 0);
472 break;
473 case OptIgnoreStandby:
474 node.ignore_standby = strtoul(optarg, nullptr, 0);
475 break;
476 case OptIgnoreViewOn:
477 node.ignore_view_on = strtoul(optarg, nullptr, 0);
478 break;
479 case OptIgnore: {
480 bool all_la = !strncmp(optarg, "all", 3);
481 bool all_opcodes = true;
482 const char *sep = std::strchr(optarg, ',');
483 unsigned la_mask = 0xffff, opcode, la = 0;
484
485 if (sep)
486 all_opcodes = !strncmp(sep + 1, "all", 3);
487 if (!all_la) {
488 la = strtoul(optarg, nullptr, 0);
489
490 if (la > 15) {
491 fprintf(stderr, "invalid logical address (> 15)\n");
492 usage();
493 return 1;
494 }
495 la_mask = 1 << la;
496 }
497 if (!all_opcodes) {
498 opcode = strtoul(sep + 1, nullptr, 0);
499 if (opcode > 255) {
500 fprintf(stderr, "invalid opcode (> 255)\n");
501 usage();
502 return 1;
503 }
504 node.ignore_opcode[opcode] |= la_mask;
505 break;
506 }
507 if (all_la && all_opcodes) {
508 fprintf(stderr, "all,all is invalid\n");
509 usage();
510 return 1;
511 }
512 node.ignore_la[la] = true;
513 break;
514 }
515 case OptWallClock:
516 case OptVerbose:
517 show_info = true;
518 show_msgs = true;
519 show_state = true;
520 break;
521 case OptVersion:
522 printf("cec-follower %s%s\n", PACKAGE_VERSION, STRING(GIT_COMMIT_CNT));
523 if (strlen(STRING(GIT_SHA)))
524 printf("cec-follower SHA: %s %s\n",
525 STRING(GIT_SHA), STRING(GIT_COMMIT_DATE));
526 std::exit(EXIT_SUCCESS);
527 case ':':
528 fprintf(stderr, "Option '%s' requires a value\n",
529 argv[optind]);
530 usage();
531 return 1;
532 case '?':
533 if (argv[optind])
534 fprintf(stderr, "Unknown argument '%s'\n", argv[optind]);
535 usage();
536 return 1;
537 }
538 }
539 if (optind < argc) {
540 printf("unknown arguments: ");
541 while (optind < argc)
542 printf("%s ", argv[optind++]);
543 printf("\n");
544 usage();
545 return 1;
546 }
547
548 if (device.empty() && (driver || adapter)) {
549 device = cec_device_find(driver, adapter);
550 if (device.empty()) {
551 fprintf(stderr,
552 "Could not find a CEC device for the given driver/adapter combination\n");
553 std::exit(EXIT_FAILURE);
554 }
555 }
556 if (device.empty())
557 device = "/dev/cec0";
558
559 if ((fd = open(device.c_str(), O_RDWR)) < 0) {
560 fprintf(stderr, "Failed to open %s: %s\n", device.c_str(),
561 strerror(errno));
562 std::exit(EXIT_FAILURE);
563 }
564
565 struct cec_caps caps = { };
566
567 node.fd = fd;
568 node.device = device.c_str();
569 doioctl(&node, CEC_ADAP_G_CAPS, &caps);
570 node.caps = caps.capabilities;
571 node.available_log_addrs = caps.available_log_addrs;
572 node.state.service_by_dig_id = options[OptServiceByDigID];
573 node.state.toggle_power_status = toggle_power_status;
574 state_init(node);
575
576 if (strlen(STRING(GIT_SHA)))
577 printf("cec-follower SHA : %s %s\n",
578 STRING(GIT_SHA), STRING(GIT_COMMIT_DATE));
579
580 doioctl(&node, CEC_ADAP_G_PHYS_ADDR, &node.phys_addr);
581
582 struct cec_log_addrs laddrs = { };
583 doioctl(&node, CEC_ADAP_G_LOG_ADDRS, &laddrs);
584 node.adap_la_mask = laddrs.log_addr_mask;
585 node.cec_version = laddrs.cec_version;
586
587 struct cec_connector_info conn_info = {};
588
589 doioctl(&node, CEC_ADAP_G_CONNECTOR_INFO, &conn_info);
590
591 cec_driver_info(caps, laddrs, node.phys_addr, conn_info);
592
593 /*
594 * For CEC 1.4, features of a logical address may still be
595 * filled in according to the CEC 2.0 guidelines even though
596 * the CEC framework won’t use the features in the CEC 2.0
597 * CEC_MSG_REPORT_FEATURES.
598 */
599 bool is_dev_feat = false;
600
601 for (__u8 byte : laddrs.features[0]) {
602 if (is_dev_feat) {
603 node.source_has_arc_rx = (byte & CEC_OP_FEAT_DEV_SOURCE_HAS_ARC_RX) != 0;
604 node.sink_has_arc_tx = (byte & CEC_OP_FEAT_DEV_SINK_HAS_ARC_TX) != 0;
605 node.has_aud_rate = (byte & CEC_OP_FEAT_DEV_HAS_SET_AUDIO_RATE) != 0;
606 node.has_deck_ctl = (byte & CEC_OP_FEAT_DEV_HAS_DECK_CONTROL) != 0;
607 node.has_rec_tv = (byte & CEC_OP_FEAT_DEV_HAS_RECORD_TV_SCREEN) != 0;
608 node.has_osd_string = (byte & CEC_OP_FEAT_DEV_HAS_SET_OSD_STRING) != 0;
609 break;
610 }
611 if (byte & CEC_OP_FEAT_EXT)
612 continue;
613 if (!is_dev_feat)
614 is_dev_feat = true;
615 else
616 break;
617 }
618 printf("\n");
619
620 if (laddrs.num_log_addrs == 0 && (node.caps & CEC_CAP_LOG_ADDRS)) {
621 printf("\nFAIL: missing logical address(es), use cec-ctl to configure this\n");
622 std::exit(EXIT_FAILURE);
623 }
624
625 testProcessing(&node, options[OptExclusive], options[OptWallClock]);
626 }
627