| // 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 <fcntl.h> |
| #include <sys/ioctl.h> |
| #include <sys/select.h> |
| #include <unistd.h> |
| |
| #include "cec-compliance.h" |
| |
| static constexpr __u8 tx_ok_retry_mask = CEC_TX_STATUS_OK | CEC_TX_STATUS_MAX_RETRIES; |
| static constexpr __u32 msg_fl_mask = CEC_MSG_FL_REPLY_TO_FOLLOWERS | CEC_MSG_FL_RAW; |
| |
| // Flush any pending messages |
| static int flush_pending_msgs(struct node *node) |
| { |
| int res; |
| |
| do { |
| struct cec_msg msg; |
| |
| memset(&msg, 0xff, sizeof(msg)); |
| msg.timeout = 1; |
| res = doioctl(node, CEC_RECEIVE, &msg); |
| fail_on_test(res && res != ETIMEDOUT); |
| } while (res == 0); |
| return 0; |
| } |
| |
| static int testCap(struct node *node) |
| { |
| struct cec_caps caps; |
| |
| memset(&caps, 0xff, sizeof(caps)); |
| fail_on_test(doioctl(node, CEC_ADAP_G_CAPS, nullptr) != EFAULT); |
| fail_on_test(doioctl(node, CEC_ADAP_G_CAPS, &caps)); |
| fail_on_test(caps.available_log_addrs == 0 || |
| caps.available_log_addrs > CEC_MAX_LOG_ADDRS); |
| fail_on_test((caps.capabilities & CEC_CAP_PASSTHROUGH) && |
| !(caps.capabilities & CEC_CAP_TRANSMIT)); |
| return 0; |
| } |
| |
| static int testInvalidIoctls(struct node *node) |
| { |
| const char type = 'a'; |
| unsigned ioc = _IOC(_IOC_NONE, type, 0xff, 0); |
| unsigned char buf[0x4000] = {}; |
| |
| fail_on_test(doioctl(node, ioc, nullptr) != ENOTTY); |
| ioc = _IOC(_IOC_NONE, type, 0, 0x3fff); |
| fail_on_test(doioctl(node, ioc, nullptr) != ENOTTY); |
| ioc = _IOC(_IOC_READ, type, 0, 0x3fff); |
| fail_on_test(doioctl(node, ioc, buf) != ENOTTY); |
| fail_on_test(check_0(buf, sizeof(buf))); |
| ioc = _IOC(_IOC_WRITE, type, 0, 0x3fff); |
| fail_on_test(doioctl(node, ioc, buf) != ENOTTY); |
| fail_on_test(check_0(buf, sizeof(buf))); |
| ioc = _IOC(_IOC_READ | _IOC_WRITE, type, 0, 0x3fff); |
| fail_on_test(doioctl(node, ioc, buf) != ENOTTY); |
| fail_on_test(check_0(buf, sizeof(buf))); |
| return 0; |
| } |
| |
| static int testDQEvent(struct node *node) |
| { |
| struct cec_event ev; |
| |
| memset(&ev, 0xff, sizeof(ev)); |
| fail_on_test(doioctl(node, CEC_DQEVENT, &ev)); |
| fail_on_test(!(ev.flags & CEC_EVENT_FL_INITIAL_STATE)); |
| fail_on_test(ev.flags & ~CEC_EVENT_FL_INITIAL_STATE); |
| fail_on_test(ev.ts == 0 || ev.ts == ~0ULL); |
| fail_on_test(ev.event != CEC_EVENT_STATE_CHANGE); |
| fail_on_test(ev.state_change.log_addr_mask == 0xffff); |
| memset(&ev.state_change, 0, sizeof(ev.state_change)); |
| fail_on_test(check_0(ev.raw, sizeof(ev.raw))); |
| return 0; |
| } |
| |
| static int testAdapPhysAddr(struct node *node) |
| { |
| __u16 old_pa = 0xefff; |
| __u16 pa = 0x1000; |
| |
| fail_on_test(doioctl(node, CEC_ADAP_G_PHYS_ADDR, &old_pa)); |
| fail_on_test(old_pa == 0xefff); |
| if (node->caps & CEC_CAP_PHYS_ADDR) { |
| fail_on_test(doioctl(node, CEC_ADAP_S_PHYS_ADDR, &pa)); |
| fail_on_test(doioctl(node, CEC_ADAP_G_PHYS_ADDR, &pa)); |
| fail_on_test(pa != 0x1000); |
| |
| fail_on_test(doioctl(node, CEC_ADAP_S_PHYS_ADDR, &old_pa)); |
| fail_on_test(doioctl(node, CEC_ADAP_G_PHYS_ADDR, &pa)); |
| fail_on_test(pa != old_pa); |
| } else { |
| fail_on_test(doioctl(node, CEC_ADAP_S_PHYS_ADDR, &pa) != ENOTTY); |
| } |
| return 0; |
| } |
| |
| static int testAdapLogAddrs(struct node *node) |
| { |
| static constexpr __u8 la_types[] = { |
| CEC_LOG_ADDR_TYPE_TV, |
| CEC_LOG_ADDR_TYPE_RECORD, |
| CEC_LOG_ADDR_TYPE_TUNER, |
| CEC_LOG_ADDR_TYPE_AUDIOSYSTEM |
| }; |
| static constexpr __u8 prim_dev_types[] = { |
| CEC_OP_PRIM_DEVTYPE_TV, |
| CEC_OP_PRIM_DEVTYPE_RECORD, |
| CEC_OP_PRIM_DEVTYPE_TUNER, |
| CEC_OP_PRIM_DEVTYPE_AUDIOSYSTEM |
| }; |
| static constexpr __u8 all_dev_types[2] = { |
| CEC_OP_ALL_DEVTYPE_TV | CEC_OP_ALL_DEVTYPE_RECORD | |
| CEC_OP_ALL_DEVTYPE_AUDIOSYSTEM, |
| CEC_OP_ALL_DEVTYPE_RECORD | CEC_OP_ALL_DEVTYPE_AUDIOSYSTEM, |
| }; |
| static constexpr __u8 features[12] = { |
| 0x90, 0x00, 0x8e, 0x00, |
| 0xff, 0xff, 0xff, 0xff, |
| 0xff, 0xff, 0xff, 0xff |
| }; |
| struct cec_log_addrs clear = { }; |
| struct cec_log_addrs laddrs; |
| struct cec_event ev; |
| int res; |
| |
| memset(&laddrs, 0xff, sizeof(laddrs)); |
| fail_on_test(doioctl(node, CEC_ADAP_G_LOG_ADDRS, &laddrs)); |
| fail_on_test(laddrs.vendor_id != CEC_VENDOR_ID_NONE && |
| (laddrs.vendor_id & 0xff000000)); |
| fail_on_test(laddrs.cec_version != CEC_OP_CEC_VERSION_1_4 && |
| laddrs.cec_version != CEC_OP_CEC_VERSION_2_0); |
| fail_on_test(laddrs.num_log_addrs > CEC_MAX_LOG_ADDRS); |
| |
| if (node->phys_addr == CEC_PHYS_ADDR_INVALID || laddrs.num_log_addrs == 0) |
| fail_on_test(laddrs.log_addr_mask); |
| else |
| fail_on_test(!laddrs.log_addr_mask); |
| |
| if (!(node->caps & CEC_CAP_LOG_ADDRS)) { |
| fail_on_test(!laddrs.num_log_addrs); |
| fail_on_test(doioctl(node, CEC_ADAP_S_LOG_ADDRS, &laddrs) != ENOTTY); |
| return 0; |
| } |
| |
| fail_on_test(doioctl(node, CEC_ADAP_S_LOG_ADDRS, &clear)); |
| fail_on_test(clear.num_log_addrs != 0); |
| fail_on_test(clear.log_addr_mask != 0); |
| fail_on_test(doioctl(node, CEC_ADAP_G_LOG_ADDRS, &laddrs)); |
| fail_on_test(laddrs.num_log_addrs != 0); |
| fail_on_test(laddrs.log_addr_mask != 0); |
| |
| __u16 pa; |
| |
| fail_on_test(doioctl(node, CEC_ADAP_G_PHYS_ADDR, &pa)); |
| fail_on_test(pa != node->phys_addr); |
| unsigned skip_tv = pa ? 1 : 0; |
| unsigned available_log_addrs = node->available_log_addrs; |
| |
| if (skip_tv && available_log_addrs == CEC_MAX_LOG_ADDRS) |
| available_log_addrs--; |
| memset(&laddrs, 0, sizeof(laddrs)); |
| strcpy(laddrs.osd_name, "Compliance"); |
| laddrs.num_log_addrs = available_log_addrs; |
| laddrs.cec_version = laddrs.num_log_addrs > 2 ? |
| CEC_OP_CEC_VERSION_1_4: CEC_OP_CEC_VERSION_2_0; |
| |
| for (unsigned i = 0; i < CEC_MAX_LOG_ADDRS - skip_tv; i++) { |
| laddrs.log_addr_type[i] = la_types[i + skip_tv]; |
| laddrs.primary_device_type[i] = prim_dev_types[i + skip_tv]; |
| laddrs.all_device_types[i] = all_dev_types[skip_tv]; |
| memcpy(laddrs.features[i], features, sizeof(features)); |
| } |
| |
| fail_on_test(doioctl(node, CEC_ADAP_S_LOG_ADDRS, &laddrs)); |
| fail_on_test(laddrs.num_log_addrs != available_log_addrs); |
| fail_on_test(laddrs.log_addr_mask == 0); |
| for (unsigned i = 0; i < laddrs.num_log_addrs; i++) { |
| fail_on_test(laddrs.log_addr[i] == CEC_LOG_ADDR_INVALID); |
| fail_on_test(memcmp(laddrs.features[i], features, 4)); |
| fail_on_test(check_0(laddrs.features[i] + 4, 8)); |
| } |
| for (unsigned i = laddrs.num_log_addrs; i < CEC_MAX_LOG_ADDRS; i++) { |
| fail_on_test(laddrs.log_addr_type[i]); |
| fail_on_test(laddrs.primary_device_type[i]); |
| fail_on_test(laddrs.all_device_types[i]); |
| fail_on_test(check_0(laddrs.features[i], sizeof(laddrs.features[i]))); |
| } |
| fail_on_test(doioctl(node, CEC_ADAP_S_LOG_ADDRS, &laddrs) != EBUSY); |
| |
| fcntl(node->fd, F_SETFL, fcntl(node->fd, F_GETFL) | O_NONBLOCK); |
| do { |
| res = doioctl(node, CEC_DQEVENT, &ev); |
| fail_on_test(res && res != EAGAIN); |
| if (!res) { |
| struct timeval tv = { 0, 10000 }; // 10 ms |
| |
| switch (ev.event) { |
| case CEC_EVENT_STATE_CHANGE: |
| fail_on_test(ev.flags & CEC_EVENT_FL_INITIAL_STATE); |
| break; |
| case CEC_EVENT_PIN_CEC_LOW: |
| case CEC_EVENT_PIN_CEC_HIGH: |
| case CEC_EVENT_PIN_HPD_LOW: |
| case CEC_EVENT_PIN_HPD_HIGH: |
| case CEC_EVENT_PIN_5V_LOW: |
| case CEC_EVENT_PIN_5V_HIGH: |
| fail_on_test(!(ev.flags & CEC_EVENT_FL_INITIAL_STATE)); |
| break; |
| case CEC_EVENT_LOST_MSGS: |
| fail("Unexpected event %d\n", ev.event); |
| break; |
| default: |
| fail("Unknown event %d\n", ev.event); |
| break; |
| } |
| select(0, nullptr, nullptr, nullptr, &tv); |
| } |
| } while (!res); |
| fail_on_test(doioctl(node, CEC_ADAP_S_LOG_ADDRS, &clear)); |
| do { |
| struct timeval tv = { 0, 10000 }; // 10 ms |
| |
| res = doioctl(node, CEC_DQEVENT, &ev); |
| fail_on_test(res && res != EAGAIN); |
| if (res) |
| select(0, nullptr, nullptr, nullptr, &tv); |
| } while (res); |
| fail_on_test(ev.flags & CEC_EVENT_FL_INITIAL_STATE); |
| fail_on_test(ev.ts == 0); |
| fail_on_test(ev.event != CEC_EVENT_STATE_CHANGE); |
| fail_on_test(ev.state_change.phys_addr != node->phys_addr); |
| fail_on_test(ev.state_change.log_addr_mask); |
| |
| fail_on_test(doioctl(node, CEC_ADAP_S_LOG_ADDRS, &laddrs)); |
| do { |
| struct timeval tv = { 0, 10000 }; // 10 ms |
| |
| res = doioctl(node, CEC_DQEVENT, &ev); |
| fail_on_test(res && res != EAGAIN); |
| if (res) |
| select(0, nullptr, nullptr, nullptr, &tv); |
| } while (res); |
| fail_on_test(ev.flags & CEC_EVENT_FL_INITIAL_STATE); |
| fail_on_test(ev.ts == 0); |
| fail_on_test(ev.event != CEC_EVENT_STATE_CHANGE); |
| fail_on_test(ev.state_change.phys_addr != node->phys_addr); |
| fail_on_test(ev.state_change.log_addr_mask == 0); |
| return 0; |
| } |
| |
| static int testTransmit(struct node *node) |
| { |
| struct cec_msg msg; |
| unsigned i, la = node->log_addr[0]; |
| unsigned valid_la = 15, invalid_la = 15; |
| bool tested_self = false; |
| bool tested_valid_la = false; |
| bool tested_invalid_la = false; |
| |
| if (!(node->caps & CEC_CAP_TRANSMIT)) { |
| cec_msg_init(&msg, la, 0); |
| fail_on_test(doioctl(node, CEC_TRANSMIT, &msg) != ENOTTY); |
| return OK_NOT_SUPPORTED; |
| } |
| |
| /* Check invalid messages */ |
| cec_msg_init(&msg, la, 0xf); |
| fail_on_test(doioctl(node, CEC_TRANSMIT, &msg) != EINVAL); |
| |
| cec_msg_init(&msg, la, 0); |
| msg.timeout = 1000; |
| fail_on_test(doioctl(node, CEC_TRANSMIT, &msg) != EINVAL); |
| |
| cec_msg_init(&msg, la, 0); |
| msg.len = 0; |
| fail_on_test(doioctl(node, CEC_TRANSMIT, &msg) != EINVAL); |
| |
| cec_msg_init(&msg, la, 0); |
| msg.len = CEC_MAX_MSG_SIZE + 1; |
| fail_on_test(doioctl(node, CEC_TRANSMIT, &msg) != EINVAL); |
| |
| cec_msg_init(&msg, la, 0); |
| msg.reply = CEC_MSG_CEC_VERSION; |
| fail_on_test(doioctl(node, CEC_TRANSMIT, &msg) != EINVAL); |
| |
| for (i = 0; i < 15; i++) { |
| if (tested_self && (node->adap_la_mask & (1 << i))) |
| continue; |
| |
| memset(&msg, 0xff, sizeof(msg)); |
| msg.msg[0] = 0xf0 | i; |
| msg.len = 1; |
| msg.timeout = 0; |
| msg.reply = 0; |
| msg.flags &= ~msg_fl_mask; |
| |
| fail_on_test(doioctl(node, CEC_TRANSMIT, &msg)); |
| |
| fail_on_test(msg.len != 1); |
| fail_on_test(check_0(msg.msg + msg.len, sizeof(msg.msg) - msg.len)); |
| fail_on_test(msg.timeout); |
| fail_on_test(msg.reply); |
| fail_on_test(msg.flags); |
| fail_on_test(msg.rx_status); |
| fail_on_test(msg.rx_ts); |
| fail_on_test(msg.tx_status == 0); |
| fail_on_test(msg.tx_ts == 0); |
| fail_on_test(msg.tx_ts == ~0ULL); |
| fail_on_test(msg.msg[0] != (0xf0 | i)); |
| fail_on_test(msg.sequence == 0); |
| fail_on_test(msg.sequence == ~0U); |
| fail_on_test(msg.tx_status & ~0x3f); |
| fail_on_test((msg.tx_status & tx_ok_retry_mask) == tx_ok_retry_mask); |
| fail_on_test(!(msg.tx_status & tx_ok_retry_mask)); |
| |
| if (node->adap_la_mask & (1 << i)) { |
| // Send message to yourself |
| fail_on_test(msg.tx_status != |
| (CEC_TX_STATUS_NACK | CEC_TX_STATUS_MAX_RETRIES)); |
| fail_on_test(msg.tx_nack_cnt != 1); |
| fail_on_test(msg.tx_arb_lost_cnt); |
| fail_on_test(msg.tx_low_drive_cnt); |
| fail_on_test(msg.tx_error_cnt); |
| |
| cec_msg_init(&msg, i, i); |
| cec_msg_give_physical_addr(&msg, true); |
| fail_on_test(doioctl(node, CEC_TRANSMIT, &msg) != EINVAL); |
| } else if (msg.tx_status & CEC_TX_STATUS_OK) { |
| if (tested_valid_la) |
| continue; |
| tested_valid_la = true; |
| valid_la = i; |
| // Send message to a remote LA |
| memset(&msg, 0xff, sizeof(msg)); |
| msg.msg[0] = (la << 4) | i; |
| msg.timeout = 1001; |
| msg.flags &= ~msg_fl_mask; |
| cec_msg_give_physical_addr(&msg, true); |
| fail_on_test(doioctl(node, CEC_TRANSMIT, &msg)); |
| fail_on_test(!(msg.tx_status & CEC_TX_STATUS_OK)); |
| fail_on_test(msg.rx_status & CEC_RX_STATUS_TIMEOUT); |
| fail_on_test(!(msg.rx_status & CEC_RX_STATUS_OK)); |
| fail_on_test(msg.len != 5); |
| fail_on_test(check_0(msg.msg + msg.len, sizeof(msg.msg) - msg.len)); |
| fail_on_test(msg.timeout != 1001); |
| fail_on_test(msg.sequence == 0 || msg.sequence == ~0U); |
| fail_on_test(msg.reply != CEC_MSG_REPORT_PHYSICAL_ADDR); |
| fail_on_test(msg.rx_ts == 0); |
| fail_on_test(msg.rx_ts == ~0ULL); |
| fail_on_test(msg.flags); |
| fail_on_test(msg.rx_status == 0); |
| fail_on_test(msg.rx_status & ~0x07); |
| fail_on_test(msg.tx_ts == 0); |
| fail_on_test(msg.tx_ts == ~0ULL); |
| fail_on_test(msg.rx_ts <= msg.tx_ts); |
| fail_on_test((msg.tx_status & tx_ok_retry_mask) == tx_ok_retry_mask); |
| fail_on_test(msg.tx_nack_cnt == 0xff); |
| fail_on_test(msg.tx_arb_lost_cnt == 0xff); |
| fail_on_test(msg.tx_low_drive_cnt == 0xff); |
| fail_on_test(msg.tx_error_cnt == 0xff); |
| |
| memset(&msg, 0xff, sizeof(msg)); |
| msg.msg[0] = (la << 4) | i; |
| msg.timeout = 0; |
| msg.flags &= ~msg_fl_mask; |
| cec_msg_give_physical_addr(&msg, false); |
| fail_on_test(doioctl(node, CEC_TRANSMIT, &msg)); |
| fail_on_test(msg.timeout); |
| fail_on_test(msg.sequence == 0 || msg.sequence == ~0U); |
| fail_on_test(msg.reply); |
| fail_on_test(check_0(msg.msg + msg.len, sizeof(msg.msg) - msg.len)); |
| fail_on_test(msg.rx_status); |
| fail_on_test(msg.rx_ts); |
| fail_on_test(msg.flags); |
| fail_on_test(msg.tx_ts == 0); |
| fail_on_test(msg.tx_ts == ~0ULL); |
| fail_on_test(!(msg.tx_status & CEC_TX_STATUS_OK)); |
| fail_on_test((msg.tx_status & tx_ok_retry_mask) == tx_ok_retry_mask); |
| fail_on_test(msg.tx_nack_cnt == 0xff); |
| fail_on_test(msg.tx_arb_lost_cnt == 0xff); |
| fail_on_test(msg.tx_low_drive_cnt == 0xff); |
| fail_on_test(msg.tx_error_cnt == 0xff); |
| } else { |
| if (tested_invalid_la) |
| continue; |
| tested_invalid_la = true; |
| invalid_la = i; |
| // Send message to a remote non-existent LA |
| memset(&msg, 0xff, sizeof(msg)); |
| msg.msg[0] = (la << 4) | i; |
| msg.timeout = 1002; |
| msg.flags &= ~msg_fl_mask; |
| cec_msg_give_physical_addr(&msg, true); |
| fail_on_test(doioctl(node, CEC_TRANSMIT, &msg)); |
| fail_on_test(msg.timeout != 1002); |
| fail_on_test(msg.sequence == 0); |
| fail_on_test(msg.sequence == ~0U); |
| fail_on_test(msg.len != 2); |
| fail_on_test(check_0(msg.msg + msg.len, sizeof(msg.msg) - msg.len)); |
| fail_on_test(msg.flags); |
| fail_on_test(msg.reply != CEC_MSG_REPORT_PHYSICAL_ADDR); |
| fail_on_test(!(msg.tx_status & CEC_TX_STATUS_MAX_RETRIES)); |
| fail_on_test((msg.tx_status & tx_ok_retry_mask) == tx_ok_retry_mask); |
| fail_on_test(msg.rx_status); |
| fail_on_test(msg.rx_ts); |
| fail_on_test(msg.tx_ts == 0); |
| fail_on_test(msg.tx_ts == ~0ULL); |
| fail_on_test(msg.tx_nack_cnt == 0xff); |
| fail_on_test(msg.tx_arb_lost_cnt == 0xff); |
| fail_on_test(msg.tx_low_drive_cnt == 0xff); |
| fail_on_test(msg.tx_error_cnt == 0xff); |
| |
| memset(&msg, 0xff, sizeof(msg)); |
| msg.msg[0] = (la << 4) | i; |
| msg.timeout = 0; |
| msg.flags &= ~msg_fl_mask; |
| cec_msg_give_physical_addr(&msg, false); |
| fail_on_test(doioctl(node, CEC_TRANSMIT, &msg)); |
| fail_on_test(msg.timeout); |
| fail_on_test(msg.sequence == 0); |
| fail_on_test(msg.sequence == ~0U); |
| fail_on_test(msg.reply); |
| fail_on_test(msg.len != 2); |
| fail_on_test(check_0(msg.msg + msg.len, sizeof(msg.msg) - msg.len)); |
| fail_on_test(msg.rx_status); |
| fail_on_test(msg.rx_ts); |
| fail_on_test(msg.flags); |
| fail_on_test(msg.tx_ts == 0); |
| fail_on_test(msg.tx_ts == ~0ULL); |
| fail_on_test(!(msg.tx_status & CEC_TX_STATUS_MAX_RETRIES)); |
| fail_on_test((msg.tx_status & tx_ok_retry_mask) == tx_ok_retry_mask); |
| fail_on_test(msg.tx_nack_cnt == 0xff); |
| fail_on_test(msg.tx_arb_lost_cnt == 0xff); |
| fail_on_test(msg.tx_low_drive_cnt == 0xff); |
| fail_on_test(msg.tx_error_cnt == 0xff); |
| } |
| } |
| |
| if (tested_valid_la) { |
| time_t cur_t = time(nullptr), t; |
| time_t last_t = cur_t + 7; |
| unsigned max_cnt = 0; |
| unsigned cnt = 0; |
| |
| do { |
| t = time(nullptr); |
| if (t != cur_t) { |
| if (cnt > max_cnt) |
| max_cnt = cnt; |
| cnt = 0; |
| cur_t = t; |
| } |
| cec_msg_init(&msg, la, valid_la); |
| fail_on_test(doioctl(node, CEC_TRANSMIT, &msg)); |
| cnt++; |
| } while (t < last_t); |
| // A ping can take 10 * 2.4 + 4.5 + 7 * 2.4 = 45.3 ms (SFT) |
| if (max_cnt < 21) |
| warn("Could only do %u pings per second to a valid LA, expected at least 21\n", |
| max_cnt); |
| } |
| |
| if (tested_invalid_la) { |
| time_t cur_t = time(nullptr), t; |
| time_t last_t = cur_t + 7; |
| unsigned max_cnt = 0; |
| unsigned cnt = 0; |
| |
| do { |
| t = time(nullptr); |
| if (t != cur_t) { |
| if (cnt > max_cnt) |
| max_cnt = cnt; |
| cnt = 0; |
| cur_t = t; |
| } |
| cec_msg_init(&msg, la, invalid_la); |
| fail_on_test(doioctl(node, CEC_TRANSMIT, &msg)); |
| cnt++; |
| } while (t < last_t); |
| // A ping to an invalid LA can take 2 * (10 * 2.4 + 4.5) + (3 + 7) * 2.4 = 81 ms (SFT) |
| if (max_cnt < 12) |
| warn("Could only do %u pings per second to an invalid LA, expected at least 12\n", |
| max_cnt); |
| } |
| |
| return 0; |
| } |
| |
| static int testReceive(struct node *node) |
| { |
| unsigned la = node->log_addr[0], remote_la = 0; |
| struct cec_msg msg; |
| |
| if (!(node->caps & CEC_CAP_TRANSMIT)) { |
| fail_on_test(doioctl(node, CEC_RECEIVE, &msg) != ENOTTY); |
| return OK_NOT_SUPPORTED; |
| } |
| |
| for (unsigned i = 0; i < 15; i++) { |
| if (node->remote_la_mask & (1 << i)) |
| break; |
| remote_la++; |
| } |
| |
| fail_on_test(flush_pending_msgs(node)); |
| |
| if (remote_la == 15) |
| return OK_PRESUMED; |
| |
| cec_msg_init(&msg, la, remote_la); |
| cec_msg_give_physical_addr(&msg, false); |
| fail_on_test(doioctl(node, CEC_TRANSMIT, &msg)); |
| |
| msg.timeout = 1500; |
| fail_on_test(doioctl(node, CEC_RECEIVE, &msg) != ETIMEDOUT); |
| fail_on_test(msg.timeout != 1500); |
| |
| __u32 mode = CEC_MODE_INITIATOR | CEC_MODE_FOLLOWER; |
| |
| fail_on_test(doioctl(node, CEC_S_MODE, &mode)); |
| |
| cec_msg_init(&msg, la, remote_la); |
| cec_msg_give_physical_addr(&msg, false); |
| fail_on_test(doioctl(node, CEC_TRANSMIT, &msg)); |
| |
| memset(&msg, 0xff, sizeof(msg)); |
| msg.timeout = 1500; |
| fail_on_test(doioctl(node, CEC_RECEIVE, &msg)); |
| |
| fail_on_test(msg.tx_ts); |
| fail_on_test(msg.tx_status); |
| fail_on_test(msg.tx_arb_lost_cnt); |
| fail_on_test(msg.tx_nack_cnt); |
| fail_on_test(msg.tx_low_drive_cnt); |
| fail_on_test(msg.tx_error_cnt); |
| fail_on_test(msg.timeout != 1500); |
| fail_on_test(msg.len != 5); |
| fail_on_test(msg.msg[1] != CEC_MSG_REPORT_PHYSICAL_ADDR); |
| fail_on_test(check_0(msg.msg + msg.len, sizeof(msg.msg) - msg.len)); |
| fail_on_test(msg.msg[0] != ((remote_la << 4) | 0xf)); |
| fail_on_test(msg.sequence); |
| fail_on_test(msg.rx_ts == 0); |
| fail_on_test(msg.flags); |
| fail_on_test(msg.reply); |
| fail_on_test(msg.rx_status != CEC_RX_STATUS_OK); |
| |
| return 0; |
| } |
| |
| static int testNonBlocking(struct node *node) |
| { |
| unsigned la = node->log_addr[0], remote_la = 0, invalid_remote = 0xf; |
| struct cec_msg msg; |
| |
| if (!(node->caps & CEC_CAP_TRANSMIT)) |
| return OK_NOT_SUPPORTED; |
| |
| for (unsigned i = 0; i < 15; i++) { |
| if (node->remote_la_mask & (1 << i)) |
| break; |
| if (invalid_remote == 0xf && !(node->adap_la_mask & (1 << i))) |
| invalid_remote = i; |
| remote_la++; |
| } |
| |
| fail_on_test(flush_pending_msgs(node)); |
| |
| fcntl(node->fd, F_SETFL, fcntl(node->fd, F_GETFL) | O_NONBLOCK); |
| |
| fail_on_test(doioctl(node, CEC_RECEIVE, &msg) != EAGAIN); |
| |
| cec_msg_init(&msg, la, la); |
| fail_on_test(doioctl(node, CEC_TRANSMIT, &msg)); |
| fail_on_test(msg.tx_status != (CEC_TX_STATUS_NACK | CEC_TX_STATUS_MAX_RETRIES)); |
| |
| if (invalid_remote < 15) { |
| __u32 seq; |
| |
| // Send non-blocking non-reply message to invalid remote LA |
| memset(&msg, 0xff, sizeof(msg)); |
| msg.msg[0] = (la << 4) | invalid_remote; |
| msg.timeout = 0; |
| msg.flags &= ~msg_fl_mask; |
| cec_msg_give_physical_addr(&msg, false); |
| fail_on_test(doioctl(node, CEC_TRANSMIT, &msg)); |
| fail_on_test(msg.len != 2); |
| fail_on_test(check_0(msg.msg + msg.len, sizeof(msg.msg) - msg.len)); |
| fail_on_test(msg.msg[0] != ((la << 4) | invalid_remote)); |
| fail_on_test(msg.msg[1] != CEC_MSG_GIVE_PHYSICAL_ADDR); |
| fail_on_test(msg.timeout); |
| fail_on_test(msg.flags); |
| fail_on_test(msg.sequence == 0); |
| fail_on_test(msg.sequence == ~0U); |
| fail_on_test(msg.reply); |
| fail_on_test(msg.tx_status); |
| fail_on_test(msg.rx_ts); |
| fail_on_test(msg.rx_status); |
| fail_on_test(msg.tx_ts); |
| fail_on_test(msg.tx_nack_cnt); |
| fail_on_test(msg.tx_arb_lost_cnt); |
| fail_on_test(msg.tx_low_drive_cnt); |
| fail_on_test(msg.tx_error_cnt); |
| seq = msg.sequence; |
| |
| sleep(1); |
| while (true) { |
| memset(&msg, 0xff, sizeof(msg)); |
| msg.timeout = 1500; |
| fail_on_test(doioctl(node, CEC_RECEIVE, &msg)); |
| if (msg.sequence == 0) |
| continue; |
| fail_on_test(msg.sequence != seq); |
| fail_on_test(msg.len != 2); |
| fail_on_test(check_0(msg.msg + msg.len, sizeof(msg.msg) - msg.len)); |
| fail_on_test(msg.msg[0] != ((la << 4) | invalid_remote)); |
| fail_on_test(msg.msg[1] != CEC_MSG_GIVE_PHYSICAL_ADDR); |
| fail_on_test(msg.timeout != 1500); |
| fail_on_test(msg.flags); |
| fail_on_test(msg.reply); |
| fail_on_test(!(msg.tx_status & CEC_TX_STATUS_MAX_RETRIES)); |
| fail_on_test((msg.tx_status & tx_ok_retry_mask) == tx_ok_retry_mask); |
| fail_on_test(msg.tx_nack_cnt == 0xff); |
| fail_on_test(msg.tx_arb_lost_cnt == 0xff); |
| fail_on_test(msg.tx_low_drive_cnt == 0xff); |
| fail_on_test(msg.tx_error_cnt == 0xff); |
| fail_on_test(msg.rx_ts); |
| fail_on_test(msg.rx_status); |
| break; |
| } |
| |
| // Send non-blocking reply message to invalid remote LA |
| memset(&msg, 0xff, sizeof(msg)); |
| msg.msg[0] = (la << 4) | invalid_remote; |
| msg.timeout = 0; |
| msg.flags &= ~msg_fl_mask; |
| cec_msg_give_physical_addr(&msg, true); |
| fail_on_test(doioctl(node, CEC_TRANSMIT, &msg)); |
| fail_on_test(msg.len != 2); |
| fail_on_test(check_0(msg.msg + msg.len, sizeof(msg.msg) - msg.len)); |
| fail_on_test(msg.msg[0] != ((la << 4) | invalid_remote)); |
| fail_on_test(msg.msg[1] != CEC_MSG_GIVE_PHYSICAL_ADDR); |
| fail_on_test(msg.timeout != 1000); |
| fail_on_test(msg.flags); |
| fail_on_test(msg.sequence == 0); |
| fail_on_test(msg.sequence == ~0U); |
| fail_on_test(msg.reply != CEC_MSG_REPORT_PHYSICAL_ADDR); |
| fail_on_test(msg.tx_status); |
| fail_on_test(msg.rx_ts); |
| fail_on_test(msg.rx_status); |
| fail_on_test(msg.tx_ts); |
| fail_on_test(msg.tx_nack_cnt); |
| fail_on_test(msg.tx_arb_lost_cnt); |
| fail_on_test(msg.tx_low_drive_cnt); |
| fail_on_test(msg.tx_error_cnt); |
| seq = msg.sequence; |
| |
| sleep(1); |
| while (true) { |
| memset(&msg, 0xff, sizeof(msg)); |
| msg.timeout = 1500; |
| fail_on_test(doioctl(node, CEC_RECEIVE, &msg)); |
| if (msg.sequence == 0) |
| continue; |
| fail_on_test(msg.sequence != seq); |
| fail_on_test(msg.len != 2); |
| fail_on_test(check_0(msg.msg + msg.len, sizeof(msg.msg) - msg.len)); |
| fail_on_test(msg.msg[0] != ((la << 4) | invalid_remote)); |
| fail_on_test(msg.msg[1] != CEC_MSG_GIVE_PHYSICAL_ADDR); |
| fail_on_test(msg.timeout != 1500); |
| fail_on_test(msg.flags); |
| fail_on_test(msg.reply != CEC_MSG_REPORT_PHYSICAL_ADDR); |
| fail_on_test(!(msg.tx_status & CEC_TX_STATUS_MAX_RETRIES)); |
| fail_on_test((msg.tx_status & tx_ok_retry_mask) == tx_ok_retry_mask); |
| fail_on_test(msg.tx_nack_cnt == 0xff); |
| fail_on_test(msg.tx_arb_lost_cnt == 0xff); |
| fail_on_test(msg.tx_low_drive_cnt == 0xff); |
| fail_on_test(msg.tx_error_cnt == 0xff); |
| fail_on_test(msg.rx_ts); |
| fail_on_test(msg.rx_status); |
| break; |
| } |
| |
| __u32 mode = CEC_MODE_INITIATOR | CEC_MODE_FOLLOWER; |
| |
| fail_on_test(doioctl(node, CEC_S_MODE, &mode)); |
| } |
| if (remote_la < 15) { |
| __u32 seq; |
| |
| // Send non-blocking non-reply message to valid remote LA |
| memset(&msg, 0xff, sizeof(msg)); |
| msg.msg[0] = (la << 4) | remote_la; |
| msg.timeout = 0; |
| msg.flags &= ~msg_fl_mask; |
| cec_msg_give_physical_addr(&msg, false); |
| fail_on_test(doioctl(node, CEC_TRANSMIT, &msg)); |
| fail_on_test(msg.len != 2); |
| fail_on_test(check_0(msg.msg + msg.len, sizeof(msg.msg) - msg.len)); |
| fail_on_test(msg.msg[0] != ((la << 4) | remote_la)); |
| fail_on_test(msg.msg[1] != CEC_MSG_GIVE_PHYSICAL_ADDR); |
| fail_on_test(msg.timeout); |
| fail_on_test(msg.flags); |
| fail_on_test(msg.sequence == 0); |
| fail_on_test(msg.sequence == ~0U); |
| fail_on_test(msg.reply); |
| fail_on_test(msg.tx_status); |
| fail_on_test(msg.rx_ts); |
| fail_on_test(msg.rx_status); |
| fail_on_test(msg.tx_ts); |
| fail_on_test(msg.tx_nack_cnt); |
| fail_on_test(msg.tx_arb_lost_cnt); |
| fail_on_test(msg.tx_low_drive_cnt); |
| fail_on_test(msg.tx_error_cnt); |
| seq = msg.sequence; |
| |
| sleep(1); |
| while (true) { |
| memset(&msg, 0xff, sizeof(msg)); |
| msg.timeout = 1500; |
| fail_on_test(doioctl(node, CEC_RECEIVE, &msg)); |
| if (msg.sequence == 0) |
| continue; |
| fail_on_test(msg.sequence != seq); |
| fail_on_test(msg.len != 2); |
| fail_on_test(check_0(msg.msg + msg.len, sizeof(msg.msg) - msg.len)); |
| fail_on_test(msg.msg[0] != ((la << 4) | remote_la)); |
| fail_on_test(msg.msg[1] != CEC_MSG_GIVE_PHYSICAL_ADDR); |
| fail_on_test(msg.timeout != 1500); |
| fail_on_test(msg.flags); |
| fail_on_test(msg.reply); |
| fail_on_test(!(msg.tx_status & CEC_TX_STATUS_OK)); |
| fail_on_test((msg.tx_status & tx_ok_retry_mask) == tx_ok_retry_mask); |
| fail_on_test(msg.tx_nack_cnt == 0xff); |
| fail_on_test(msg.tx_arb_lost_cnt == 0xff); |
| fail_on_test(msg.tx_low_drive_cnt == 0xff); |
| fail_on_test(msg.tx_error_cnt == 0xff); |
| fail_on_test(msg.rx_ts || msg.rx_status); |
| break; |
| } |
| |
| // Send non-blocking reply message to valid remote LA |
| memset(&msg, 0xff, sizeof(msg)); |
| msg.msg[0] = (la << 4) | remote_la; |
| msg.timeout = 0; |
| msg.flags &= ~msg_fl_mask; |
| cec_msg_give_physical_addr(&msg, true); |
| fail_on_test(doioctl(node, CEC_TRANSMIT, &msg)); |
| fail_on_test(msg.len != 2); |
| fail_on_test(check_0(msg.msg + msg.len, sizeof(msg.msg) - msg.len)); |
| fail_on_test(msg.msg[0] != ((la << 4) | remote_la)); |
| fail_on_test(msg.msg[1] != CEC_MSG_GIVE_PHYSICAL_ADDR); |
| fail_on_test(msg.timeout != 1000); |
| fail_on_test(msg.flags); |
| fail_on_test(msg.sequence == 0); |
| fail_on_test(msg.sequence == ~0U); |
| fail_on_test(msg.reply != CEC_MSG_REPORT_PHYSICAL_ADDR); |
| fail_on_test(msg.tx_status); |
| fail_on_test(msg.rx_ts); |
| fail_on_test(msg.rx_status); |
| fail_on_test(msg.tx_ts); |
| fail_on_test(msg.tx_nack_cnt); |
| fail_on_test(msg.tx_arb_lost_cnt); |
| fail_on_test(msg.tx_low_drive_cnt); |
| fail_on_test(msg.tx_error_cnt); |
| seq = msg.sequence; |
| |
| sleep(1); |
| while (true) { |
| memset(&msg, 0xff, sizeof(msg)); |
| msg.timeout = 1500; |
| fail_on_test(doioctl(node, CEC_RECEIVE, &msg)); |
| if (msg.sequence == 0) |
| continue; |
| fail_on_test(msg.sequence != seq); |
| fail_on_test(msg.len != 5); |
| fail_on_test(check_0(msg.msg + msg.len, sizeof(msg.msg) - msg.len)); |
| fail_on_test(msg.msg[0] != ((remote_la << 4) | 0xf)); |
| fail_on_test(msg.msg[1] != CEC_MSG_REPORT_PHYSICAL_ADDR); |
| fail_on_test(msg.timeout != 1500); |
| fail_on_test(msg.flags); |
| fail_on_test(msg.reply != CEC_MSG_REPORT_PHYSICAL_ADDR); |
| fail_on_test(!(msg.tx_status & CEC_TX_STATUS_OK)); |
| fail_on_test((msg.tx_status & tx_ok_retry_mask) == tx_ok_retry_mask); |
| fail_on_test(msg.tx_nack_cnt == 0xff); |
| fail_on_test(msg.tx_arb_lost_cnt == 0xff); |
| fail_on_test(msg.tx_low_drive_cnt == 0xff); |
| fail_on_test(msg.tx_error_cnt == 0xff); |
| fail_on_test(msg.rx_ts == 0); |
| fail_on_test(msg.rx_ts == ~0ULL); |
| fail_on_test(msg.rx_status != CEC_RX_STATUS_OK); |
| fail_on_test(msg.rx_ts < msg.tx_ts); |
| break; |
| } |
| |
| __u32 mode = CEC_MODE_INITIATOR | CEC_MODE_FOLLOWER; |
| |
| fail_on_test(doioctl(node, CEC_S_MODE, &mode)); |
| } |
| if (remote_la == 15 || invalid_remote == 15) |
| return OK_PRESUMED; |
| return 0; |
| } |
| |
| static int testModes(struct node *node, struct node *node2) |
| { |
| struct cec_msg msg; |
| __u8 me = node->log_addr[0]; |
| __u8 remote = CEC_LOG_ADDR_INVALID; |
| __u32 mode = 0, m; |
| |
| for (unsigned i = 0; i < 15; i++) { |
| if (node->remote_la_mask & (1 << i)) { |
| remote = i; |
| break; |
| } |
| } |
| |
| cec_msg_init(&msg, me, 0); |
| fail_on_test(doioctl(node, CEC_TRANSMIT, &msg)); |
| fail_on_test(doioctl(node2, CEC_TRANSMIT, &msg)); |
| |
| fail_on_test(doioctl(node, CEC_G_MODE, &m)); |
| fail_on_test(m != CEC_MODE_INITIATOR); |
| fail_on_test(doioctl(node2, CEC_G_MODE, &m)); |
| fail_on_test(m != CEC_MODE_INITIATOR); |
| |
| fail_on_test(doioctl(node, CEC_S_MODE, &mode)); |
| fail_on_test(doioctl(node, CEC_G_MODE, &m)); |
| fail_on_test(m != mode); |
| fail_on_test(doioctl(node, CEC_TRANSMIT, &msg) != EBUSY); |
| fail_on_test(doioctl(node2, CEC_TRANSMIT, &msg)); |
| |
| mode = CEC_MODE_INITIATOR; |
| fail_on_test(doioctl(node, CEC_S_MODE, &mode)); |
| fail_on_test(doioctl(node, CEC_G_MODE, &m)); |
| fail_on_test(m != mode); |
| fail_on_test(doioctl(node, CEC_TRANSMIT, &msg)); |
| |
| mode = CEC_MODE_EXCL_INITIATOR; |
| fail_on_test(doioctl(node, CEC_S_MODE, &mode)); |
| fail_on_test(doioctl(node, CEC_G_MODE, &m)); |
| fail_on_test(m != mode); |
| fail_on_test(doioctl(node, CEC_TRANSMIT, &msg)); |
| fail_on_test(doioctl(node2, CEC_TRANSMIT, &msg) != EBUSY); |
| |
| mode = CEC_MODE_INITIATOR; |
| fail_on_test(doioctl(node, CEC_S_MODE, &mode)); |
| fail_on_test(doioctl(node, CEC_G_MODE, &m)); |
| fail_on_test(m != mode); |
| |
| if (remote == CEC_LOG_ADDR_INVALID) |
| return 0; |
| |
| fail_on_test(flush_pending_msgs(node)); |
| cec_msg_init(&msg, me, remote); |
| cec_msg_give_physical_addr(&msg, false); |
| fail_on_test(doioctl(node, CEC_TRANSMIT, &msg)); |
| msg.timeout = 1200; |
| fail_on_test(doioctl(node, CEC_RECEIVE, &msg) != ETIMEDOUT); |
| |
| mode = CEC_MODE_INITIATOR | CEC_MODE_FOLLOWER; |
| fail_on_test(doioctl(node, CEC_S_MODE, &mode)); |
| fail_on_test(doioctl(node, CEC_G_MODE, &m)); |
| fail_on_test(m != mode); |
| |
| fail_on_test(flush_pending_msgs(node)); |
| cec_msg_init(&msg, me, remote); |
| cec_msg_give_physical_addr(&msg, false); |
| fail_on_test(doioctl(node, CEC_TRANSMIT, &msg)); |
| msg.timeout = 1200; |
| fail_on_test(doioctl(node, CEC_RECEIVE, &msg)); |
| fail_on_test(msg.msg[1] != CEC_MSG_REPORT_PHYSICAL_ADDR); |
| |
| mode = CEC_MODE_INITIATOR | CEC_MODE_FOLLOWER; |
| fail_on_test(doioctl(node2, CEC_S_MODE, &mode)); |
| fail_on_test(doioctl(node2, CEC_G_MODE, &m)); |
| fail_on_test(m != mode); |
| |
| fail_on_test(flush_pending_msgs(node)); |
| fail_on_test(flush_pending_msgs(node2)); |
| cec_msg_init(&msg, me, remote); |
| cec_msg_give_physical_addr(&msg, false); |
| fail_on_test(doioctl(node, CEC_TRANSMIT, &msg)); |
| msg.timeout = 1200; |
| fail_on_test(doioctl(node, CEC_RECEIVE, &msg)); |
| fail_on_test(msg.msg[1] != CEC_MSG_REPORT_PHYSICAL_ADDR); |
| msg.timeout = 1; |
| fail_on_test(doioctl(node2, CEC_RECEIVE, &msg)); |
| fail_on_test(msg.msg[1] != CEC_MSG_REPORT_PHYSICAL_ADDR); |
| |
| mode = CEC_MODE_INITIATOR | CEC_MODE_EXCL_FOLLOWER; |
| fail_on_test(doioctl(node, CEC_S_MODE, &mode)); |
| fail_on_test(doioctl(node, CEC_G_MODE, &m)); |
| fail_on_test(m != mode); |
| |
| fail_on_test(flush_pending_msgs(node)); |
| fail_on_test(flush_pending_msgs(node2)); |
| cec_msg_init(&msg, me, remote); |
| cec_msg_give_physical_addr(&msg, false); |
| fail_on_test(doioctl(node, CEC_TRANSMIT, &msg)); |
| msg.timeout = 1200; |
| fail_on_test(doioctl(node, CEC_RECEIVE, &msg)); |
| fail_on_test(msg.msg[1] != CEC_MSG_REPORT_PHYSICAL_ADDR); |
| msg.timeout = 1; |
| fail_on_test(doioctl(node2, CEC_RECEIVE, &msg) != ETIMEDOUT); |
| |
| mode = CEC_MODE_INITIATOR | CEC_MODE_EXCL_FOLLOWER_PASSTHRU; |
| fail_on_test(doioctl(node, CEC_S_MODE, &mode)); |
| fail_on_test(doioctl(node, CEC_G_MODE, &m)); |
| fail_on_test(m != mode); |
| |
| // Note: this test is the same as for CEC_MODE_EXCL_FOLLOWER. |
| // Ideally the remote device would send us a passthrough message, |
| // but there is no way to trigger that. |
| fail_on_test(flush_pending_msgs(node)); |
| fail_on_test(flush_pending_msgs(node2)); |
| cec_msg_init(&msg, me, remote); |
| cec_msg_give_physical_addr(&msg, false); |
| fail_on_test(doioctl(node, CEC_TRANSMIT, &msg)); |
| msg.timeout = 1200; |
| fail_on_test(doioctl(node, CEC_RECEIVE, &msg)); |
| fail_on_test(msg.msg[1] != CEC_MSG_REPORT_PHYSICAL_ADDR); |
| msg.timeout = 1; |
| fail_on_test(doioctl(node2, CEC_RECEIVE, &msg) != ETIMEDOUT); |
| |
| mode = CEC_MODE_INITIATOR; |
| fail_on_test(doioctl(node, CEC_S_MODE, &mode)); |
| fail_on_test(doioctl(node, CEC_G_MODE, &m)); |
| fail_on_test(m != mode); |
| |
| mode = CEC_MODE_INITIATOR | CEC_MODE_MONITOR; |
| fail_on_test(doioctl(node2, CEC_S_MODE, &mode) != EINVAL); |
| fail_on_test(doioctl(node2, CEC_G_MODE, &m)); |
| fail_on_test(m != (CEC_MODE_INITIATOR | CEC_MODE_FOLLOWER)); |
| |
| bool is_root = getuid() == 0; |
| |
| mode = CEC_MODE_MONITOR; |
| fail_on_test(doioctl(node2, CEC_S_MODE, &mode) != (is_root ? 0 : EPERM)); |
| |
| if (is_root) { |
| fail_on_test(doioctl(node2, CEC_G_MODE, &m)); |
| fail_on_test(m != mode); |
| fail_on_test(flush_pending_msgs(node2)); |
| cec_msg_init(&msg, me, remote); |
| cec_msg_give_physical_addr(&msg, false); |
| fail_on_test(doioctl(node, CEC_TRANSMIT, &msg)); |
| msg.timeout = 1200; |
| fail_on_test(doioctl(node2, CEC_RECEIVE, &msg)); |
| fail_on_test(msg.msg[1] != CEC_MSG_GIVE_PHYSICAL_ADDR); |
| while (true) { |
| fail_on_test(doioctl(node2, CEC_RECEIVE, &msg)); |
| if (msg.msg[1] == CEC_MSG_REPORT_PHYSICAL_ADDR) |
| break; |
| fail_on_test(!cec_msg_is_broadcast(&msg) && |
| !(node2->adap_la_mask & (1 << cec_msg_destination(&msg)))); |
| } |
| } |
| |
| mode = CEC_MODE_MONITOR_ALL; |
| int res = doioctl(node2, CEC_S_MODE, &mode); |
| if (node2->caps & CEC_CAP_MONITOR_ALL) |
| fail_on_test(res != (is_root ? 0 : EPERM)); |
| else |
| fail_on_test(res != EINVAL); |
| |
| if (is_root && (node2->caps & CEC_CAP_MONITOR_ALL)) { |
| fail_on_test(doioctl(node2, CEC_G_MODE, &m)); |
| fail_on_test(m != mode); |
| fail_on_test(flush_pending_msgs(node2)); |
| cec_msg_init(&msg, me, remote); |
| cec_msg_give_physical_addr(&msg, false); |
| fail_on_test(doioctl(node, CEC_TRANSMIT, &msg)); |
| msg.timeout = 1200; |
| fail_on_test(doioctl(node2, CEC_RECEIVE, &msg)); |
| fail_on_test(msg.msg[1] != CEC_MSG_GIVE_PHYSICAL_ADDR); |
| while (true) { |
| fail_on_test(doioctl(node2, CEC_RECEIVE, &msg)); |
| if (msg.msg[1] == CEC_MSG_REPORT_PHYSICAL_ADDR) |
| break; |
| } |
| } |
| return 0; |
| } |
| |
| static void print_sfts(unsigned sft[12], const char *descr) |
| { |
| std::string s; |
| char num[20]; |
| |
| for (unsigned i = 0; i < 12; i++) { |
| if (sft[i] == 0) |
| continue; |
| if (!s.empty()) |
| s += ", "; |
| sprintf(num, "%u: %d", i, sft[i]); |
| s += num; |
| } |
| if (!s.empty()) |
| printf("\t\t%s: %s\n", descr, s.c_str()); |
| } |
| |
| // testLostMsgs() checks if the CEC_EVENT_LOST_MSGS event works. But it |
| // actually tests a lot more: it also checks Signal Free Time behavior |
| // and if the speed of the CEC bus is as expected. |
| |
| // Some defines dealing with SFTs (from include/media/cec.h): |
| // Correct Signal Free times are: |
| #define CEC_SIGNAL_FREE_TIME_RETRY 3 |
| #define CEC_SIGNAL_FREE_TIME_NEW_INITIATOR 5 |
| #define CEC_SIGNAL_FREE_TIME_NEXT_XFER 7 |
| // but for measuring we support up to 11: |
| #define CEC_SIGNAL_FREE_TIME_MAX 11 |
| |
| // Two defines (copied from include/media/cec.h) that give the maximum |
| // number of CEC messages that can be queued up in the transmit and |
| // receive queues (this is per filehandle): |
| #define CEC_MAX_MSG_TX_QUEUE_SZ (18 * 1) |
| #define CEC_MAX_MSG_RX_QUEUE_SZ (18 * 3) |
| |
| static int testLostMsgs(struct node *node) |
| { |
| struct cec_msg msg; |
| struct cec_event ev; |
| __u8 me = node->log_addr[0]; |
| __u8 remote = CEC_LOG_ADDR_INVALID; |
| __u32 mode = CEC_MODE_INITIATOR | CEC_MODE_FOLLOWER; |
| // Store counts of detected SFTs: the first dimension |
| // is whether it is a repeating initiator (1) or not (0). |
| // The second dimension is whether it is a transmit (1) |
| // or not (0). |
| // The third dimension is a count of the corresponding SFT |
| // (0-CEC_SIGNAL_FREE_TIME_MAX). |
| unsigned sft[2][2][CEC_SIGNAL_FREE_TIME_MAX + 1] = {}; |
| |
| // Find the first available remote LA to use for this test |
| for (unsigned i = 0; i < 15; i++) { |
| if (node->remote_la_mask & (1 << i)) { |
| remote = i; |
| break; |
| } |
| } |
| |
| fail_on_test(flush_pending_msgs(node)); |
| // We now switch to non-blocking mode. |
| // This will cause CEC_TRANSMIT to return at once, and when the |
| // transmit is done it will appear in the receive queue. |
| fcntl(node->fd, F_SETFL, fcntl(node->fd, F_GETFL) | O_NONBLOCK); |
| |
| // Become follower for this test, otherwise all the replies to |
| // the GET_CEC_VERSION message would have been 'Feature Abort'ed, |
| // unless some other follower was running. By becoming a follower |
| // all the CEC_VERSION replies are just ignored. |
| fail_on_test(doioctl(node, CEC_S_MODE, &mode)); |
| cec_msg_init(&msg, me, remote); |
| cec_msg_get_cec_version(&msg, false); |
| |
| // flush pending events |
| while (doioctl(node, CEC_DQEVENT, &ev) == 0) { } |
| |
| bool got_busy = false; |
| unsigned xfer_cnt = 0; |
| unsigned tx_queue_depth = 0; |
| unsigned tx_first_seq = 0; |
| unsigned rx_first_seq = 0; |
| |
| // Transmit <Get CEC Version> until an event was queued up. |
| // We never dequeue received messages, so once the receive |
| // queue is full the LOST_MSGS event is queued by the CEC |
| // core framework, at which point we stop. |
| do { |
| int res; |
| |
| do { |
| res = doioctl(node, CEC_TRANSMIT, &msg); |
| |
| fail_on_test(res && res != EBUSY); |
| if (!res) { |
| if (!xfer_cnt) |
| tx_first_seq = msg.sequence; |
| xfer_cnt++; |
| } |
| |
| if (res == EBUSY) { |
| // If the CEC device was busy (i.e. the transmit |
| // queue was full), then wait 10 ms and try again. |
| struct timeval tv = { 0, 10000 }; // 10 ms |
| |
| select(0, nullptr, nullptr, nullptr, &tv); |
| // Mark that we got a busy error |
| got_busy = true; |
| } else if (!got_busy) { |
| // If we didn't get a EBUSY yet, then increment this |
| // variable to detect the TX queue depth. |
| tx_queue_depth++; |
| } |
| // Keep retrying as long as the transmit failed with EBUSY. |
| } while (res == EBUSY); |
| // Keep transmitting until an event was queued |
| } while (doioctl(node, CEC_DQEVENT, &ev)); |
| // We expect the LOST_MSGS event due to a full RX queue. |
| fail_on_test(ev.event != CEC_EVENT_LOST_MSGS); |
| // Since the transmit queue is one-third of the receive queue, |
| // it would be very weird if we never got an EBUSY error. |
| fail_on_test(!got_busy); |
| |
| /* |
| * No more than max 18 transmits can be queued, but one message |
| * might finish transmitting before the queue fills up, so |
| * check for 19 instead. |
| */ |
| fail_on_test(tx_queue_depth == 0 || tx_queue_depth > CEC_MAX_MSG_TX_QUEUE_SZ + 1); |
| |
| if (show_info) |
| printf("\n\t\tFinished transmitting\n\n"); |
| |
| unsigned pending_msgs = 0; |
| unsigned pending_quick_msgs = 0; |
| unsigned pending_tx_ok_msgs = 0; |
| unsigned pending_tx_timed_out_msgs = 0; |
| unsigned pending_tx_arb_lost_msgs = 0; |
| unsigned pending_tx_nack_msgs = 0; |
| unsigned pending_tx_low_drive_msgs = 0; |
| unsigned pending_tx_error_msgs = 0; |
| unsigned pending_tx_aborted_msgs = 0; |
| unsigned pending_tx_rx_msgs = 0; |
| unsigned pending_rx_msgs = 0; |
| unsigned pending_rx_cec_version_msgs = 0; |
| time_t start = time(nullptr); |
| __u8 last_init = 0xff; |
| __u64 last_ts = 0; |
| unsigned tx_repeats = 0; |
| |
| // Now we start reading from the receive queue and analyze the |
| // timestamps. Remember that the receive queue contains both |
| // the finished transmit messages from the local LA ('me') and |
| // the received replies from the remote LA. |
| // |
| // We do the while-loop twice, the first time (i == 0) the file handle |
| // is still in non-blocking mode and so we effectively just drain the |
| // receive queue quickly. Then we switch to blocking mode (i == 1) and |
| // keep receiving messages (waiting up to 3 seconds) until no more |
| // messages are received. |
| for (unsigned i = 0; i < 2; i++) { |
| // note: timeout is ignored in non-blocking mode (i == 0) |
| msg.timeout = 3000; |
| |
| while (!doioctl(node, CEC_RECEIVE, &msg)) { |
| __u64 ts = msg.tx_ts ? : msg.rx_ts; |
| __u8 initiator = cec_msg_initiator(&msg); |
| bool ok_or_nack = (msg.tx_status & (CEC_TX_STATUS_OK | CEC_TX_STATUS_NACK)) || |
| (msg.rx_status & CEC_RX_STATUS_OK); |
| |
| if (last_ts && ok_or_nack) { |
| __u64 delta = last_ts ? ts - last_ts : 0; |
| |
| // The timestamp ts is when the transmit was done |
| // or the message was received. |
| // To get the time when the message started transmitting |
| // we subtract the time spent transmitting the message. |
| delta -= msg.len * 24000000ULL + 4500000ULL; |
| //unsigned sft_real = (delta + 1200000ULL) / 2400000ULL; |
| unsigned sft_real = delta / 2400000ULL; |
| |
| // Count the detected SFT in the sft array. |
| if (last_ts && sft_real <= CEC_SIGNAL_FREE_TIME_MAX) |
| sft[last_init == initiator][initiator == me][sft_real]++; |
| |
| if (!i && last_ts) { |
| if (last_init == initiator && initiator == me) { |
| tx_repeats++; |
| } else { |
| // Too many repeated transmits means that |
| // the transmitter is hogging the bus, preventing |
| // the remote LA to transmit the reply. |
| if (tx_repeats > 2) |
| warn("Too many transmits (%d) without receives\n", |
| tx_repeats); |
| tx_repeats = 0; |
| } |
| } |
| } |
| if (ok_or_nack) { |
| last_ts = ts; |
| last_init = initiator; |
| } |
| |
| // Count total number of received messages (both non-blocking |
| // transmit results and actual received replies). |
| pending_msgs++; |
| // Count number of received msgs when quickly draining the |
| // receive queue in the non-blocking phase. |
| if (i == 0) { |
| // The result of the first transmit will also be the |
| // message that was lost since it was the oldest: check |
| // that that's actually the case. |
| if (!rx_first_seq && msg.sequence) |
| rx_first_seq = msg.sequence; |
| pending_quick_msgs++; |
| } |
| if (!msg.sequence) { |
| // Count remote messages and the number of |
| // received <CEC Version> messages. |
| pending_rx_msgs++; |
| if (msg.len == 3 && msg.msg[1] == CEC_MSG_CEC_VERSION) |
| pending_rx_cec_version_msgs++; |
| } |
| else if (msg.tx_status & CEC_TX_STATUS_TIMEOUT) |
| pending_tx_timed_out_msgs++; |
| else if (msg.tx_status & CEC_TX_STATUS_ABORTED) |
| pending_tx_aborted_msgs++; |
| else if (msg.tx_status & CEC_TX_STATUS_OK) { |
| pending_tx_ok_msgs++; |
| if (msg.rx_status) |
| pending_tx_rx_msgs++; |
| } |
| if (msg.tx_status & CEC_TX_STATUS_NACK) |
| pending_tx_nack_msgs += msg.tx_nack_cnt; |
| if (msg.tx_status & CEC_TX_STATUS_ARB_LOST) |
| pending_tx_arb_lost_msgs += msg.tx_arb_lost_cnt; |
| if (msg.tx_status & CEC_TX_STATUS_LOW_DRIVE) |
| pending_tx_low_drive_msgs += msg.tx_low_drive_cnt; |
| if (msg.tx_status & CEC_TX_STATUS_ERROR) |
| pending_tx_error_msgs += msg.tx_error_cnt; |
| } |
| // Go back to blocking mode after draining the receive queue. |
| fcntl(node->fd, F_SETFL, fcntl(node->fd, F_GETFL) & ~O_NONBLOCK); |
| if (!i && show_info) |
| printf("\n\t\tDrained receive queue\n\n"); |
| } |
| |
| /* |
| * Should be at least the size of the internal message queue and |
| * close to the number of transmitted messages. There should also be |
| * no timed out or aborted transmits. |
| */ |
| |
| bool fail_msg = pending_tx_error_msgs || pending_tx_timed_out_msgs || pending_tx_aborted_msgs || |
| pending_tx_rx_msgs || pending_quick_msgs < CEC_MAX_MSG_RX_QUEUE_SZ || |
| pending_rx_cec_version_msgs > xfer_cnt; |
| bool warn_msg = pending_rx_cec_version_msgs < xfer_cnt - 2; |
| |
| if (fail_msg || warn_msg || show_info) { |
| if (show_info) |
| printf("\n"); |
| if (pending_tx_ok_msgs) { |
| printf("\t\tSuccessful transmits: %d\n", pending_tx_ok_msgs); |
| if (pending_tx_rx_msgs) |
| printf("\t\t\tUnexpected replies: %d\n", pending_tx_rx_msgs); |
| } |
| if (pending_tx_nack_msgs) |
| printf("\t\tNACKed transmits: %d\n", pending_tx_nack_msgs); |
| if (pending_tx_timed_out_msgs) |
| printf("\t\tTimed out transmits: %d\n", pending_tx_timed_out_msgs); |
| if (pending_tx_aborted_msgs) |
| printf("\t\tAborted transmits: %d\n", pending_tx_aborted_msgs); |
| if (pending_tx_arb_lost_msgs) |
| printf("\t\tArbitration Lost transmits: %d\n", pending_tx_arb_lost_msgs); |
| if (pending_tx_low_drive_msgs) |
| printf("\t\tLow Drive transmits: %d\n", pending_tx_low_drive_msgs); |
| if (pending_tx_error_msgs) |
| printf("\t\tError transmits: %d\n", pending_tx_error_msgs); |
| if (pending_rx_msgs) |
| printf("\t\tReceived messages: %d of which %d were CEC_MSG_CEC_VERSION\n", |
| pending_rx_msgs, pending_rx_cec_version_msgs); |
| if (pending_quick_msgs < pending_msgs) |
| printf("\t\tReceived %d messages immediately, and %d over %ld seconds\n", |
| pending_quick_msgs, pending_msgs - pending_quick_msgs, |
| time(nullptr) - start); |
| } |
| print_sfts(sft[1][1], "SFTs for repeating messages (>= 7)"); |
| print_sfts(sft[1][0], "SFTs for repeating remote messages (>= 7)"); |
| print_sfts(sft[0][1], "SFTs for newly transmitted messages (>= 5)"); |
| print_sfts(sft[0][0], "SFTs for newly transmitted remote messages (>= 5)"); |
| if (fail_msg) |
| return fail("There were %d messages in the receive queue for %d transmits\n", |
| pending_msgs, xfer_cnt); |
| if (warn_msg) |
| warn("There were %d CEC_GET_VERSION transmits but only %d CEC_VERSION receives\n", |
| xfer_cnt, pending_rx_cec_version_msgs); |
| |
| // Final check if everything else is OK: check that only the oldest |
| // message (the result of the first transmit) was dropped in the |
| // receive queue. |
| if (!fail_msg && !warn_msg) |
| warn_on_test(rx_first_seq != tx_first_seq + 1); |
| return 0; |
| } |
| |
| void testAdapter(struct node &node, struct cec_log_addrs &laddrs, |
| const char *device) |
| { |
| /* Required ioctls */ |
| |
| printf("\nCEC API:\n"); |
| printf("\tCEC_ADAP_G_CAPS: %s\n", ok(testCap(&node))); |
| printf("\tInvalid ioctls: %s\n", ok(testInvalidIoctls(&node))); |
| printf("\tCEC_DQEVENT: %s\n", ok(testDQEvent(&node))); |
| printf("\tCEC_ADAP_G/S_PHYS_ADDR: %s\n", ok(testAdapPhysAddr(&node))); |
| if (node.caps & CEC_CAP_PHYS_ADDR) |
| doioctl(&node, CEC_ADAP_S_PHYS_ADDR, &node.phys_addr); |
| if (node.phys_addr == CEC_PHYS_ADDR_INVALID) { |
| fprintf(stderr, "FAIL: without a valid physical address this test cannot proceed.\n"); |
| fprintf(stderr, "Make sure that this CEC adapter is connected to another HDMI sink or source.\n"); |
| std::exit(EXIT_FAILURE); |
| } |
| printf("\tCEC_ADAP_G/S_LOG_ADDRS: %s\n", ok(testAdapLogAddrs(&node))); |
| fcntl(node.fd, F_SETFL, fcntl(node.fd, F_GETFL) & ~O_NONBLOCK); |
| sleep(1); |
| if (node.caps & CEC_CAP_LOG_ADDRS) { |
| struct cec_log_addrs clear = { }; |
| |
| doioctl(&node, CEC_ADAP_S_LOG_ADDRS, &clear); |
| doioctl(&node, CEC_ADAP_S_LOG_ADDRS, &laddrs); |
| } |
| doioctl(&node, CEC_ADAP_G_LOG_ADDRS, &laddrs); |
| if (laddrs.log_addr_mask != node.adap_la_mask) |
| printf("\tNew Logical Address Mask : 0x%04x\n", laddrs.log_addr_mask); |
| // The LAs may have changed after these tests, so update these node fields |
| node.num_log_addrs = laddrs.num_log_addrs; |
| memcpy(node.log_addr, laddrs.log_addr, laddrs.num_log_addrs); |
| node.adap_la_mask = laddrs.log_addr_mask; |
| |
| printf("\tCEC_TRANSMIT: %s\n", ok(testTransmit(&node))); |
| printf("\tCEC_RECEIVE: %s\n", ok(testReceive(&node))); |
| __u32 mode = CEC_MODE_INITIATOR; |
| doioctl(&node, CEC_S_MODE, &mode); |
| printf("\tCEC_TRANSMIT/RECEIVE (non-blocking): %s\n", ok(testNonBlocking(&node))); |
| fcntl(node.fd, F_SETFL, fcntl(node.fd, F_GETFL) & ~O_NONBLOCK); |
| doioctl(&node, CEC_S_MODE, &mode); |
| |
| struct node node2 = node; |
| |
| if ((node2.fd = open(device, O_RDWR)) < 0) { |
| fprintf(stderr, "Failed to open %s: %s\n", device, |
| strerror(errno)); |
| std::exit(EXIT_FAILURE); |
| } |
| |
| printf("\tCEC_G/S_MODE: %s\n", ok(testModes(&node, &node2))); |
| close(node2.fd); |
| doioctl(&node, CEC_S_MODE, &mode); |
| |
| printf("\tCEC_EVENT_LOST_MSGS: %s\n", ok(testLostMsgs(&node))); |
| fcntl(node.fd, F_SETFL, fcntl(node.fd, F_GETFL) & ~O_NONBLOCK); |
| doioctl(&node, CEC_S_MODE, &mode); |
| } |