| /* |
| * Copyright (c) 2020 Nordic Semiconductor ASA |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <errno.h> |
| #include <stdio.h> |
| |
| #include <zephyr/fff.h> |
| #include <zephyr/net/ieee802154_radio.h> |
| #include <zephyr/net/net_pkt.h> |
| #include <zephyr/kernel.h> |
| #include <zephyr/ztest.h> |
| |
| #include <openthread/message.h> |
| #include <openthread/platform/radio.h> |
| #include <platform-zephyr.h> |
| |
| 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(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(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_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(1, 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(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); |