| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Copyright 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. |
| */ |
| |
| #include <cerrno> |
| #include <ctime> |
| #include <string> |
| #include <vector> |
| |
| #include <sys/ioctl.h> |
| #include <unistd.h> |
| |
| #include "cec-compliance.h" |
| |
| |
| static bool get_power_status(struct node *node, unsigned me, unsigned la, __u8 &power_status) |
| { |
| struct cec_msg msg; |
| |
| cec_msg_init(&msg, me, la); |
| cec_msg_give_device_power_status(&msg, true); |
| msg.timeout = 2000; |
| int res = doioctl(node, CEC_TRANSMIT, &msg); |
| if (res == ENONET) { |
| power_status = CEC_OP_POWER_STATUS_STANDBY; |
| return true; |
| } |
| if (res || !(msg.tx_status & CEC_TX_STATUS_OK) || timed_out_or_abort(&msg)) |
| return false; |
| cec_ops_report_power_status(&msg, &power_status); |
| return true; |
| } |
| |
| bool util_interactive_ensure_power_state(struct node *node, unsigned me, unsigned la, bool interactive, |
| __u8 target_pwr) |
| { |
| interactive_info(true, "Please ensure that the device is in state %s.", |
| power_status2s(target_pwr)); |
| |
| if (!node->remote[la].has_power_status) |
| return true; |
| |
| while (interactive) { |
| __u8 pwr; |
| |
| if (!get_power_status(node, me, la, pwr)) |
| announce("Failed to retrieve power status."); |
| else if (pwr == target_pwr) |
| return true; |
| else |
| announce("The device reported power status %s.", power_status2s(pwr)); |
| if (!question("Retry?")) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| |
| /* Give Device Power Status */ |
| |
| static int power_status_give(struct node *node, unsigned me, unsigned la, bool interactive) |
| { |
| struct cec_msg msg; |
| |
| cec_msg_init(&msg, me, la); |
| cec_msg_give_device_power_status(&msg, true); |
| fail_on_test(!transmit_timeout(node, &msg)); |
| fail_on_test(timed_out(&msg)); |
| fail_on_test(unrecognized_op(&msg)); |
| if (refused(&msg)) |
| return OK_REFUSED; |
| if (cec_msg_status_is_abort(&msg)) |
| return OK_PRESUMED; |
| |
| __u8 power_status; |
| cec_ops_report_power_status(&msg, &power_status); |
| fail_on_test(power_status >= 4); |
| |
| return 0; |
| } |
| |
| static int power_status_report(struct node *node, unsigned me, unsigned la, bool interactive) |
| { |
| struct cec_msg msg; |
| |
| cec_msg_init(&msg, me, la); |
| cec_msg_report_power_status(&msg, CEC_OP_POWER_STATUS_ON); |
| fail_on_test(!transmit_timeout(node, &msg)); |
| if (unrecognized_op(&msg)) |
| return OK_NOT_SUPPORTED; |
| if (refused(&msg)) |
| return OK_REFUSED; |
| |
| return OK_PRESUMED; |
| } |
| |
| const vec_remote_subtests power_status_subtests{ |
| { "Give Device Power Status", CEC_LOG_ADDR_MASK_ALL, power_status_give }, |
| { "Report Device Power Status", CEC_LOG_ADDR_MASK_ALL, power_status_report }, |
| }; |
| |
| /* One Touch Play */ |
| |
| static int one_touch_play_view_on(struct node *node, unsigned me, unsigned la, bool interactive, |
| __u8 opcode) |
| { |
| struct cec_msg msg; |
| |
| cec_msg_init(&msg, me, la); |
| if (opcode == CEC_MSG_IMAGE_VIEW_ON) |
| cec_msg_image_view_on(&msg); |
| else if (opcode == CEC_MSG_TEXT_VIEW_ON) |
| cec_msg_text_view_on(&msg); |
| |
| int res = doioctl(node, CEC_TRANSMIT, &msg); |
| |
| if (res == ENONET && la == CEC_LOG_ADDR_TV) { |
| msg.msg[0] = (CEC_LOG_ADDR_UNREGISTERED << 4) | la; |
| res = doioctl(node, CEC_TRANSMIT, &msg); |
| } |
| fail_on_test(res || !(msg.tx_status & CEC_TX_STATUS_OK)); |
| |
| fail_on_test(is_tv(la, node->remote[la].prim_type) && unrecognized_op(&msg)); |
| if (refused(&msg)) |
| return OK_REFUSED; |
| if (cec_msg_status_is_abort(&msg)) |
| return OK_PRESUMED; |
| if (opcode == CEC_MSG_IMAGE_VIEW_ON) |
| node->remote[la].has_image_view_on = true; |
| else if (opcode == CEC_MSG_TEXT_VIEW_ON) |
| node->remote[la].has_text_view_on = true; |
| |
| return 0; |
| } |
| |
| static int one_touch_play_image_view_on(struct node *node, unsigned me, unsigned la, bool interactive) |
| { |
| return one_touch_play_view_on(node, me, la, interactive, CEC_MSG_IMAGE_VIEW_ON); |
| } |
| |
| static int one_touch_play_text_view_on(struct node *node, unsigned me, unsigned la, bool interactive) |
| { |
| return one_touch_play_view_on(node, me, la, interactive, CEC_MSG_TEXT_VIEW_ON); |
| } |
| |
| static int one_touch_play_view_on_wakeup(struct node *node, unsigned me, unsigned la, bool interactive, |
| __u8 opcode) |
| { |
| fail_on_test(!util_interactive_ensure_power_state(node, me, la, interactive, CEC_OP_POWER_STATUS_STANDBY)); |
| |
| int ret = one_touch_play_view_on(node, me, la, interactive, opcode); |
| |
| if (ret && ret != OK_PRESUMED) |
| return ret; |
| fail_on_test(interactive && !question("Did the TV turn on?")); |
| |
| if (interactive) |
| return 0; |
| |
| return OK_PRESUMED; |
| } |
| |
| static int one_touch_play_image_view_on_wakeup(struct node *node, unsigned me, unsigned la, bool interactive) |
| { |
| if (!interactive || !node->remote[la].has_image_view_on) |
| return NOTAPPLICABLE; |
| return one_touch_play_view_on_wakeup(node, me, la, interactive, CEC_MSG_IMAGE_VIEW_ON); |
| } |
| |
| static int one_touch_play_text_view_on_wakeup(struct node *node, unsigned me, unsigned la, bool interactive) |
| { |
| if (!interactive || !node->remote[la].has_text_view_on) |
| return NOTAPPLICABLE; |
| return one_touch_play_view_on_wakeup(node, me, la, interactive, CEC_MSG_TEXT_VIEW_ON); |
| } |
| |
| static int one_touch_play_view_on_change(struct node *node, unsigned me, unsigned la, bool interactive, |
| __u8 opcode) |
| { |
| struct cec_msg msg; |
| int ret; |
| |
| fail_on_test(!util_interactive_ensure_power_state(node, me, la, interactive, CEC_OP_POWER_STATUS_ON)); |
| |
| interactive_info(true, "Please switch the TV to another source."); |
| ret = one_touch_play_view_on(node, me, la, interactive, opcode); |
| if (ret && ret != OK_PRESUMED) |
| return ret; |
| 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 one_touch_play_image_view_on_change(struct node *node, unsigned me, unsigned la, bool interactive) |
| { |
| if (!interactive || !node->remote[la].has_text_view_on) |
| return NOTAPPLICABLE; |
| return one_touch_play_view_on_change(node, me, la, interactive, CEC_MSG_IMAGE_VIEW_ON); |
| } |
| |
| static int one_touch_play_text_view_on_change(struct node *node, unsigned me, unsigned la, bool interactive) |
| { |
| if (!interactive || !node->remote[la].has_text_view_on) |
| return NOTAPPLICABLE; |
| return one_touch_play_view_on_change(node, me, la, interactive, CEC_MSG_TEXT_VIEW_ON); |
| } |
| |
| static int one_touch_play_req_active_source(struct node *node, unsigned me, unsigned la, bool interactive) |
| { |
| struct cec_msg msg; |
| |
| cec_msg_init(&msg, me, la); |
| cec_msg_active_source(&msg, node->phys_addr); |
| fail_on_test(!transmit_timeout(node, &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; |
| } |
| |
| const vec_remote_subtests one_touch_play_subtests{ |
| { "Image View On", CEC_LOG_ADDR_MASK_TV, one_touch_play_image_view_on }, |
| { "Text View On", CEC_LOG_ADDR_MASK_TV, one_touch_play_text_view_on }, |
| { "Wakeup on Image View On", CEC_LOG_ADDR_MASK_TV, one_touch_play_image_view_on_wakeup }, |
| { "Wakeup Text View On", CEC_LOG_ADDR_MASK_TV, one_touch_play_text_view_on_wakeup }, |
| { "Input change on Image View On", CEC_LOG_ADDR_MASK_TV, one_touch_play_image_view_on_change }, |
| { "Input change on Text View On", CEC_LOG_ADDR_MASK_TV, one_touch_play_text_view_on_change }, |
| { "Active Source and Request Active Source", CEC_LOG_ADDR_MASK_ALL, one_touch_play_req_active_source }, |
| }; |
| |
| /* Standby / Resume */ |
| |
| /* The default sleep time between power status requests. */ |
| #define SLEEP_POLL_POWER_STATUS 2 |
| |
| static bool wait_changing_power_status(struct node *node, unsigned me, unsigned la, __u8 &new_status, |
| unsigned &unresponsive_cnt) |
| { |
| __u8 old_status; |
| time_t t = time(nullptr); |
| |
| announce("Checking for power status change. This may take up to %llu s.", (long long)long_timeout); |
| if (!get_power_status(node, me, la, old_status)) |
| return false; |
| while (time(nullptr) - t < long_timeout) { |
| __u8 power_status; |
| |
| if (!get_power_status(node, me, la, power_status)) { |
| /* Some TVs become completely unresponsive when transitioning |
| between power modes. Register that this happens, but continue |
| the test. */ |
| unresponsive_cnt++; |
| } else if (old_status != power_status) { |
| new_status = power_status; |
| return true; |
| } |
| sleep(SLEEP_POLL_POWER_STATUS); |
| } |
| new_status = old_status; |
| return false; |
| } |
| |
| static bool poll_stable_power_status(struct node *node, unsigned me, unsigned la, |
| __u8 expected_status, unsigned &unresponsive_cnt) |
| { |
| bool transient = false; |
| unsigned time_to_transient = 0; |
| time_t t = time(nullptr); |
| |
| /* Some devices can use several seconds to transition from one power |
| state to another, so the power state must be repeatedly polled */ |
| announce("Waiting for new stable power status. This may take up to %llu s.", (long long)long_timeout); |
| while (time(nullptr) - t < long_timeout) { |
| __u8 power_status; |
| |
| if (!get_power_status(node, me, la, power_status)) { |
| /* Some TVs become completely unresponsive when transitioning |
| between power modes. Register that this happens, but continue |
| the test. */ |
| unresponsive_cnt++; |
| sleep(SLEEP_POLL_POWER_STATUS); |
| continue; |
| } |
| if (!transient && (power_status == CEC_OP_POWER_STATUS_TO_ON || |
| power_status == CEC_OP_POWER_STATUS_TO_STANDBY)) { |
| time_to_transient = time(nullptr) - t; |
| transient = true; |
| warn_once_on_test(expected_status == CEC_OP_POWER_STATUS_ON && |
| power_status == CEC_OP_POWER_STATUS_TO_STANDBY); |
| warn_once_on_test(expected_status == CEC_OP_POWER_STATUS_STANDBY && |
| power_status == CEC_OP_POWER_STATUS_TO_ON); |
| } |
| if (power_status == expected_status) { |
| if (transient) |
| announce("Transient state after %d s, stable state %s after %d s", |
| time_to_transient, power_status2s(power_status), (int)(time(NULL) - t)); |
| else |
| announce("No transient state reported, stable state %s after %d s", |
| power_status2s(power_status), (int)(time(NULL) - t)); |
| return true; |
| } |
| sleep(SLEEP_POLL_POWER_STATUS); |
| } |
| return false; |
| } |
| |
| static int standby_resume_standby(struct node *node, unsigned me, unsigned la, bool interactive) |
| { |
| if (!node->remote[la].has_power_status) |
| return NOTAPPLICABLE; |
| |
| struct cec_msg msg; |
| unsigned unresponsive_cnt = 0; |
| |
| fail_on_test(!util_interactive_ensure_power_state(node, me, la, interactive, CEC_OP_POWER_STATUS_ON)); |
| |
| /* |
| * Some displays only accept Standby from the Active Source. |
| * So make us the Active Source before sending Standby. |
| */ |
| if (is_tv(la, node->remote[la].prim_type)) { |
| announce("Sending Active Source message."); |
| cec_msg_init(&msg, me, la); |
| cec_msg_active_source(&msg, node->phys_addr); |
| fail_on_test(doioctl(node, CEC_TRANSMIT, &msg)); |
| } |
| announce("Sending Standby message."); |
| |
| cec_msg_init(&msg, me, la); |
| cec_msg_standby(&msg); |
| fail_on_test(!transmit_timeout(node, &msg)); |
| fail_on_test(cec_msg_status_is_abort(&msg)); |
| fail_on_test(!poll_stable_power_status(node, me, la, CEC_OP_POWER_STATUS_STANDBY, unresponsive_cnt)); |
| fail_on_test(interactive && !question("Is the device in standby?")); |
| node->remote[la].in_standby = true; |
| |
| if (unresponsive_cnt > 0) |
| warn("The device went correctly into standby, but was unresponsive %d times during the transition.\n", |
| unresponsive_cnt); |
| |
| return 0; |
| } |
| |
| static int standby_resume_standby_toggle(struct node *node, unsigned me, unsigned la, bool interactive) |
| { |
| if (!node->remote[la].in_standby) |
| return NOTAPPLICABLE; |
| |
| struct cec_msg msg; |
| unsigned unresponsive_cnt = 0; |
| __u8 new_status; |
| |
| node->remote[la].in_standby = false; |
| |
| /* Send Standby again to test that it is not acting like a toggle */ |
| announce("Sending Standby message."); |
| cec_msg_init(&msg, me, la); |
| cec_msg_standby(&msg); |
| int res = doioctl(node, CEC_TRANSMIT, &msg); |
| fail_on_test(res && res != ENONET); |
| fail_on_test(cec_msg_status_is_abort(&msg)); |
| fail_on_test(wait_changing_power_status(node, me, la, new_status, unresponsive_cnt)); |
| fail_on_test(new_status != CEC_OP_POWER_STATUS_STANDBY); |
| |
| if (res == ENONET) { |
| struct cec_caps caps = { }; |
| doioctl(node, CEC_ADAP_G_CAPS, &caps); |
| unsigned major = caps.version >> 16; |
| unsigned minor = (caps.version >> 8) & 0xff; |
| if (!strcmp(caps.driver, "pulse8-cec") && |
| !((major == 4 && minor == 19) || major > 5 || |
| (major == 5 && minor >= 4))) { |
| // The cec framework had a bug that prevented it from reliably |
| // working with displays that pull down the HPD. This was fixed |
| // in commit ac479b51f3f4 for kernel 5.5 and backported to kernels |
| // 4.19.94 and 5.4.9. We only warn when the pulse8-cec driver is used, |
| // for other CEC devices you hopefully know what you are doing... |
| warn("This display appears to pull down the HPD when in Standby. For such\n"); |
| warn("displays kernel 4.19 or kernel 5.4 or higher is required.\n"); |
| } |
| } |
| |
| fail_on_test(interactive && !question("Is the device still in standby?")); |
| node->remote[la].in_standby = true; |
| if (unresponsive_cnt > 0) |
| warn("The device went correctly into standby, but was unresponsive %d times during the transition.\n", |
| unresponsive_cnt); |
| |
| return 0; |
| } |
| |
| static int standby_resume_active_source_nowake(struct node *node, unsigned me, unsigned la, bool interactive) |
| { |
| if (!node->remote[la].in_standby) |
| return NOTAPPLICABLE; |
| |
| struct cec_msg msg; |
| unsigned unresponsive_cnt = 0; |
| __u8 new_status; |
| |
| node->remote[la].in_standby = false; |
| |
| /* |
| * In CEC 2.0 it is specified that a device shall not go out of standby |
| * if an Active Source message is received. The CEC 1.4 implies this as |
| * well, even though it is not as clear about this as the 2.0 spec. |
| */ |
| announce("Sending Active Source message."); |
| cec_msg_init(&msg, me, la); |
| cec_msg_active_source(&msg, node->phys_addr); |
| int res = doioctl(node, CEC_TRANSMIT, &msg); |
| fail_on_test(res && res != ENONET); |
| fail_on_test_v2_warn(node->remote[la].cec_version, |
| wait_changing_power_status(node, me, la, new_status, unresponsive_cnt)); |
| fail_on_test_v2_warn(node->remote[la].cec_version, |
| new_status != CEC_OP_POWER_STATUS_STANDBY); |
| if (new_status != CEC_OP_POWER_STATUS_STANDBY) |
| return standby_resume_standby(node, me, la, interactive); |
| |
| node->remote[la].in_standby = true; |
| if (unresponsive_cnt > 0) |
| warn("The device stayed correctly in standby, but was unresponsive %d times.\n", |
| unresponsive_cnt); |
| return 0; |
| } |
| |
| static int wakeup_rc(struct node *node, unsigned me, unsigned la) |
| { |
| struct cec_msg msg; |
| struct cec_op_ui_command rc_press = {}; |
| |
| /* Todo: A release should be sent after this */ |
| cec_msg_init(&msg, me, la); |
| rc_press.ui_cmd = CEC_OP_UI_CMD_POWER_ON_FUNCTION; |
| cec_msg_user_control_pressed(&msg, &rc_press); |
| fail_on_test(!transmit_timeout(node, &msg)); |
| fail_on_test(cec_msg_status_is_abort(&msg)); |
| |
| return 0; |
| } |
| |
| static int wakeup_tv(struct node *node, unsigned me, unsigned la) |
| { |
| struct cec_msg msg; |
| |
| cec_msg_init(&msg, me, la); |
| cec_msg_image_view_on(&msg); |
| msg.timeout = 2000; |
| int res = doioctl(node, CEC_TRANSMIT, &msg); |
| if (res == ENONET && la == CEC_LOG_ADDR_TV) { |
| msg.msg[0] = (CEC_LOG_ADDR_UNREGISTERED << 4) | la; |
| res = doioctl(node, CEC_TRANSMIT, &msg); |
| } |
| fail_on_test(res || !(msg.tx_status & CEC_TX_STATUS_OK)); |
| if (!cec_msg_status_is_abort(&msg)) |
| return 0; |
| |
| cec_msg_init(&msg, me, la); |
| cec_msg_text_view_on(&msg); |
| fail_on_test(!transmit_timeout(node, &msg)); |
| if (!cec_msg_status_is_abort(&msg)) |
| return 0; |
| |
| return wakeup_rc(node, me, la); |
| } |
| |
| static int wakeup_source(struct node *node, unsigned me, unsigned la) |
| { |
| struct cec_msg msg; |
| |
| cec_msg_init(&msg, me, la); |
| cec_msg_set_stream_path(&msg, node->remote[la].phys_addr); |
| fail_on_test(!transmit_timeout(node, &msg)); |
| if (!cec_msg_status_is_abort(&msg)) |
| return 0; |
| |
| return wakeup_rc(node, me, la); |
| } |
| |
| int standby_resume_wakeup(struct node *node, unsigned me, unsigned la, bool interactive) |
| { |
| if (!node->remote[la].in_standby) |
| return NOTAPPLICABLE; |
| |
| int ret; |
| |
| if (is_tv(la, node->remote[la].prim_type)) |
| ret = wakeup_tv(node, me, la); |
| else |
| ret = wakeup_source(node, me, la); |
| if (ret) |
| return ret; |
| |
| unsigned unresponsive_cnt = 0; |
| |
| announce("Wait for device to wake up"); |
| fail_on_test(!poll_stable_power_status(node, me, la, CEC_OP_POWER_STATUS_ON, unresponsive_cnt)); |
| fail_on_test(interactive && !question("Is the device in On state?")); |
| |
| if (unresponsive_cnt > 0) |
| warn("The device went correctly out of standby, but was unresponsive %d times during the transition.\n", |
| unresponsive_cnt); |
| |
| return 0; |
| } |
| |
| static int standby_resume_wakeup_view_on(struct node *node, unsigned me, unsigned la, bool interactive, __u8 opcode) |
| { |
| if (!is_tv(la, node->remote[la].prim_type)) |
| return NOTAPPLICABLE; |
| |
| unsigned unresponsive_cnt = 0; |
| |
| sleep(5); |
| fail_on_test(!poll_stable_power_status(node, me, la, CEC_OP_POWER_STATUS_ON, unresponsive_cnt)); |
| |
| int ret = standby_resume_standby(node, me, la, interactive); |
| |
| if (ret && opcode == CEC_MSG_TEXT_VIEW_ON) { |
| ret = standby_resume_standby(node, me, la, interactive); |
| if (!ret) |
| warn("A STANDBY was sent right after the display reports it was powered on, but it was ignored.\n"); |
| } |
| |
| if (ret) |
| return ret; |
| |
| sleep(6); |
| |
| ret = one_touch_play_view_on(node, me, la, interactive, opcode); |
| |
| if (ret) |
| return ret; |
| |
| announce("Wait for device to wake up"); |
| unresponsive_cnt = 0; |
| fail_on_test(!poll_stable_power_status(node, me, la, CEC_OP_POWER_STATUS_ON, unresponsive_cnt)); |
| fail_on_test(interactive && !question("Is the device in On state?")); |
| |
| struct cec_msg msg; |
| |
| cec_msg_init(&msg, me, la); |
| cec_msg_active_source(&msg, node->phys_addr); |
| fail_on_test(!transmit_timeout(node, &msg)); |
| |
| if (unresponsive_cnt > 0) |
| warn("The device went correctly out of standby, but was unresponsive %d times during the transition.\n", |
| unresponsive_cnt); |
| |
| return 0; |
| } |
| |
| static int standby_resume_wakeup_image_view_on(struct node *node, unsigned me, unsigned la, bool interactive) |
| { |
| return standby_resume_wakeup_view_on(node, me, la, interactive, CEC_MSG_IMAGE_VIEW_ON); |
| } |
| |
| static int standby_resume_wakeup_text_view_on(struct node *node, unsigned me, unsigned la, bool interactive) |
| { |
| return standby_resume_wakeup_view_on(node, me, la, interactive, CEC_MSG_TEXT_VIEW_ON); |
| } |
| |
| /* Test CEC 2.0 Power State Transitions (see HDMI 2.1, 11.5.5) */ |
| static int power_state_transitions(struct node *node, unsigned me, unsigned la, bool interactive) |
| { |
| struct cec_msg msg = {}; |
| |
| mode_set_follower(node); |
| msg.timeout = 1000; |
| doioctl(node, CEC_RECEIVE, &msg); |
| cec_msg_init(&msg, me, la); |
| cec_msg_standby(&msg); |
| fail_on_test(!transmit_timeout(node, &msg)); |
| time_t start = time(nullptr); |
| int res = util_receive(node, la, long_timeout * 1000, &msg, CEC_MSG_STANDBY, |
| CEC_MSG_REPORT_POWER_STATUS); |
| fail_on_test(!res); |
| if (res < 0) { |
| warn("No Report Power Status seen when going to standby.\n"); |
| info("This might be due to the bug fix in commit cec935ce69fc\n"); |
| info("However, this was fixed in 5.5 and has been backported to LTS kernels,\n"); |
| info("so any kernel released after January 2020 should have this fix.\n"); |
| return OK_PRESUMED; |
| } |
| if (time(nullptr) - start > 3) |
| warn("The first Report Power Status broadcast arrived > 3s after sending <Standby>\n"); |
| if (msg.msg[2] == CEC_OP_POWER_STATUS_STANDBY) |
| return 0; |
| fail_on_test(msg.msg[2] != CEC_OP_POWER_STATUS_TO_STANDBY); |
| fail_on_test(util_receive(node, la, long_timeout * 1000, &msg, CEC_MSG_STANDBY, |
| CEC_MSG_REPORT_POWER_STATUS) <= 0); |
| fail_on_test(msg.msg[2] != CEC_OP_POWER_STATUS_STANDBY); |
| |
| cec_msg_init(&msg, me, la); |
| __u8 opcode; |
| if (is_tv(la, node->remote[la].prim_type)) { |
| cec_msg_image_view_on(&msg); |
| opcode = msg.msg[2]; |
| |
| int res = doioctl(node, CEC_TRANSMIT, &msg); |
| |
| if (res == ENONET && la == CEC_LOG_ADDR_TV) { |
| msg.msg[0] = (CEC_LOG_ADDR_UNREGISTERED << 4) | la; |
| fail_on_test(doioctl(node, CEC_TRANSMIT, &msg)); |
| } |
| } else { |
| cec_msg_set_stream_path(&msg, node->remote[la].phys_addr); |
| opcode = msg.msg[2]; |
| fail_on_test(doioctl(node, CEC_TRANSMIT, &msg)); |
| } |
| fail_on_test(!(msg.tx_status & CEC_TX_STATUS_OK)); |
| start = time(nullptr); |
| fail_on_test(util_receive(node, la, long_timeout * 1000, &msg, opcode, |
| CEC_MSG_REPORT_POWER_STATUS) <= 0); |
| if (time(nullptr) - start > 3) |
| warn("The first Report Power Status broadcast arrived > 3s after sending <%s>\n", |
| opcode == CEC_MSG_IMAGE_VIEW_ON ? "Image View On" : "Set Stream Path"); |
| if (msg.msg[2] == CEC_OP_POWER_STATUS_ON) |
| return 0; |
| fail_on_test(msg.msg[2] != CEC_OP_POWER_STATUS_TO_ON); |
| fail_on_test(util_receive(node, la, long_timeout * 1000, &msg, opcode, |
| CEC_MSG_REPORT_POWER_STATUS) <= 0); |
| fail_on_test(msg.msg[2] != CEC_OP_POWER_STATUS_ON); |
| |
| return 0; |
| } |
| |
| static int standby_resume_wakeup_deck(struct node *node, unsigned me, unsigned la, bool interactive, __u8 opcode) |
| { |
| 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)); |
| if (timed_out_or_abort(&msg)) |
| return OK_NOT_SUPPORTED; |
| |
| unsigned unresponsive_cnt = 0; |
| |
| fail_on_test(!poll_stable_power_status(node, me, la, CEC_OP_POWER_STATUS_ON, unresponsive_cnt)); |
| |
| int ret = standby_resume_standby(node, me, la, interactive); |
| |
| if (ret) |
| return ret; |
| |
| cec_msg_init(&msg, me, la); |
| if (opcode == CEC_OP_PLAY_MODE_PLAY_FWD) |
| cec_msg_play(&msg, CEC_OP_PLAY_MODE_PLAY_FWD); |
| else |
| 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)); |
| |
| unresponsive_cnt = 0; |
| fail_on_test(!poll_stable_power_status(node, me, la, CEC_OP_POWER_STATUS_ON, unresponsive_cnt)); |
| fail_on_test(interactive && !question("Is the device in On state?")); |
| |
| return OK; |
| } |
| |
| static int standby_resume_wakeup_deck_eject(struct node *node, unsigned me, unsigned la, bool interactive) |
| { |
| return standby_resume_wakeup_deck(node, me, la, interactive, CEC_OP_DECK_CTL_MODE_EJECT); |
| } |
| |
| static int standby_resume_wakeup_deck_play(struct node *node, unsigned me, unsigned la, bool interactive) |
| { |
| return standby_resume_wakeup_deck(node, me, la, interactive, CEC_OP_PLAY_MODE_PLAY_FWD); |
| } |
| |
| static int standby_record(struct node *node, unsigned me, unsigned la, bool interactive, bool active_source) |
| { |
| struct cec_msg msg; |
| __u8 rec_status; |
| unsigned unresponsive_cnt = 0; |
| |
| cec_msg_init(&msg, me, la); |
| cec_msg_record_on_own(&msg); |
| msg.reply = CEC_MSG_RECORD_STATUS; |
| fail_on_test(!transmit_timeout(node, &msg, 10000)); |
| if (timed_out_or_abort(&msg)) |
| return OK_NOT_SUPPORTED; |
| cec_ops_record_status(&msg, &rec_status); |
| fail_on_test(rec_status != CEC_OP_RECORD_STATUS_CUR_SRC && |
| rec_status != CEC_OP_RECORD_STATUS_ALREADY_RECORDING); |
| |
| cec_msg_init(&msg, me, la); |
| if (active_source) |
| cec_msg_active_source(&msg, node->remote[la].phys_addr); |
| else |
| cec_msg_active_source(&msg, me); |
| fail_on_test(!transmit_timeout(node, &msg)); |
| |
| cec_msg_init(&msg, me, la); |
| cec_msg_standby(&msg); |
| fail_on_test(!transmit_timeout(node, &msg)); |
| /* Standby should not interrupt the recording. */ |
| fail_on_test(!poll_stable_power_status(node, me, la, CEC_OP_POWER_STATUS_ON, unresponsive_cnt)); |
| |
| cec_msg_init(&msg, me, la); |
| cec_msg_record_off(&msg, false); |
| fail_on_test(!transmit_timeout(node, &msg)); |
| |
| /* When the recording stops, recorder should standby unless it is the active source. */ |
| if (active_source) { |
| fail_on_test(!poll_stable_power_status(node, me, la, CEC_OP_POWER_STATUS_ON, unresponsive_cnt)); |
| } else { |
| fail_on_test(!poll_stable_power_status(node, me, la, CEC_OP_POWER_STATUS_STANDBY, unresponsive_cnt)); |
| fail_on_test(interactive && !question("Is the device in standby?")); |
| node->remote[la].in_standby = true; |
| |
| int ret = standby_resume_wakeup(node, me, la, interactive); |
| if (ret) |
| return ret; |
| node->remote[la].in_standby = false; |
| } |
| |
| return OK; |
| } |
| |
| static int standby_record_active_source(struct node *node, unsigned me, unsigned la, bool interactive) |
| { |
| return standby_record(node, me, la, interactive, true); |
| } |
| |
| static int standby_record_inactive_source(struct node *node, unsigned me, unsigned la, bool interactive) |
| { |
| return standby_record(node, me, la, interactive, false); |
| } |
| |
| const vec_remote_subtests standby_resume_subtests{ |
| { "Standby", CEC_LOG_ADDR_MASK_ALL, standby_resume_standby }, |
| { "Repeated Standby message does not wake up", CEC_LOG_ADDR_MASK_ALL, standby_resume_standby_toggle }, |
| { "Standby: Feature aborts unknown messages", CEC_LOG_ADDR_MASK_ALL, core_unknown, true }, |
| { "Standby: Feature aborts Abort message", CEC_LOG_ADDR_MASK_ALL, core_abort, true }, |
| { "Standby: Polling Message", CEC_LOG_ADDR_MASK_ALL, system_info_polling, true }, |
| { "Standby: Give Device Power Status", CEC_LOG_ADDR_MASK_ALL, power_status_give, true }, |
| { "Standby: Give Physical Address", CEC_LOG_ADDR_MASK_ALL, system_info_phys_addr, true }, |
| { "Standby: Give CEC Version", CEC_LOG_ADDR_MASK_ALL, system_info_version, true }, |
| { "Standby: Give Device Vendor ID", CEC_LOG_ADDR_MASK_ALL, vendor_specific_commands_id, true }, |
| { "Standby: Give OSD Name", CEC_LOG_ADDR_MASK_ALL, device_osd_transfer_give, true }, |
| { "Standby: Get Menu Language", CEC_LOG_ADDR_MASK_ALL, system_info_get_menu_lang, true }, |
| { "Standby: Give Device Features", CEC_LOG_ADDR_MASK_ALL, system_info_give_features, true }, |
| { "No wakeup on Active Source", CEC_LOG_ADDR_MASK_ALL, standby_resume_active_source_nowake }, |
| { "Wake up", CEC_LOG_ADDR_MASK_ALL, standby_resume_wakeup }, |
| { "Wake up TV on Image View On", CEC_LOG_ADDR_MASK_TV, standby_resume_wakeup_image_view_on }, |
| { "Wake up TV on Text View On", CEC_LOG_ADDR_MASK_TV, standby_resume_wakeup_text_view_on }, |
| { "Power State Transitions", CEC_LOG_ADDR_MASK_TV, power_state_transitions, false, true }, |
| { "Deck Eject Standby Resume", CEC_LOG_ADDR_MASK_PLAYBACK | CEC_LOG_ADDR_MASK_RECORD, standby_resume_wakeup_deck_eject }, |
| { "Deck Play Standby Resume", CEC_LOG_ADDR_MASK_PLAYBACK | CEC_LOG_ADDR_MASK_RECORD, standby_resume_wakeup_deck_play }, |
| { "Record Standby Active Source", CEC_LOG_ADDR_MASK_RECORD | CEC_LOG_ADDR_MASK_BACKUP, standby_record_active_source }, |
| { "Record Standby Inactive Source", CEC_LOG_ADDR_MASK_RECORD | CEC_LOG_ADDR_MASK_BACKUP, standby_record_inactive_source }, |
| }; |