-
Notifications
You must be signed in to change notification settings - Fork 6.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
samples: coop_synchronization: new cooperative thread switching sampl…
…e app A simple application that demonstrates the most basic sanity of the kernel. Two threads (A and B) take turns printing a greeting message to the console They use yields and semaphores to control the whose turn it is to print. It also enforces polled uart mode to avoid serial IRQ fires triggering unpredictable context switch. The rate is uncontrolled. This demonstrates kernel communication only. It's a good example to test a new arch for testing cooperative context switching without sys timer first. It's also useful to test cooperative-only architectures which may not have any timing or clock facilities. I.e. this will still work when `CONFIG_SYS_CLOCK_EXISTS`=n Signed-off-by: Alp Sayin <[email protected]>
- Loading branch information
Showing
6 changed files
with
275 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
# Copyright 2023 Advanced Micro Devices, Inc. (AMD) | ||
# Copyright 2023 Alp Sayin | ||
# SPDX-License-Identifier: Apache-2.0 | ||
|
||
cmake_minimum_required(VERSION 3.20.0) | ||
|
||
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) | ||
project(coop_synchronization) | ||
|
||
target_sources(app PRIVATE src/main.c) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
.. _coop_synchronization_sample: | ||
|
||
Cooperative Synchronization Sample | ||
################################## | ||
|
||
Overview | ||
******** | ||
|
||
A simple application that demonstrates even more basic sanity of the kernel. | ||
Two threads (A and B) take turns printing a greeting message to the console. | ||
They use yields and semaphores to control the whose turn it is to print. | ||
The rate is only controlled via busy waits. This demonstrates kernel communication only. | ||
This is a good example to test new platforms that may not have a working clock. | ||
I.e. this will still work when :kconfig:option:`CONFIG_SYS_CLOCK_EXISTS` =n | ||
It's also a very simplistic demo allowing users to follow the execution trace in | ||
a no-timer (i.e. nonexistent, disabled, deferred) scenario and experience predictability. | ||
To that end, sample should ideally be built with no interrupt-driven-UART to avoid spurious | ||
IRQ fires which may trigger unpredictable context switch. However, some platforms do not | ||
support polling api for UART. For those, only the regular variant is built. | ||
|
||
Building and Running | ||
******************** | ||
|
||
This project outputs to the console. It can be built and executed | ||
on QEMU as follows: | ||
|
||
.. zephyr-app-commands:: | ||
:zephyr-app: samples/coop_synchronization | ||
:host-os: unix | ||
:board: qemu_x86 | ||
:goals: run | ||
:compact: | ||
|
||
Sample Output | ||
============= | ||
|
||
.. code-block:: console | ||
thread_a: level 1 irq_lock key = 534 | ||
thread_a: level 2 irq_lock key = 22 | ||
thread_a: Hello World from cpu 0 on qemu_x86! counter = 1 | ||
thread_b: level 1 irq_lock key = 534 | ||
thread_b: level 2 irq_lock key = 22 | ||
thread_b: Hello World from cpu 0 on qemu_x86! counter = -1 | ||
thread_a: level 1 irq_lock key = 534 | ||
thread_a: level 2 irq_lock key = 22 | ||
thread_a: Hello World from cpu 0 on qemu_x86! counter = 2 | ||
thread_b: level 1 irq_lock key = 534 | ||
thread_b: level 2 irq_lock key = 22 | ||
thread_b: Hello World from cpu 0 on qemu_x86! counter = -2 | ||
thread_a: level 1 irq_lock key = 534 | ||
thread_a: level 2 irq_lock key = 22 | ||
thread_a: Hello World from cpu 0 on qemu_x86! counter = 3 | ||
thread_b: level 1 irq_lock key = 534 | ||
thread_b: level 2 irq_lock key = 22 | ||
thread_b: Hello World from cpu 0 on qemu_x86! counter = -3 | ||
thread_a: level 1 irq_lock key = 534 | ||
thread_a: level 2 irq_lock key = 22 | ||
thread_a: Hello World from cpu 0 on qemu_x86! counter = 4 | ||
thread_b: level 1 irq_lock key = 534 | ||
thread_b: level 2 irq_lock key = 22 | ||
thread_b: Hello World from cpu 0 on qemu_x86! counter = -4 | ||
thread_a: level 1 irq_lock key = 534 | ||
thread_a: level 2 irq_lock key = 22 | ||
thread_a: Hello World from cpu 0 on qemu_x86! counter = 5 | ||
thread_b: level 1 irq_lock key = 534 | ||
thread_b: level 2 irq_lock key = 22 | ||
thread_b: Hello World from cpu 0 on qemu_x86! counter = -5 | ||
<repeats endlessly> | ||
Exit QEMU by pressing :kbd:`CTRL+A` :kbd:`x`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
CONFIG_STDOUT_CONSOLE=y | ||
# enable to use thread names | ||
CONFIG_THREAD_NAME=y | ||
CONFIG_SCHED_CPU_MASK=y |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
sample: | ||
description: A simple application that demonstrates even more basic sanity of the kernel | ||
compared to samples/synchronization. The main variant is the polled_uart which shouldn't | ||
go through any interrupt driven context switch in a no-systick scenario. | ||
name: Coop Synchronization Sample | ||
tests: | ||
sample.kernel.coop_synchronization.polled_uart: | ||
# Platforms below do not implement a polled uart driver therefore they dont build | ||
platform_exclude: | ||
- rpi_pico | ||
- rpi_pico_w | ||
- adafruit_kb2040 | ||
- m2gl025_miv | ||
- lpcxpresso51u68 | ||
- lpcxpresso54114_m4 | ||
- lpcxpresso55s06 | ||
- lpcxpresso55s16 | ||
- lpcxpresso55s28 | ||
- lpcxpresso55s36 | ||
- lpcxpresso55s69_cpu0 | ||
- lpcxpresso55s69_ns | ||
- s32z270dc2_rtu0_r52 | ||
- s32z270dc2_rtu1_r52 | ||
- sparkfun_pro_micro_rp2040 | ||
tags: synchronization | ||
extra_configs: | ||
- CONFIG_UART_INTERRUPT_DRIVEN=n | ||
harness: console | ||
harness_config: | ||
type: multi_line | ||
regex: | ||
- "thread_a: level 1 spinlock key = (\\d+)" | ||
- "thread_a: level 2 spinlock key = (\\d+)" | ||
- "thread_a: Hello World from cpu (\\d+) on (\\w+)! counter = (\\d+)" | ||
- "thread_b: level 1 spinlock key = (\\d+)" | ||
- "thread_b: level 2 spinlock key = (\\d+)" | ||
- "thread_b: Hello World from cpu (\\d+) on (\\w+)! counter = -(\\d+)" | ||
sample.kernel.coop_synchronization.interrupt_driven_uart: | ||
tags: synchronization | ||
harness: console | ||
harness_config: | ||
type: multi_line | ||
regex: | ||
- "thread_a: level 1 spinlock key = (\\d+)" | ||
- "thread_a: level 2 spinlock key = (\\d+)" | ||
- "thread_a: Hello World from cpu (\\d+) on (\\w+)! counter = (\\d+)" | ||
- "thread_b: level 1 spinlock key = (\\d+)" | ||
- "thread_b: level 2 spinlock key = (\\d+)" | ||
- "thread_b: Hello World from cpu (\\d+) on (\\w+)! counter = -(\\d+)" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
/* | ||
* Copyright (c) 2012-2014 Wind River Systems, Inc. | ||
* Copyright (c) 2023 Advanced Micro Devices, Inc. (AMD) | ||
* Copyright (c) 2023 Alp Sayin <[email protected]> | ||
* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
#include <zephyr/kernel.h> | ||
#include <zephyr/sys/printk.h> | ||
|
||
/* | ||
* The coop synchronization demo has two threads that utilize semaphores and k_yield | ||
* to take turns printing a greeting message with a somewhat uncontrolled rate. | ||
* The rate is controlled via a busy waiting loop. The advantage of this demo is that | ||
* it'll work even if there are no timing utilities provided by the architecture. | ||
*/ | ||
|
||
/* size of stack area used by each thread */ | ||
#define STACKSIZE 1024 | ||
|
||
/* Some locks to take and release to make things interesting */ | ||
static struct k_spinlock lock1, lock2; | ||
|
||
/* | ||
* @param my_name thread identification string | ||
* @param my_sem thread's own semaphore | ||
* @param other_sem other thread's semaphore | ||
*/ | ||
void helloLoop(const char *my_name, struct k_sem *my_sem, | ||
struct k_sem *other_sem, int incrementer) | ||
{ | ||
const char *tname; | ||
uint8_t cpu = 0; | ||
struct k_thread *current_thread; | ||
int task_local_counter = 0; | ||
|
||
while (1) { | ||
/* take my semaphore */ | ||
k_sem_take(my_sem, K_FOREVER); | ||
|
||
k_spinlock_key_t key1 = k_spin_lock(&lock1); | ||
{ | ||
current_thread = k_current_get(); | ||
tname = k_thread_name_get(current_thread); | ||
#if defined(CONFIG_ARC) || defined(CONFIG_ARM) || defined(CONFIG_X86_64) \ | ||
|| defined(CONFIG_RISCV) || defined(CONFIG_XTENSA) | ||
cpu = arch_curr_cpu()->id; | ||
#endif | ||
printk("%s: level 1 spinlock key = %d\n", tname, key1.key); | ||
k_spinlock_key_t key2 = k_spin_lock(&lock2); | ||
{ | ||
k_yield(); | ||
printk("%s: level 2 spinlock key = %d\n", tname, key2.key); | ||
} | ||
k_spin_unlock(&lock2, key2); | ||
} | ||
k_spin_unlock(&lock1, key1); | ||
/* say "hello" */ | ||
if (tname == NULL) { | ||
printk("%s: Hello World from cpu %d on %s! counter = %d\n", my_name, cpu, | ||
CONFIG_BOARD, task_local_counter += incrementer); | ||
} else { | ||
printk("%s: Hello World from cpu %d on %s! counter = %d\n", tname, cpu, | ||
CONFIG_BOARD, task_local_counter += incrementer); | ||
} | ||
|
||
/* We wait a while here, then let other thread have a turn. | ||
* We use a busy wait here because arch provided k_busy_wait() | ||
* implementation is still likely to depend on the system timer, | ||
* for which the assumption is to be non-existent/disabled/broken. | ||
* On another note: native posix doesn't play nice with busy waits | ||
* as it requires a different API (Z_SPIN_DELAY) for the threads to | ||
* be preemtable inside the busy wait. But the point of this sample | ||
* is not to be preempted by timers (nor UART if possible). | ||
*/ | ||
for (int i = 0; i < 10000000; i++) { | ||
arch_nop(); | ||
} | ||
|
||
k_sem_give(other_sem); | ||
k_yield(); | ||
} | ||
} | ||
|
||
/* define semaphores */ | ||
|
||
K_SEM_DEFINE(threadA_sem, 1, 1); /* starts off "available" */ | ||
K_SEM_DEFINE(threadB_sem, 0, 1); /* starts off "not available" */ | ||
|
||
/* threadB is a dynamic thread that is spawned by threadA */ | ||
|
||
void threadB(void *arg1, void *arg2, void *arg3) | ||
{ | ||
int incrementer = (intptr_t)arg1; | ||
|
||
ARG_UNUSED(arg2); | ||
ARG_UNUSED(arg3); | ||
|
||
/* invoke routine to ping-pong hello messages with threadA */ | ||
helloLoop(__func__, &threadB_sem, &threadA_sem, incrementer); | ||
} | ||
|
||
K_THREAD_STACK_DEFINE(threadA_stack_area, STACKSIZE); | ||
static struct k_thread threadA_data; | ||
|
||
K_THREAD_STACK_DEFINE(threadB_stack_area, STACKSIZE); | ||
static struct k_thread threadB_data; | ||
|
||
/* threadA is a static thread that is spawned automatically */ | ||
|
||
void threadA(void *arg1, void *arg2, void *arg3) | ||
{ | ||
int incrementer = (intptr_t)arg1; | ||
|
||
ARG_UNUSED(arg2); | ||
ARG_UNUSED(arg3); | ||
|
||
/* invoke routine to ping-pong hello messages with threadB */ | ||
helloLoop(__func__, &threadA_sem, &threadB_sem, incrementer); | ||
} | ||
|
||
void main(void) | ||
{ | ||
k_thread_create(&threadA_data, threadA_stack_area, | ||
K_THREAD_STACK_SIZEOF(threadA_stack_area), | ||
threadA, (void *)1, NULL, NULL, | ||
0, 0, K_FOREVER); | ||
k_thread_name_set(&threadA_data, "thread_a"); | ||
|
||
k_thread_create(&threadB_data, threadB_stack_area, | ||
K_THREAD_STACK_SIZEOF(threadB_stack_area), | ||
threadB, (void *)-1, NULL, NULL, | ||
0, 0, K_FOREVER); | ||
k_thread_name_set(&threadB_data, "thread_b"); | ||
|
||
k_thread_start(&threadA_data); | ||
k_thread_start(&threadB_data); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters