| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Copyright 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. |
| */ |
| |
| #include <cstring> |
| #include <map> |
| #include <sstream> |
| #include <vector> |
| |
| #include <sys/ioctl.h> |
| #include <unistd.h> |
| |
| #include "cec-compliance.h" |
| |
| enum Months { Jan = 1, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec }; |
| |
| struct remote_test { |
| const char *name; |
| const unsigned tags; |
| const vec_remote_subtests &subtests; |
| }; |
| |
| static int deck_status_get(struct node *node, unsigned me, unsigned la, __u8 &deck_status) |
| { |
| struct cec_msg msg; |
| deck_status = 0; |
| |
| cec_msg_init(&msg, me, la); |
| cec_msg_give_deck_status(&msg, true, CEC_OP_STATUS_REQ_ONCE); |
| fail_on_test(!transmit_timeout(node, &msg)); |
| fail_on_test(timed_out_or_abort(&msg)); |
| cec_ops_deck_status(&msg, &deck_status); |
| |
| return OK; |
| } |
| |
| static int test_play_mode(struct node *node, unsigned me, unsigned la, __u8 play_mode, __u8 expected) |
| { |
| struct cec_msg msg; |
| __u8 deck_status; |
| |
| cec_msg_init(&msg, me, la); |
| cec_msg_play(&msg, play_mode); |
| fail_on_test(!transmit_timeout(node, &msg)); |
| fail_on_test(cec_msg_status_is_abort(&msg)); /* Assumes deck has media. */ |
| fail_on_test(deck_status_get(node, me, la, deck_status)); |
| fail_on_test(deck_status != expected); |
| |
| return OK; |
| } |
| |
| |
| /* System Information */ |
| |
| int system_info_polling(struct node *node, unsigned me, unsigned la, bool interactive) |
| { |
| struct cec_msg msg; |
| |
| cec_msg_init(&msg, me, la); |
| fail_on_test(doioctl(node, CEC_TRANSMIT, &msg)); |
| if (node->remote_la_mask & (1 << la)) { |
| if (!cec_msg_status_is_ok(&msg)) { |
| fail("Polling a valid remote LA failed\n"); |
| return FAIL_CRITICAL; |
| } |
| } else { |
| if (cec_msg_status_is_ok(&msg)) { |
| fail("Polling an invalid remote LA was successful\n"); |
| return FAIL_CRITICAL; |
| } |
| return OK_NOT_SUPPORTED; |
| } |
| |
| return 0; |
| } |
| |
| int system_info_phys_addr(struct node *node, unsigned me, unsigned la, bool interactive) |
| { |
| struct cec_msg msg; |
| |
| cec_msg_init(&msg, me, la); |
| cec_msg_give_physical_addr(&msg, true); |
| if (!transmit_timeout(node, &msg) || timed_out_or_abort(&msg)) { |
| fail_or_warn(node, "Give Physical Addr timed out\n"); |
| return node->in_standby ? 0 : FAIL_CRITICAL; |
| } |
| fail_on_test(node->remote[la].phys_addr != ((msg.msg[2] << 8) | msg.msg[3])); |
| fail_on_test(node->remote[la].prim_type != msg.msg[4]); |
| return 0; |
| } |
| |
| int system_info_version(struct node *node, unsigned me, unsigned la, bool interactive) |
| { |
| struct cec_msg msg; |
| |
| cec_msg_init(&msg, me, la); |
| cec_msg_get_cec_version(&msg, true); |
| if (!transmit_timeout(node, &msg) || timed_out(&msg)) |
| return fail_or_warn(node, "Get CEC Version timed out\n"); |
| if (unrecognized_op(&msg)) |
| return OK_NOT_SUPPORTED; |
| if (refused(&msg)) |
| return OK_REFUSED; |
| |
| /* This needs to be kept in sync with newer CEC versions */ |
| fail_on_test(msg.msg[2] < CEC_OP_CEC_VERSION_1_3A || |
| msg.msg[2] > CEC_OP_CEC_VERSION_2_0); |
| fail_on_test(node->remote[la].cec_version != msg.msg[2]); |
| |
| return 0; |
| } |
| |
| int system_info_get_menu_lang(struct node *node, unsigned me, unsigned la, bool interactive) |
| { |
| struct cec_msg msg; |
| char language[4]; |
| |
| cec_msg_init(&msg, me, la); |
| cec_msg_get_menu_language(&msg, true); |
| if (!transmit_timeout(node, &msg) || timed_out(&msg)) |
| return fail_or_warn(node, "Get Menu Languages timed out\n"); |
| |
| /* Devices other than TVs shall send Feature Abort [Unregcognized Opcode] |
| in reply to Get Menu Language. */ |
| fail_on_test(!is_tv(la, node->remote[la].prim_type) && !unrecognized_op(&msg)); |
| |
| if (unrecognized_op(&msg)) { |
| if (is_tv(la, node->remote[la].prim_type)) |
| warn("TV did not respond to Get Menu Language.\n"); |
| return OK_NOT_SUPPORTED; |
| } |
| if (refused(&msg)) |
| return OK_REFUSED; |
| if (cec_msg_status_is_abort(&msg)) |
| return OK_PRESUMED; |
| cec_ops_set_menu_language(&msg, language); |
| fail_on_test(strcmp(node->remote[la].language, language)); |
| |
| return 0; |
| } |
| |
| static int system_info_set_menu_lang(struct node *node, unsigned me, unsigned la, bool interactive) |
| { |
| struct cec_msg msg; |
| |
| cec_msg_init(&msg, me, la); |
| cec_msg_set_menu_language(&msg, "eng"); |
| fail_on_test(!transmit_timeout(node, &msg)); |
| if (unrecognized_op(&msg)) |
| return OK_NOT_SUPPORTED; |
| if (refused(&msg)) |
| return OK_REFUSED; |
| |
| return OK_PRESUMED; |
| } |
| |
| int system_info_give_features(struct node *node, unsigned me, unsigned la, bool interactive) |
| { |
| struct cec_msg msg; |
| |
| cec_msg_init(&msg, me, la); |
| cec_msg_give_features(&msg, true); |
| if (!transmit_timeout(node, &msg) || timed_out(&msg)) |
| return fail_or_warn(node, "Give Features timed out\n"); |
| if (unrecognized_op(&msg)) { |
| if (node->remote[la].cec_version < CEC_OP_CEC_VERSION_2_0) |
| return OK_NOT_SUPPORTED; |
| fail_on_test_v2(node->remote[la].cec_version, true); |
| } |
| if (refused(&msg)) |
| return OK_REFUSED; |
| if (node->remote[la].cec_version < CEC_OP_CEC_VERSION_2_0) |
| info("Device has CEC Version < 2.0 but supports Give Features.\n"); |
| |
| /* RC Profile and Device Features are assumed to be 1 byte. As of CEC 2.0 only |
| 1 byte is used, but this might be extended in future versions. */ |
| __u8 cec_version, all_device_types; |
| const __u8 *rc_profile, *dev_features; |
| |
| cec_ops_report_features(&msg, &cec_version, &all_device_types, &rc_profile, &dev_features); |
| fail_on_test(rc_profile == nullptr || dev_features == nullptr); |
| info("All Device Types: \t\t%s\n", cec_all_dev_types2s(all_device_types).c_str()); |
| info("RC Profile: \t%s", cec_rc_src_prof2s(*rc_profile, "").c_str()); |
| info("Device Features: \t%s", cec_dev_feat2s(*dev_features, "").c_str()); |
| |
| if (!(cec_has_playback(1 << la) || cec_has_record(1 << la) || cec_has_tuner(1 << la)) && |
| (*dev_features & CEC_OP_FEAT_DEV_HAS_SET_AUDIO_RATE)) { |
| return fail("Only Playback, Recording or Tuner devices shall set the Set Audio Rate bit\n"); |
| } |
| if (!(cec_has_playback(1 << la) || cec_has_record(1 << la)) && |
| (*dev_features & CEC_OP_FEAT_DEV_HAS_DECK_CONTROL)) |
| return fail("Only Playback and Recording devices shall set the Supports Deck Control bit\n"); |
| if (!cec_has_tv(1 << la) && node->remote[la].has_rec_tv) |
| return fail("Only TVs shall set the Record TV Screen bit\n"); |
| if (cec_has_playback(1 << la) && (*dev_features & CEC_OP_FEAT_DEV_SINK_HAS_ARC_TX)) |
| return fail("A Playback device cannot set the Sink Supports ARC Tx bit\n"); |
| if (cec_has_tv(1 << la) && (*dev_features & CEC_OP_FEAT_DEV_SOURCE_HAS_ARC_RX)) |
| return fail("A TV cannot set the Source Supports ARC Rx bit\n"); |
| |
| fail_on_test(cec_version != node->remote[la].cec_version); |
| fail_on_test(node->remote[la].rc_profile != *rc_profile); |
| fail_on_test(node->remote[la].dev_features != *dev_features); |
| fail_on_test(node->remote[la].all_device_types != all_device_types); |
| return 0; |
| } |
| |
| static const vec_remote_subtests system_info_subtests{ |
| { "Polling Message", CEC_LOG_ADDR_MASK_ALL, system_info_polling }, |
| { "Give Physical Address", CEC_LOG_ADDR_MASK_ALL, system_info_phys_addr }, |
| { "Give CEC Version", CEC_LOG_ADDR_MASK_ALL, system_info_version }, |
| { "Get Menu Language", CEC_LOG_ADDR_MASK_ALL, system_info_get_menu_lang }, |
| { "Set Menu Language", CEC_LOG_ADDR_MASK_ALL, system_info_set_menu_lang }, |
| { "Give Device Features", CEC_LOG_ADDR_MASK_ALL, system_info_give_features }, |
| }; |
| |
| /* Core behavior */ |
| |
| int core_unknown(struct node *node, unsigned me, unsigned la, bool interactive) |
| { |
| struct cec_msg msg; |
| const __u8 unknown_opcode = 0xfe; |
| |
| /* Unknown opcodes should be responded to with Feature Abort, with abort |
| reason Unknown Opcode. |
| |
| For CEC 2.0 and before, 0xfe is an unused opcode. The test possibly |
| needs to be updated for future CEC versions. */ |
| cec_msg_init(&msg, me, la); |
| msg.len = 2; |
| msg.msg[1] = unknown_opcode; |
| if (!transmit_timeout(node, &msg) || timed_out(&msg)) |
| return fail_or_warn(node, "Unknown Opcode timed out\n"); |
| fail_on_test(!cec_msg_status_is_abort(&msg)); |
| |
| __u8 abort_msg, reason; |
| |
| cec_ops_feature_abort(&msg, &abort_msg, &reason); |
| fail_on_test(reason != CEC_OP_ABORT_UNRECOGNIZED_OP); |
| fail_on_test(abort_msg != 0xfe); |
| |
| /* Unknown opcodes that are broadcast should be ignored */ |
| cec_msg_init(&msg, me, CEC_LOG_ADDR_BROADCAST); |
| msg.len = 2; |
| msg.msg[1] = unknown_opcode; |
| fail_on_test(!transmit_timeout(node, &msg)); |
| fail_on_test(!timed_out(&msg)); |
| |
| return 0; |
| } |
| |
| int core_abort(struct node *node, unsigned me, unsigned la, bool interactive) |
| { |
| struct cec_msg msg; |
| |
| /* The Abort message should always be responded to with Feature Abort |
| (with any abort reason) */ |
| cec_msg_init(&msg, me, la); |
| cec_msg_abort(&msg); |
| if (!transmit_timeout(node, &msg) || timed_out(&msg)) |
| return fail_or_warn(node, "Abort timed out\n"); |
| fail_on_test(!cec_msg_status_is_abort(&msg)); |
| return 0; |
| } |
| |
| static const vec_remote_subtests core_subtests{ |
| { "Wake up", CEC_LOG_ADDR_MASK_ALL, standby_resume_wakeup, true }, |
| { "Feature aborts unknown messages", CEC_LOG_ADDR_MASK_ALL, core_unknown }, |
| { "Feature aborts Abort message", CEC_LOG_ADDR_MASK_ALL, core_abort }, |
| }; |
| |
| /* Vendor Specific Commands */ |
| |
| int vendor_specific_commands_id(struct node *node, unsigned me, unsigned la, bool interactive) |
| { |
| struct cec_msg msg; |
| |
| cec_msg_init(&msg, me, la); |
| cec_msg_give_device_vendor_id(&msg, true); |
| if (!transmit(node, &msg)) |
| return fail_or_warn(node, "Give Device Vendor ID timed out\n"); |
| if (unrecognized_op(&msg)) |
| return OK_NOT_SUPPORTED; |
| if (refused(&msg)) |
| return OK_REFUSED; |
| if (cec_msg_status_is_abort(&msg)) |
| return OK_PRESUMED; |
| fail_on_test(node->remote[la].vendor_id != |
| (__u32)((msg.msg[2] << 16) | (msg.msg[3] << 8) | msg.msg[4])); |
| |
| return 0; |
| } |
| |
| static const vec_remote_subtests vendor_specific_subtests{ |
| { "Give Device Vendor ID", CEC_LOG_ADDR_MASK_ALL, vendor_specific_commands_id }, |
| }; |
| |
| /* Device OSD Transfer */ |
| |
| static int device_osd_transfer_set(struct node *node, unsigned me, unsigned la, bool interactive) |
| { |
| struct cec_msg msg; |
| |
| cec_msg_init(&msg, me, la); |
| cec_msg_set_osd_name(&msg, "Whatever"); |
| fail_on_test(!transmit_timeout(node, &msg)); |
| if (unrecognized_op(&msg)) { |
| if (is_tv(la, node->remote[la].prim_type) && |
| node->remote[la].cec_version >= CEC_OP_CEC_VERSION_2_0) |
| warn("TV feature aborted Set OSD Name\n"); |
| return OK_NOT_SUPPORTED; |
| } |
| if (refused(&msg)) |
| return OK_REFUSED; |
| |
| return OK_PRESUMED; |
| } |
| |
| int device_osd_transfer_give(struct node *node, unsigned me, unsigned la, bool interactive) |
| { |
| struct cec_msg msg; |
| |
| /* Todo: CEC 2.0: devices with several logical addresses shall report |
| the same for each logical address. */ |
| cec_msg_init(&msg, me, la); |
| cec_msg_give_osd_name(&msg, true); |
| if (!transmit_timeout(node, &msg) || timed_out(&msg)) |
| return fail_or_warn(node, "Give OSD Name timed out\n"); |
| fail_on_test(!is_tv(la, node->remote[la].prim_type) && unrecognized_op(&msg)); |
| if (unrecognized_op(&msg)) |
| return OK_NOT_SUPPORTED; |
| if (refused(&msg)) |
| return OK_REFUSED; |
| if (cec_msg_status_is_abort(&msg)) |
| return OK_PRESUMED; |
| char osd_name[15]; |
| cec_ops_set_osd_name(&msg, osd_name); |
| fail_on_test(!osd_name[0]); |
| fail_on_test(strcmp(node->remote[la].osd_name, osd_name)); |
| fail_on_test(msg.len != strlen(osd_name) + 2); |
| |
| return 0; |
| } |
| |
| static const vec_remote_subtests device_osd_transfer_subtests{ |
| { "Set OSD Name", CEC_LOG_ADDR_MASK_ALL, device_osd_transfer_set }, |
| { "Give OSD Name", CEC_LOG_ADDR_MASK_ALL, device_osd_transfer_give }, |
| }; |
| |
| /* OSD Display */ |
| |
| static int osd_string_set_default(struct node *node, unsigned me, unsigned la, bool interactive) |
| { |
| struct cec_msg msg; |
| char osd[14]; |
| bool unsuitable = false; |
| |
| sprintf(osd, "Rept %x from %x", la, me); |
| |
| interactive_info(true, "You should see \"%s\" appear on the screen", osd); |
| cec_msg_init(&msg, me, la); |
| cec_msg_set_osd_string(&msg, CEC_OP_DISP_CTL_DEFAULT, osd); |
| fail_on_test(!transmit_timeout(node, &msg)); |
| /* In CEC 2.0 it is mandatory for a TV to support this if it reports so |
| in its Device Features. */ |
| fail_on_test_v2(node->remote[la].cec_version, |
| unrecognized_op(&msg) && |
| (node->remote[la].dev_features & CEC_OP_FEAT_DEV_HAS_SET_OSD_STRING)); |
| if (unrecognized_op(&msg)) |
| return OK_NOT_SUPPORTED; |
| if (refused(&msg)) |
| return OK_REFUSED; |
| if (cec_msg_status_is_abort(&msg)) { |
| warn("The device is in an unsuitable state or cannot display the complete message.\n"); |
| unsuitable = true; |
| } |
| node->remote[la].has_osd = true; |
| if (!interactive) |
| return OK_PRESUMED; |
| |
| /* The CEC 1.4b CTS specifies that one should wait at least 20 seconds for the |
| string to be cleared on the remote device */ |
| interactive_info(true, "Waiting 20s for OSD string to be cleared on the remote device"); |
| sleep(20); |
| fail_on_test(!unsuitable && interactive && !question("Did the string appear and then disappear?")); |
| |
| return 0; |
| } |
| |
| static int osd_string_set_until_clear(struct node *node, unsigned me, unsigned la, bool interactive) |
| { |
| if (!node->remote[la].has_osd) |
| return NOTAPPLICABLE; |
| |
| struct cec_msg msg; |
| char osd[14]; |
| bool unsuitable = false; |
| |
| strcpy(osd, "Appears 1 sec"); |
| // Make sure the string is the maximum possible length |
| fail_on_test(strlen(osd) != 13); |
| |
| interactive_info(true, "You should see \"%s\" appear on the screen for approximately three seconds.", osd); |
| cec_msg_init(&msg, me, la); |
| cec_msg_set_osd_string(&msg, CEC_OP_DISP_CTL_UNTIL_CLEARED, osd); |
| fail_on_test(!transmit(node, &msg)); |
| if (cec_msg_status_is_abort(&msg) && !unrecognized_op(&msg)) { |
| warn("The device is in an unsuitable state or cannot display the complete message.\n"); |
| unsuitable = true; |
| } |
| sleep(3); |
| |
| cec_msg_init(&msg, me, la); |
| cec_msg_set_osd_string(&msg, CEC_OP_DISP_CTL_CLEAR, ""); |
| fail_on_test(!transmit_timeout(node, &msg, 250)); |
| fail_on_test(cec_msg_status_is_abort(&msg)); |
| fail_on_test(!unsuitable && interactive && !question("Did the string appear?")); |
| |
| if (interactive) |
| return 0; |
| |
| return OK_PRESUMED; |
| } |
| |
| static int osd_string_invalid(struct node *node, unsigned me, unsigned la, bool interactive) |
| { |
| if (!node->remote[la].has_osd) |
| return NOTAPPLICABLE; |
| |
| struct cec_msg msg; |
| |
| /* Send Set OSD String with an Display Control operand. A Feature Abort is |
| expected in reply. */ |
| interactive_info(true, "You should observe no change on the on screen display"); |
| cec_msg_init(&msg, me, la); |
| cec_msg_set_osd_string(&msg, 0xff, ""); |
| fail_on_test(!transmit_timeout(node, &msg)); |
| fail_on_test(timed_out(&msg)); |
| fail_on_test(!cec_msg_status_is_abort(&msg)); |
| fail_on_test(interactive && question("Did the display change?")); |
| |
| return 0; |
| } |
| |
| static const vec_remote_subtests osd_string_subtests{ |
| { "Set OSD String with default timeout", CEC_LOG_ADDR_MASK_TV, osd_string_set_default }, |
| { "Set OSD String with no timeout", CEC_LOG_ADDR_MASK_TV, osd_string_set_until_clear }, |
| { "Set OSD String with invalid operand", CEC_LOG_ADDR_MASK_TV, osd_string_invalid }, |
| }; |
| |
| /* Routing Control */ |
| |
| static int routing_control_inactive_source(struct node *node, unsigned me, unsigned la, bool interactive) |
| { |
| struct cec_msg msg; |
| int response; |
| |
| interactive_info(true, "Please make sure that the TV is currently viewing this source."); |
| mode_set_follower(node); |
| cec_msg_init(&msg, me, la); |
| cec_msg_inactive_source(&msg, node->phys_addr); |
| fail_on_test(!transmit(node, &msg)); |
| if (unrecognized_op(&msg)) |
| return OK_NOT_SUPPORTED; |
| if (refused(&msg)) |
| return OK_REFUSED; |
| // It may take a bit of time for the Inactive Source message to take |
| // effect, so sleep a bit. |
| response = util_receive(node, CEC_LOG_ADDR_TV, 10000, &msg, |
| CEC_MSG_INACTIVE_SOURCE, |
| CEC_MSG_ACTIVE_SOURCE, CEC_MSG_SET_STREAM_PATH); |
| if (me == CEC_LOG_ADDR_TV) { |
| // Inactive Source should be ignored by all other devices |
| if (response >= 0) |
| return fail("Unexpected reply to Inactive Source\n"); |
| fail_on_test(response >= 0); |
| } else { |
| if (response < 0) |
| warn("Expected Active Source or Set Stream Path reply to Inactive Source\n"); |
| fail_on_test(interactive && !question("Did the TV switch away from or stop showing this source?")); |
| } |
| |
| return 0; |
| } |
| |
| static int routing_control_active_source(struct node *node, unsigned me, unsigned la, bool interactive) |
| { |
| struct cec_msg msg; |
| |
| interactive_info(true, "Please switch the TV to another source."); |
| cec_msg_init(&msg, me, la); |
| cec_msg_active_source(&msg, node->phys_addr); |
| fail_on_test(!transmit_timeout(node, &msg)); |
| fail_on_test(interactive && !question("Did the TV switch to this source?")); |
| |
| if (interactive) |
| return 0; |
| |
| return OK_PRESUMED; |
| } |
| |
| static int routing_control_req_active_source(struct node *node, unsigned me, unsigned la, bool interactive) |
| { |
| struct cec_msg msg; |
| |
| /* We have now said that we are active source, so receiving a reply to |
| Request Active Source should fail the test. */ |
| cec_msg_init(&msg, me, la); |
| cec_msg_request_active_source(&msg, true); |
| fail_on_test(!transmit_timeout(node, &msg)); |
| fail_on_test(!timed_out(&msg)); |
| |
| return 0; |
| } |
| |
| static int routing_control_set_stream_path(struct node *node, unsigned me, unsigned la, bool interactive) |
| { |
| struct cec_msg msg; |
| __u16 phys_addr; |
| |
| /* Send Set Stream Path with the remote physical address. We expect the |
| source to eventually send Active Source. The timeout of long_timeout |
| seconds is necessary because the device might have to wake up from standby. |
| |
| In CEC 2.0 it is mandatory for sources to send Active Source. */ |
| if (is_tv(la, node->remote[la].prim_type)) |
| interactive_info(true, "Please ensure that the device is in standby."); |
| announce("Sending Set Stream Path and waiting for reply. This may take up to %llu s.", (long long)long_timeout); |
| cec_msg_init(&msg, me, la); |
| cec_msg_set_stream_path(&msg, node->remote[la].phys_addr); |
| msg.reply = CEC_MSG_ACTIVE_SOURCE; |
| fail_on_test(!transmit_timeout(node, &msg, long_timeout * 1000)); |
| if (timed_out(&msg) && is_tv(la, node->remote[la].prim_type)) |
| return OK_NOT_SUPPORTED; |
| if (timed_out(&msg) && node->remote[la].cec_version < CEC_OP_CEC_VERSION_2_0) { |
| warn("Device did not respond to Set Stream Path.\n"); |
| return OK_NOT_SUPPORTED; |
| } |
| fail_on_test_v2(node->remote[la].cec_version, timed_out(&msg)); |
| cec_ops_active_source(&msg, &phys_addr); |
| fail_on_test(phys_addr != node->remote[la].phys_addr); |
| if (is_tv(la, node->remote[la].prim_type)) |
| fail_on_test(interactive && !question("Did the device go out of standby?")); |
| |
| if (interactive || node->remote[la].cec_version >= CEC_OP_CEC_VERSION_2_0) |
| return 0; |
| |
| return OK_PRESUMED; |
| } |
| |
| static const vec_remote_subtests routing_control_subtests{ |
| { "Active Source", CEC_LOG_ADDR_MASK_TV, routing_control_active_source }, |
| { "Request Active Source", CEC_LOG_ADDR_MASK_ALL, routing_control_req_active_source }, |
| { "Inactive Source", CEC_LOG_ADDR_MASK_TV, routing_control_inactive_source }, |
| { "Set Stream Path", CEC_LOG_ADDR_MASK_ALL, routing_control_set_stream_path }, |
| }; |
| |
| /* Remote Control Passthrough */ |
| |
| static int rc_passthrough_user_ctrl_pressed(struct node *node, unsigned me, unsigned la, bool interactive) |
| { |
| struct cec_msg msg; |
| struct cec_op_ui_command rc_press; |
| |
| cec_msg_init(&msg, me, la); |
| rc_press.ui_cmd = CEC_OP_UI_CMD_VOLUME_UP; // Volume up key (the key is not crucial here) |
| cec_msg_user_control_pressed(&msg, &rc_press); |
| fail_on_test(!transmit_timeout(node, &msg)); |
| /* Mandatory for all except devices which have taken logical address 15 */ |
| fail_on_test_v2(node->remote[la].cec_version, |
| unrecognized_op(&msg) && !(cec_is_unregistered(1 << la))); |
| if (unrecognized_op(&msg)) |
| return OK_NOT_SUPPORTED; |
| if (refused(&msg)) |
| return OK_REFUSED; |
| |
| return OK_PRESUMED; |
| } |
| |
| static int rc_passthrough_user_ctrl_released(struct node *node, unsigned me, unsigned la, bool interactive) |
| { |
| struct cec_msg msg; |
| |
| cec_msg_init(&msg, me, la); |
| cec_msg_user_control_released(&msg); |
| fail_on_test(!transmit_timeout(node, &msg)); |
| fail_on_test_v2(node->remote[la].cec_version, |
| cec_msg_status_is_abort(&msg) && !(la & CEC_LOG_ADDR_MASK_UNREGISTERED)); |
| if (unrecognized_op(&msg)) |
| return OK_NOT_SUPPORTED; |
| if (refused(&msg)) |
| return OK_REFUSED; |
| node->remote[la].has_remote_control_passthrough = true; |
| |
| return OK_PRESUMED; |
| } |
| |
| static const vec_remote_subtests rc_passthrough_subtests{ |
| { "User Control Pressed", CEC_LOG_ADDR_MASK_ALL, rc_passthrough_user_ctrl_pressed }, |
| { "User Control Released", CEC_LOG_ADDR_MASK_ALL, rc_passthrough_user_ctrl_released }, |
| }; |
| |
| /* Device Menu Control */ |
| |
| /* |
| TODO: These are very rudimentary tests which should be expanded. |
| */ |
| |
| static int dev_menu_ctl_request(struct node *node, unsigned me, unsigned la, bool interactive) |
| { |
| struct cec_msg msg; |
| |
| cec_msg_init(&msg, me, la); |
| cec_msg_menu_request(&msg, true, CEC_OP_MENU_REQUEST_QUERY); |
| fail_on_test(!transmit_timeout(node, &msg)); |
| if (unrecognized_op(&msg)) |
| return OK_NOT_SUPPORTED; |
| if (refused(&msg)) |
| return OK_REFUSED; |
| if (cec_msg_status_is_abort(&msg)) |
| return OK_PRESUMED; |
| if (node->remote[la].cec_version >= CEC_OP_CEC_VERSION_2_0) |
| warn("The Device Menu Control feature is deprecated in CEC 2.0\n"); |
| |
| return 0; |
| } |
| |
| static const vec_remote_subtests dev_menu_ctl_subtests{ |
| { "Menu Request", static_cast<__u16>(~CEC_LOG_ADDR_MASK_TV), dev_menu_ctl_request }, |
| { "User Control Pressed", CEC_LOG_ADDR_MASK_ALL, rc_passthrough_user_ctrl_pressed }, |
| { "User Control Released", CEC_LOG_ADDR_MASK_ALL, rc_passthrough_user_ctrl_released }, |
| }; |
| |
| /* Deck Control */ |
| |
| static int deck_ctl_give_status(struct node *node, unsigned me, unsigned la, bool interactive) |
| { |
| struct cec_msg msg; |
| |
| cec_msg_init(&msg, me, la); |
| cec_msg_give_deck_status(&msg, true, CEC_OP_STATUS_REQ_ONCE); |
| fail_on_test(!transmit_timeout(node, &msg)); |
| fail_on_test(timed_out(&msg)); |
| |
| fail_on_test_v2(node->remote[la].cec_version, |
| node->remote[la].has_deck_ctl && cec_msg_status_is_abort(&msg)); |
| fail_on_test_v2(node->remote[la].cec_version, |
| !node->remote[la].has_deck_ctl && !unrecognized_op(&msg)); |
| if (unrecognized_op(&msg)) |
| return OK_NOT_SUPPORTED; |
| if (refused(&msg)) |
| return OK_REFUSED; |
| if (cec_msg_status_is_abort(&msg)) |
| return OK_PRESUMED; |
| |
| __u8 deck_info; |
| |
| cec_ops_deck_status(&msg, &deck_info); |
| fail_on_test(deck_info < CEC_OP_DECK_INFO_PLAY || deck_info > CEC_OP_DECK_INFO_OTHER); |
| |
| cec_msg_init(&msg, me, la); |
| cec_msg_give_deck_status(&msg, true, CEC_OP_STATUS_REQ_ON); |
| fail_on_test(!transmit_timeout(node, &msg)); |
| fail_on_test(timed_out(&msg)); |
| cec_ops_deck_status(&msg, &deck_info); |
| fail_on_test(deck_info < CEC_OP_DECK_INFO_PLAY || deck_info > CEC_OP_DECK_INFO_OTHER); |
| |
| cec_msg_init(&msg, me, la); |
| cec_msg_give_deck_status(&msg, true, CEC_OP_STATUS_REQ_OFF); |
| /* |
| * Reply would not normally be expected for CEC_OP_STATUS_REQ_OFF. |
| * If a reply is received, then the follower failed to turn off |
| * status reporting as required. |
| */ |
| msg.reply = CEC_MSG_DECK_STATUS; |
| fail_on_test(!transmit_timeout(node, &msg)); |
| fail_on_test(!timed_out(&msg)); |
| |
| return OK; |
| } |
| |
| static int deck_ctl_give_status_invalid(struct node *node, unsigned me, unsigned la, bool interactive) |
| { |
| struct cec_msg msg; |
| |
| cec_msg_init(&msg, me, la); |
| cec_msg_give_deck_status(&msg, true, 0); /* Invalid Operand */ |
| fail_on_test(!transmit_timeout(node, &msg)); |
| if (unrecognized_op(&msg)) |
| return OK_NOT_SUPPORTED; |
| fail_on_test(!cec_msg_status_is_abort(&msg)); |
| fail_on_test(abort_reason(&msg) != CEC_OP_ABORT_INVALID_OP); |
| |
| cec_msg_init(&msg, me, la); |
| cec_msg_give_deck_status(&msg, true, 4); /* Invalid Operand */ |
| fail_on_test(!transmit_timeout(node, &msg)); |
| fail_on_test(!cec_msg_status_is_abort(&msg)); |
| fail_on_test(abort_reason(&msg) != CEC_OP_ABORT_INVALID_OP); |
| |
| return OK; |
| } |
| |
| static int deck_ctl_deck_ctl(struct node *node, unsigned me, unsigned la, bool interactive) |
| { |
| struct cec_msg msg; |
| __u8 deck_status; |
| |
| cec_msg_init(&msg, me, la); |
| cec_msg_deck_control(&msg, CEC_OP_DECK_CTL_MODE_STOP); |
| fail_on_test(!transmit_timeout(node, &msg)); |
| fail_on_test_v2(node->remote[la].cec_version, |
| node->remote[la].has_deck_ctl && unrecognized_op(&msg)); |
| fail_on_test_v2(node->remote[la].cec_version, |
| !node->remote[la].has_deck_ctl && !unrecognized_op(&msg)); |
| if (unrecognized_op(&msg)) |
| return OK_NOT_SUPPORTED; |
| if (refused(&msg)) |
| return OK_REFUSED; |
| fail_on_test(deck_status_get(node, me, la, deck_status)); |
| if (cec_msg_status_is_abort(&msg)) { |
| if (!incorrect_mode(&msg)) |
| return FAIL; |
| if (deck_status == CEC_OP_DECK_INFO_NO_MEDIA) |
| info("Stop: no media.\n"); |
| else |
| warn("Deck has media but returned Feature Abort with Incorrect Mode."); |
| return OK; |
| } |
| fail_on_test(deck_status != CEC_OP_DECK_INFO_STOP && deck_status != CEC_OP_DECK_INFO_NO_MEDIA); |
| |
| cec_msg_init(&msg, me, la); |
| cec_msg_deck_control(&msg, CEC_OP_DECK_CTL_MODE_SKIP_FWD); |
| fail_on_test(!transmit_timeout(node, &msg)); |
| fail_on_test(deck_status_get(node, me, la, deck_status)); |
| /* |
| * If there is no media, Skip Forward should Feature Abort with Incorrect Mode |
| * even if Stop did not. If Skip Forward does not Feature Abort, the deck |
| * is assumed to have media. |
| */ |
| if (incorrect_mode(&msg)) { |
| fail_on_test(deck_status != CEC_OP_DECK_INFO_NO_MEDIA); |
| return OK; |
| } |
| fail_on_test(cec_msg_status_is_abort(&msg)); |
| /* Wait for Deck to finish Skip Forward. */ |
| for (int i = 0; deck_status == CEC_OP_DECK_INFO_SKIP_FWD && i < long_timeout; i++) { |
| sleep(1); |
| fail_on_test(deck_status_get(node, me, la, deck_status)); |
| } |
| fail_on_test(deck_status != CEC_OP_DECK_INFO_PLAY); |
| |
| cec_msg_init(&msg, me, la); |
| cec_msg_deck_control(&msg, CEC_OP_DECK_CTL_MODE_SKIP_REV); |
| fail_on_test(!transmit_timeout(node, &msg)); |
| fail_on_test(cec_msg_status_is_abort(&msg)); /* Assumes deck has media. */ |
| fail_on_test(deck_status_get(node, me, la, deck_status)); |
| /* Wait for Deck to finish Skip Reverse. */ |
| for (int i = 0; deck_status == CEC_OP_DECK_INFO_SKIP_REV && i < long_timeout; i++) { |
| sleep(1); |
| fail_on_test(deck_status_get(node, me, la, deck_status)); |
| } |
| fail_on_test(deck_status != CEC_OP_DECK_INFO_PLAY); |
| |
| cec_msg_init(&msg, me, la); |
| cec_msg_deck_control(&msg, CEC_OP_DECK_CTL_MODE_EJECT); |
| fail_on_test(!transmit_timeout(node, &msg)); |
| fail_on_test(cec_msg_status_is_abort(&msg)); |
| fail_on_test(deck_status_get(node, me, la, deck_status)); |
| fail_on_test(deck_status != CEC_OP_DECK_INFO_NO_MEDIA); |
| |
| return OK; |
| } |
| |
| static int deck_ctl_deck_ctl_invalid(struct node *node, unsigned me, unsigned la, bool interactive) |
| { |
| struct cec_msg msg; |
| |
| cec_msg_init(&msg, me, la); |
| cec_msg_deck_control(&msg, 0); /* Invalid Deck Control operand */ |
| fail_on_test(!transmit_timeout(node, &msg)); |
| if (unrecognized_op(&msg)) |
| return OK_NOT_SUPPORTED; |
| fail_on_test(!cec_msg_status_is_abort(&msg)); |
| fail_on_test(abort_reason(&msg) != CEC_OP_ABORT_INVALID_OP); |
| |
| cec_msg_init(&msg, me, la); |
| cec_msg_deck_control(&msg, 5); /* Invalid Deck Control operand */ |
| fail_on_test(!transmit_timeout(node, &msg)); |
| fail_on_test(!cec_msg_status_is_abort(&msg)); |
| fail_on_test(abort_reason(&msg) != CEC_OP_ABORT_INVALID_OP); |
| |
| return OK; |
| } |
| |
| static int deck_ctl_play(struct node *node, unsigned me, unsigned la, bool interactive) |
| { |
| struct cec_msg msg; |
| __u8 deck_status; |
| |
| cec_msg_init(&msg, me, la); |
| cec_msg_play(&msg, CEC_OP_PLAY_MODE_PLAY_FWD); |
| fail_on_test(!transmit_timeout(node, &msg)); |
| fail_on_test_v2(node->remote[la].cec_version, |
| node->remote[la].has_deck_ctl && unrecognized_op(&msg)); |
| fail_on_test_v2(node->remote[la].cec_version, |
| !node->remote[la].has_deck_ctl && !unrecognized_op(&msg)); |
| if (unrecognized_op(&msg)) |
| return OK_NOT_SUPPORTED; |
| if (refused(&msg)) |
| return OK_REFUSED; |
| fail_on_test(deck_status_get(node, me, la, deck_status)); |
| if (cec_msg_status_is_abort(&msg)) { |
| if (!incorrect_mode(&msg)) |
| return FAIL; |
| if (deck_status == CEC_OP_DECK_INFO_NO_MEDIA) |
| info("Play Still: no media.\n"); |
| else |
| warn("Deck has media but returned Feature Abort with Incorrect Mode."); |
| return OK; |
| } |
| fail_on_test(deck_status != CEC_OP_DECK_INFO_PLAY); |
| |
| fail_on_test(test_play_mode(node, me, la, CEC_OP_PLAY_MODE_PLAY_STILL, CEC_OP_DECK_INFO_STILL)); |
| fail_on_test(test_play_mode(node, me, la, CEC_OP_PLAY_MODE_PLAY_REV, CEC_OP_DECK_INFO_PLAY_REV)); |
| fail_on_test(test_play_mode(node, me, la, CEC_OP_PLAY_MODE_PLAY_FAST_FWD_MIN, CEC_OP_DECK_INFO_FAST_FWD)); |
| fail_on_test(test_play_mode(node, me, la, CEC_OP_PLAY_MODE_PLAY_FAST_REV_MIN, CEC_OP_DECK_INFO_FAST_REV)); |
| fail_on_test(test_play_mode(node, me, la, CEC_OP_PLAY_MODE_PLAY_FAST_FWD_MED, CEC_OP_DECK_INFO_FAST_FWD)); |
| fail_on_test(test_play_mode(node, me, la, CEC_OP_PLAY_MODE_PLAY_FAST_REV_MED, CEC_OP_DECK_INFO_FAST_REV)); |
| fail_on_test(test_play_mode(node, me, la, CEC_OP_PLAY_MODE_PLAY_FAST_FWD_MAX, CEC_OP_DECK_INFO_FAST_FWD)); |
| fail_on_test(test_play_mode(node, me, la, CEC_OP_PLAY_MODE_PLAY_FAST_REV_MAX, CEC_OP_DECK_INFO_FAST_REV)); |
| fail_on_test(test_play_mode(node, me, la, CEC_OP_PLAY_MODE_PLAY_SLOW_FWD_MIN, CEC_OP_DECK_INFO_SLOW)); |
| fail_on_test(test_play_mode(node, me, la, CEC_OP_PLAY_MODE_PLAY_SLOW_REV_MIN, CEC_OP_DECK_INFO_SLOW_REV)); |
| fail_on_test(test_play_mode(node, me, la, CEC_OP_PLAY_MODE_PLAY_SLOW_FWD_MED, CEC_OP_DECK_INFO_SLOW)); |
| fail_on_test(test_play_mode(node, me, la, CEC_OP_PLAY_MODE_PLAY_SLOW_REV_MED, CEC_OP_DECK_INFO_SLOW_REV)); |
| fail_on_test(test_play_mode(node, me, la, CEC_OP_PLAY_MODE_PLAY_SLOW_FWD_MAX, CEC_OP_DECK_INFO_SLOW)); |
| fail_on_test(test_play_mode(node, me, la, CEC_OP_PLAY_MODE_PLAY_SLOW_REV_MAX, CEC_OP_DECK_INFO_SLOW_REV)); |
| |
| cec_msg_init(&msg, me, la); |
| cec_msg_deck_control(&msg, CEC_OP_DECK_CTL_MODE_STOP); |
| fail_on_test(!transmit_timeout(node, &msg)); |
| |
| return OK; |
| } |
| |
| static int deck_ctl_play_invalid(struct node *node, unsigned me, unsigned la, bool interactive) |
| { |
| struct cec_msg msg; |
| |
| cec_msg_init(&msg, me, la); |
| cec_msg_play(&msg, 0); /* Invalid Operand */ |
| fail_on_test(!transmit_timeout(node, &msg)); |
| if (unrecognized_op(&msg)) |
| return OK_NOT_SUPPORTED; |
| fail_on_test(!cec_msg_status_is_abort(&msg)); |
| fail_on_test(abort_reason(&msg) != CEC_OP_ABORT_INVALID_OP); |
| |
| cec_msg_init(&msg, me, la); |
| cec_msg_play(&msg, 4); /* Invalid Operand */ |
| fail_on_test(!transmit_timeout(node, &msg)); |
| fail_on_test(!cec_msg_status_is_abort(&msg)); |
| fail_on_test(abort_reason(&msg) != CEC_OP_ABORT_INVALID_OP); |
| |
| cec_msg_init(&msg, me, la); |
| cec_msg_play(&msg, 0x26); /* Invalid Operand */ |
| fail_on_test(!transmit_timeout(node, &msg)); |
| fail_on_test(!cec_msg_status_is_abort(&msg)); |
| fail_on_test(abort_reason(&msg) != CEC_OP_ABORT_INVALID_OP); |
| |
| return OK; |
| } |
| |
| static const vec_remote_subtests deck_ctl_subtests{ |
| { |
| "Give Deck Status", |
| CEC_LOG_ADDR_MASK_PLAYBACK | CEC_LOG_ADDR_MASK_RECORD, |
| deck_ctl_give_status, |
| }, |
| { |
| "Give Deck Status Invalid Operand", |
| CEC_LOG_ADDR_MASK_PLAYBACK | CEC_LOG_ADDR_MASK_RECORD, |
| deck_ctl_give_status_invalid, |
| }, |
| { |
| "Deck Control", |
| CEC_LOG_ADDR_MASK_PLAYBACK | CEC_LOG_ADDR_MASK_RECORD, |
| deck_ctl_deck_ctl, |
| }, |
| { |
| "Deck Control Invalid Operand", |
| CEC_LOG_ADDR_MASK_PLAYBACK | CEC_LOG_ADDR_MASK_RECORD, |
| deck_ctl_deck_ctl_invalid, |
| }, |
| { |
| "Play", |
| CEC_LOG_ADDR_MASK_PLAYBACK | CEC_LOG_ADDR_MASK_RECORD, |
| deck_ctl_play, |
| }, |
| { |
| "Play Invalid Operand", |
| CEC_LOG_ADDR_MASK_PLAYBACK | CEC_LOG_ADDR_MASK_RECORD, |
| deck_ctl_play_invalid, |
| }, |
| }; |
| |
| static const char *hec_func_state2s(__u8 hfs) |
| { |
| switch (hfs) { |
| case CEC_OP_HEC_FUNC_STATE_NOT_SUPPORTED: |
| return "HEC Not Supported"; |
| case CEC_OP_HEC_FUNC_STATE_INACTIVE: |
| return "HEC Inactive"; |
| case CEC_OP_HEC_FUNC_STATE_ACTIVE: |
| return "HEC Active"; |
| case CEC_OP_HEC_FUNC_STATE_ACTIVATION_FIELD: |
| return "HEC Activation Field"; |
| default: |
| return "Unknown"; |
| } |
| } |
| |
| static const char *host_func_state2s(__u8 hfs) |
| { |
| switch (hfs) { |
| case CEC_OP_HOST_FUNC_STATE_NOT_SUPPORTED: |
| return "Host Not Supported"; |
| case CEC_OP_HOST_FUNC_STATE_INACTIVE: |
| return "Host Inactive"; |
| case CEC_OP_HOST_FUNC_STATE_ACTIVE: |
| return "Host Active"; |
| default: |
| return "Unknown"; |
| } |
| } |
| |
| static const char *enc_func_state2s(__u8 efs) |
| { |
| switch (efs) { |
| case CEC_OP_ENC_FUNC_STATE_EXT_CON_NOT_SUPPORTED: |
| return "Ext Con Not Supported"; |
| case CEC_OP_ENC_FUNC_STATE_EXT_CON_INACTIVE: |
| return "Ext Con Inactive"; |
| case CEC_OP_ENC_FUNC_STATE_EXT_CON_ACTIVE: |
| return "Ext Con Active"; |
| default: |
| return "Unknown"; |
| } |
| } |
| |
| static const char *cdc_errcode2s(__u8 cdc_errcode) |
| { |
| switch (cdc_errcode) { |
| case CEC_OP_CDC_ERROR_CODE_NONE: |
| return "No error"; |
| case CEC_OP_CDC_ERROR_CODE_CAP_UNSUPPORTED: |
| return "Initiator does not have requested capability"; |
| case CEC_OP_CDC_ERROR_CODE_WRONG_STATE: |
| return "Initiator is in wrong state"; |
| case CEC_OP_CDC_ERROR_CODE_OTHER: |
| return "Other error"; |
| default: |
| return "Unknown"; |
| } |
| } |
| |
| static int cdc_hec_discover(struct node *node, unsigned me, unsigned la, bool print) |
| { |
| /* TODO: For future use cases, it might be necessary to store the results |
| from the HEC discovery to know which HECs are possible to form, etc. */ |
| struct cec_msg msg; |
| __u32 mode = CEC_MODE_INITIATOR | CEC_MODE_FOLLOWER; |
| bool has_cdc = false; |
| |
| doioctl(node, CEC_S_MODE, &mode); |
| cec_msg_init(&msg, me, la); |
| cec_msg_cdc_hec_discover(&msg); |
| fail_on_test(!transmit(node, &msg)); |
| |
| /* The spec describes that we shall wait for messages |
| up to 1 second, and extend the deadline for every received |
| message. The maximum time to wait for incoming state reports |
| is 5 seconds. */ |
| unsigned ts_start = get_ts_ms(); |
| while (get_ts_ms() - ts_start < 5000) { |
| __u8 from; |
| |
| memset(&msg, 0, sizeof(msg)); |
| msg.timeout = 1000; |
| if (doioctl(node, CEC_RECEIVE, &msg)) |
| break; |
| from = cec_msg_initiator(&msg); |
| if (msg.msg[1] == CEC_MSG_FEATURE_ABORT) { |
| if (from == la) |
| return fail("Device replied Feature Abort to broadcast message\n"); |
| |
| warn("Device %d replied Feature Abort to broadcast message\n", cec_msg_initiator(&msg)); |
| } |
| if (msg.msg[1] != CEC_MSG_CDC_MESSAGE) |
| continue; |
| if (msg.msg[4] != CEC_MSG_CDC_HEC_REPORT_STATE) |
| continue; |
| |
| __u16 phys_addr, target_phys_addr, hec_field; |
| __u8 hec_func_state, host_func_state, enc_func_state, cdc_errcode, has_field; |
| |
| cec_ops_cdc_hec_report_state(&msg, &phys_addr, &target_phys_addr, |
| &hec_func_state, &host_func_state, |
| &enc_func_state, &cdc_errcode, |
| &has_field, &hec_field); |
| |
| if (target_phys_addr != node->phys_addr) |
| continue; |
| if (phys_addr == node->remote[la].phys_addr) |
| has_cdc = true; |
| if (!print) |
| continue; |
| |
| from = cec_msg_initiator(&msg); |
| info("Received CDC HEC State report from device %d (%s):\n", from, cec_la2s(from)); |
| info("Physical address : %x.%x.%x.%x\n", |
| cec_phys_addr_exp(phys_addr)); |
| info("Target physical address : %x.%x.%x.%x\n", |
| cec_phys_addr_exp(target_phys_addr)); |
| info("HEC Functionality State : %s\n", hec_func_state2s(hec_func_state)); |
| info("Host Functionality State : %s\n", host_func_state2s(host_func_state)); |
| info("ENC Functionality State : %s\n", enc_func_state2s(enc_func_state)); |
| info("CDC Error Code : %s\n", cdc_errcode2s(cdc_errcode)); |
| |
| if (has_field) { |
| std::ostringstream oss; |
| |
| /* Bit 14 indicates whether or not the device's HDMI |
| output has HEC support/is active. */ |
| if (!hec_field) |
| oss << "None"; |
| else { |
| if (hec_field & (1 << 14)) |
| oss << "out, "; |
| for (int i = 13; i >= 0; i--) { |
| if (hec_field & (1 << i)) |
| oss << "in" << (14 - i) << ", "; |
| } |
| oss << "\b\b "; |
| } |
| info("HEC Support Field : %s\n", oss.str().c_str()); |
| } |
| } |
| |
| mode = CEC_MODE_INITIATOR; |
| doioctl(node, CEC_S_MODE, &mode); |
| |
| if (has_cdc) |
| return 0; |
| return OK_NOT_SUPPORTED; |
| } |
| |
| static const vec_remote_subtests cdc_subtests{ |
| { "CDC_HEC_Discover", CEC_LOG_ADDR_MASK_ALL, cdc_hec_discover }, |
| }; |
| |
| /* Post-test checks */ |
| |
| static int post_test_check_recognized(struct node *node, unsigned me, unsigned la, bool interactive) |
| { |
| bool fail = false; |
| |
| for (unsigned i = 0; i < 256; i++) { |
| if (node->remote[la].recognized_op[i] && node->remote[la].unrecognized_op[i]) { |
| struct cec_msg msg = {}; |
| msg.msg[1] = i; |
| fail("Opcode %s has been both recognized by and has been replied\n", opcode2s(&msg).c_str()); |
| fail("Feature Abort [Unrecognized Opcode] to by the device.\n"); |
| fail = true; |
| } |
| } |
| fail_on_test(fail); |
| |
| return 0; |
| } |
| |
| static const vec_remote_subtests post_test_subtests{ |
| { "Recognized/unrecognized message consistency", CEC_LOG_ADDR_MASK_ALL, post_test_check_recognized }, |
| }; |
| |
| static const remote_test tests[] = { |
| { "Core", TAG_CORE, core_subtests }, |
| { "Give Device Power Status feature", TAG_POWER_STATUS, power_status_subtests }, |
| { "System Information feature", TAG_SYSTEM_INFORMATION, system_info_subtests }, |
| { "Vendor Specific Commands feature", TAG_VENDOR_SPECIFIC_COMMANDS, vendor_specific_subtests }, |
| { "Device OSD Transfer feature", TAG_DEVICE_OSD_TRANSFER, device_osd_transfer_subtests }, |
| { "OSD String feature", TAG_OSD_DISPLAY, osd_string_subtests }, |
| { "Remote Control Passthrough feature", TAG_REMOTE_CONTROL_PASSTHROUGH, rc_passthrough_subtests }, |
| { "Device Menu Control feature", TAG_DEVICE_MENU_CONTROL, dev_menu_ctl_subtests }, |
| { "Deck Control feature", TAG_DECK_CONTROL, deck_ctl_subtests }, |
| { "Tuner Control feature", TAG_TUNER_CONTROL, tuner_ctl_subtests }, |
| { "One Touch Record feature", TAG_ONE_TOUCH_RECORD, one_touch_rec_subtests }, |
| { "Timer Programming feature", TAG_TIMER_PROGRAMMING, timer_prog_subtests }, |
| { "Capability Discovery and Control feature", TAG_CAP_DISCOVERY_CONTROL, cdc_subtests }, |
| { "Dynamic Auto Lipsync feature", TAG_DYNAMIC_AUTO_LIPSYNC, dal_subtests }, |
| { "Audio Return Channel feature", TAG_ARC_CONTROL, arc_subtests }, |
| { "System Audio Control feature", TAG_SYSTEM_AUDIO_CONTROL, sac_subtests }, |
| { "Audio Rate Control feature", TAG_AUDIO_RATE_CONTROL, audio_rate_ctl_subtests }, |
| { "Routing Control feature", TAG_ROUTING_CONTROL, routing_control_subtests }, |
| { "Standby/Resume and Power Status", TAG_POWER_STATUS | TAG_STANDBY_RESUME, standby_resume_subtests }, |
| { "Post-test checks", TAG_CORE, post_test_subtests }, |
| }; |
| |
| static std::map<std::string, int> mapTests; |
| static std::map<std::string, bool> mapTestsNoWarnings; |
| |
| void collectTests() |
| { |
| std::map<std::string, __u64> mapTestFuncs; |
| |
| for (const auto &test : tests) { |
| for (const auto &subtest : test.subtests) { |
| std::string name = safename(subtest.name); |
| auto func = (__u64)subtest.test_fn; |
| |
| if (mapTestFuncs.find(name) != mapTestFuncs.end() && |
| mapTestFuncs[name] != func) { |
| fprintf(stderr, "Duplicate subtest name, but different tests: %s\n", subtest.name); |
| std::exit(EXIT_FAILURE); |
| } |
| mapTestFuncs[name] = func; |
| mapTests[name] = DONT_CARE; |
| mapTestsNoWarnings[name] = false; |
| } |
| } |
| } |
| |
| void listTests() |
| { |
| for (const auto &test : tests) { |
| printf("%s:\n", test.name); |
| for (const auto &subtest : test.subtests) { |
| printf("\t%s\n", safename(subtest.name).c_str()); |
| } |
| } |
| } |
| |
| int setExpectedResult(char *optarg, bool no_warnings) |
| { |
| char *equal = std::strchr(optarg, '='); |
| |
| if (!equal || equal == optarg || !isdigit(equal[1])) |
| return 1; |
| *equal = 0; |
| std::string name = safename(optarg); |
| if (mapTests.find(name) == mapTests.end()) |
| return 1; |
| mapTests[name] = strtoul(equal + 1, nullptr, 0); |
| mapTestsNoWarnings[name] = no_warnings; |
| return 0; |
| } |
| |
| void testRemote(struct node *node, unsigned me, unsigned la, unsigned test_tags, |
| bool interactive) |
| { |
| printf("testing CEC local LA %d (%s) to remote LA %d (%s):\n", |
| me, cec_la2s(me), la, cec_la2s(la)); |
| |
| if (!util_interactive_ensure_power_state(node, me, la, interactive, CEC_OP_POWER_STATUS_ON)) |
| return; |
| if (!node->remote[la].has_power_status) { |
| announce("The device didn't support Give Device Power Status."); |
| announce("Assuming that the device is powered on."); |
| } |
| |
| /* Ensure that the remote device knows the initiator's primary device type.*/ |
| struct cec_msg msg; |
| |
| cec_msg_init(&msg, me, la); |
| cec_msg_report_physical_addr(&msg, node->phys_addr, node->prim_devtype); |
| transmit_timeout(node, &msg); |
| |
| int ret = 0; |
| |
| for (const auto &test : tests) { |
| if ((test.tags & test_tags) != test.tags) |
| continue; |
| |
| printf("\t%s:\n", test.name); |
| for (const auto &subtest : test.subtests) { |
| const char *name = subtest.name; |
| |
| if (subtest.for_cec20 && |
| (node->remote[la].cec_version < CEC_OP_CEC_VERSION_2_0 || !node->has_cec20)) |
| continue; |
| |
| if (subtest.in_standby) { |
| struct cec_log_addrs laddrs = { }; |
| doioctl(node, CEC_ADAP_G_LOG_ADDRS, &laddrs); |
| |
| if (!laddrs.log_addr_mask) |
| continue; |
| } |
| node->in_standby = subtest.in_standby; |
| mode_set_initiator(node); |
| unsigned old_warnings = warnings; |
| ret = subtest.test_fn(node, me, la, interactive); |
| bool has_warnings = old_warnings < warnings; |
| if (!(subtest.la_mask & (1 << la)) && !ret) |
| ret = OK_UNEXPECTED; |
| |
| if (mapTests[safename(name)] != DONT_CARE) { |
| if (ret != mapTests[safename(name)]) |
| printf("\t %s: %s (Expected '%s', got '%s')\n", |
| name, ok(FAIL), |
| result_name(mapTests[safename(name)], false), |
| result_name(ret, false)); |
| else if (has_warnings && mapTestsNoWarnings[safename(name)]) |
| printf("\t %s: %s (Expected no warnings, but got %d)\n", |
| name, ok(FAIL), warnings - old_warnings); |
| else if (ret == FAIL) |
| printf("\t %s: %s\n", name, ok(OK_EXPECTED_FAIL)); |
| else |
| printf("\t %s: %s\n", name, ok(ret)); |
| } else if (ret != NOTAPPLICABLE) |
| printf("\t %s: %s\n", name, ok(ret)); |
| if (ret == FAIL_CRITICAL) |
| return; |
| } |
| printf("\n"); |
| } |
| } |