zephyr/modules/openthread/openthread.c
Robert Lubos c63ca134c6 net: openthread: Fix tasklet error printed on boot
OpenThread tries to signal pending tasklets during initialization, which
currently generate an error, as the work queue configured to handle
tasklets is initialized later. This is not a fatal issue, as the
initialization code on the Zephyr side triggers a work item immediately
after the work queue was initialized, but it doesn't look good to have
errors printed on boot.

Therefore, move the work queue initialization before the OpenThread
stack initialization, so that tasklets triggered from OT side can be
signalled properly w/o an error.

Signed-off-by: Robert Lubos <robert.lubos@nordicsemi.no>
2025-06-09 14:59:35 -07:00

512 lines
12 KiB
C

/*
* 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 */