You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
511 lines
12 KiB
511 lines
12 KiB
/* |
|
* Copyright (c) 2025 Nordic Semiconductor ASA |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
|
|
/** |
|
* @file |
|
* This file implements the OpenThread module initialization and state change handling. |
|
* |
|
*/ |
|
|
|
#include <zephyr/logging/log.h> |
|
LOG_MODULE_REGISTER(net_openthread_platform, CONFIG_OPENTHREAD_PLATFORM_LOG_LEVEL); |
|
|
|
#include <zephyr/kernel.h> |
|
#include <zephyr/init.h> |
|
#include <zephyr/version.h> |
|
#include <zephyr/sys/check.h> |
|
|
|
#include "platform/platform-zephyr.h" |
|
|
|
#include <openthread.h> |
|
#include <openthread_utils.h> |
|
|
|
#include <openthread/child_supervision.h> |
|
#include <openthread/cli.h> |
|
#include <openthread/ip6.h> |
|
#include <openthread/link.h> |
|
#include <openthread/link_raw.h> |
|
#include <openthread/ncp.h> |
|
#include <openthread/message.h> |
|
#include <openthread/platform/diag.h> |
|
#include <openthread/tasklet.h> |
|
#include <openthread/thread.h> |
|
#include <openthread/dataset.h> |
|
#include <openthread/joiner.h> |
|
#include <openthread-system.h> |
|
#include <utils/uart.h> |
|
|
|
#if defined(CONFIG_OPENTHREAD_NAT64_TRANSLATOR) |
|
#include <openthread/nat64.h> |
|
#endif /* CONFIG_OPENTHREAD_NAT64_TRANSLATOR */ |
|
|
|
#define OT_STACK_SIZE (CONFIG_OPENTHREAD_THREAD_STACK_SIZE) |
|
|
|
#if defined(CONFIG_OPENTHREAD_THREAD_PREEMPTIVE) |
|
#define OT_PRIORITY K_PRIO_PREEMPT(CONFIG_OPENTHREAD_THREAD_PRIORITY) |
|
#else |
|
#define OT_PRIORITY K_PRIO_COOP(CONFIG_OPENTHREAD_THREAD_PRIORITY) |
|
#endif |
|
|
|
#if defined(CONFIG_OPENTHREAD_NETWORK_NAME) |
|
#define OT_NETWORK_NAME CONFIG_OPENTHREAD_NETWORK_NAME |
|
#else |
|
#define OT_NETWORK_NAME "" |
|
#endif |
|
|
|
#if defined(CONFIG_OPENTHREAD_CHANNEL) |
|
#define OT_CHANNEL CONFIG_OPENTHREAD_CHANNEL |
|
#else |
|
#define OT_CHANNEL 0 |
|
#endif |
|
|
|
#if defined(CONFIG_OPENTHREAD_PANID) |
|
#define OT_PANID CONFIG_OPENTHREAD_PANID |
|
#else |
|
#define OT_PANID 0 |
|
#endif |
|
|
|
#if defined(CONFIG_OPENTHREAD_XPANID) |
|
#define OT_XPANID CONFIG_OPENTHREAD_XPANID |
|
#else |
|
#define OT_XPANID "" |
|
#endif |
|
|
|
#if defined(CONFIG_OPENTHREAD_NETWORKKEY) |
|
#define OT_NETWORKKEY CONFIG_OPENTHREAD_NETWORKKEY |
|
#else |
|
#define OT_NETWORKKEY "" |
|
#endif |
|
|
|
#if defined(CONFIG_OPENTHREAD_JOINER_PSKD) |
|
#define OT_JOINER_PSKD CONFIG_OPENTHREAD_JOINER_PSKD |
|
#else |
|
#define OT_JOINER_PSKD "" |
|
#endif |
|
|
|
#if defined(CONFIG_OPENTHREAD_PLATFORM_INFO) |
|
#define OT_PLATFORM_INFO CONFIG_OPENTHREAD_PLATFORM_INFO |
|
#else |
|
#define OT_PLATFORM_INFO "" |
|
#endif |
|
|
|
#if defined(CONFIG_OPENTHREAD_POLL_PERIOD) |
|
#define OT_POLL_PERIOD CONFIG_OPENTHREAD_POLL_PERIOD |
|
#else |
|
#define OT_POLL_PERIOD 0 |
|
#endif |
|
|
|
#define ZEPHYR_PACKAGE_NAME "Zephyr" |
|
#define PACKAGE_VERSION KERNEL_VERSION_STRING |
|
|
|
static void openthread_process(struct k_work *work); |
|
|
|
/* Global variables to store the OpenThread module context */ |
|
static otInstance *openthread_instance; |
|
static sys_slist_t openthread_state_change_cbs = SYS_SLIST_STATIC_INIT(openthread_state_change_cbs); |
|
static struct k_work_q openthread_work_q; |
|
|
|
static K_WORK_DEFINE(openthread_work, openthread_process); |
|
static K_MUTEX_DEFINE(openthread_lock); |
|
K_KERNEL_STACK_DEFINE(ot_stack_area, OT_STACK_SIZE); |
|
|
|
k_tid_t openthread_thread_id_get(void) |
|
{ |
|
return (k_tid_t)&openthread_work_q.thread; |
|
} |
|
|
|
static int ncp_hdlc_send(const uint8_t *buf, uint16_t len) |
|
{ |
|
otError err = OT_ERROR_NONE; |
|
|
|
err = otPlatUartSend(buf, len); |
|
if (err != OT_ERROR_NONE) { |
|
return 0; |
|
} |
|
|
|
return len; |
|
} |
|
|
|
static void openthread_process(struct k_work *work) |
|
{ |
|
ARG_UNUSED(work); |
|
|
|
openthread_mutex_lock(); |
|
|
|
while (otTaskletsArePending(openthread_instance)) { |
|
otTaskletsProcess(openthread_instance); |
|
} |
|
|
|
otSysProcessDrivers(openthread_instance); |
|
|
|
openthread_mutex_unlock(); |
|
} |
|
|
|
static void ot_joiner_start_handler(otError error, void *context) |
|
{ |
|
ARG_UNUSED(context); |
|
|
|
if (error != OT_ERROR_NONE) { |
|
LOG_ERR("Join failed [%d]", error); |
|
} else { |
|
LOG_INF("Join success"); |
|
error = otThreadSetEnabled(openthread_instance, true); |
|
if (error != OT_ERROR_NONE) { |
|
LOG_ERR("Failed to start the OpenThread network [%d]", error); |
|
} |
|
} |
|
} |
|
|
|
static bool ot_setup_default_configuration(void) |
|
{ |
|
otExtendedPanId xpanid = {0}; |
|
otNetworkKey networkKey = {0}; |
|
otError error = OT_ERROR_NONE; |
|
|
|
error = otThreadSetNetworkName(openthread_instance, OT_NETWORK_NAME); |
|
if (error != OT_ERROR_NONE) { |
|
LOG_ERR("Failed to set %s [%d]", "network name", error); |
|
return false; |
|
} |
|
|
|
error = otLinkSetChannel(openthread_instance, OT_CHANNEL); |
|
if (error != OT_ERROR_NONE) { |
|
LOG_ERR("Failed to set %s [%d]", "channel", error); |
|
return false; |
|
} |
|
|
|
error = otLinkSetPanId(openthread_instance, OT_PANID); |
|
if (error != OT_ERROR_NONE) { |
|
LOG_ERR("Failed to set %s [%d]", "PAN ID", error); |
|
return false; |
|
} |
|
|
|
if (bytes_from_str(xpanid.m8, 8, (char *)OT_XPANID) != 0) { |
|
LOG_ERR("Failed to parse extended PAN ID"); |
|
return false; |
|
} |
|
error = otThreadSetExtendedPanId(openthread_instance, &xpanid); |
|
if (error != OT_ERROR_NONE) { |
|
LOG_ERR("Failed to set %s [%d]", "ext PAN ID", error); |
|
return false; |
|
} |
|
|
|
if (strlen(OT_NETWORKKEY)) { |
|
if (bytes_from_str(networkKey.m8, OT_NETWORK_KEY_SIZE, (char *)OT_NETWORKKEY) != |
|
0) { |
|
LOG_ERR("Failed to parse network key"); |
|
return false; |
|
} |
|
error = otThreadSetNetworkKey(openthread_instance, &networkKey); |
|
if (error != OT_ERROR_NONE) { |
|
LOG_ERR("Failed to set %s [%d]", "network key", error); |
|
return false; |
|
} |
|
} |
|
|
|
return true; |
|
} |
|
|
|
static void ot_state_changed_handler(uint32_t flags, void *context) |
|
{ |
|
ARG_UNUSED(context); |
|
|
|
struct openthread_state_changed_callback *entry, *next; |
|
|
|
bool is_up = otIp6IsEnabled(openthread_instance); |
|
|
|
LOG_INF("State changed! Flags: 0x%08" PRIx32 " Current role: %s Ip6: %s", flags, |
|
otThreadDeviceRoleToString(otThreadGetDeviceRole(openthread_instance)), |
|
(is_up ? "up" : "down")); |
|
|
|
SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&openthread_state_change_cbs, entry, next, node) { |
|
if (entry->otCallback != NULL) { |
|
entry->otCallback(flags, entry->user_data); |
|
} |
|
} |
|
} |
|
|
|
void otTaskletsSignalPending(otInstance *instance) |
|
{ |
|
ARG_UNUSED(instance); |
|
|
|
int error = k_work_submit_to_queue(&openthread_work_q, &openthread_work); |
|
|
|
if (error < 0) { |
|
LOG_ERR("Failed to submit work to queue, error: %d", error); |
|
} |
|
} |
|
|
|
void otSysEventSignalPending(void) |
|
{ |
|
otTaskletsSignalPending(NULL); |
|
} |
|
|
|
int openthread_state_changed_callback_register(struct openthread_state_changed_callback *cb) |
|
{ |
|
CHECKIF(cb == NULL || cb->otCallback == NULL) { |
|
return -EINVAL; |
|
} |
|
|
|
openthread_mutex_lock(); |
|
sys_slist_append(&openthread_state_change_cbs, &cb->node); |
|
openthread_mutex_unlock(); |
|
|
|
return 0; |
|
} |
|
|
|
int openthread_state_changed_callback_unregister(struct openthread_state_changed_callback *cb) |
|
{ |
|
bool removed = false; |
|
|
|
CHECKIF(cb == NULL) { |
|
return -EINVAL; |
|
} |
|
|
|
openthread_mutex_lock(); |
|
removed = sys_slist_find_and_remove(&openthread_state_change_cbs, &cb->node); |
|
openthread_mutex_unlock(); |
|
|
|
if (!removed) { |
|
return -EALREADY; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
struct otInstance *openthread_get_default_instance(void) |
|
{ |
|
__ASSERT(openthread_instance, "OT instance is not initialized"); |
|
return openthread_instance; |
|
} |
|
|
|
int openthread_init(void) |
|
{ |
|
struct k_work_queue_config q_cfg = { |
|
.name = "openthread", |
|
.no_yield = true, |
|
}; |
|
otError error = OT_ERROR_NONE; |
|
|
|
/* Prevent multiple initializations */ |
|
if (openthread_instance) { |
|
return 0; |
|
} |
|
|
|
/* Start work queue for the OpenThread module */ |
|
k_work_queue_start(&openthread_work_q, ot_stack_area, |
|
K_KERNEL_STACK_SIZEOF(ot_stack_area), |
|
OT_PRIORITY, &q_cfg); |
|
|
|
openthread_mutex_lock(); |
|
|
|
otSysInit(0, NULL); |
|
openthread_instance = otInstanceInitSingle(); |
|
|
|
__ASSERT(openthread_instance, "OT instance initialization failed"); |
|
|
|
if (IS_ENABLED(CONFIG_OPENTHREAD_SHELL)) { |
|
platformShellInit(openthread_instance); |
|
} |
|
|
|
if (IS_ENABLED(CONFIG_OPENTHREAD_COPROCESSOR)) { |
|
error = otPlatUartEnable(); |
|
if (error != OT_ERROR_NONE) { |
|
LOG_ERR("Failed to enable UART: [%d]", error); |
|
} |
|
|
|
otNcpHdlcInit(openthread_instance, ncp_hdlc_send); |
|
} else { |
|
otIp6SetReceiveFilterEnabled(openthread_instance, true); |
|
|
|
#if defined(CONFIG_OPENTHREAD_NAT64_TRANSLATOR) |
|
|
|
otIp4Cidr nat64_cidr; |
|
|
|
if (otIp4CidrFromString(CONFIG_OPENTHREAD_NAT64_CIDR, &nat64_cidr) == |
|
OT_ERROR_NONE) { |
|
if (otNat64SetIp4Cidr(openthread_instance, &nat64_cidr) != OT_ERROR_NONE) { |
|
LOG_ERR("Incorrect NAT64 CIDR"); |
|
return -EIO; |
|
} |
|
} else { |
|
LOG_ERR("Failed to parse NAT64 CIDR"); |
|
return -EIO; |
|
} |
|
#endif /* CONFIG_OPENTHREAD_NAT64_TRANSLATOR */ |
|
|
|
error = otSetStateChangedCallback(openthread_instance, &ot_state_changed_handler, |
|
NULL); |
|
if (error != OT_ERROR_NONE) { |
|
LOG_ERR("Could not set state changed callback: %d", error); |
|
return -EIO; |
|
} |
|
} |
|
|
|
openthread_mutex_unlock(); |
|
|
|
(void)k_work_submit_to_queue(&openthread_work_q, &openthread_work); |
|
|
|
return error == OT_ERROR_NONE ? 0 : -EIO; |
|
} |
|
|
|
int openthread_run(void) |
|
{ |
|
openthread_mutex_lock(); |
|
otError error = OT_ERROR_NONE; |
|
|
|
LOG_INF("OpenThread version: %s", otGetVersionString()); |
|
|
|
if (IS_ENABLED(CONFIG_OPENTHREAD_COPROCESSOR)) { |
|
LOG_DBG("OpenThread co-processor."); |
|
goto exit; |
|
} |
|
|
|
error = otIp6SetEnabled(openthread_instance, true); |
|
if (error != OT_ERROR_NONE) { |
|
LOG_ERR("Failed to set %s [%d]", "IPv6 support", error); |
|
goto exit; |
|
} |
|
|
|
/* Sleepy End Device specific configuration. */ |
|
if (IS_ENABLED(CONFIG_OPENTHREAD_MTD_SED)) { |
|
otLinkModeConfig ot_mode = otThreadGetLinkMode(openthread_instance); |
|
|
|
/* A SED should always attach the network as a SED to indicate |
|
* increased buffer requirement to a parent. |
|
*/ |
|
ot_mode.mRxOnWhenIdle = false; |
|
|
|
error = otThreadSetLinkMode(openthread_instance, ot_mode); |
|
if (error != OT_ERROR_NONE) { |
|
LOG_ERR("Failed to set %s [%d]", "link mode", error); |
|
goto exit; |
|
} |
|
|
|
error = otLinkSetPollPeriod(openthread_instance, OT_POLL_PERIOD); |
|
if (error != OT_ERROR_NONE) { |
|
LOG_ERR("Failed to set %s [%d]", "poll period", error); |
|
goto exit; |
|
} |
|
} |
|
|
|
/* Configure Child Supervision and MLE Child timeouts. */ |
|
otChildSupervisionSetInterval(openthread_instance, |
|
CONFIG_OPENTHREAD_CHILD_SUPERVISION_INTERVAL); |
|
otChildSupervisionSetCheckTimeout(openthread_instance, |
|
CONFIG_OPENTHREAD_CHILD_SUPERVISION_CHECK_TIMEOUT); |
|
otThreadSetChildTimeout(openthread_instance, CONFIG_OPENTHREAD_MLE_CHILD_TIMEOUT); |
|
|
|
if (otDatasetIsCommissioned(openthread_instance)) { |
|
/* OpenThread already has dataset stored - skip the |
|
* configuration. |
|
*/ |
|
LOG_DBG("OpenThread already commissioned."); |
|
} else if (IS_ENABLED(CONFIG_OPENTHREAD_JOINER_AUTOSTART)) { |
|
/* No dataset - initiate network join procedure. */ |
|
LOG_DBG("Starting OpenThread join procedure."); |
|
|
|
error = otJoinerStart(openthread_instance, OT_JOINER_PSKD, NULL, |
|
ZEPHYR_PACKAGE_NAME, OT_PLATFORM_INFO, PACKAGE_VERSION, NULL, |
|
&ot_joiner_start_handler, NULL); |
|
|
|
if (error != OT_ERROR_NONE) { |
|
LOG_ERR("Failed to start joiner [%d]", error); |
|
} |
|
|
|
goto exit; |
|
} else { |
|
/* No dataset - load the default configuration. */ |
|
LOG_DBG("Loading OpenThread default configuration."); |
|
|
|
if (!ot_setup_default_configuration()) { |
|
goto exit; |
|
} |
|
} |
|
|
|
LOG_INF("Network name: %s", otThreadGetNetworkName(openthread_instance)); |
|
|
|
/* Start the network. */ |
|
error = otThreadSetEnabled(openthread_instance, true); |
|
if (error != OT_ERROR_NONE) { |
|
LOG_ERR("Failed to start the OpenThread network [%d]", error); |
|
} |
|
|
|
exit: |
|
|
|
openthread_mutex_unlock(); |
|
|
|
return error == OT_ERROR_NONE ? 0 : -EIO; |
|
} |
|
|
|
int openthread_stop(void) |
|
{ |
|
otError error = OT_ERROR_NONE; |
|
|
|
if (IS_ENABLED(CONFIG_OPENTHREAD_COPROCESSOR)) { |
|
return 0; |
|
} |
|
|
|
openthread_mutex_lock(); |
|
|
|
error = otThreadSetEnabled(openthread_instance, false); |
|
if (error == OT_ERROR_INVALID_STATE) { |
|
LOG_DBG("Openthread interface was not up [%d]", error); |
|
} |
|
|
|
openthread_mutex_unlock(); |
|
|
|
return 0; |
|
} |
|
|
|
void openthread_set_receive_cb(openthread_receive_cb cb, void *context) |
|
{ |
|
__ASSERT(cb != NULL, "Receive callback is not set"); |
|
__ASSERT(openthread_instance != NULL, "OpenThread instance is not initialized"); |
|
|
|
if (!IS_ENABLED(CONFIG_OPENTHREAD_COPROCESSOR)) { |
|
openthread_mutex_lock(); |
|
otIp6SetReceiveCallback(openthread_instance, cb, context); |
|
|
|
#if defined(CONFIG_OPENTHREAD_NAT64_TRANSLATOR) |
|
otNat64SetReceiveIp4Callback(openthread_instance, cb, context); |
|
#endif /* CONFIG_OPENTHREAD_NAT64_TRANSLATOR */ |
|
|
|
openthread_mutex_unlock(); |
|
} |
|
} |
|
|
|
void openthread_mutex_lock(void) |
|
{ |
|
(void)k_mutex_lock(&openthread_lock, K_FOREVER); |
|
} |
|
|
|
int openthread_mutex_try_lock(void) |
|
{ |
|
return k_mutex_lock(&openthread_lock, K_NO_WAIT); |
|
} |
|
|
|
void openthread_mutex_unlock(void) |
|
{ |
|
(void)k_mutex_unlock(&openthread_lock); |
|
} |
|
|
|
#ifdef CONFIG_OPENTHREAD_SYS_INIT |
|
static int openthread_sys_init(void) |
|
{ |
|
int error = openthread_init(); |
|
|
|
if (error == 0) { |
|
#ifndef CONFIG_OPENTHREAD_MANUAL_START |
|
error = openthread_run(); |
|
#endif |
|
} |
|
|
|
return error; |
|
} |
|
|
|
SYS_INIT(openthread_sys_init, POST_KERNEL, CONFIG_OPENTHREAD_SYS_INIT_PRIORITY); |
|
#endif /* CONFIG_OPENTHREAD_SYS_INIT */
|
|
|