// SPDX-License-Identifier: GPL-2.0-only /* * Copyright 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. */ #include #include #include #include #include #include #include #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 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 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 %llu seconds\n", pending_quick_msgs, pending_msgs - pending_quick_msgs, (__u64)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); }