/* * 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 LOG_MODULE_REGISTER(net_openthread_platform, CONFIG_OPENTHREAD_PLATFORM_LOG_LEVEL); #include #include #include #include #include "platform/platform-zephyr.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(CONFIG_OPENTHREAD_NAT64_TRANSLATOR) #include #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 */