blob: c96297f3bba4ba4c2b4e801c688e72eac744adca [file] [log] [blame]
/*
* Copyright (c) 2021, The OpenThread Authors.
* Copyright (c) 2022-2024, NXP.
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @file
* This file implements OpenThread platform driver API in openthread/platform/radio.h.
*
*/
#include <openthread/platform/radio.h>
#include <lib/platform/exit_code.h>
#include <lib/spinel/radio_spinel.hpp>
#include <lib/spinel/spinel.h>
#include <lib/url/url.hpp>
#include "hdlc_interface.hpp"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(LOG_MODULE_NAME, CONFIG_OPENTHREAD_PLATFORM_LOG_LEVEL);
#include <zephyr/kernel.h>
#include <zephyr/net/net_pkt.h>
#include <openthread-system.h>
enum pending_events {
PENDING_EVENT_FRAME_TO_SEND, /* There is a tx frame to send */
PENDING_EVENT_COUNT /* Keep last */
};
ATOMIC_DEFINE(pending_events, PENDING_EVENT_COUNT);
K_FIFO_DEFINE(tx_pkt_fifo);
static ot::Spinel::RadioSpinel *psRadioSpinel;
static ot::Url::Url *psRadioUrl;
static ot::Hdlc::HdlcInterface *pSpinelInterface;
static ot::Spinel::SpinelDriver *psSpinelDriver;
static const otRadioCaps sRequiredRadioCaps =
#if OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2
OT_RADIO_CAPS_TRANSMIT_SEC | OT_RADIO_CAPS_TRANSMIT_TIMING |
#endif
OT_RADIO_CAPS_ACK_TIMEOUT | OT_RADIO_CAPS_TRANSMIT_RETRIES | OT_RADIO_CAPS_CSMA_BACKOFF;
static inline bool is_pending_event_set(enum pending_events event)
{
return atomic_test_bit(pending_events, event);
}
static void set_pending_event(enum pending_events event)
{
atomic_set_bit(pending_events, event);
otSysEventSignalPending();
}
static void reset_pending_event(enum pending_events event)
{
atomic_clear_bit(pending_events, event);
}
static void openthread_handle_frame_to_send(otInstance *instance, struct net_pkt *pkt)
{
struct net_buf *buf;
otMessage *message;
otMessageSettings settings;
NET_DBG("Sending Ip6 packet to ot stack");
settings.mPriority = OT_MESSAGE_PRIORITY_NORMAL;
settings.mLinkSecurityEnabled = true;
message = otIp6NewMessage(instance, &settings);
if (message == NULL) {
goto exit;
}
for (buf = pkt->buffer; buf; buf = buf->frags) {
if (otMessageAppend(message, buf->data, buf->len) != OT_ERROR_NONE) {
NET_ERR("Error while appending to otMessage");
otMessageFree(message);
goto exit;
}
}
if (otIp6Send(instance, message) != OT_ERROR_NONE) {
NET_ERR("Error while calling otIp6Send");
goto exit;
}
exit:
net_pkt_unref(pkt);
}
void otPlatRadioGetIeeeEui64(otInstance *aInstance, uint8_t *aIeeeEui64)
{
OT_UNUSED_VARIABLE(aInstance);
SuccessOrDie(psRadioSpinel->GetIeeeEui64(aIeeeEui64));
}
void otPlatRadioSetPanId(otInstance *aInstance, uint16_t panid)
{
OT_UNUSED_VARIABLE(aInstance);
SuccessOrDie(psRadioSpinel->SetPanId(panid));
}
void otPlatRadioSetExtendedAddress(otInstance *aInstance, const otExtAddress *aAddress)
{
OT_UNUSED_VARIABLE(aInstance);
otExtAddress addr;
for (size_t i = 0; i < sizeof(addr); i++) {
addr.m8[i] = aAddress->m8[sizeof(addr) - 1 - i];
}
SuccessOrDie(psRadioSpinel->SetExtendedAddress(addr));
}
void otPlatRadioSetShortAddress(otInstance *aInstance, uint16_t aAddress)
{
OT_UNUSED_VARIABLE(aInstance);
SuccessOrDie(psRadioSpinel->SetShortAddress(aAddress));
}
void otPlatRadioSetPromiscuous(otInstance *aInstance, bool aEnable)
{
OT_UNUSED_VARIABLE(aInstance);
SuccessOrDie(psRadioSpinel->SetPromiscuous(aEnable));
}
bool otPlatRadioIsEnabled(otInstance *aInstance)
{
OT_UNUSED_VARIABLE(aInstance);
return psRadioSpinel->IsEnabled();
}
otError otPlatRadioEnable(otInstance *aInstance)
{
return psRadioSpinel->Enable(aInstance);
}
otError otPlatRadioDisable(otInstance *aInstance)
{
OT_UNUSED_VARIABLE(aInstance);
return psRadioSpinel->Disable();
}
otError otPlatRadioSleep(otInstance *aInstance)
{
OT_UNUSED_VARIABLE(aInstance);
return psRadioSpinel->Sleep();
}
otError otPlatRadioReceive(otInstance *aInstance, uint8_t aChannel)
{
OT_UNUSED_VARIABLE(aInstance);
return psRadioSpinel->Receive(aChannel);
}
otError otPlatRadioTransmit(otInstance *aInstance, otRadioFrame *aFrame)
{
OT_UNUSED_VARIABLE(aInstance);
return psRadioSpinel->Transmit(*aFrame);
}
otRadioFrame *otPlatRadioGetTransmitBuffer(otInstance *aInstance)
{
OT_UNUSED_VARIABLE(aInstance);
return &psRadioSpinel->GetTransmitFrame();
}
int8_t otPlatRadioGetRssi(otInstance *aInstance)
{
OT_UNUSED_VARIABLE(aInstance);
return psRadioSpinel->GetRssi();
}
otRadioCaps otPlatRadioGetCaps(otInstance *aInstance)
{
OT_UNUSED_VARIABLE(aInstance);
return psRadioSpinel->GetRadioCaps();
}
const char *otPlatRadioGetVersionString(otInstance *aInstance)
{
OT_UNUSED_VARIABLE(aInstance);
return psRadioSpinel->GetVersion();
}
bool otPlatRadioGetPromiscuous(otInstance *aInstance)
{
OT_UNUSED_VARIABLE(aInstance);
return psRadioSpinel->IsPromiscuous();
}
void otPlatRadioEnableSrcMatch(otInstance *aInstance, bool aEnable)
{
OT_UNUSED_VARIABLE(aInstance);
SuccessOrDie(psRadioSpinel->EnableSrcMatch(aEnable));
}
otError otPlatRadioAddSrcMatchShortEntry(otInstance *aInstance, uint16_t aShortAddress)
{
OT_UNUSED_VARIABLE(aInstance);
return psRadioSpinel->AddSrcMatchShortEntry(aShortAddress);
}
otError otPlatRadioAddSrcMatchExtEntry(otInstance *aInstance, const otExtAddress *aExtAddress)
{
OT_UNUSED_VARIABLE(aInstance);
otExtAddress addr;
for (size_t i = 0; i < sizeof(addr); i++) {
addr.m8[i] = aExtAddress->m8[sizeof(addr) - 1 - i];
}
return psRadioSpinel->AddSrcMatchExtEntry(addr);
}
otError otPlatRadioClearSrcMatchShortEntry(otInstance *aInstance, uint16_t aShortAddress)
{
OT_UNUSED_VARIABLE(aInstance);
return psRadioSpinel->ClearSrcMatchShortEntry(aShortAddress);
}
otError otPlatRadioClearSrcMatchExtEntry(otInstance *aInstance, const otExtAddress *aExtAddress)
{
OT_UNUSED_VARIABLE(aInstance);
otExtAddress addr;
for (size_t i = 0; i < sizeof(addr); i++) {
addr.m8[i] = aExtAddress->m8[sizeof(addr) - 1 - i];
}
return psRadioSpinel->ClearSrcMatchExtEntry(addr);
}
void otPlatRadioClearSrcMatchShortEntries(otInstance *aInstance)
{
OT_UNUSED_VARIABLE(aInstance);
SuccessOrDie(psRadioSpinel->ClearSrcMatchShortEntries());
}
void otPlatRadioClearSrcMatchExtEntries(otInstance *aInstance)
{
OT_UNUSED_VARIABLE(aInstance);
SuccessOrDie(psRadioSpinel->ClearSrcMatchExtEntries());
}
otError otPlatRadioEnergyScan(otInstance *aInstance, uint8_t aScanChannel, uint16_t aScanDuration)
{
OT_UNUSED_VARIABLE(aInstance);
return psRadioSpinel->EnergyScan(aScanChannel, aScanDuration);
}
otError otPlatRadioGetTransmitPower(otInstance *aInstance, int8_t *aPower)
{
otError error;
OT_UNUSED_VARIABLE(aInstance);
VerifyOrExit(aPower != NULL, error = OT_ERROR_INVALID_ARGS);
error = psRadioSpinel->GetTransmitPower(*aPower);
exit:
return error;
}
otError otPlatRadioSetTransmitPower(otInstance *aInstance, int8_t aPower)
{
OT_UNUSED_VARIABLE(aInstance);
return psRadioSpinel->SetTransmitPower(aPower);
}
otError otPlatRadioGetCcaEnergyDetectThreshold(otInstance *aInstance, int8_t *aThreshold)
{
otError error;
OT_UNUSED_VARIABLE(aInstance);
VerifyOrExit(aThreshold != NULL, error = OT_ERROR_INVALID_ARGS);
error = psRadioSpinel->GetCcaEnergyDetectThreshold(*aThreshold);
exit:
return error;
}
otError otPlatRadioSetCcaEnergyDetectThreshold(otInstance *aInstance, int8_t aThreshold)
{
OT_UNUSED_VARIABLE(aInstance);
return psRadioSpinel->SetCcaEnergyDetectThreshold(aThreshold);
}
int8_t otPlatRadioGetReceiveSensitivity(otInstance *aInstance)
{
OT_UNUSED_VARIABLE(aInstance);
return psRadioSpinel->GetReceiveSensitivity();
}
#if OPENTHREAD_CONFIG_PLATFORM_RADIO_COEX_ENABLE
otError otPlatRadioSetCoexEnabled(otInstance *aInstance, bool aEnabled)
{
OT_UNUSED_VARIABLE(aInstance);
return psRadioSpinel->SetCoexEnabled(aEnabled);
}
bool otPlatRadioIsCoexEnabled(otInstance *aInstance)
{
OT_UNUSED_VARIABLE(aInstance);
return psRadioSpinel->IsCoexEnabled();
}
otError otPlatRadioGetCoexMetrics(otInstance *aInstance, otRadioCoexMetrics *aCoexMetrics)
{
OT_UNUSED_VARIABLE(aInstance);
otError error = OT_ERROR_NONE;
VerifyOrExit(aCoexMetrics != NULL, error = OT_ERROR_INVALID_ARGS);
error = psRadioSpinel->GetCoexMetrics(*aCoexMetrics);
exit:
return error;
}
#endif
#if OPENTHREAD_CONFIG_DIAG_ENABLE
otError otPlatDiagProcess(otInstance *aInstance, int argc, char *argv[], char *aOutput,
size_t aOutputMaxLen)
{
/* Deliver the platform specific diags commands to radio only ncp */
OT_UNUSED_VARIABLE(aInstance);
char cmd[OPENTHREAD_CONFIG_DIAG_CMD_LINE_BUFFER_SIZE] = {'\0'};
char *cur = cmd;
char *end = cmd + sizeof(cmd);
for (int index = 0; index < argc; index++) {
cur += snprintf(cur, static_cast<size_t>(end - cur), "%s ", argv[index]);
}
return psRadioSpinel->PlatDiagProcess(cmd, aOutput, aOutputMaxLen);
}
void otPlatDiagModeSet(bool aMode)
{
SuccessOrExit(psRadioSpinel->PlatDiagProcess(aMode ? "start" : "stop", NULL, 0));
psRadioSpinel->SetDiagEnabled(aMode);
exit:
return;
}
bool otPlatDiagModeGet(void)
{
return psRadioSpinel->IsDiagEnabled();
}
void otPlatDiagTxPowerSet(int8_t aTxPower)
{
char cmd[OPENTHREAD_CONFIG_DIAG_CMD_LINE_BUFFER_SIZE];
snprintf(cmd, sizeof(cmd), "power %d", aTxPower);
SuccessOrExit(psRadioSpinel->PlatDiagProcess(cmd, NULL, 0));
exit:
return;
}
void otPlatDiagChannelSet(uint8_t aChannel)
{
char cmd[OPENTHREAD_CONFIG_DIAG_CMD_LINE_BUFFER_SIZE];
snprintf(cmd, sizeof(cmd), "channel %d", aChannel);
SuccessOrExit(psRadioSpinel->PlatDiagProcess(cmd, NULL, 0));
exit:
return;
}
void otPlatDiagRadioReceived(otInstance *aInstance, otRadioFrame *aFrame, otError aError)
{
OT_UNUSED_VARIABLE(aInstance);
OT_UNUSED_VARIABLE(aFrame);
OT_UNUSED_VARIABLE(aError);
}
void otPlatDiagAlarmCallback(otInstance *aInstance)
{
OT_UNUSED_VARIABLE(aInstance);
}
#endif /* OPENTHREAD_CONFIG_DIAG_ENABLE */
uint32_t otPlatRadioGetSupportedChannelMask(otInstance *aInstance)
{
OT_UNUSED_VARIABLE(aInstance);
return psRadioSpinel->GetRadioChannelMask(false);
}
uint32_t otPlatRadioGetPreferredChannelMask(otInstance *aInstance)
{
OT_UNUSED_VARIABLE(aInstance);
return psRadioSpinel->GetRadioChannelMask(true);
}
otRadioState otPlatRadioGetState(otInstance *aInstance)
{
OT_UNUSED_VARIABLE(aInstance);
return psRadioSpinel->GetState();
}
void otPlatRadioSetMacKey(otInstance *aInstance, uint8_t aKeyIdMode, uint8_t aKeyId,
const otMacKeyMaterial *aPrevKey, const otMacKeyMaterial *aCurrKey,
const otMacKeyMaterial *aNextKey, otRadioKeyType aKeyType)
{
SuccessOrDie(psRadioSpinel->SetMacKey(aKeyIdMode, aKeyId, aPrevKey, aCurrKey, aNextKey));
OT_UNUSED_VARIABLE(aInstance);
OT_UNUSED_VARIABLE(aKeyType);
}
void otPlatRadioSetMacFrameCounter(otInstance *aInstance, uint32_t aMacFrameCounter)
{
SuccessOrDie(psRadioSpinel->SetMacFrameCounter(aMacFrameCounter, false));
OT_UNUSED_VARIABLE(aInstance);
}
void otPlatRadioSetMacFrameCounterIfLarger(otInstance *aInstance, uint32_t aMacFrameCounter)
{
SuccessOrDie(psRadioSpinel->SetMacFrameCounter(aMacFrameCounter, true));
OT_UNUSED_VARIABLE(aInstance);
}
uint64_t otPlatRadioGetNow(otInstance *aInstance)
{
OT_UNUSED_VARIABLE(aInstance);
return psRadioSpinel->GetNow();
}
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE || OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
uint8_t otPlatRadioGetCslAccuracy(otInstance *aInstance)
{
OT_UNUSED_VARIABLE(aInstance);
return psRadioSpinel->GetCslAccuracy();
}
#endif
#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
uint8_t otPlatRadioGetCslUncertainty(otInstance *aInstance)
{
OT_UNUSED_VARIABLE(aInstance);
return psRadioSpinel->GetCslUncertainty();
}
#endif
otError otPlatRadioSetChannelMaxTransmitPower(otInstance *aInstance, uint8_t aChannel,
int8_t aMaxPower)
{
OT_UNUSED_VARIABLE(aInstance);
return psRadioSpinel->SetChannelMaxTransmitPower(aChannel, aMaxPower);
}
otError otPlatRadioSetRegion(otInstance *aInstance, uint16_t aRegionCode)
{
OT_UNUSED_VARIABLE(aInstance);
return psRadioSpinel->SetRadioRegion(aRegionCode);
}
otError otPlatRadioGetRegion(otInstance *aInstance, uint16_t *aRegionCode)
{
OT_UNUSED_VARIABLE(aInstance);
return psRadioSpinel->GetRadioRegion(aRegionCode);
}
#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE
otError otPlatRadioConfigureEnhAckProbing(otInstance *aInstance, otLinkMetrics aLinkMetrics,
const otShortAddress aShortAddress,
const otExtAddress *aExtAddress)
{
OT_UNUSED_VARIABLE(aInstance);
return psRadioSpinel->ConfigureEnhAckProbing(aLinkMetrics, aShortAddress, *aExtAddress);
}
#endif
otError otPlatRadioReceiveAt(otInstance *aInstance, uint8_t aChannel, uint32_t aStart,
uint32_t aDuration)
{
OT_UNUSED_VARIABLE(aInstance);
OT_UNUSED_VARIABLE(aChannel);
OT_UNUSED_VARIABLE(aStart);
OT_UNUSED_VARIABLE(aDuration);
return OT_ERROR_NOT_IMPLEMENTED;
}
extern "C" void platformRadioInit(void)
{
spinel_iid_t iidList[ot::Spinel::kSpinelHeaderMaxNumIid];
struct ot::Spinel::RadioSpinelCallbacks callbacks;
iidList[0] = 0;
psRadioSpinel = new ot::Spinel::RadioSpinel();
psSpinelDriver = new ot::Spinel::SpinelDriver();
psRadioUrl = new ot::Url::Url();
pSpinelInterface = new ot::Hdlc::HdlcInterface(*psRadioUrl);
OT_UNUSED_VARIABLE(psSpinelDriver->Init(*pSpinelInterface, true /* aSoftwareReset */,
iidList, OT_ARRAY_LENGTH(iidList)));
memset(&callbacks, 0, sizeof(callbacks));
#if OPENTHREAD_CONFIG_DIAG_ENABLE
callbacks.mDiagReceiveDone = otPlatDiagRadioReceiveDone;
callbacks.mDiagTransmitDone = otPlatDiagRadioTransmitDone;
#endif /* OPENTHREAD_CONFIG_DIAG_ENABLE */
callbacks.mEnergyScanDone = otPlatRadioEnergyScanDone;
callbacks.mReceiveDone = otPlatRadioReceiveDone;
callbacks.mTransmitDone = otPlatRadioTxDone;
callbacks.mTxStarted = otPlatRadioTxStarted;
psRadioSpinel->SetCallbacks(callbacks);
#if OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2 && \
OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
bool aEnableRcpTimeSync = true;
#else
bool aEnableRcpTimeSync = false;
#endif
psRadioSpinel->Init(false /*aSkipRcpCompatibilityCheck*/, true /*aSoftwareReset*/,
psSpinelDriver, sRequiredRadioCaps, aEnableRcpTimeSync);
psRadioSpinel->SetTimeSyncState(true);
}
extern "C" void platformRadioDeinit(void)
{
psRadioSpinel->Deinit();
psSpinelDriver->Deinit();
}
extern "C" int notify_new_rx_frame(struct net_pkt *pkt)
{
/* The RX frame is handled by Openthread stack */
net_pkt_unref(pkt);
return 0;
}
extern "C" int notify_new_tx_frame(struct net_pkt *pkt)
{
k_fifo_put(&tx_pkt_fifo, pkt);
set_pending_event(PENDING_EVENT_FRAME_TO_SEND);
return 0;
}
extern "C" void platformRadioProcess(otInstance *aInstance)
{
if (is_pending_event_set(PENDING_EVENT_FRAME_TO_SEND)) {
struct net_pkt *evt_pkt;
reset_pending_event(PENDING_EVENT_FRAME_TO_SEND);
while ((evt_pkt = (struct net_pkt *)k_fifo_get(&tx_pkt_fifo, K_NO_WAIT)) != NULL) {
openthread_handle_frame_to_send(aInstance, evt_pkt);
}
}
psSpinelDriver->Process(aInstance);
psRadioSpinel->Process(aInstance);
}