samples/subsys/llext: EDK sample
Shows a simple application which loads extensions and some simple extensions. While everything is inside Zephyr tree, everything can actually be build from different directories (even machines), as long as the EDK is generated from the application and used by the extensions. More information is available at sample's README. This sample is build only for twister, as it requires a few steps to be properly run, namely build the EDK, install it somewhere, build the extensions using the EDK and finally build the application with the extensions. Signed-off-by: Ederson de Souza <ederson.desouza@intel.com>
This commit is contained in:
parent
a0ee472a6f
commit
972eecfc17
19 changed files with 973 additions and 0 deletions
137
samples/subsys/llext/edk/README.rst
Normal file
137
samples/subsys/llext/edk/README.rst
Normal file
|
@ -0,0 +1,137 @@
|
|||
.. zephyr:code-sample:: llext-edk
|
||||
:name: Linkable loadable extensions EDK
|
||||
:relevant-api: llext
|
||||
|
||||
Enable linkable loadable extension development outside the Zephyr tree using
|
||||
LLEXT EDK (Extension Development Kit).
|
||||
|
||||
Overview
|
||||
********
|
||||
|
||||
This sample demonstrates how to use the Zephyr LLEXT EDK (Extension Development
|
||||
Kit). It is composed of one Zephyr application, which provides APIs for the
|
||||
extensions that it loads. The provided API is a simple publish/subscribe system,
|
||||
based on :ref:`Zbus <zbus>`, which extensions use to communicate with each other.
|
||||
|
||||
The application is composed of a subscriber thread, which listens for events
|
||||
published and republishes them via Zbus to the extensions that are
|
||||
subscribers. There are four extensions, which are loaded by the application and
|
||||
run in different contexts. Extensions ``ext1``, ``ext2`` and ``ext3`` run in
|
||||
userspace, each demonstrating different application and Zephyr API usage, such as
|
||||
semaphores, spawning threads to listen for events or simply publishing or
|
||||
subscribing to events. Extension ``kext1`` runs in a kernel thread, albeit similar
|
||||
to ``ext3``.
|
||||
|
||||
The application also creates different memory domains for each extension, thus
|
||||
providing some level of isolation - although the kernel one still has access
|
||||
to all of Zephyr kernel.
|
||||
|
||||
Note that the kernel extension is only available when the EDK is built with
|
||||
the :kconfig:option:`CONFIG_LLEXT_EDK_USERSPACE_ONLY` option disabled.
|
||||
|
||||
|
||||
The application is built using the Zephyr build system. The EDK is built using
|
||||
the Zephyr build system as well, via ``llext-edk`` target. The EDK is then
|
||||
extracted and the extensions are built using CMake.
|
||||
|
||||
Finally, the way the application loads the extensions is by including them
|
||||
during build time, which is not really practical. This sample is about the EDK
|
||||
providing the ability to build extensions independently from the application.
|
||||
One could build the extensions in different directories, not related to the
|
||||
Zephyr application - even on different machines, using only the EDK. At the
|
||||
limit, one could even imagine a scenario where the extensions are built by
|
||||
different teams, using the EDK provided by the application developer.
|
||||
|
||||
Building the EDK
|
||||
****************
|
||||
|
||||
To build the EDK, use the ``llext-edk`` target. For example:
|
||||
|
||||
.. zephyr-app-commands::
|
||||
:zephyr-app: samples/subsys/llext/edk/app
|
||||
:board: qemu_cortex_r5
|
||||
:goals: build llext-edk
|
||||
:west-args: -p=always
|
||||
:compact:
|
||||
|
||||
Copy the EDK to some place and extract it:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
mkdir /tmp/edk
|
||||
cp build/zephyr/llext-edk.tar.xz /tmp/edk
|
||||
cd /tmp/edk
|
||||
tar -xf llext-edk.tar.xz
|
||||
|
||||
Then set ``LLEXT_EDK_INSTALL_DIR`` to the extracted directory:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
export LLEXT_EDK_INSTALL_DIR=/tmp/edk/llext-edk
|
||||
|
||||
This variable is used by the extensions to find the EDK.
|
||||
|
||||
Building the extensions
|
||||
***********************
|
||||
|
||||
The :envvar:`ZEPHYR_SDK_INSTALL_DIR` environment variable is used by the
|
||||
extensions to find the Zephyr SDK, so you need to ensure it's properly set:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
export ZEPHYR_SDK_INSTALL_DIR=</path/to/zephyr-sdk>
|
||||
|
||||
To build the extensions, in the ``ext1``, ``ext2``, ``ext3`` and ``kext1``
|
||||
directories:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
cmake -B build
|
||||
make -C build
|
||||
|
||||
Alternatively, you can set the ``LLEXT_EDK_INSTALL_DIR`` directly in the
|
||||
CMake invocation:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
cmake -B build -DLLEXT_EDK_INSTALL_DIR=/tmp/edk/llext-edk
|
||||
make -C build
|
||||
|
||||
Building the application
|
||||
************************
|
||||
|
||||
Now, build the application, including the extensions, and run it:
|
||||
|
||||
.. zephyr-app-commands::
|
||||
:zephyr-app: samples/subsys/llext/edk/app
|
||||
:board: qemu_cortex_r5
|
||||
:goals: build run
|
||||
:west-args: -p=always
|
||||
:compact:
|
||||
|
||||
You should see something like:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
[app]Subscriber thread [0x20b28] started.
|
||||
[app]Loading extension [kext1].
|
||||
[app]Thread 0x20840 created to run extension [kext1], at privileged mode.
|
||||
[k-ext1]Waiting sem
|
||||
[app]Thread [0x222a0] registered event [0x223c0]
|
||||
[k-ext1]Waiting event
|
||||
[app]Loading extension [ext1].
|
||||
[app]Thread 0x20a30 created to run extension [ext1], at userspace.
|
||||
[app]Thread [0x20a30] registered event [0x26060]
|
||||
[ext1]Waiting event
|
||||
[app]Loading extension [ext2].
|
||||
[app]Thread 0x20938 created to run extension [ext2], at userspace.
|
||||
[ext2]Publishing tick
|
||||
[app][subscriber_thread]Got channel tick_chan
|
||||
[ext1]Got event, reading channel
|
||||
[ext1]Read val: 0
|
||||
[ext1]Waiting event
|
||||
[k-ext1]Got event, giving sem
|
||||
[k-ext1]Got sem, reading channel
|
||||
[k-ext1]Read val: 0
|
||||
[k-ext1]Waiting sem
|
||||
(...)
|
17
samples/subsys/llext/edk/app/CMakeLists.txt
Normal file
17
samples/subsys/llext/edk/app/CMakeLists.txt
Normal file
|
@ -0,0 +1,17 @@
|
|||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# It seems llext currently doesn't support some Thumb32 relocation
|
||||
# instructions generated when building the extensions with default
|
||||
# flags. As a workaround, we remove these *unrelated* flags from the
|
||||
# default flags. This allows the extensions to be built without the
|
||||
# unsupported instructions.
|
||||
# See issue #72832 for more details.
|
||||
list(APPEND LLEXT_EDK_REMOVE_FLAGS -mcpu=cortex-r5 -mfloat-abi=hard)
|
||||
|
||||
cmake_minimum_required(VERSION 3.20.0)
|
||||
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
|
||||
|
||||
project(app LANGUAGES C)
|
||||
|
||||
target_sources(app PRIVATE src/main.c src/pubsub.c)
|
||||
zephyr_include_directories(include)
|
38
samples/subsys/llext/edk/app/include/app_api.h
Normal file
38
samples/subsys/llext/edk/app/include/app_api.h
Normal file
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright (c) 2024 Intel Corporation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef _TEST_EDK_H_
|
||||
#define _TEST_EDK_H_
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/toolchain.h>
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
enum Channels {
|
||||
CHAN_TICK = 1,
|
||||
CHAN_LAST
|
||||
};
|
||||
|
||||
struct channel_tick_data {
|
||||
unsigned long l;
|
||||
};
|
||||
|
||||
__syscall int publish(enum Channels channel, void *data,
|
||||
size_t data_len);
|
||||
__syscall int receive(enum Channels channel, void *data,
|
||||
size_t data_len);
|
||||
__syscall int register_subscriber(enum Channels channel,
|
||||
struct k_event *evt);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#include <syscalls/app_api.h>
|
||||
#endif /* _TEST_EDK_H_ */
|
19
samples/subsys/llext/edk/app/prj.conf
Normal file
19
samples/subsys/llext/edk/app/prj.conf
Normal file
|
@ -0,0 +1,19 @@
|
|||
CONFIG_APPLICATION_DEFINED_SYSCALL=y
|
||||
CONFIG_USERSPACE=y
|
||||
CONFIG_LLEXT=y
|
||||
|
||||
CONFIG_MAIN_STACK_SIZE=4096
|
||||
|
||||
CONFIG_DYNAMIC_OBJECTS=y
|
||||
CONFIG_DYNAMIC_THREAD=y
|
||||
CONFIG_DYNAMIC_THREAD_ALLOC=y
|
||||
CONFIG_DYNAMIC_THREAD_PREFER_ALLOC=y
|
||||
CONFIG_ZBUS=y
|
||||
CONFIG_ZBUS_CHANNEL_NAME=y
|
||||
|
||||
CONFIG_LLEXT_HEAP_SIZE=32
|
||||
|
||||
# Uncomment to disable kext1.
|
||||
#CONFIG_LLEXT_EDK_USERSPACE_ONLY=y
|
||||
|
||||
CONFIG_EVENTS=y
|
12
samples/subsys/llext/edk/app/sample.yaml
Normal file
12
samples/subsys/llext/edk/app/sample.yaml
Normal file
|
@ -0,0 +1,12 @@
|
|||
common:
|
||||
tags: llext edk
|
||||
arch_allow:
|
||||
- arm
|
||||
filter: CONFIG_ARCH_HAS_USERSPACE
|
||||
sample:
|
||||
description: EDK sample application
|
||||
name: EDK sample application
|
||||
tests:
|
||||
sample.edk.app:
|
||||
build_only: true
|
||||
tags: edk llext
|
187
samples/subsys/llext/edk/app/src/main.c
Normal file
187
samples/subsys/llext/edk/app/src/main.c
Normal file
|
@ -0,0 +1,187 @@
|
|||
/*
|
||||
* Copyright (c) 2024 Intel Corporation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/app_memory/mem_domain.h>
|
||||
#include <zephyr/llext/llext.h>
|
||||
#include <zephyr/llext/buf_loader.h>
|
||||
|
||||
#include <app_api.h>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
/**
|
||||
* Assume that if the extension 1 is not built, we are building the
|
||||
* EDK. If others are not built, this will just fail.
|
||||
*/
|
||||
#if defined __has_include
|
||||
# if __has_include("../../ext1/build/ext1.inc")
|
||||
# undef EDK_BUILD
|
||||
# else
|
||||
# pragma message "Extension 1 not built, assuming EDK build."
|
||||
# define EDK_BUILD
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifndef EDK_BUILD
|
||||
#include "../../ext1/build/ext1.inc"
|
||||
#define ext1_inc ext1_llext
|
||||
#define ext1_len ext1_llext_len
|
||||
#include "../../ext2/build/ext2.inc"
|
||||
#define ext2_inc ext2_llext
|
||||
#define ext2_len ext2_llext_len
|
||||
#include "../../ext3/build/ext3.inc"
|
||||
#define ext3_inc ext3_llext
|
||||
#define ext3_len ext3_llext_len
|
||||
#ifndef CONFIG_LLEXT_EDK_USERSPACE_ONLY
|
||||
#include "../../k-ext1/build/kext1.inc"
|
||||
#define kext1_inc kext1_llext
|
||||
#define kext1_len kext1_llext_len
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#define USER_STACKSIZE 2048
|
||||
#define USER_HEAPSIZE 8192
|
||||
#define MAX_EXTENSIONS 4
|
||||
|
||||
extern k_tid_t start_subscriber_thread(void);
|
||||
|
||||
/* Maybe make all this depend on MAX_EXTENSIONS? */
|
||||
struct k_thread user_thread1, user_thread2, user_thread3, kernel_thread1;
|
||||
K_THREAD_STACK_DEFINE(user_stack1, USER_STACKSIZE);
|
||||
K_THREAD_STACK_DEFINE(user_stack2, USER_STACKSIZE);
|
||||
K_THREAD_STACK_DEFINE(user_stack3, USER_STACKSIZE);
|
||||
K_THREAD_STACK_DEFINE(kernel_stack1, USER_STACKSIZE);
|
||||
|
||||
K_HEAP_DEFINE(user_heap1, USER_HEAPSIZE);
|
||||
K_HEAP_DEFINE(user_heap2, USER_HEAPSIZE);
|
||||
K_HEAP_DEFINE(user_heap3, USER_HEAPSIZE);
|
||||
K_HEAP_DEFINE(kernel_heap1, USER_HEAPSIZE);
|
||||
|
||||
struct {
|
||||
k_tid_t thread;
|
||||
struct llext *ext;
|
||||
} extension_threads[MAX_EXTENSIONS];
|
||||
int max_extension_thread_idx;
|
||||
|
||||
static const void * const load(const char *name, struct llext **ext, void *buf,
|
||||
size_t len)
|
||||
{
|
||||
#ifndef EDK_BUILD
|
||||
struct llext_buf_loader buf_loader = LLEXT_BUF_LOADER(buf, len);
|
||||
struct llext_loader *loader = &buf_loader.loader;
|
||||
struct llext_load_param ldr_parm = LLEXT_LOAD_PARAM_DEFAULT;
|
||||
|
||||
llext_load(loader, name, ext, &ldr_parm);
|
||||
|
||||
return llext_find_sym(&(*ext)->exp_tab, "start");
|
||||
#else
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void unload(struct llext **ext)
|
||||
{
|
||||
llext_unload(ext);
|
||||
}
|
||||
|
||||
static void user_function(void *p1, void *p2, void *p3)
|
||||
{
|
||||
int (*start_fn)(void) = p1;
|
||||
|
||||
printk("[app]Thread %p created to run extension [%s], at %s\n",
|
||||
k_current_get(), (char *)p2,
|
||||
k_is_user_context() ? "userspace." : "privileged mode.");
|
||||
|
||||
start_fn();
|
||||
printk("[app]Thread %p done\n", k_current_get());
|
||||
}
|
||||
|
||||
void k_sys_fatal_error_handler(unsigned int reason, const z_arch_esf_t *esf)
|
||||
{
|
||||
int i;
|
||||
|
||||
printk("[app]Fatal handler! Thread: %p\n", k_current_get());
|
||||
|
||||
for (i = 0; i < max_extension_thread_idx; i++) {
|
||||
if (extension_threads[i].thread == k_current_get()) {
|
||||
unload(&extension_threads[i].ext);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void run_extension_on_thread(void *ext_inc, size_t ext_len,
|
||||
struct k_mem_domain *domain,
|
||||
struct k_thread *thread,
|
||||
k_thread_stack_t *stack,
|
||||
struct k_heap *heap,
|
||||
const char *name,
|
||||
k_tid_t subscriber_thread_id,
|
||||
int flag)
|
||||
{
|
||||
int (*start_fn)(void);
|
||||
struct llext **ext = &extension_threads[max_extension_thread_idx].ext;
|
||||
|
||||
printk("[app]Loading extension [%s].\n", name);
|
||||
start_fn = load(name, ext, ext_inc, ext_len);
|
||||
|
||||
llext_add_domain(*ext, domain);
|
||||
|
||||
k_thread_create(thread, stack, USER_STACKSIZE,
|
||||
user_function, start_fn, (void *)name, NULL,
|
||||
-1, flag | K_INHERIT_PERMS, K_FOREVER);
|
||||
k_mem_domain_add_thread(domain, thread);
|
||||
k_mem_domain_add_thread(domain, subscriber_thread_id);
|
||||
|
||||
k_thread_heap_assign(thread, heap);
|
||||
|
||||
extension_threads[max_extension_thread_idx].thread = thread;
|
||||
max_extension_thread_idx++;
|
||||
|
||||
k_thread_start(thread);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
struct k_mem_domain domain1, domain2, domain3, kdomain1;
|
||||
|
||||
#ifndef EDK_BUILD
|
||||
k_tid_t subscriber_thread_id = start_subscriber_thread();
|
||||
#endif
|
||||
/* This and all other similar sleeps are here to provide a chance for
|
||||
* the newly created thread to run.
|
||||
*/
|
||||
k_sleep(K_MSEC(1));
|
||||
|
||||
k_mem_domain_init(&domain1, 0, NULL);
|
||||
k_mem_domain_init(&domain2, 0, NULL);
|
||||
k_mem_domain_init(&domain3, 0, NULL);
|
||||
k_mem_domain_init(&kdomain1, 0, NULL);
|
||||
|
||||
#ifndef EDK_BUILD
|
||||
#ifndef CONFIG_LLEXT_EDK_USERSPACE_ONLY
|
||||
run_extension_on_thread(kext1_inc, kext1_len, &kdomain1,
|
||||
&kernel_thread1, kernel_stack1, &kernel_heap1,
|
||||
"kext1", subscriber_thread_id, 0);
|
||||
k_sleep(K_MSEC(1));
|
||||
#endif
|
||||
run_extension_on_thread(ext1_inc, ext1_len, &domain1, &user_thread1,
|
||||
user_stack1, &user_heap1, "ext1",
|
||||
subscriber_thread_id, K_USER);
|
||||
k_sleep(K_MSEC(1));
|
||||
run_extension_on_thread(ext2_inc, ext2_len, &domain2, &user_thread2,
|
||||
user_stack2, &user_heap2, "ext2",
|
||||
subscriber_thread_id, K_USER);
|
||||
k_sleep(K_MSEC(1));
|
||||
run_extension_on_thread(ext3_inc, ext3_len, &domain3, &user_thread3,
|
||||
user_stack3, &user_heap3, "ext3",
|
||||
subscriber_thread_id, K_USER);
|
||||
#endif
|
||||
|
||||
k_sleep(K_FOREVER);
|
||||
|
||||
return 0;
|
||||
}
|
222
samples/subsys/llext/edk/app/src/pubsub.c
Normal file
222
samples/subsys/llext/edk/app/src/pubsub.c
Normal file
|
@ -0,0 +1,222 @@
|
|||
/*
|
||||
* Copyright (c) 2024 Intel Corporation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <app_api.h>
|
||||
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/internal/syscall_handler.h>
|
||||
#include <zephyr/zbus/zbus.h>
|
||||
#include <zephyr/llext/symbol.h>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#define MAX_SUBSCRIBERS 64
|
||||
#define SUBSCRIBER_THREAD_STACK_SIZE 1024
|
||||
#define SUBSCRIBER_THREAD_PRIORITY K_PRIO_PREEMPT(1)
|
||||
|
||||
ZBUS_CHAN_DEFINE(tick_chan,
|
||||
struct channel_tick_data,
|
||||
NULL,
|
||||
NULL,
|
||||
ZBUS_OBSERVERS(default_sub),
|
||||
ZBUS_MSG_INIT(.l = 0));
|
||||
|
||||
ZBUS_SUBSCRIBER_DEFINE(default_sub, 4);
|
||||
|
||||
K_THREAD_STACK_DEFINE(subscriber_thread_stack, SUBSCRIBER_THREAD_STACK_SIZE);
|
||||
static struct k_thread subscriber_thread;
|
||||
|
||||
static struct subs {
|
||||
struct {
|
||||
k_tid_t thread;
|
||||
struct k_event *evt;
|
||||
} subscribers[MAX_SUBSCRIBERS];
|
||||
struct k_mutex subscribers_mtx;
|
||||
int subscribers_count;
|
||||
const struct zbus_channel *chan;
|
||||
} channel_subscribers[] = {
|
||||
/* Empty one first, so no channel is zero (first item on enum == 1) */
|
||||
{{ }},
|
||||
{ .chan = &tick_chan },
|
||||
{{ }}
|
||||
};
|
||||
|
||||
static int remove_subscriber(k_tid_t thread, struct subs *sus)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < sus->subscribers_count; i++) {
|
||||
if (sus->subscribers[i].thread == thread) {
|
||||
sus->subscribers[i].thread = NULL;
|
||||
sus->subscribers[i].evt = NULL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (i == sus->subscribers_count) {
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
/* Move all entries after excluded one, to keep things tidy */
|
||||
memmove(&sus->subscribers[i], &sus->subscribers[i + 1],
|
||||
(sus->subscribers_count - i - 1) *
|
||||
sizeof(sus->subscribers[0]));
|
||||
sus->subscribers_count--;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int add_subscriber(k_tid_t thread, struct subs *sus,
|
||||
struct k_event *evt)
|
||||
{
|
||||
if (sus->subscribers_count >= MAX_SUBSCRIBERS) {
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
sus->subscribers[sus->subscribers_count].thread = thread;
|
||||
sus->subscribers[sus->subscribers_count].evt = evt;
|
||||
sus->subscribers_count++;
|
||||
|
||||
printk("[app]Thread [%p] registered event [%p]\n", thread, evt);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void notify_subscribers(enum Channels channel)
|
||||
{
|
||||
int i;
|
||||
struct subs *subs = &channel_subscribers[channel];
|
||||
|
||||
for (i = 0; i < subs->subscribers_count; i++) {
|
||||
k_event_post(subs->subscribers[i].evt, channel);
|
||||
}
|
||||
}
|
||||
|
||||
static void subscriber_thread_fn(void *p0, void *p1, void *p2)
|
||||
{
|
||||
const struct zbus_channel *chan;
|
||||
int i;
|
||||
|
||||
printk("[app]Subscriber thread [%p] started.\n", k_current_get());
|
||||
while (zbus_sub_wait(&default_sub, &chan, K_FOREVER) == 0) {
|
||||
printk("[app][subscriber_thread]Got channel %s\n",
|
||||
zbus_chan_name(chan));
|
||||
|
||||
for (i = 0; i < CHAN_LAST; i++) {
|
||||
if (channel_subscribers[i].chan == chan) {
|
||||
notify_subscribers((enum Channels)i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
k_tid_t start_subscriber_thread(void)
|
||||
{
|
||||
return k_thread_create(&subscriber_thread, subscriber_thread_stack,
|
||||
SUBSCRIBER_THREAD_STACK_SIZE,
|
||||
subscriber_thread_fn, NULL, NULL, NULL,
|
||||
SUBSCRIBER_THREAD_PRIORITY, 0, K_NO_WAIT);
|
||||
}
|
||||
|
||||
int z_impl_publish(enum Channels channel, void *data, size_t data_len)
|
||||
{
|
||||
const struct zbus_channel *chan = channel_subscribers[channel].chan;
|
||||
|
||||
if (channel == CHAN_LAST) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return zbus_chan_pub(chan, data, K_NO_WAIT);
|
||||
}
|
||||
EXPORT_SYMBOL(z_impl_publish);
|
||||
|
||||
#ifdef CONFIG_USERSPACE
|
||||
static inline int z_vrfy_publish(enum Channels channel, void *data,
|
||||
size_t data_len)
|
||||
{
|
||||
int ret;
|
||||
void *copy_data;
|
||||
|
||||
copy_data = k_usermode_alloc_from_copy(data, data_len);
|
||||
if (copy_data == NULL) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = z_impl_publish(channel, copy_data, data_len);
|
||||
|
||||
k_free(copy_data);
|
||||
|
||||
return ret;
|
||||
}
|
||||
#include <syscalls/publish_mrsh.c>
|
||||
#endif
|
||||
|
||||
int z_impl_receive(enum Channels channel, void *data, size_t data_len)
|
||||
{
|
||||
size_t msg_size;
|
||||
|
||||
if (channel == CHAN_LAST || data == NULL) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
msg_size = channel_subscribers[channel].chan->message_size;
|
||||
if (data_len < msg_size) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return zbus_chan_read(channel_subscribers[channel].chan, data,
|
||||
K_NO_WAIT);
|
||||
}
|
||||
EXPORT_SYMBOL(z_impl_receive);
|
||||
|
||||
#ifdef CONFIG_USERSPACE
|
||||
static inline int z_vrfy_receive(enum Channels channel, void *data,
|
||||
size_t data_len)
|
||||
{
|
||||
if (K_SYSCALL_MEMORY_WRITE(data, data_len)) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return z_impl_receive(channel, data, data_len);
|
||||
}
|
||||
#include <syscalls/receive_mrsh.c>
|
||||
#endif
|
||||
|
||||
int z_impl_register_subscriber(enum Channels channel, struct k_event *evt)
|
||||
{
|
||||
struct subs *subs = &channel_subscribers[channel];
|
||||
int ret;
|
||||
|
||||
if (channel == CHAN_LAST) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
k_mutex_lock(&subs->subscribers_mtx, K_FOREVER);
|
||||
|
||||
if (evt == NULL) {
|
||||
ret = remove_subscriber(k_current_get(), subs);
|
||||
} else {
|
||||
ret = add_subscriber(k_current_get(), subs, evt);
|
||||
}
|
||||
|
||||
k_mutex_unlock(&subs->subscribers_mtx);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(z_impl_register_subscriber);
|
||||
|
||||
#ifdef CONFIG_USERSPACE
|
||||
static inline int z_vrfy_register_subscriber(enum Channels channel,
|
||||
struct k_event *evt)
|
||||
{
|
||||
if (K_SYSCALL_OBJ(evt, K_OBJ_EVENT)) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return z_impl_register_subscriber(channel, evt);
|
||||
}
|
||||
#include <syscalls/register_subscriber_mrsh.c>
|
||||
#endif
|
35
samples/subsys/llext/edk/ext1/CMakeLists.txt
Normal file
35
samples/subsys/llext/edk/ext1/CMakeLists.txt
Normal file
|
@ -0,0 +1,35 @@
|
|||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
cmake_minimum_required(VERSION 3.20.0)
|
||||
|
||||
set(CMAKE_TOOLCHAIN_FILE toolchain.cmake)
|
||||
set(CMAKE_C_COMPILER_FORCED TRUE)
|
||||
set(CMAKE_CXX_COMPILER_FORCED TRUE)
|
||||
|
||||
project(ext1)
|
||||
|
||||
# Include EDK CFLAGS
|
||||
if(NOT DEFINED LLEXT_EDK_INSTALL_DIR)
|
||||
set(LLEXT_EDK_INSTALL_DIR $ENV{LLEXT_EDK_INSTALL_DIR})
|
||||
endif()
|
||||
include(${LLEXT_EDK_INSTALL_DIR}/cmake.cflags)
|
||||
|
||||
# Add LLEXT_CFLAGS to our flags
|
||||
add_compile_options(${LLEXT_CFLAGS})
|
||||
add_compile_options("-c")
|
||||
|
||||
# Get flags from COMPILE_OPTIONS
|
||||
get_property(COMPILE_OPTIONS_PROP DIRECTORY PROPERTY COMPILE_OPTIONS)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT
|
||||
${PROJECT_BINARY_DIR}/${PROJECT_NAME}.llext
|
||||
${PROJECT_BINARY_DIR}/${PROJECT_NAME}.inc
|
||||
COMMAND ${CMAKE_C_COMPILER} ${COMPILE_OPTIONS_PROP}
|
||||
-o ${PROJECT_BINARY_DIR}/${PROJECT_NAME}.llext
|
||||
${PROJECT_SOURCE_DIR}/src/main.c
|
||||
COMMAND xxd -ip ${PROJECT_NAME}.llext
|
||||
${PROJECT_NAME}.inc
|
||||
)
|
||||
|
||||
add_custom_target(ext1 ALL DEPENDS ${PROJECT_BINARY_DIR}/ext1.llext)
|
34
samples/subsys/llext/edk/ext1/src/main.c
Normal file
34
samples/subsys/llext/edk/ext1/src/main.c
Normal file
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright (c) 2024 Intel Corporation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/llext/symbol.h>
|
||||
|
||||
#include <app_api.h>
|
||||
|
||||
int start(void)
|
||||
{
|
||||
/* Inside extensions, all kobjects need to be dynamically allocated */
|
||||
struct k_event *tick_evt = k_object_alloc(K_OBJ_EVENT);
|
||||
|
||||
k_event_init(tick_evt);
|
||||
|
||||
register_subscriber(CHAN_TICK, tick_evt);
|
||||
|
||||
while (true) {
|
||||
long l;
|
||||
|
||||
printk("[ext1]Waiting event\n");
|
||||
k_event_wait(tick_evt, CHAN_TICK, true, K_FOREVER);
|
||||
|
||||
printk("[ext1]Got event, reading channel\n");
|
||||
receive(CHAN_TICK, &l, sizeof(l));
|
||||
printk("[ext1]Read val: %ld\n", l);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
LL_EXTENSION_SYMBOL(start);
|
4
samples/subsys/llext/edk/ext1/toolchain.cmake
Normal file
4
samples/subsys/llext/edk/ext1/toolchain.cmake
Normal file
|
@ -0,0 +1,4 @@
|
|||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
set(CMAKE_C_COMPILER arm-zephyr-eabi-gcc)
|
||||
set(CMAKE_FIND_ROOT_PATH $ENV{ZEPHYR_SDK_INSTALL_DIR}/arm-zephyr-eabi)
|
31
samples/subsys/llext/edk/ext2/CMakeLists.txt
Normal file
31
samples/subsys/llext/edk/ext2/CMakeLists.txt
Normal file
|
@ -0,0 +1,31 @@
|
|||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
cmake_minimum_required(VERSION 3.20.0)
|
||||
|
||||
set(CMAKE_TOOLCHAIN_FILE toolchain.cmake)
|
||||
set(CMAKE_C_COMPILER_FORCED TRUE)
|
||||
set(CMAKE_CXX_COMPILER_FORCED TRUE)
|
||||
|
||||
project(ext2)
|
||||
|
||||
# Include EDK CFLAGS
|
||||
if(NOT DEFINED LLEXT_EDK_INSTALL_DIR)
|
||||
set(LLEXT_EDK_INSTALL_DIR $ENV{LLEXT_EDK_INSTALL_DIR})
|
||||
endif()
|
||||
include(${LLEXT_EDK_INSTALL_DIR}/cmake.cflags)
|
||||
|
||||
# Add LLEXT_CFLAGS to our flags
|
||||
set(CMAKE_C_FLAGS ${LLEXT_CFLAGS} "-c")
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT
|
||||
${PROJECT_BINARY_DIR}/${PROJECT_NAME}.llext
|
||||
${PROJECT_BINARY_DIR}/${PROJECT_NAME}.inc
|
||||
COMMAND ${CMAKE_C_COMPILER} ${CMAKE_C_FLAGS}
|
||||
-o ${PROJECT_BINARY_DIR}/${PROJECT_NAME}.llext
|
||||
${PROJECT_SOURCE_DIR}/src/main.c
|
||||
COMMAND xxd -ip ${PROJECT_NAME}.llext
|
||||
${PROJECT_NAME}.inc
|
||||
)
|
||||
|
||||
add_custom_target(ext2 ALL DEPENDS ${PROJECT_BINARY_DIR}/ext2.llext)
|
26
samples/subsys/llext/edk/ext2/src/main.c
Normal file
26
samples/subsys/llext/edk/ext2/src/main.c
Normal file
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright (c) 2024 Intel Corporation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/llext/symbol.h>
|
||||
|
||||
#include <app_api.h>
|
||||
|
||||
int start(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 5; i++) {
|
||||
struct channel_tick_data ctd = { .l = i };
|
||||
|
||||
printk("[ext2]Publishing tick\n");
|
||||
publish(CHAN_TICK, &ctd, sizeof(ctd));
|
||||
k_sleep(K_SECONDS(1));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
LL_EXTENSION_SYMBOL(start);
|
4
samples/subsys/llext/edk/ext2/toolchain.cmake
Normal file
4
samples/subsys/llext/edk/ext2/toolchain.cmake
Normal file
|
@ -0,0 +1,4 @@
|
|||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
set(CMAKE_C_COMPILER arm-zephyr-eabi-gcc)
|
||||
set(CMAKE_FIND_ROOT_PATH $ENV{ZEPHYR_SDK_INSTALL_DIR}/arm-zephyr-eabi)
|
35
samples/subsys/llext/edk/ext3/CMakeLists.txt
Normal file
35
samples/subsys/llext/edk/ext3/CMakeLists.txt
Normal file
|
@ -0,0 +1,35 @@
|
|||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
cmake_minimum_required(VERSION 3.20.0)
|
||||
|
||||
set(CMAKE_TOOLCHAIN_FILE toolchain.cmake)
|
||||
set(CMAKE_C_COMPILER_FORCED TRUE)
|
||||
set(CMAKE_CXX_COMPILER_FORCED TRUE)
|
||||
|
||||
project(ext3)
|
||||
|
||||
# Include EDK CFLAGS
|
||||
if(NOT DEFINED LLEXT_EDK_INSTALL_DIR)
|
||||
set(LLEXT_EDK_INSTALL_DIR $ENV{LLEXT_EDK_INSTALL_DIR})
|
||||
endif()
|
||||
include(${LLEXT_EDK_INSTALL_DIR}/cmake.cflags)
|
||||
|
||||
# Add LLEXT_CFLAGS to our flags
|
||||
add_compile_options(${LLEXT_CFLAGS})
|
||||
add_compile_options("-c")
|
||||
|
||||
# Get flags from COMPILE_OPTIONS
|
||||
get_property(COMPILE_OPTIONS_PROP DIRECTORY PROPERTY COMPILE_OPTIONS)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT
|
||||
${PROJECT_BINARY_DIR}/${PROJECT_NAME}.llext
|
||||
${PROJECT_BINARY_DIR}/${PROJECT_NAME}.inc
|
||||
COMMAND ${CMAKE_C_COMPILER} ${COMPILE_OPTIONS_PROP}
|
||||
-o ${PROJECT_BINARY_DIR}/${PROJECT_NAME}.llext
|
||||
${PROJECT_SOURCE_DIR}/src/main.c
|
||||
COMMAND xxd -ip ${PROJECT_NAME}.llext
|
||||
${PROJECT_NAME}.inc
|
||||
)
|
||||
|
||||
add_custom_target(ext3 ALL DEPENDS ${PROJECT_BINARY_DIR}/ext3.llext)
|
65
samples/subsys/llext/edk/ext3/src/main.c
Normal file
65
samples/subsys/llext/edk/ext3/src/main.c
Normal file
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* Copyright (c) 2024 Intel Corporation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/llext/symbol.h>
|
||||
|
||||
#include <app_api.h>
|
||||
|
||||
#define STACKSIZE 512
|
||||
#define PRIORITY 2
|
||||
|
||||
static struct k_sem *my_sem;
|
||||
|
||||
static void tick_sub(void *p1, void *p2, void *p3)
|
||||
{
|
||||
struct k_event *tick_evt = k_object_alloc(K_OBJ_EVENT);
|
||||
|
||||
k_event_init(tick_evt);
|
||||
|
||||
register_subscriber(CHAN_TICK, tick_evt);
|
||||
|
||||
while (true) {
|
||||
printk("[ext3]Waiting event\n");
|
||||
k_event_wait(tick_evt, CHAN_TICK, true, K_FOREVER);
|
||||
printk("[ext3]Got event, giving sem\n");
|
||||
|
||||
k_sem_give(my_sem);
|
||||
}
|
||||
}
|
||||
|
||||
int start(void)
|
||||
{
|
||||
k_thread_stack_t *sub_stack;
|
||||
struct k_thread *sub_thread;
|
||||
|
||||
/* Currently, any kobjects need to be dynamic on extensions,
|
||||
* so the semaphore, thread stack and thread objects are created
|
||||
* dynamically.
|
||||
*/
|
||||
my_sem = k_object_alloc(K_OBJ_SEM);
|
||||
k_sem_init(my_sem, 0, 1);
|
||||
|
||||
sub_stack = k_thread_stack_alloc(STACKSIZE, K_USER);
|
||||
sub_thread = k_object_alloc(K_OBJ_THREAD);
|
||||
printk("[ext3]%p - %p\n", sub_stack, sub_thread);
|
||||
k_thread_create(sub_thread, sub_stack, STACKSIZE, tick_sub, NULL, NULL,
|
||||
NULL, PRIORITY, K_INHERIT_PERMS | K_USER, K_NO_WAIT);
|
||||
|
||||
while (true) {
|
||||
long l;
|
||||
|
||||
printk("[ext3]Waiting sem\n");
|
||||
k_sem_take(my_sem, K_FOREVER);
|
||||
|
||||
printk("[ext3]Got sem, reading channel\n");
|
||||
receive(CHAN_TICK, &l, sizeof(l));
|
||||
printk("[ext3]Read val: %ld\n", l);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
LL_EXTENSION_SYMBOL(start);
|
4
samples/subsys/llext/edk/ext3/toolchain.cmake
Normal file
4
samples/subsys/llext/edk/ext3/toolchain.cmake
Normal file
|
@ -0,0 +1,4 @@
|
|||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
set(CMAKE_C_COMPILER arm-zephyr-eabi-gcc)
|
||||
set(CMAKE_FIND_ROOT_PATH $ENV{ZEPHYR_SDK_INSTALL_DIR}/arm-zephyr-eabi)
|
35
samples/subsys/llext/edk/k-ext1/CMakeLists.txt
Normal file
35
samples/subsys/llext/edk/k-ext1/CMakeLists.txt
Normal file
|
@ -0,0 +1,35 @@
|
|||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
cmake_minimum_required(VERSION 3.20.0)
|
||||
|
||||
set(CMAKE_TOOLCHAIN_FILE toolchain.cmake)
|
||||
set(CMAKE_C_COMPILER_FORCED TRUE)
|
||||
set(CMAKE_CXX_COMPILER_FORCED TRUE)
|
||||
|
||||
project(kext1)
|
||||
|
||||
# Include EDK CFLAGS
|
||||
if(NOT DEFINED LLEXT_EDK_INSTALL_DIR)
|
||||
set(LLEXT_EDK_INSTALL_DIR $ENV{LLEXT_EDK_INSTALL_DIR})
|
||||
endif()
|
||||
include(${LLEXT_EDK_INSTALL_DIR}/cmake.cflags)
|
||||
|
||||
# Add LLEXT_CFLAGS to our flags
|
||||
add_compile_options(${LLEXT_CFLAGS})
|
||||
add_compile_options("-c")
|
||||
|
||||
# Get flags from COMPILE_OPTIONS
|
||||
get_property(COMPILE_OPTIONS_PROP DIRECTORY PROPERTY COMPILE_OPTIONS)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT
|
||||
${PROJECT_BINARY_DIR}/${PROJECT_NAME}.llext
|
||||
${PROJECT_BINARY_DIR}/${PROJECT_NAME}.inc
|
||||
COMMAND ${CMAKE_C_COMPILER} ${COMPILE_OPTIONS_PROP}
|
||||
-o ${PROJECT_BINARY_DIR}/${PROJECT_NAME}.llext
|
||||
${PROJECT_SOURCE_DIR}/src/main.c
|
||||
COMMAND xxd -ip ${PROJECT_NAME}.llext
|
||||
${PROJECT_NAME}.inc
|
||||
)
|
||||
|
||||
add_custom_target(kext1 ALL DEPENDS ${PROJECT_BINARY_DIR}/kext1.llext)
|
64
samples/subsys/llext/edk/k-ext1/src/main.c
Normal file
64
samples/subsys/llext/edk/k-ext1/src/main.c
Normal file
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Copyright (c) 2024 Intel Corporation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/llext/symbol.h>
|
||||
|
||||
#include <app_api.h>
|
||||
|
||||
#define STACKSIZE 512
|
||||
#define PRIORITY 2
|
||||
|
||||
static struct k_sem *my_sem;
|
||||
|
||||
static void tick_sub(void *p1, void *p2, void *p3)
|
||||
{
|
||||
struct k_event *tick_evt = k_object_alloc(K_OBJ_EVENT);
|
||||
|
||||
k_event_init(tick_evt);
|
||||
|
||||
register_subscriber(CHAN_TICK, tick_evt);
|
||||
|
||||
while (true) {
|
||||
printk("[k-ext1]Waiting event\n");
|
||||
k_event_wait(tick_evt, CHAN_TICK, true, K_FOREVER);
|
||||
printk("[k-ext1]Got event, giving sem\n");
|
||||
|
||||
k_sem_give(my_sem);
|
||||
}
|
||||
}
|
||||
|
||||
int start(void)
|
||||
{
|
||||
k_thread_stack_t *sub_stack;
|
||||
struct k_thread *sub_thread;
|
||||
|
||||
/* Currently, any kobjects need to be dynamic on extensions,
|
||||
* so the semaphore, thread stack and thread objects are created
|
||||
* dynamically.
|
||||
*/
|
||||
my_sem = k_object_alloc(K_OBJ_SEM);
|
||||
k_sem_init(my_sem, 0, 1);
|
||||
|
||||
sub_stack = k_thread_stack_alloc(STACKSIZE, 0);
|
||||
sub_thread = k_object_alloc(K_OBJ_THREAD);
|
||||
k_thread_create(sub_thread, sub_stack, STACKSIZE, tick_sub, NULL, NULL,
|
||||
NULL, PRIORITY, K_INHERIT_PERMS, K_NO_WAIT);
|
||||
|
||||
while (true) {
|
||||
long l;
|
||||
|
||||
printk("[k-ext1]Waiting sem\n");
|
||||
k_sem_take(my_sem, K_FOREVER);
|
||||
|
||||
printk("[k-ext1]Got sem, reading channel\n");
|
||||
receive(CHAN_TICK, &l, sizeof(l));
|
||||
printk("[k-ext1]Read val: %ld\n", l);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
LL_EXTENSION_SYMBOL(start);
|
4
samples/subsys/llext/edk/k-ext1/toolchain.cmake
Normal file
4
samples/subsys/llext/edk/k-ext1/toolchain.cmake
Normal file
|
@ -0,0 +1,4 @@
|
|||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
set(CMAKE_C_COMPILER arm-zephyr-eabi-gcc)
|
||||
set(CMAKE_FIND_ROOT_PATH $ENV{ZEPHYR_SDK_INSTALL_DIR}/arm-zephyr-eabi)
|
Loading…
Add table
Add a link
Reference in a new issue