/* * Copyright (c) 2020 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #include #include #include #include DEFINE_FFF_GLOBALS; /** * @brief Tests for the radio.c - OpenThread radio api * @defgroup openthread_tests radio * @ingroup all_tests * @{ */ #define ACK_PKT_LENGTH 3 #define FRAME_TYPE_MASK 0x07 #define FRAME_TYPE_ACK 0x02 K_SEM_DEFINE(ot_sem, 0, 1); /** * Fake pointer as it should not be accessed by the code. * Should not be null to be sure it was properly passed. */ otInstance *ot = (otInstance *)0xAAAA; otMessage *ip_msg = (otMessage *)0xBBBB; /* forward declarations */ FAKE_VALUE_FUNC(int, scan_mock, const struct device *, uint16_t, energy_scan_done_cb_t); FAKE_VALUE_FUNC(int, cca_mock, const struct device *); FAKE_VALUE_FUNC(int, set_channel_mock, const struct device *, uint16_t); FAKE_VALUE_FUNC(int, filter_mock, const struct device *, bool, enum ieee802154_filter_type, const struct ieee802154_filter *); FAKE_VALUE_FUNC(int, set_txpower_mock, const struct device *, int16_t); FAKE_VALUE_FUNC(int64_t, get_time_mock, const struct device *); FAKE_VALUE_FUNC(int, tx_mock, const struct device *, enum ieee802154_tx_mode, struct net_pkt *, struct net_buf *); FAKE_VALUE_FUNC(int, start_mock, const struct device *); FAKE_VALUE_FUNC(int, stop_mock, const struct device *); FAKE_VALUE_FUNC(int, configure_mock, const struct device *, enum ieee802154_config_type, const struct ieee802154_config *); FAKE_VALUE_FUNC(enum ieee802154_hw_caps, get_capabilities_caps_mock, const struct device *); static enum ieee802154_hw_caps get_capabilities(const struct device *dev); /* mocks */ static struct ieee802154_radio_api rapi = {.get_capabilities = get_capabilities, .cca = cca_mock, .set_channel = set_channel_mock, .filter = filter_mock, .set_txpower = set_txpower_mock, .get_time = get_time_mock, .tx = tx_mock, .start = start_mock, .stop = stop_mock, .configure = configure_mock, .ed_scan = scan_mock}; #define DT_DRV_COMPAT vnd_ieee802154 DEVICE_DT_INST_DEFINE(0, NULL, NULL, NULL, NULL, POST_KERNEL, 0, &rapi); static const struct device *const radio = DEVICE_DT_INST_GET(0); static int16_t rssi_scan_mock_max_ed; static int rssi_scan_mock(const struct device *dev, uint16_t duration, energy_scan_done_cb_t done_cb) { zassert_equal(dev, radio, "Device handle incorrect."); zassert_equal(duration, 1, "otPlatRadioGetRssi shall pass minimal allowed value."); /* use return value as callback param */ done_cb(radio, rssi_scan_mock_max_ed); return 0; } FAKE_VOID_FUNC(otPlatRadioEnergyScanDone, otInstance *, int8_t); void otSysEventSignalPending(void) { k_sem_give(&ot_sem); } void otTaskletsSignalPending(otInstance *aInstance) { zassert_equal(aInstance, ot, "Incorrect instance."); k_sem_give(&ot_sem); } static void make_sure_sem_set(k_timeout_t timeout) { zassert_equal(k_sem_take(&ot_sem, timeout), 0, "Sem not released."); } static otRadioFrame otPlatRadioReceiveDone_expected_aframe; static otError otPlatRadioReceiveDone_expected_error; void otPlatRadioReceiveDone(otInstance *aInstance, otRadioFrame *aFrame, otError aError) { zassert_equal(aInstance, ot, "Incorrect instance."); zassert_equal(otPlatRadioReceiveDone_expected_aframe.mChannel, aFrame->mChannel); zassert_equal(otPlatRadioReceiveDone_expected_aframe.mLength, aFrame->mLength); zassert_mem_equal(otPlatRadioReceiveDone_expected_aframe.mPsdu, aFrame->mPsdu, aFrame->mLength, NULL); zassert_equal(otPlatRadioReceiveDone_expected_error, aError); } FAKE_VOID_FUNC(otPlatRadioTxDone, otInstance *, otRadioFrame *, otRadioFrame *, otError); static enum ieee802154_hw_caps get_capabilities(const struct device *dev) { enum ieee802154_hw_caps caps; zassert_equal(dev, radio, "Device handle incorrect."); caps = IEEE802154_HW_FCS | IEEE802154_HW_TX_RX_ACK | IEEE802154_HW_FILTER | IEEE802154_HW_ENERGY_SCAN | IEEE802154_HW_SLEEP_TO_TX; if (IS_ENABLED(CONFIG_NET_PKT_TXTIME)) { caps |= IEEE802154_HW_TXTIME; } return caps; } FAKE_VALUE_FUNC(otError, otIp6Send, otInstance *, otMessage *); otMessage *otIp6NewMessage(otInstance *aInstance, const otMessageSettings *aSettings) { zassert_equal(aInstance, ot, "Incorrect instance."); return ip_msg; } FAKE_VALUE_FUNC(otError, otMessageAppend, otMessage *, const void *, uint16_t); FAKE_VOID_FUNC(otMessageFree, otMessage *); void otPlatRadioTxStarted(otInstance *aInstance, otRadioFrame *aFrame) { zassert_equal(aInstance, ot, "Incorrect instance."); } /** * @brief Test for immediate energy scan * Tests for case when radio energy scan returns success at the first call. * */ ZTEST(openthread_radio, test_energy_scan_immediate_test) { const uint8_t chan = 10; const uint8_t dur = 100; const int16_t energy = -94; set_channel_mock_fake.return_val = 0; scan_mock_fake.return_val = 0; zassert_equal(otPlatRadioEnergyScan(ot, chan, dur), OT_ERROR_NONE, "Energy scan returned error."); zassert_equal(1, scan_mock_fake.call_count); zassert_equal(dur, scan_mock_fake.arg1_val); zassert_not_null(scan_mock_fake.arg2_val, "Scan callback not specified."); zassert_equal(1, set_channel_mock_fake.call_count); zassert_equal(chan, set_channel_mock_fake.arg1_val); scan_mock_fake.arg2_val(radio, energy); make_sure_sem_set(K_NO_WAIT); platformRadioProcess(ot); zassert_equal(1, otPlatRadioEnergyScanDone_fake.call_count); zassert_equal_ptr(ot, otPlatRadioEnergyScanDone_fake.arg0_val, NULL); zassert_equal(energy, otPlatRadioEnergyScanDone_fake.arg1_val); } /** * @brief Test for delayed energy scan * Tests for case when radio returns not being able to start energy scan and * the scan should be scheduled for later. * */ ZTEST(openthread_radio, test_energy_scan_delayed_test) { const uint8_t chan = 10; const uint8_t dur = 100; const int16_t energy = -94; /* request scan */ set_channel_mock_fake.return_val = 0; scan_mock_fake.return_val = -EBUSY; zassert_equal(otPlatRadioEnergyScan(ot, chan, dur), OT_ERROR_NONE, "Energy scan returned error."); zassert_equal(1, scan_mock_fake.call_count); zassert_equal(dur, scan_mock_fake.arg1_val); zassert_not_null(scan_mock_fake.arg2_val, "Scan callback not specified."); zassert_equal(1, set_channel_mock_fake.call_count); zassert_equal(chan, set_channel_mock_fake.arg1_val); make_sure_sem_set(K_NO_WAIT); /* process reported event */ RESET_FAKE(scan_mock); RESET_FAKE(set_channel_mock); FFF_RESET_HISTORY(); scan_mock_fake.return_val = 0; set_channel_mock_fake.return_val = 0; platformRadioProcess(ot); zassert_equal(1, scan_mock_fake.call_count); zassert_equal(dur, scan_mock_fake.arg1_val); zassert_not_null(scan_mock_fake.arg2_val, "Scan callback not specified."); zassert_equal(1, set_channel_mock_fake.call_count); zassert_equal(chan, set_channel_mock_fake.arg1_val); /* invoke scan done */ scan_mock_fake.arg2_val(radio, energy); make_sure_sem_set(K_NO_WAIT); platformRadioProcess(ot); zassert_equal(1, otPlatRadioEnergyScanDone_fake.call_count); zassert_equal_ptr(ot, otPlatRadioEnergyScanDone_fake.arg0_val, NULL); zassert_equal(energy, otPlatRadioEnergyScanDone_fake.arg1_val); } static void create_ack_frame(void) { struct net_pkt *packet; struct net_buf *buf; const uint8_t lqi = 230; const int8_t rssi = -80; packet = net_pkt_alloc(K_NO_WAIT); buf = net_pkt_get_reserve_tx_data(ACK_PKT_LENGTH, K_NO_WAIT); net_pkt_append_buffer(packet, buf); buf->len = ACK_PKT_LENGTH; buf->data[0] = FRAME_TYPE_ACK; net_pkt_set_ieee802154_rssi_dbm(packet, rssi); net_pkt_set_ieee802154_lqi(packet, lqi); zassert_equal(ieee802154_handle_ack(NULL, packet), NET_OK, "Handling ack failed."); net_pkt_unref(packet); } /** * @brief Test for tx data handling * Tests if OT frame is correctly passed to the radio driver. * Additionally verifies ACK frame passing back to the OT. * */ ZTEST(openthread_radio, test_tx_test) { const uint8_t chan = 20; uint8_t chan2 = chan - 1; const int8_t power = -3; net_time_t expected_target_time = 0; otRadioFrame *frm = otPlatRadioGetTransmitBuffer(ot); zassert_not_null(frm, "Transmit buffer is null."); zassert_equal(otPlatRadioSetTransmitPower(ot, power), OT_ERROR_NONE, "Failed to set TX power."); set_channel_mock_fake.return_val = 0; zassert_equal(otPlatRadioReceive(ot, chan), OT_ERROR_NONE, "Failed to receive."); zassert_equal(1, set_channel_mock_fake.call_count); zassert_equal(chan, set_channel_mock_fake.arg1_val); zassert_equal(1, set_txpower_mock_fake.call_count); zassert_equal(power, set_txpower_mock_fake.arg1_val); zassert_equal(1, start_mock_fake.call_count); zassert_equal_ptr(radio, start_mock_fake.arg0_val, NULL); RESET_FAKE(set_channel_mock); RESET_FAKE(set_txpower_mock); RESET_FAKE(start_mock); FFF_RESET_HISTORY(); if (IS_ENABLED(CONFIG_NET_PKT_TXTIME)) { /* cover dealing with wrapped scheduling time: * current time: (UINT32_MAX + 1) us * target time wrapped: (3 + 5) us, unwrapped: (UINT32_MAX + 3 + 5) us */ get_time_mock_fake.return_val = (int64_t)UINT32_MAX * NSEC_PER_USEC + 1000; frm->mInfo.mTxInfo.mTxDelayBaseTime = 3U; frm->mInfo.mTxInfo.mTxDelay = 5U; expected_target_time = get_time_mock_fake.return_val + (frm->mInfo.mTxInfo.mTxDelayBaseTime + frm->mInfo.mTxInfo.mTxDelay) * NSEC_PER_USEC; } /* ACKed frame */ frm->mChannel = chan2; frm->mInfo.mTxInfo.mCsmaCaEnabled = true; frm->mPsdu[0] = IEEE802154_AR_FLAG_SET; set_channel_mock_fake.return_val = 0; zassert_equal(otPlatRadioTransmit(ot, frm), OT_ERROR_NONE, "Transmit failed."); k_yield(); create_ack_frame(); make_sure_sem_set(Z_TIMEOUT_MS(100)); platformRadioProcess(ot); zassert_equal(1, set_channel_mock_fake.call_count); zassert_equal(chan2, set_channel_mock_fake.arg1_val); if (IS_ENABLED(CONFIG_NET_PKT_TXTIME)) { zassert_equal(0, cca_mock_fake.call_count); } else { zassert_equal(1, cca_mock_fake.call_count); zassert_equal_ptr(radio, cca_mock_fake.arg0_val, NULL); } zassert_equal(1, set_txpower_mock_fake.call_count); zassert_equal(power, set_txpower_mock_fake.arg1_val); zassert_equal(1, tx_mock_fake.call_count); zassert_equal_ptr(frm->mPsdu, tx_mock_fake.arg3_val->data, NULL); zassert_equal(expected_target_time, net_pkt_timestamp_ns(tx_mock_fake.arg2_val)); zassert_equal(IS_ENABLED(CONFIG_NET_PKT_TXTIME) ? IEEE802154_TX_MODE_TXTIME_CCA : IEEE802154_TX_MODE_DIRECT, tx_mock_fake.arg1_val); zassert_equal(1, otPlatRadioTxDone_fake.call_count); zassert_equal_ptr(ot, otPlatRadioTxDone_fake.arg0_val, NULL); zassert_equal(OT_ERROR_NONE, otPlatRadioTxDone_fake.arg3_val); RESET_FAKE(set_channel_mock); RESET_FAKE(set_txpower_mock); RESET_FAKE(tx_mock); RESET_FAKE(otPlatRadioTxDone); FFF_RESET_HISTORY(); /* Non-ACKed frame */ frm->mChannel = --chan2; frm->mInfo.mTxInfo.mCsmaCaEnabled = false; frm->mPsdu[0] = 0; set_channel_mock_fake.return_val = 0; zassert_equal(otPlatRadioTransmit(ot, frm), OT_ERROR_NONE, "Transmit failed."); make_sure_sem_set(Z_TIMEOUT_MS(100)); platformRadioProcess(ot); zassert_equal(1, set_channel_mock_fake.call_count); zassert_equal(chan2, set_channel_mock_fake.arg1_val); zassert_equal(1, set_txpower_mock_fake.call_count); zassert_equal(power, set_txpower_mock_fake.arg1_val); zassert_equal(1, tx_mock_fake.call_count); zassert_equal_ptr(frm->mPsdu, tx_mock_fake.arg3_val->data, NULL); zassert_equal(1, otPlatRadioTxDone_fake.call_count); zassert_equal_ptr(ot, otPlatRadioTxDone_fake.arg0_val, NULL); zassert_equal(OT_ERROR_NONE, otPlatRadioTxDone_fake.arg3_val); } /** * @brief Test for tx power setting * Tests if tx power requested by the OT is correctly passed to the radio. * */ ZTEST(openthread_radio, test_tx_power_test) { int8_t out_power = 0; zassert_equal(otPlatRadioSetTransmitPower(ot, -3), OT_ERROR_NONE, "Failed to set TX power."); zassert_equal(otPlatRadioGetTransmitPower(ot, &out_power), OT_ERROR_NONE, "Failed to obtain TX power."); zassert_equal(out_power, -3, "Got different power than set."); zassert_equal(otPlatRadioSetTransmitPower(ot, -6), OT_ERROR_NONE, "Failed to set TX power."); zassert_equal(otPlatRadioGetTransmitPower(ot, &out_power), OT_ERROR_NONE, "Failed to obtain TX power."); zassert_equal(out_power, -6, "Second call to otPlatRadioSetTransmitPower failed."); } /** * @brief Test for getting radio sensitivity * There is no api to get radio sensitivity from the radio so the value is * hardcoded in radio.c. Test only verifies if the value returned makes any * sense. * */ ZTEST(openthread_radio, test_sensitivity_test) { /* * Nothing to test actually as this is constant 100. * When radio interface will be extended to get sensitivity this test * can be extended with the radio api call. For now just verify if the * value is reasonable. */ zassert_true(-80 > otPlatRadioGetReceiveSensitivity(ot), "Radio sensitivity not in range."); } static enum ieee802154_config_type custom_configure_match_mock_expected_type; static struct ieee802154_config custom_configure_match_mock_expected_config; static int custom_configure_match_mock(const struct device *dev, enum ieee802154_config_type type, const struct ieee802154_config *config) { zassert_equal_ptr(dev, radio, "Device handle incorrect."); zassert_equal(custom_configure_match_mock_expected_type, type); switch (type) { case IEEE802154_CONFIG_AUTO_ACK_FPB: zassert_equal(custom_configure_match_mock_expected_config.auto_ack_fpb.mode, config->auto_ack_fpb.mode, NULL); zassert_equal(custom_configure_match_mock_expected_config.auto_ack_fpb.enabled, config->auto_ack_fpb.enabled, NULL); break; case IEEE802154_CONFIG_ACK_FPB: zassert_equal(custom_configure_match_mock_expected_config.ack_fpb.extended, config->ack_fpb.extended, NULL); zassert_equal(custom_configure_match_mock_expected_config.ack_fpb.enabled, config->ack_fpb.enabled, NULL); if (custom_configure_match_mock_expected_config.ack_fpb.addr == NULL) { zassert_is_null(config->ack_fpb.addr, NULL); } else { zassert_mem_equal(custom_configure_match_mock_expected_config.ack_fpb.addr, config->ack_fpb.addr, (config->ack_fpb.extended) ? sizeof(otExtAddress) : 2, NULL); } break; default: zassert_unreachable("Unexpected config type %d.", type); break; } return 0; } static void set_expected_match_values(enum ieee802154_config_type type, uint8_t *addr, bool extended, bool enabled) { custom_configure_match_mock_expected_type = type; switch (type) { case IEEE802154_CONFIG_AUTO_ACK_FPB: custom_configure_match_mock_expected_config.auto_ack_fpb.enabled = enabled; custom_configure_match_mock_expected_config.auto_ack_fpb.mode = IEEE802154_FPB_ADDR_MATCH_THREAD; break; case IEEE802154_CONFIG_ACK_FPB: custom_configure_match_mock_expected_config.ack_fpb.extended = extended; custom_configure_match_mock_expected_config.ack_fpb.enabled = enabled; custom_configure_match_mock_expected_config.ack_fpb.addr = addr; break; default: break; } } /** * @brief Test different types of OT source match. * Tests if Enable, Disable, Add and Clear Source Match calls are passed to the * radio driver correctly. * */ ZTEST(openthread_radio, test_source_match_test) { otExtAddress ext_addr; configure_mock_fake.custom_fake = custom_configure_match_mock; /* Enable/Disable */ set_expected_match_values(IEEE802154_CONFIG_AUTO_ACK_FPB, NULL, false, true); otPlatRadioEnableSrcMatch(ot, true); set_expected_match_values(IEEE802154_CONFIG_AUTO_ACK_FPB, NULL, false, false); otPlatRadioEnableSrcMatch(ot, false); set_expected_match_values(IEEE802154_CONFIG_AUTO_ACK_FPB, NULL, false, true); otPlatRadioEnableSrcMatch(ot, true); /* Add */ sys_put_le16(12345, ext_addr.m8); set_expected_match_values(IEEE802154_CONFIG_ACK_FPB, ext_addr.m8, false, true); zassert_equal(otPlatRadioAddSrcMatchShortEntry(ot, 12345), OT_ERROR_NONE, "Failed to add short src entry."); for (int i = 0; i < sizeof(ext_addr.m8); i++) { ext_addr.m8[i] = i; } set_expected_match_values(IEEE802154_CONFIG_ACK_FPB, ext_addr.m8, true, true); zassert_equal(otPlatRadioAddSrcMatchExtEntry(ot, &ext_addr), OT_ERROR_NONE, "Failed to add ext src entry."); /* Clear */ sys_put_le16(12345, ext_addr.m8); set_expected_match_values(IEEE802154_CONFIG_ACK_FPB, ext_addr.m8, false, false); zassert_equal(otPlatRadioClearSrcMatchShortEntry(ot, 12345), OT_ERROR_NONE, "Failed to clear short src entry."); set_expected_match_values(IEEE802154_CONFIG_ACK_FPB, ext_addr.m8, true, false); zassert_equal(otPlatRadioClearSrcMatchExtEntry(ot, &ext_addr), OT_ERROR_NONE, "Failed to clear ext src entry."); set_expected_match_values(IEEE802154_CONFIG_ACK_FPB, NULL, false, false); otPlatRadioClearSrcMatchShortEntries(ot); set_expected_match_values(IEEE802154_CONFIG_ACK_FPB, NULL, true, false); otPlatRadioClearSrcMatchExtEntries(ot); } static bool custom_configure_promiscuous_mock_promiscuous; static int custom_configure_promiscuous_mock(const struct device *dev, enum ieee802154_config_type type, const struct ieee802154_config *config) { zassert_equal(dev, radio, "Device handle incorrect."); zassert_equal(type, IEEE802154_CONFIG_PROMISCUOUS, "Config type incorrect."); custom_configure_promiscuous_mock_promiscuous = config->promiscuous; return 0; } /** * @brief Test for enabling or disabling promiscuous mode * Tests if OT can successfully enable or disable promiscuous mode. * */ ZTEST(openthread_radio, test_promiscuous_mode_set_test) { zassert_false(otPlatRadioGetPromiscuous(ot), "By default promiscuous mode shall be disabled."); configure_mock_fake.custom_fake = custom_configure_promiscuous_mock; otPlatRadioSetPromiscuous(ot, true); zassert_true(otPlatRadioGetPromiscuous(ot), "Mode not enabled."); zassert_equal(1, configure_mock_fake.call_count); zassert_true(custom_configure_promiscuous_mock_promiscuous); RESET_FAKE(configure_mock); configure_mock_fake.custom_fake = custom_configure_promiscuous_mock; otPlatRadioSetPromiscuous(ot, false); zassert_false(otPlatRadioGetPromiscuous(ot), "Mode still enabled."); zassert_equal(1, configure_mock_fake.call_count); zassert_false(custom_configure_promiscuous_mock_promiscuous); } /** * @brief Test of proper radio to OT capabilities mapping * Tests if different radio capabilities map for their corresponding OpenThread * capability */ ZTEST(openthread_radio, test_get_caps_test) { rapi.get_capabilities = get_capabilities_caps_mock; /* no caps */ get_capabilities_caps_mock_fake.return_val = 0; zassert_equal(otPlatRadioGetCaps(ot), OT_RADIO_CAPS_NONE, "Incorrect capabilities returned."); /* not used by OT */ get_capabilities_caps_mock_fake.return_val = IEEE802154_HW_FCS; zassert_equal(otPlatRadioGetCaps(ot), OT_RADIO_CAPS_NONE, "Incorrect capabilities returned."); /* not implemented or not fully supported */ get_capabilities_caps_mock_fake.return_val = IEEE802154_HW_PROMISC; zassert_equal(otPlatRadioGetCaps(ot), OT_RADIO_CAPS_NONE, "Incorrect capabilities returned."); /* proper mapping */ get_capabilities_caps_mock_fake.return_val = IEEE802154_HW_CSMA; zassert_equal(otPlatRadioGetCaps(ot), OT_RADIO_CAPS_CSMA_BACKOFF, "Incorrect capabilities returned."); get_capabilities_caps_mock_fake.return_val = IEEE802154_HW_ENERGY_SCAN; zassert_equal(otPlatRadioGetCaps(ot), OT_RADIO_CAPS_ENERGY_SCAN, "Incorrect capabilities returned."); get_capabilities_caps_mock_fake.return_val = IEEE802154_HW_TX_RX_ACK; zassert_equal(otPlatRadioGetCaps(ot), OT_RADIO_CAPS_ACK_TIMEOUT, "Incorrect capabilities returned."); get_capabilities_caps_mock_fake.return_val = IEEE802154_HW_TXTIME; zassert_equal(otPlatRadioGetCaps(ot), IS_ENABLED(CONFIG_NET_PKT_TXTIME) ? OT_RADIO_CAPS_TRANSMIT_TIMING : OT_RADIO_CAPS_NONE, "Incorrect capabilities returned."); get_capabilities_caps_mock_fake.return_val = IEEE802154_HW_SLEEP_TO_TX; zassert_equal(otPlatRadioGetCaps(ot), OT_RADIO_CAPS_SLEEP_TO_TX, "Incorrect capabilities returned."); /* all at once */ get_capabilities_caps_mock_fake.return_val = IEEE802154_HW_FCS | IEEE802154_HW_PROMISC | IEEE802154_HW_FILTER | IEEE802154_HW_CSMA | IEEE802154_HW_TX_RX_ACK | IEEE802154_HW_ENERGY_SCAN | IEEE802154_HW_TXTIME | IEEE802154_HW_SLEEP_TO_TX; zassert_equal( otPlatRadioGetCaps(ot), OT_RADIO_CAPS_CSMA_BACKOFF | OT_RADIO_CAPS_ENERGY_SCAN | OT_RADIO_CAPS_ACK_TIMEOUT | OT_RADIO_CAPS_SLEEP_TO_TX | (IS_ENABLED(CONFIG_NET_PKT_TXTIME) ? OT_RADIO_CAPS_TRANSMIT_TIMING : 0), "Incorrect capabilities returned."); rapi.get_capabilities = get_capabilities; } /** * @brief Test for getting the rssi value from the radio * Tests if correct value is returned from the otPlatRadioGetRssi function. * */ ZTEST(openthread_radio, test_get_rssi_test) { const int8_t rssi = -103; rapi.ed_scan = rssi_scan_mock; rssi_scan_mock_max_ed = rssi; zassert_equal(otPlatRadioGetRssi(ot), rssi, "Invalid RSSI value received."); rapi.ed_scan = scan_mock; } /** * @brief Test switching between radio states * Tests if radio is correctly switched between states. * */ ZTEST(openthread_radio, test_radio_state_test) { const uint8_t channel = 12; const uint8_t power = 10; zassert_equal(otPlatRadioSetTransmitPower(ot, power), OT_ERROR_NONE, "Failed to set TX power."); zassert_equal(otPlatRadioSleep(ot), OT_ERROR_NONE, "Failed to switch to sleep mode."); zassert_equal(otPlatRadioDisable(ot), OT_ERROR_NONE, "Failed to disable radio."); zassert_false(otPlatRadioIsEnabled(ot), "Radio reports as enabled."); zassert_equal(otPlatRadioSleep(ot), OT_ERROR_INVALID_STATE, "Changed to sleep regardless being disabled."); zassert_equal(otPlatRadioReceive(ot, channel), OT_ERROR_INVALID_STATE, "Changed to receive regardless being disabled."); zassert_equal(otPlatRadioEnable(ot), OT_ERROR_NONE, "Enabling radio failed."); zassert_true(otPlatRadioIsEnabled(ot), "Radio reports disabled."); zassert_equal(otPlatRadioSleep(ot), OT_ERROR_NONE, "Failed to switch to sleep mode."); zassert_true(otPlatRadioIsEnabled(ot), "Radio reports as disabled."); set_channel_mock_fake.return_val = 0; zassert_equal(otPlatRadioReceive(ot, channel), OT_ERROR_NONE, "Failed to receive."); zassert_equal(platformRadioChannelGet(ot), channel, "Channel number not remembered."); zassert_equal(otPlatRadioDisable(ot), OT_ERROR_INVALID_STATE, "Changed to disabled regardless being in receive state."); zassert_true(otPlatRadioIsEnabled(ot), "Radio reports as disabled."); zassert_equal(1, set_channel_mock_fake.call_count); zassert_equal(channel, set_channel_mock_fake.arg1_val); zassert_equal(1, set_txpower_mock_fake.call_count); zassert_equal(power, set_txpower_mock_fake.arg1_val); zassert_equal(1, start_mock_fake.call_count); zassert_equal_ptr(radio, start_mock_fake.arg0_val, NULL); zassert_equal(2, stop_mock_fake.call_count); zassert_equal_ptr(radio, stop_mock_fake.arg0_val, NULL); } static uint16_t custom_filter_mock_pan_id; static uint16_t custom_filter_mock_short_addr; static uint8_t *custom_filter_mock_ieee_addr; static int custom_filter_mock(const struct device *dev, bool set, enum ieee802154_filter_type type, const struct ieee802154_filter *filter) { switch (type) { case IEEE802154_FILTER_TYPE_IEEE_ADDR: custom_filter_mock_ieee_addr = filter->ieee_addr; break; case IEEE802154_FILTER_TYPE_SHORT_ADDR: custom_filter_mock_short_addr = filter->short_addr; break; case IEEE802154_FILTER_TYPE_PAN_ID: custom_filter_mock_pan_id = filter->pan_id; break; default: zassert_false(true, "Type not supported in mock: %d.", type); break; } return 0; } /** * @brief Test address filtering * Tests if short, extended address and PanID are correctly passed to the radio * driver. * */ ZTEST(openthread_radio, test_address_test) { const uint16_t pan_id = 0xDEAD; const uint16_t short_add = 0xCAFE; otExtAddress ieee_addr; for (int i = 0; i < sizeof(ieee_addr.m8); i++) { ieee_addr.m8[i] = 'a' + i; } filter_mock_fake.custom_fake = custom_filter_mock; otPlatRadioSetPanId(ot, pan_id); zassert_equal(1, filter_mock_fake.call_count); zassert_true(filter_mock_fake.arg1_val); zassert_equal(IEEE802154_FILTER_TYPE_PAN_ID, filter_mock_fake.arg2_val); zassert_equal(pan_id, custom_filter_mock_pan_id); RESET_FAKE(filter_mock); FFF_RESET_HISTORY(); filter_mock_fake.custom_fake = custom_filter_mock; otPlatRadioSetShortAddress(ot, short_add); zassert_equal(1, filter_mock_fake.call_count); zassert_true(filter_mock_fake.arg1_val); zassert_equal(IEEE802154_FILTER_TYPE_SHORT_ADDR, filter_mock_fake.arg2_val); zassert_equal(short_add, custom_filter_mock_short_addr); RESET_FAKE(filter_mock); FFF_RESET_HISTORY(); filter_mock_fake.custom_fake = custom_filter_mock; otPlatRadioSetExtendedAddress(ot, &ieee_addr); zassert_equal(1, filter_mock_fake.call_count); zassert_true(filter_mock_fake.arg1_val); zassert_equal(IEEE802154_FILTER_TYPE_IEEE_ADDR, filter_mock_fake.arg2_val); zassert_mem_equal(ieee_addr.m8, custom_filter_mock_ieee_addr, OT_EXT_ADDRESS_SIZE, NULL); } uint8_t alloc_pkt(struct net_pkt **out_packet, uint8_t buf_ct, uint8_t offset) { struct net_pkt *packet; struct net_buf *buf; uint8_t len = 0; uint8_t buf_num; packet = net_pkt_alloc(K_NO_WAIT); for (buf_num = 0; buf_num < buf_ct; buf_num++) { buf = net_pkt_get_reserve_tx_data(IEEE802154_MAX_PHY_PACKET_SIZE, K_NO_WAIT); net_pkt_append_buffer(packet, buf); for (int i = 0; i < buf->size; i++) { buf->data[i] = (offset + i + buf_num) & 0xFF; } len = buf->size - 3; buf->len = len; } *out_packet = packet; return len; } /** * @brief Test received messages handling. * Tests if received frames are properly passed to the OpenThread * */ ZTEST(openthread_radio, test_receive_test) { struct net_pkt *packet; struct net_buf *buf; const uint8_t channel = 21; const int8_t power = -5; const uint8_t lqi = 240; const int8_t rssi = -90; uint8_t len; len = alloc_pkt(&packet, 1, 'a'); buf = packet->buffer; net_pkt_set_ieee802154_lqi(packet, lqi); net_pkt_set_ieee802154_rssi_dbm(packet, rssi); zassert_equal(otPlatRadioSetTransmitPower(ot, power), OT_ERROR_NONE, "Failed to set TX power."); set_channel_mock_fake.return_val = 0; zassert_equal(otPlatRadioReceive(ot, channel), OT_ERROR_NONE, "Failed to receive."); zassert_equal(1, set_channel_mock_fake.call_count); zassert_equal(channel, set_channel_mock_fake.arg1_val); zassert_equal(1, set_txpower_mock_fake.call_count); zassert_equal(power, set_txpower_mock_fake.arg1_val); zassert_equal(1, start_mock_fake.call_count); zassert_equal_ptr(radio, start_mock_fake.arg0_val, NULL); /* * Not setting any expect values as nothing shall be called from * notify_new_rx_frame calling thread. OT functions can be called only * after semaphore for main thread is released. */ notify_new_rx_frame(packet); make_sure_sem_set(Z_TIMEOUT_MS(100)); otPlatRadioReceiveDone_expected_error = OT_ERROR_NONE; otPlatRadioReceiveDone_expected_aframe.mChannel = channel; otPlatRadioReceiveDone_expected_aframe.mLength = len; otPlatRadioReceiveDone_expected_aframe.mPsdu = buf->data; platformRadioProcess(ot); } /** * @brief Test received messages handling. * Tests if received frames are properly passed to the OpenThread * */ ZTEST(openthread_radio, test_net_pkt_transmit) { void *expected_data_ptrs[2]; struct net_pkt *packet; struct net_buf *buf; const uint8_t channel = 21; const int8_t power = -5; uint8_t len; /* success */ len = alloc_pkt(&packet, 2, 'a'); buf = packet->buffer; zassert_equal(otPlatRadioSetTransmitPower(ot, power), OT_ERROR_NONE, "Failed to set TX power."); set_channel_mock_fake.return_val = 0; zassert_equal(otPlatRadioEnable(ot), OT_ERROR_NONE, "Failed to enable."); zassert_equal(otPlatRadioReceive(ot, channel), OT_ERROR_NONE, "Failed to receive."); zassert_equal(1, set_channel_mock_fake.call_count); zassert_equal(channel, set_channel_mock_fake.arg1_val); zassert_equal(1, set_txpower_mock_fake.call_count); zassert_equal(power, set_txpower_mock_fake.arg1_val); zassert_equal(1, start_mock_fake.call_count); zassert_equal_ptr(radio, start_mock_fake.arg0_val, NULL); notify_new_tx_frame(packet); make_sure_sem_set(Z_TIMEOUT_MS(100)); otMessageAppend_fake.return_val = OT_ERROR_NONE; otIp6Send_fake.return_val = OT_ERROR_NONE; /* Do not expect free in case of success */ expected_data_ptrs[0] = buf->data; expected_data_ptrs[1] = buf->frags->data; platformRadioProcess(ot); zassert_equal(2, otMessageAppend_fake.call_count); zassert_equal_ptr(ip_msg, otMessageAppend_fake.arg0_history[0], NULL); zassert_equal_ptr(ip_msg, otMessageAppend_fake.arg0_history[1], NULL); zassert_equal_ptr(expected_data_ptrs[0], otMessageAppend_fake.arg1_history[0], NULL); zassert_equal_ptr(expected_data_ptrs[1], otMessageAppend_fake.arg1_history[1], NULL); zassert_equal(len, otMessageAppend_fake.arg2_history[0]); zassert_equal(len, otMessageAppend_fake.arg2_history[1]); zassert_equal(1, otIp6Send_fake.call_count); zassert_equal_ptr(ot, otIp6Send_fake.arg0_val, NULL); zassert_equal_ptr(ip_msg, otIp6Send_fake.arg1_val, NULL); RESET_FAKE(otMessageAppend); RESET_FAKE(otIp6Send); FFF_RESET_HISTORY(); /* fail on append */ len = alloc_pkt(&packet, 2, 'b'); buf = packet->buffer; notify_new_tx_frame(packet); make_sure_sem_set(Z_TIMEOUT_MS(100)); otMessageAppend_fake.return_val = OT_ERROR_NO_BUFS; expected_data_ptrs[0] = buf->data; platformRadioProcess(ot); zassert_equal(1, otMessageAppend_fake.call_count); zassert_equal_ptr(ip_msg, otMessageAppend_fake.arg0_val, NULL); zassert_equal_ptr(expected_data_ptrs[0], otMessageAppend_fake.arg1_val, NULL); zassert_equal(len, otMessageAppend_fake.arg2_val); zassert_equal_ptr(ip_msg, otMessageFree_fake.arg0_val, NULL); RESET_FAKE(otMessageAppend); FFF_RESET_HISTORY(); /* fail on send */ len = alloc_pkt(&packet, 1, 'c'); buf = packet->buffer; notify_new_tx_frame(packet); make_sure_sem_set(Z_TIMEOUT_MS(100)); otMessageAppend_fake.return_val = OT_ERROR_NONE; otIp6Send_fake.return_val = OT_ERROR_BUSY; expected_data_ptrs[0] = buf->data; /* Do not expect free in case of failure in send */ platformRadioProcess(ot); zassert_equal(1, otMessageAppend_fake.call_count); zassert_equal_ptr(ip_msg, otMessageAppend_fake.arg0_val, NULL); zassert_equal_ptr(expected_data_ptrs[0], otMessageAppend_fake.arg1_val, NULL); zassert_equal(len, otMessageAppend_fake.arg2_val); zassert_equal(1, otIp6Send_fake.call_count); zassert_equal_ptr(ot, otIp6Send_fake.arg0_val, NULL); zassert_equal_ptr(ip_msg, otIp6Send_fake.arg1_val, NULL); } #ifdef CONFIG_OPENTHREAD_CSL_RECEIVER static int64_t custom_configure_csl_rx_time_mock_csl_rx_time; static int custom_configure_csl_rx_time(const struct device *dev, enum ieee802154_config_type type, const struct ieee802154_config *config) { zassert_equal(dev, radio, "Device handle incorrect."); zassert_equal(type, IEEE802154_CONFIG_EXPECTED_RX_TIME, "Config type incorrect."); custom_configure_csl_rx_time_mock_csl_rx_time = config->expected_rx_time; return 0; } ZTEST(openthread_radio, test_csl_receiver_sample_time) { uint32_t sample_time = 50U; uint32_t phr_duration = 32U; configure_mock_fake.custom_fake = custom_configure_csl_rx_time; otPlatRadioUpdateCslSampleTime(NULL, sample_time); zassert_equal(1, configure_mock_fake.call_count); zassert_equal((sample_time - phr_duration) * NSEC_PER_USEC, custom_configure_csl_rx_time_mock_csl_rx_time); } static struct ieee802154_config custom_configure_rx_slot_mock_config; static int custom_configure_csl_rx_slot(const struct device *dev, enum ieee802154_config_type type, const struct ieee802154_config *config) { zassert_equal(dev, radio, "Device handle incorrect."); zassert_equal(type, IEEE802154_CONFIG_RX_SLOT, "Config type incorrect."); custom_configure_rx_slot_mock_config.rx_slot.channel = config->rx_slot.channel; custom_configure_rx_slot_mock_config.rx_slot.start = config->rx_slot.start; custom_configure_rx_slot_mock_config.rx_slot.duration = config->rx_slot.duration; return 0; } ZTEST(openthread_radio, test_csl_receiver_receive_at) { uint8_t channel = 11U; uint32_t start = 1000U; uint32_t duration = 100U; int res; configure_mock_fake.custom_fake = custom_configure_csl_rx_slot; res = otPlatRadioReceiveAt(NULL, channel, start, duration); zassert_ok(res); zassert_equal(1, configure_mock_fake.call_count); zassert_equal(channel, custom_configure_rx_slot_mock_config.rx_slot.channel); zassert_equal(start * NSEC_PER_USEC, custom_configure_rx_slot_mock_config.rx_slot.start); zassert_equal(duration * NSEC_PER_USEC, custom_configure_rx_slot_mock_config.rx_slot.duration); } #endif static void *openthread_radio_setup(void) { platformRadioInit(); return NULL; } static void openthread_radio_before(void *f) { ARG_UNUSED(f); RESET_FAKE(scan_mock); RESET_FAKE(cca_mock); RESET_FAKE(set_channel_mock); RESET_FAKE(filter_mock); RESET_FAKE(set_txpower_mock); RESET_FAKE(tx_mock); RESET_FAKE(start_mock); RESET_FAKE(stop_mock); RESET_FAKE(configure_mock); RESET_FAKE(get_capabilities_caps_mock); RESET_FAKE(otPlatRadioEnergyScanDone); RESET_FAKE(otPlatRadioTxDone); RESET_FAKE(otMessageFree); FFF_RESET_HISTORY(); } ZTEST_SUITE(openthread_radio, NULL, openthread_radio_setup, openthread_radio_before, NULL, NULL);