blob: c351b3df4d10298df558c1edfaf0d76631ea0b31 [file] [log] [blame]
/*
* Copyright (c) 2016 Intel Corporation.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(net_mgmt, CONFIG_NET_MGMT_EVENT_LOG_LEVEL);
#include <zephyr/kernel.h>
#include <zephyr/toolchain.h>
#include <zephyr/linker/sections.h>
#include <zephyr/sys/util.h>
#include <zephyr/sys/slist.h>
#include <zephyr/net/net_mgmt.h>
#include <zephyr/debug/stack.h>
#include "net_private.h"
struct mgmt_event_entry {
#if defined(CONFIG_NET_MGMT_EVENT_INFO)
#if defined(CONFIG_NET_MGMT_EVENT_QUEUE)
uint8_t info[NET_EVENT_INFO_MAX_SIZE];
#else
const void *info;
#endif /* CONFIG_NET_MGMT_EVENT_QUEUE */
size_t info_length;
#endif /* CONFIG_NET_MGMT_EVENT_INFO */
uint32_t event;
struct net_if *iface;
};
BUILD_ASSERT((sizeof(struct mgmt_event_entry) % sizeof(uint32_t)) == 0,
"The structure must be a multiple of sizeof(uint32_t)");
struct mgmt_event_wait {
struct k_sem sync_call;
struct net_if *iface;
};
static K_MUTEX_DEFINE(net_mgmt_callback_lock);
#if defined(CONFIG_NET_MGMT_EVENT_THREAD)
K_KERNEL_STACK_DEFINE(mgmt_stack, CONFIG_NET_MGMT_EVENT_STACK_SIZE);
static struct k_work_q mgmt_work_q_obj;
#endif
static uint32_t global_event_mask;
static sys_slist_t event_callbacks = SYS_SLIST_STATIC_INIT(&event_callbacks);
/* Forward declaration for the actual caller */
static void mgmt_run_callbacks(const struct mgmt_event_entry * const mgmt_event);
#if defined(CONFIG_NET_MGMT_EVENT_QUEUE)
static K_MUTEX_DEFINE(net_mgmt_event_lock);
/* event structure used to prevent increasing the stack usage on the caller thread */
static struct mgmt_event_entry new_event;
K_MSGQ_DEFINE(event_msgq, sizeof(struct mgmt_event_entry),
CONFIG_NET_MGMT_EVENT_QUEUE_SIZE, sizeof(uint32_t));
static struct k_work_q *mgmt_work_q = COND_CODE_1(CONFIG_NET_MGMT_EVENT_SYSTEM_WORKQUEUE,
(&k_sys_work_q), (&mgmt_work_q_obj));
static void mgmt_event_work_handler(struct k_work *work);
static K_WORK_DEFINE(mgmt_work, mgmt_event_work_handler);
static inline void mgmt_push_event(uint32_t mgmt_event, struct net_if *iface,
const void *info, size_t length)
{
#ifndef CONFIG_NET_MGMT_EVENT_INFO
ARG_UNUSED(info);
ARG_UNUSED(length);
#endif /* CONFIG_NET_MGMT_EVENT_INFO */
(void)k_mutex_lock(&net_mgmt_event_lock, K_FOREVER);
memset(&new_event, 0, sizeof(struct mgmt_event_entry));
#ifdef CONFIG_NET_MGMT_EVENT_INFO
if (info && length) {
if (length <= NET_EVENT_INFO_MAX_SIZE) {
memcpy(new_event.info, info, length);
new_event.info_length = length;
} else {
NET_ERR("Event %u info length %zu > max size %zu",
mgmt_event, length, NET_EVENT_INFO_MAX_SIZE);
(void)k_mutex_unlock(&net_mgmt_event_lock);
return;
}
}
#endif /* CONFIG_NET_MGMT_EVENT_INFO */
new_event.event = mgmt_event;
new_event.iface = iface;
if (k_msgq_put(&event_msgq, &new_event,
K_MSEC(CONFIG_NET_MGMT_EVENT_QUEUE_TIMEOUT)) != 0) {
NET_WARN("Failure to push event (%u), "
"try increasing the 'CONFIG_NET_MGMT_EVENT_QUEUE_SIZE' "
"or 'CONFIG_NET_MGMT_EVENT_QUEUE_TIMEOUT' options.",
mgmt_event);
}
(void)k_mutex_unlock(&net_mgmt_event_lock);
k_work_submit_to_queue(mgmt_work_q, &mgmt_work);
}
static void mgmt_event_work_handler(struct k_work *work)
{
struct mgmt_event_entry mgmt_event;
ARG_UNUSED(work);
while (k_msgq_get(&event_msgq, &mgmt_event, K_FOREVER) == 0) {
NET_DBG("Handling events, forwarding it relevantly");
mgmt_run_callbacks(&mgmt_event);
/* forcefully give up our timeslot, to give time to the callback */
k_yield();
}
}
#else
static inline void mgmt_push_event(uint32_t event, struct net_if *iface,
const void *info, size_t length)
{
const struct mgmt_event_entry mgmt_event = {
.info = info,
.info_length = length,
.event = event,
.iface = iface,
};
mgmt_run_callbacks(&mgmt_event);
}
#endif /* CONFIG_NET_MGMT_EVENT_QUEUE */
static inline void mgmt_add_event_mask(uint32_t event_mask)
{
global_event_mask |= event_mask;
}
static inline void mgmt_rebuild_global_event_mask(void)
{
struct net_mgmt_event_callback *cb, *tmp;
global_event_mask = 0U;
STRUCT_SECTION_FOREACH(net_mgmt_event_static_handler, it) {
mgmt_add_event_mask(it->event_mask);
}
SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&event_callbacks, cb, tmp, node) {
mgmt_add_event_mask(cb->event_mask);
}
}
static inline bool mgmt_is_event_handled(uint32_t mgmt_event)
{
return (((NET_MGMT_GET_LAYER(mgmt_event) &
NET_MGMT_GET_LAYER(global_event_mask)) ==
NET_MGMT_GET_LAYER(mgmt_event)) &&
((NET_MGMT_GET_LAYER_CODE(mgmt_event) &
NET_MGMT_GET_LAYER_CODE(global_event_mask)) ==
NET_MGMT_GET_LAYER_CODE(mgmt_event)) &&
((NET_MGMT_GET_COMMAND(mgmt_event) &
NET_MGMT_GET_COMMAND(global_event_mask)) ==
NET_MGMT_GET_COMMAND(mgmt_event)));
}
static inline void mgmt_run_slist_callbacks(const struct mgmt_event_entry * const mgmt_event)
{
sys_snode_t *prev = NULL;
struct net_mgmt_event_callback *cb, *tmp;
NET_DBG("Event layer %u code %u cmd %u",
NET_MGMT_GET_LAYER(mgmt_event->event),
NET_MGMT_GET_LAYER_CODE(mgmt_event->event),
NET_MGMT_GET_COMMAND(mgmt_event->event));
SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&event_callbacks, cb, tmp, node) {
if (!(NET_MGMT_GET_LAYER(mgmt_event->event) ==
NET_MGMT_GET_LAYER(cb->event_mask)) ||
!(NET_MGMT_GET_LAYER_CODE(mgmt_event->event) ==
NET_MGMT_GET_LAYER_CODE(cb->event_mask)) ||
(NET_MGMT_GET_COMMAND(mgmt_event->event) &&
NET_MGMT_GET_COMMAND(cb->event_mask) &&
!(NET_MGMT_GET_COMMAND(mgmt_event->event) &
NET_MGMT_GET_COMMAND(cb->event_mask)))) {
continue;
}
#ifdef CONFIG_NET_MGMT_EVENT_INFO
if (mgmt_event->info_length) {
cb->info = (void *)mgmt_event->info;
cb->info_length = mgmt_event->info_length;
} else {
cb->info = NULL;
cb->info_length = 0;
}
#endif /* CONFIG_NET_MGMT_EVENT_INFO */
if (NET_MGMT_EVENT_SYNCHRONOUS(cb->event_mask)) {
struct mgmt_event_wait *sync_data =
CONTAINER_OF(cb->sync_call,
struct mgmt_event_wait, sync_call);
if (sync_data->iface &&
sync_data->iface != mgmt_event->iface) {
continue;
}
NET_DBG("Unlocking %p synchronous call", cb);
cb->raised_event = mgmt_event->event;
sync_data->iface = mgmt_event->iface;
sys_slist_remove(&event_callbacks, prev, &cb->node);
k_sem_give(cb->sync_call);
} else {
NET_DBG("Running callback %p : %p",
cb, cb->handler);
cb->handler(cb, mgmt_event->event, mgmt_event->iface);
prev = &cb->node;
}
}
#ifdef CONFIG_NET_DEBUG_MGMT_EVENT_STACK
log_stack_usage(&mgmt_work_q->thread);
#endif
}
static inline void mgmt_run_static_callbacks(const struct mgmt_event_entry * const mgmt_event)
{
STRUCT_SECTION_FOREACH(net_mgmt_event_static_handler, it) {
if (!(NET_MGMT_GET_LAYER(mgmt_event->event) ==
NET_MGMT_GET_LAYER(it->event_mask)) ||
!(NET_MGMT_GET_LAYER_CODE(mgmt_event->event) ==
NET_MGMT_GET_LAYER_CODE(it->event_mask)) ||
(NET_MGMT_GET_COMMAND(mgmt_event->event) &&
NET_MGMT_GET_COMMAND(it->event_mask) &&
!(NET_MGMT_GET_COMMAND(mgmt_event->event) &
NET_MGMT_GET_COMMAND(it->event_mask)))) {
continue;
}
it->handler(mgmt_event->event, mgmt_event->iface,
#ifdef CONFIG_NET_MGMT_EVENT_INFO
(void *)mgmt_event->info, mgmt_event->info_length,
#else
NULL, 0U,
#endif
it->user_data);
}
}
static void mgmt_run_callbacks(const struct mgmt_event_entry * const mgmt_event)
{
/* take the lock to prevent changes to the callback structure during use */
(void)k_mutex_lock(&net_mgmt_callback_lock, K_FOREVER);
mgmt_run_static_callbacks(mgmt_event);
mgmt_run_slist_callbacks(mgmt_event);
(void)k_mutex_unlock(&net_mgmt_callback_lock);
}
static int mgmt_event_wait_call(struct net_if *iface,
uint32_t mgmt_event_mask,
uint32_t *raised_event,
struct net_if **event_iface,
const void **info,
size_t *info_length,
k_timeout_t timeout)
{
struct mgmt_event_wait sync_data = {
.sync_call = Z_SEM_INITIALIZER(sync_data.sync_call, 0, 1),
};
struct net_mgmt_event_callback sync = {
.sync_call = &sync_data.sync_call,
.event_mask = mgmt_event_mask | NET_MGMT_SYNC_EVENT_BIT,
};
int ret;
if (iface) {
sync_data.iface = iface;
}
NET_DBG("Synchronous event 0x%08x wait %p", sync.event_mask, &sync);
net_mgmt_add_event_callback(&sync);
ret = k_sem_take(sync.sync_call, timeout);
if (ret < 0) {
if (ret == -EAGAIN) {
ret = -ETIMEDOUT;
}
net_mgmt_del_event_callback(&sync);
return ret;
}
if (raised_event) {
*raised_event = sync.raised_event;
}
if (event_iface) {
*event_iface = sync_data.iface;
}
#ifdef CONFIG_NET_MGMT_EVENT_INFO
if (info) {
*info = sync.info;
if (info_length) {
*info_length = sync.info_length;
}
}
#endif /* CONFIG_NET_MGMT_EVENT_INFO */
return ret;
}
void net_mgmt_add_event_callback(struct net_mgmt_event_callback *cb)
{
NET_DBG("Adding event callback %p", cb);
(void)k_mutex_lock(&net_mgmt_callback_lock, K_FOREVER);
sys_slist_prepend(&event_callbacks, &cb->node);
mgmt_add_event_mask(cb->event_mask);
(void)k_mutex_unlock(&net_mgmt_callback_lock);
}
void net_mgmt_del_event_callback(struct net_mgmt_event_callback *cb)
{
NET_DBG("Deleting event callback %p", cb);
(void)k_mutex_lock(&net_mgmt_callback_lock, K_FOREVER);
sys_slist_find_and_remove(&event_callbacks, &cb->node);
mgmt_rebuild_global_event_mask();
(void)k_mutex_unlock(&net_mgmt_callback_lock);
}
void net_mgmt_event_notify_with_info(uint32_t mgmt_event, struct net_if *iface,
const void *info, size_t length)
{
if (mgmt_is_event_handled(mgmt_event)) {
NET_DBG("Notifying Event layer %u code %u type %u",
NET_MGMT_GET_LAYER(mgmt_event),
NET_MGMT_GET_LAYER_CODE(mgmt_event),
NET_MGMT_GET_COMMAND(mgmt_event));
mgmt_push_event(mgmt_event, iface, info, length);
}
}
int net_mgmt_event_wait(uint32_t mgmt_event_mask,
uint32_t *raised_event,
struct net_if **iface,
const void **info,
size_t *info_length,
k_timeout_t timeout)
{
return mgmt_event_wait_call(NULL, mgmt_event_mask,
raised_event, iface, info, info_length,
timeout);
}
int net_mgmt_event_wait_on_iface(struct net_if *iface,
uint32_t mgmt_event_mask,
uint32_t *raised_event,
const void **info,
size_t *info_length,
k_timeout_t timeout)
{
NET_ASSERT(NET_MGMT_ON_IFACE(mgmt_event_mask));
NET_ASSERT(iface);
return mgmt_event_wait_call(iface, mgmt_event_mask,
raised_event, NULL, info, info_length,
timeout);
}
void net_mgmt_event_init(void)
{
mgmt_rebuild_global_event_mask();
#if defined(CONFIG_NET_MGMT_EVENT_THREAD)
#if defined(CONFIG_NET_TC_THREAD_COOPERATIVE)
/* Lowest priority cooperative thread */
#define THREAD_PRIORITY K_PRIO_COOP(CONFIG_NUM_COOP_PRIORITIES - 1)
#else
#define THREAD_PRIORITY K_PRIO_PREEMPT(CONFIG_NUM_PREEMPT_PRIORITIES - 1)
#endif
struct k_work_queue_config q_cfg = {
.name = "net_mgmt",
.no_yield = false,
};
k_work_queue_init(&mgmt_work_q_obj);
k_work_queue_start(&mgmt_work_q_obj, mgmt_stack,
K_KERNEL_STACK_SIZEOF(mgmt_stack),
THREAD_PRIORITY, &q_cfg);
NET_DBG("Net MGMT initialized: queue of %u entries, stack size of %u",
CONFIG_NET_MGMT_EVENT_QUEUE_SIZE,
CONFIG_NET_MGMT_EVENT_STACK_SIZE);
#endif /* CONFIG_NET_MGMT_EVENT_THREAD */
}