Merge patch series "Uthreads"
Jerome Forissier <jerome.forissier@linaro.org> says: This series introduces threads and uses them to improve the performance of the USB bus scanning code and to implement background jobs in the shell via two new commands: 'spawn' and 'wait'. The threading framework is called 'uthread' and is inspired from the barebox threads [2]. setjmp() and longjmp() are used to save and restore contexts, as well as a non-standard extension called initjmp(). This new function is added in several patches, one for each architecture that supports HAVE_SETJMP. A new symbol is defined: HAVE_INITJMP. Two tests, one for initjmp() and one for the uthread scheduling, are added to the lib suite. After introducing threads and making schedule() and udelay() a thread re-scheduling point, the USB stack initialization is modified to benefit from concurrency when UTHREAD is enabled, where uthreads are used in usb_init() to initialize and scan multiple busses at the same time. The code was tested on arm64 and arm QEMU with 4 simulated XHCI buses and some devices. On this platform the USB scan takes 2.2 s instead of 5.6 s. Tested on i.MX93 EVK with two USB hubs, one ethernet adapter and one webcam on each, "usb start" takes 2.4 s instead of 4.6 s. Finally, the spawn and wait commands are introduced, allowing the use of threads from the shell. Tested on the i.MX93 EVK with a spinning HDD connected to USB1 and the network connected to ENET1. The USB plus DHCP init sequence "spawn usb start; spawn dhcp; wait" takes 4.5 seconds instead of 8 seconds for "usb start; dhcp". [1] https://patchwork.ozlabs.org/project/uboot/list/?series=446674 [2] https://github.com/barebox/barebox/blob/master/common/bthread.c Link: https://lore.kernel.org/r/20250418141114.2056981-1-jerome.forissier@linaro.org
This commit is contained in:
@@ -1825,6 +1825,15 @@ T: git https://source.denx.de/u-boot/custodians/u-boot-usb.git topic-xhci
|
|||||||
F: drivers/usb/host/xhci*
|
F: drivers/usb/host/xhci*
|
||||||
F: include/usb/xhci.h
|
F: include/usb/xhci.h
|
||||||
|
|
||||||
|
UTHREAD
|
||||||
|
M: Jerome Forissier <jerome.forissier@linaro.org>
|
||||||
|
S: Maintained
|
||||||
|
F: cmd/spawn.c
|
||||||
|
F: include/uthread.h
|
||||||
|
F: lib/uthread.c
|
||||||
|
F: test/cmd/spawn.c
|
||||||
|
F: test/lib/uthread.c
|
||||||
|
|
||||||
UUID testing
|
UUID testing
|
||||||
M: Abdellatif El Khlifi <abdellatif.elkhlifi@arm.com>
|
M: Abdellatif El Khlifi <abdellatif.elkhlifi@arm.com>
|
||||||
S: Maintained
|
S: Maintained
|
||||||
|
10
arch/Kconfig
10
arch/Kconfig
@@ -13,6 +13,13 @@ config HAVE_SETJMP
|
|||||||
help
|
help
|
||||||
The architecture supports setjmp() and longjmp().
|
The architecture supports setjmp() and longjmp().
|
||||||
|
|
||||||
|
config HAVE_INITJMP
|
||||||
|
bool
|
||||||
|
depends on HAVE_SETJMP
|
||||||
|
help
|
||||||
|
The architecture supports initjmp(), a non-standard companion to
|
||||||
|
setjmp() and longjmp().
|
||||||
|
|
||||||
config SUPPORT_BIG_ENDIAN
|
config SUPPORT_BIG_ENDIAN
|
||||||
bool
|
bool
|
||||||
|
|
||||||
@@ -88,6 +95,7 @@ config ARC
|
|||||||
config ARM
|
config ARM
|
||||||
bool "ARM architecture"
|
bool "ARM architecture"
|
||||||
select HAVE_SETJMP
|
select HAVE_SETJMP
|
||||||
|
select HAVE_INITJMP
|
||||||
select ARCH_SUPPORTS_LTO
|
select ARCH_SUPPORTS_LTO
|
||||||
select CREATE_ARCH_SYMLINK
|
select CREATE_ARCH_SYMLINK
|
||||||
select HAVE_PRIVATE_LIBGCC if !ARM64
|
select HAVE_PRIVATE_LIBGCC if !ARM64
|
||||||
@@ -145,6 +153,7 @@ config RISCV
|
|||||||
bool "RISC-V architecture"
|
bool "RISC-V architecture"
|
||||||
select CREATE_ARCH_SYMLINK
|
select CREATE_ARCH_SYMLINK
|
||||||
select HAVE_SETJMP
|
select HAVE_SETJMP
|
||||||
|
select HAVE_INITJMP
|
||||||
select SUPPORT_ACPI
|
select SUPPORT_ACPI
|
||||||
select SUPPORT_LITTLE_ENDIAN
|
select SUPPORT_LITTLE_ENDIAN
|
||||||
select SUPPORT_OF_CONTROL
|
select SUPPORT_OF_CONTROL
|
||||||
@@ -171,6 +180,7 @@ config RISCV
|
|||||||
config SANDBOX
|
config SANDBOX
|
||||||
bool "Sandbox"
|
bool "Sandbox"
|
||||||
select HAVE_SETJMP
|
select HAVE_SETJMP
|
||||||
|
select HAVE_INITJMP
|
||||||
select ARCH_SUPPORTS_LTO
|
select ARCH_SUPPORTS_LTO
|
||||||
select BOARD_LATE_INIT
|
select BOARD_LATE_INIT
|
||||||
select BZIP2
|
select BZIP2
|
||||||
|
@@ -34,3 +34,15 @@ ENTRY(longjmp)
|
|||||||
ret lr
|
ret lr
|
||||||
ENDPROC(longjmp)
|
ENDPROC(longjmp)
|
||||||
.popsection
|
.popsection
|
||||||
|
|
||||||
|
.pushsection .text.initjmp, "ax"
|
||||||
|
ENTRY(initjmp)
|
||||||
|
stm a1, {v1-v8}
|
||||||
|
/* a2: entry point address, a3: stack base, a4: stack size */
|
||||||
|
add a3, a3, a4
|
||||||
|
str a3, [a1, #32] /* where setjmp would save sp */
|
||||||
|
str a2, [a1, #36] /* where setjmp would save lr */
|
||||||
|
mov a1, #0
|
||||||
|
ret lr
|
||||||
|
ENDPROC(initjmp)
|
||||||
|
.popsection
|
||||||
|
@@ -39,3 +39,13 @@ ENTRY(longjmp)
|
|||||||
ret
|
ret
|
||||||
ENDPROC(longjmp)
|
ENDPROC(longjmp)
|
||||||
.popsection
|
.popsection
|
||||||
|
|
||||||
|
.pushsection .text.initjmp, "ax"
|
||||||
|
ENTRY(initjmp)
|
||||||
|
/* x1: entry point address, x2: stack base, x3: stack size */
|
||||||
|
add x2, x2, x3
|
||||||
|
stp x1, x2, [x0,#88]
|
||||||
|
mov x0, #0
|
||||||
|
ret
|
||||||
|
ENDPROC(initjmp)
|
||||||
|
.popsection
|
||||||
|
@@ -59,3 +59,14 @@ ENTRY(longjmp)
|
|||||||
ret
|
ret
|
||||||
ENDPROC(longjmp)
|
ENDPROC(longjmp)
|
||||||
.popsection
|
.popsection
|
||||||
|
|
||||||
|
.pushsection .text.initjmp, "ax"
|
||||||
|
ENTRY(initjmp)
|
||||||
|
/* a1: entry point address, a2: stack base, a3: stack size */
|
||||||
|
add a2, a2, a3
|
||||||
|
STORE_IDX(a1, 12)
|
||||||
|
STORE_IDX(a2, 13)
|
||||||
|
li a0, 0
|
||||||
|
ret
|
||||||
|
ENDPROC(initjmp)
|
||||||
|
.popsection
|
||||||
|
@@ -5,7 +5,7 @@
|
|||||||
# (C) Copyright 2000-2003
|
# (C) Copyright 2000-2003
|
||||||
# Wolfgang Denk, DENX Software Engineering, wd@denx.de.
|
# Wolfgang Denk, DENX Software Engineering, wd@denx.de.
|
||||||
|
|
||||||
obj-y := cache.o cpu.o state.o
|
obj-y := cache.o cpu.o state.o initjmp.o
|
||||||
extra-y := start.o os.o
|
extra-y := start.o os.o
|
||||||
extra-$(CONFIG_SANDBOX_SDL) += sdl.o
|
extra-$(CONFIG_SANDBOX_SDL) += sdl.o
|
||||||
obj-$(CONFIG_XPL_BUILD) += spl.o
|
obj-$(CONFIG_XPL_BUILD) += spl.o
|
||||||
@@ -29,6 +29,15 @@ cmd_cc_eth-raw-os.o = $(CC) $(filter-out -nostdinc, \
|
|||||||
$(obj)/eth-raw-os.o: $(src)/eth-raw-os.c FORCE
|
$(obj)/eth-raw-os.o: $(src)/eth-raw-os.c FORCE
|
||||||
$(call if_changed_dep,cc_eth-raw-os.o)
|
$(call if_changed_dep,cc_eth-raw-os.o)
|
||||||
|
|
||||||
|
# initjmp.c is build in the system environment, so needs standard includes
|
||||||
|
# CFLAGS_REMOVE_initjmp.o cannot be used to drop header include path
|
||||||
|
quiet_cmd_cc_initjmp.o = CC $(quiet_modtag) $@
|
||||||
|
cmd_cc_initjmp.o = $(CC) $(filter-out -nostdinc, \
|
||||||
|
$(patsubst -I%,-idirafter%,$(c_flags))) -c -o $@ $<
|
||||||
|
|
||||||
|
$(obj)/initjmp.o: $(src)/initjmp.c FORCE
|
||||||
|
$(call if_changed_dep,cc_initjmp.o)
|
||||||
|
|
||||||
# sdl.c fails to build with -fshort-wchar using musl
|
# sdl.c fails to build with -fshort-wchar using musl
|
||||||
cmd_cc_sdl.o = $(CC) $(filter-out -nostdinc -fshort-wchar, \
|
cmd_cc_sdl.o = $(CC) $(filter-out -nostdinc -fshort-wchar, \
|
||||||
$(patsubst -I%,-idirafter%,$(c_flags))) -fno-lto -c -o $@ $<
|
$(patsubst -I%,-idirafter%,$(c_flags))) -fno-lto -c -o $@ $<
|
||||||
|
175
arch/sandbox/cpu/initjmp.c
Normal file
175
arch/sandbox/cpu/initjmp.c
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||||
|
/*
|
||||||
|
* An implementation of initjmp() in C, that plays well with the system's
|
||||||
|
* setjmp() and longjmp() functions.
|
||||||
|
* Taken verbatim from arch/sandbox/os/setjmp.c in the barebox project.
|
||||||
|
* Modified so that initjmp() accepts a stack_size argument.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2006 Anthony Liguori <anthony@codemonkey.ws>
|
||||||
|
* Copyright (C) 2011 Kevin Wolf <kwolf@redhat.com>
|
||||||
|
* Copyright (C) 2012 Alex Barcelo <abarcelo@ac.upc.edu>
|
||||||
|
* Copyright (C) 2021 Ahmad Fatoum, Pengutronix
|
||||||
|
* Copyright (C) 2025 Linaro Ltd.
|
||||||
|
* This file is partly based on pth_mctx.c, from the GNU Portable Threads
|
||||||
|
* Copyright (c) 1999-2006 Ralf S. Engelschall <rse@engelschall.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* XXX Is there a nicer way to disable glibc's stack check for longjmp? */
|
||||||
|
#ifdef _FORTIFY_SOURCE
|
||||||
|
#undef _FORTIFY_SOURCE
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <setjmp.h>
|
||||||
|
#include <signal.h>
|
||||||
|
|
||||||
|
typedef sigjmp_buf _jmp_buf __attribute__((aligned((16))));
|
||||||
|
_Static_assert(sizeof(_jmp_buf) <= 512, "sigjmp_buf size exceeds expectation");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Information for the signal handler (trampoline)
|
||||||
|
*/
|
||||||
|
static struct {
|
||||||
|
_jmp_buf *reenter;
|
||||||
|
void (*entry)(void);
|
||||||
|
volatile sig_atomic_t called;
|
||||||
|
} tr_state;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* "boot" function
|
||||||
|
* This is what starts the coroutine, is called from the trampoline
|
||||||
|
* (from the signal handler when it is not signal handling, read ahead
|
||||||
|
* for more information).
|
||||||
|
*/
|
||||||
|
static void __attribute__((noinline, noreturn))
|
||||||
|
coroutine_bootstrap(void (*entry)(void))
|
||||||
|
{
|
||||||
|
for (;;)
|
||||||
|
entry();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is used as the signal handler. This is called with the brand new stack
|
||||||
|
* (thanks to sigaltstack). We have to return, given that this is a signal
|
||||||
|
* handler and the sigmask and some other things are changed.
|
||||||
|
*/
|
||||||
|
static void coroutine_trampoline(int signal)
|
||||||
|
{
|
||||||
|
/* Get the thread specific information */
|
||||||
|
tr_state.called = 1;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Here we have to do a bit of a ping pong between the caller, given that
|
||||||
|
* this is a signal handler and we have to do a return "soon". Then the
|
||||||
|
* caller can reestablish everything and do a siglongjmp here again.
|
||||||
|
*/
|
||||||
|
if (!sigsetjmp(*tr_state.reenter, 0)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ok, the caller has siglongjmp'ed back to us, so now prepare
|
||||||
|
* us for the real machine state switching. We have to jump
|
||||||
|
* into another function here to get a new stack context for
|
||||||
|
* the auto variables (which have to be auto-variables
|
||||||
|
* because the start of the thread happens later). Else with
|
||||||
|
* PIC (i.e. Position Independent Code which is used when PTH
|
||||||
|
* is built as a shared library) most platforms would
|
||||||
|
* horrible core dump as experience showed.
|
||||||
|
*/
|
||||||
|
coroutine_bootstrap(tr_state.entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
int __attribute__((weak)) initjmp(_jmp_buf jmp, void (*func)(void),
|
||||||
|
void *stack_base, size_t stack_size)
|
||||||
|
{
|
||||||
|
struct sigaction sa;
|
||||||
|
struct sigaction osa;
|
||||||
|
stack_t ss;
|
||||||
|
stack_t oss;
|
||||||
|
sigset_t sigs;
|
||||||
|
sigset_t osigs;
|
||||||
|
|
||||||
|
/* The way to manipulate stack is with the sigaltstack function. We
|
||||||
|
* prepare a stack, with it delivering a signal to ourselves and then
|
||||||
|
* put sigsetjmp/siglongjmp where needed.
|
||||||
|
* This has been done keeping coroutine-ucontext (from the QEMU project)
|
||||||
|
* as a model and with the pth ideas (GNU Portable Threads).
|
||||||
|
* See coroutine-ucontext for the basics of the coroutines and see
|
||||||
|
* pth_mctx.c (from the pth project) for the
|
||||||
|
* sigaltstack way of manipulating stacks.
|
||||||
|
*/
|
||||||
|
|
||||||
|
tr_state.entry = func;
|
||||||
|
tr_state.reenter = (void *)jmp;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Preserve the SIGUSR2 signal state, block SIGUSR2,
|
||||||
|
* and establish our signal handler. The signal will
|
||||||
|
* later transfer control onto the signal stack.
|
||||||
|
*/
|
||||||
|
sigemptyset(&sigs);
|
||||||
|
sigaddset(&sigs, SIGUSR2);
|
||||||
|
pthread_sigmask(SIG_BLOCK, &sigs, &osigs);
|
||||||
|
sa.sa_handler = coroutine_trampoline;
|
||||||
|
sigfillset(&sa.sa_mask);
|
||||||
|
sa.sa_flags = SA_ONSTACK;
|
||||||
|
if (sigaction(SIGUSR2, &sa, &osa) != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set the new stack.
|
||||||
|
*/
|
||||||
|
ss.ss_sp = stack_base;
|
||||||
|
ss.ss_size = stack_size;
|
||||||
|
ss.ss_flags = 0;
|
||||||
|
if (sigaltstack(&ss, &oss) < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Now transfer control onto the signal stack and set it up.
|
||||||
|
* It will return immediately via "return" after the sigsetjmp()
|
||||||
|
* was performed. Be careful here with race conditions. The
|
||||||
|
* signal can be delivered the first time sigsuspend() is
|
||||||
|
* called.
|
||||||
|
*/
|
||||||
|
tr_state.called = 0;
|
||||||
|
pthread_kill(pthread_self(), SIGUSR2);
|
||||||
|
sigfillset(&sigs);
|
||||||
|
sigdelset(&sigs, SIGUSR2);
|
||||||
|
while (!tr_state.called) {
|
||||||
|
sigsuspend(&sigs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Inform the system that we are back off the signal stack by
|
||||||
|
* removing the alternative signal stack. Be careful here: It
|
||||||
|
* first has to be disabled, before it can be removed.
|
||||||
|
*/
|
||||||
|
sigaltstack(NULL, &ss);
|
||||||
|
ss.ss_flags = SS_DISABLE;
|
||||||
|
if (sigaltstack(&ss, NULL) < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
sigaltstack(NULL, &ss);
|
||||||
|
if (!(oss.ss_flags & SS_DISABLE)) {
|
||||||
|
sigaltstack(&oss, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Restore the old SIGUSR2 signal handler and mask
|
||||||
|
*/
|
||||||
|
sigaction(SIGUSR2, &osa, NULL);
|
||||||
|
pthread_sigmask(SIG_SETMASK, &osigs, NULL);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* jmp can now be used to enter the trampoline again, but not as a
|
||||||
|
* signal handler. Instead it's longjmp'd to directly.
|
||||||
|
*/
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
17
cmd/Kconfig
17
cmd/Kconfig
@@ -3081,4 +3081,21 @@ config CMD_MESON
|
|||||||
help
|
help
|
||||||
Enable useful commands for the Meson Soc family developed by Amlogic Inc.
|
Enable useful commands for the Meson Soc family developed by Amlogic Inc.
|
||||||
|
|
||||||
|
config CMD_SPAWN
|
||||||
|
bool "spawn and wait commands"
|
||||||
|
depends on UTHREAD
|
||||||
|
help
|
||||||
|
spawn runs a command in the background and sets the job_id environment
|
||||||
|
variable. wait is used to suspend the shell execution until one or more
|
||||||
|
jobs are complete.
|
||||||
|
|
||||||
|
config CMD_SPAWN_NUM_JOBS
|
||||||
|
int "Maximum number of simultaneous jobs for spawn"
|
||||||
|
default 16
|
||||||
|
help
|
||||||
|
Job identifiers are in the range 1..CMD_SPAWN_NUM_JOBS. In other words
|
||||||
|
there can be no more that CMD_SPAWN_NUM_JOBS running simultaneously.
|
||||||
|
When a jobs exits, its identifier is available to be re-used by the next
|
||||||
|
spawn command.
|
||||||
|
|
||||||
endif
|
endif
|
||||||
|
@@ -239,6 +239,8 @@ obj-$(CONFIG_CMD_SCP03) += scp03.o
|
|||||||
|
|
||||||
obj-$(CONFIG_HUSH_SELECTABLE) += cli.o
|
obj-$(CONFIG_HUSH_SELECTABLE) += cli.o
|
||||||
|
|
||||||
|
obj-$(CONFIG_CMD_SPAWN) += spawn.o
|
||||||
|
|
||||||
obj-$(CONFIG_ARM) += arm/
|
obj-$(CONFIG_ARM) += arm/
|
||||||
obj-$(CONFIG_RISCV) += riscv/
|
obj-$(CONFIG_RISCV) += riscv/
|
||||||
obj-$(CONFIG_SANDBOX) += sandbox/
|
obj-$(CONFIG_SANDBOX) += sandbox/
|
||||||
|
187
cmd/spawn.c
Normal file
187
cmd/spawn.c
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0+
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2011 The Chromium OS Authors.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <command.h>
|
||||||
|
#include <console.h>
|
||||||
|
#include <malloc.h>
|
||||||
|
#include <vsprintf.h>
|
||||||
|
#include <uthread.h>
|
||||||
|
|
||||||
|
/* Spawn arguments and job index */
|
||||||
|
struct spa {
|
||||||
|
int argc;
|
||||||
|
char **argv;
|
||||||
|
unsigned int job_idx;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* uthread group identifiers for each running job
|
||||||
|
* 0: job slot available, != 0: uthread group id
|
||||||
|
* Note that job[0] is job_id 1, job[1] is job_id 2 etc.
|
||||||
|
*/
|
||||||
|
static unsigned int job[CONFIG_CMD_SPAWN_NUM_JOBS];
|
||||||
|
/* Return values of the commands run as jobs */
|
||||||
|
static enum command_ret_t job_ret[CONFIG_CMD_SPAWN_NUM_JOBS];
|
||||||
|
|
||||||
|
static void spa_free(struct spa *spa)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (!spa)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (i = 0; i < spa->argc; i++)
|
||||||
|
free(spa->argv[i]);
|
||||||
|
free(spa->argv);
|
||||||
|
free(spa);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct spa *spa_create(int argc, char *const argv[])
|
||||||
|
{
|
||||||
|
struct spa *spa;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
spa = calloc(1, sizeof(*spa));
|
||||||
|
if (!spa)
|
||||||
|
return NULL;
|
||||||
|
spa->argc = argc;
|
||||||
|
spa->argv = malloc(argc * sizeof(char *));
|
||||||
|
if (!spa->argv)
|
||||||
|
goto err;
|
||||||
|
for (i = 0; i < argc; i++) {
|
||||||
|
spa->argv[i] = strdup(argv[i]);
|
||||||
|
if (!spa->argv[i])
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
return spa;
|
||||||
|
err:
|
||||||
|
spa_free(spa);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void spawn_thread(void *arg)
|
||||||
|
{
|
||||||
|
struct spa *spa = (struct spa *)arg;
|
||||||
|
ulong cycles = 0;
|
||||||
|
int repeatable = 0;
|
||||||
|
|
||||||
|
job_ret[spa->job_idx] = cmd_process(0, spa->argc, spa->argv,
|
||||||
|
&repeatable, &cycles);
|
||||||
|
spa_free(spa);
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned int next_job_id(void)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < CONFIG_CMD_SPAWN_NUM_JOBS; i++)
|
||||||
|
if (!job[i])
|
||||||
|
return i + 1;
|
||||||
|
|
||||||
|
/* No job available */
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void refresh_jobs(void)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < CONFIG_CMD_SPAWN_NUM_JOBS; i++)
|
||||||
|
if (job[i] && uthread_grp_done(job[i]))
|
||||||
|
job[i] = 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static int do_spawn(struct cmd_tbl *cmdtp, int flag, int argc,
|
||||||
|
char *const argv[])
|
||||||
|
{
|
||||||
|
unsigned int id;
|
||||||
|
unsigned int idx;
|
||||||
|
struct spa *spa;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (argc == 1)
|
||||||
|
return CMD_RET_USAGE;
|
||||||
|
|
||||||
|
spa = spa_create(argc - 1, argv + 1);
|
||||||
|
if (!spa)
|
||||||
|
return CMD_RET_FAILURE;
|
||||||
|
|
||||||
|
refresh_jobs();
|
||||||
|
|
||||||
|
id = next_job_id();
|
||||||
|
if (!id)
|
||||||
|
return CMD_RET_FAILURE;
|
||||||
|
idx = id - 1;
|
||||||
|
|
||||||
|
job[idx] = uthread_grp_new_id();
|
||||||
|
|
||||||
|
ret = uthread_create(NULL, spawn_thread, spa, 0, job[idx]);
|
||||||
|
if (ret) {
|
||||||
|
job[idx] = 0;
|
||||||
|
return CMD_RET_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = env_set_ulong("job_id", id);
|
||||||
|
if (ret)
|
||||||
|
return CMD_RET_FAILURE;
|
||||||
|
|
||||||
|
return CMD_RET_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
U_BOOT_CMD(spawn, CONFIG_SYS_MAXARGS, 0, do_spawn,
|
||||||
|
"run commands and summarize execution time",
|
||||||
|
"command [args...]\n");
|
||||||
|
|
||||||
|
static enum command_ret_t wait_job(unsigned int idx)
|
||||||
|
{
|
||||||
|
int prev = disable_ctrlc(false);
|
||||||
|
|
||||||
|
while (!uthread_grp_done(job[idx])) {
|
||||||
|
if (ctrlc()) {
|
||||||
|
puts("<INTERRUPT>\n");
|
||||||
|
disable_ctrlc(prev);
|
||||||
|
return CMD_RET_FAILURE;
|
||||||
|
}
|
||||||
|
uthread_schedule();
|
||||||
|
}
|
||||||
|
|
||||||
|
job[idx] = 0;
|
||||||
|
disable_ctrlc(prev);
|
||||||
|
|
||||||
|
return job_ret[idx];
|
||||||
|
}
|
||||||
|
|
||||||
|
static int do_wait(struct cmd_tbl *cmdtp, int flag, int argc,
|
||||||
|
char *const argv[])
|
||||||
|
{
|
||||||
|
enum command_ret_t ret = CMD_RET_SUCCESS;
|
||||||
|
unsigned long id;
|
||||||
|
unsigned int idx;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (argc == 1) {
|
||||||
|
for (i = 0; i < CONFIG_CMD_SPAWN_NUM_JOBS; i++)
|
||||||
|
if (job[i])
|
||||||
|
ret = wait_job(i);
|
||||||
|
} else {
|
||||||
|
for (i = 1; i < argc; i++) {
|
||||||
|
id = dectoul(argv[i], NULL);
|
||||||
|
if (id < 0 || id > CONFIG_CMD_SPAWN_NUM_JOBS)
|
||||||
|
return CMD_RET_USAGE;
|
||||||
|
idx = (int)id - 1;
|
||||||
|
ret = wait_job(idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
U_BOOT_CMD(wait, CONFIG_SYS_MAXARGS, 0, do_wait,
|
||||||
|
"wait for one or more jobs to complete",
|
||||||
|
"[job_id ...]\n"
|
||||||
|
" - Wait until all specified jobs have exited and return the\n"
|
||||||
|
" exit status of the last job waited for. When no job_id is\n"
|
||||||
|
" given, wait for all the background jobs.\n");
|
@@ -16,6 +16,7 @@
|
|||||||
#include <linux/list.h>
|
#include <linux/list.h>
|
||||||
#include <asm/global_data.h>
|
#include <asm/global_data.h>
|
||||||
#include <u-boot/schedule.h>
|
#include <u-boot/schedule.h>
|
||||||
|
#include <uthread.h>
|
||||||
|
|
||||||
DECLARE_GLOBAL_DATA_PTR;
|
DECLARE_GLOBAL_DATA_PTR;
|
||||||
|
|
||||||
@@ -100,6 +101,8 @@ void schedule(void)
|
|||||||
*/
|
*/
|
||||||
if (gd)
|
if (gd)
|
||||||
cyclic_run();
|
cyclic_run();
|
||||||
|
|
||||||
|
uthread_schedule();
|
||||||
}
|
}
|
||||||
|
|
||||||
int cyclic_unregister_all(void)
|
int cyclic_unregister_all(void)
|
||||||
|
@@ -129,3 +129,5 @@ CONFIG_ULP_WATCHDOG=y
|
|||||||
CONFIG_WDT=y
|
CONFIG_WDT=y
|
||||||
CONFIG_LZO=y
|
CONFIG_LZO=y
|
||||||
CONFIG_BZIP2=y
|
CONFIG_BZIP2=y
|
||||||
|
CONFIG_UTHREAD=y
|
||||||
|
CONFIG_CMD_SPAWN=y
|
||||||
|
@@ -16,8 +16,10 @@ CONFIG_DISPLAY_BOARDINFO=y
|
|||||||
CONFIG_CMD_BOOTEFI_SELFTEST=y
|
CONFIG_CMD_BOOTEFI_SELFTEST=y
|
||||||
CONFIG_CMD_NVEDIT_EFI=y
|
CONFIG_CMD_NVEDIT_EFI=y
|
||||||
# CONFIG_CMD_MII is not set
|
# CONFIG_CMD_MII is not set
|
||||||
|
CONFIG_CMD_SPAWN=y
|
||||||
CONFIG_SYS_RELOC_GD_ENV_ADDR=y
|
CONFIG_SYS_RELOC_GD_ENV_ADDR=y
|
||||||
CONFIG_DM_MTD=y
|
CONFIG_DM_MTD=y
|
||||||
CONFIG_FLASH_SHOW_PROGRESS=0
|
CONFIG_FLASH_SHOW_PROGRESS=0
|
||||||
CONFIG_SYS_MAX_FLASH_BANKS=2
|
CONFIG_SYS_MAX_FLASH_BANKS=2
|
||||||
|
CONFIG_UTHREAD=y
|
||||||
CONFIG_UNIT_TEST=y
|
CONFIG_UNIT_TEST=y
|
||||||
|
@@ -17,8 +17,10 @@ CONFIG_DISPLAY_BOARDINFO=y
|
|||||||
CONFIG_CMD_BOOTEFI_SELFTEST=y
|
CONFIG_CMD_BOOTEFI_SELFTEST=y
|
||||||
CONFIG_CMD_NVEDIT_EFI=y
|
CONFIG_CMD_NVEDIT_EFI=y
|
||||||
# CONFIG_CMD_MII is not set
|
# CONFIG_CMD_MII is not set
|
||||||
|
CONFIG_CMD_SPAWN=y
|
||||||
CONFIG_SYS_RELOC_GD_ENV_ADDR=y
|
CONFIG_SYS_RELOC_GD_ENV_ADDR=y
|
||||||
CONFIG_DM_MTD=y
|
CONFIG_DM_MTD=y
|
||||||
CONFIG_FLASH_SHOW_PROGRESS=0
|
CONFIG_FLASH_SHOW_PROGRESS=0
|
||||||
CONFIG_SYS_MAX_FLASH_BANKS=2
|
CONFIG_SYS_MAX_FLASH_BANKS=2
|
||||||
|
CONFIG_UTHREAD=y
|
||||||
CONFIG_UNIT_TEST=y
|
CONFIG_UNIT_TEST=y
|
||||||
|
@@ -16,8 +16,10 @@ CONFIG_DISPLAY_BOARDINFO=y
|
|||||||
CONFIG_CMD_BOOTEFI_SELFTEST=y
|
CONFIG_CMD_BOOTEFI_SELFTEST=y
|
||||||
CONFIG_CMD_NVEDIT_EFI=y
|
CONFIG_CMD_NVEDIT_EFI=y
|
||||||
# CONFIG_CMD_MII is not set
|
# CONFIG_CMD_MII is not set
|
||||||
|
CONFIG_CMD_SPAWN=y
|
||||||
CONFIG_SYS_RELOC_GD_ENV_ADDR=y
|
CONFIG_SYS_RELOC_GD_ENV_ADDR=y
|
||||||
CONFIG_DM_MTD=y
|
CONFIG_DM_MTD=y
|
||||||
CONFIG_FLASH_SHOW_PROGRESS=0
|
CONFIG_FLASH_SHOW_PROGRESS=0
|
||||||
CONFIG_SYS_MAX_FLASH_BANKS=2
|
CONFIG_SYS_MAX_FLASH_BANKS=2
|
||||||
|
CONFIG_UTHREAD=y
|
||||||
CONFIG_UNIT_TEST=y
|
CONFIG_UNIT_TEST=y
|
||||||
|
@@ -19,8 +19,10 @@ CONFIG_DISPLAY_BOARDINFO=y
|
|||||||
CONFIG_CMD_BOOTEFI_SELFTEST=y
|
CONFIG_CMD_BOOTEFI_SELFTEST=y
|
||||||
CONFIG_CMD_NVEDIT_EFI=y
|
CONFIG_CMD_NVEDIT_EFI=y
|
||||||
# CONFIG_CMD_MII is not set
|
# CONFIG_CMD_MII is not set
|
||||||
|
CONFIG_CMD_SPAWN=y
|
||||||
CONFIG_SYS_RELOC_GD_ENV_ADDR=y
|
CONFIG_SYS_RELOC_GD_ENV_ADDR=y
|
||||||
CONFIG_DM_MTD=y
|
CONFIG_DM_MTD=y
|
||||||
CONFIG_FLASH_SHOW_PROGRESS=0
|
CONFIG_FLASH_SHOW_PROGRESS=0
|
||||||
CONFIG_SYS_MAX_FLASH_BANKS=2
|
CONFIG_SYS_MAX_FLASH_BANKS=2
|
||||||
|
CONFIG_UTHREAD=y
|
||||||
CONFIG_UNIT_TEST=y
|
CONFIG_UNIT_TEST=y
|
||||||
|
@@ -37,6 +37,7 @@ CONFIG_CMD_PCI=y
|
|||||||
CONFIG_CMD_EFIDEBUG=y
|
CONFIG_CMD_EFIDEBUG=y
|
||||||
CONFIG_CMD_TPM=y
|
CONFIG_CMD_TPM=y
|
||||||
CONFIG_CMD_MTDPARTS=y
|
CONFIG_CMD_MTDPARTS=y
|
||||||
|
CONFIG_CMD_SPAWN=y
|
||||||
CONFIG_ENV_IS_IN_FLASH=y
|
CONFIG_ENV_IS_IN_FLASH=y
|
||||||
CONFIG_SCSI_AHCI=y
|
CONFIG_SCSI_AHCI=y
|
||||||
CONFIG_AHCI_PCI=y
|
CONFIG_AHCI_PCI=y
|
||||||
@@ -75,4 +76,5 @@ CONFIG_MBEDTLS_LIB=y
|
|||||||
CONFIG_TPM=y
|
CONFIG_TPM=y
|
||||||
CONFIG_TPM_PCR_ALLOCATE=y
|
CONFIG_TPM_PCR_ALLOCATE=y
|
||||||
CONFIG_GENERATE_SMBIOS_TABLE_VERBOSE=y
|
CONFIG_GENERATE_SMBIOS_TABLE_VERBOSE=y
|
||||||
|
CONFIG_UTHREAD=y
|
||||||
CONFIG_UNIT_TEST=y
|
CONFIG_UNIT_TEST=y
|
||||||
|
@@ -35,6 +35,7 @@ CONFIG_CMD_MTD=y
|
|||||||
CONFIG_CMD_PCI=y
|
CONFIG_CMD_PCI=y
|
||||||
CONFIG_CMD_TPM=y
|
CONFIG_CMD_TPM=y
|
||||||
CONFIG_CMD_MTDPARTS=y
|
CONFIG_CMD_MTDPARTS=y
|
||||||
|
CONFIG_CMD_SPAWN=y
|
||||||
CONFIG_ENV_IS_IN_FLASH=y
|
CONFIG_ENV_IS_IN_FLASH=y
|
||||||
CONFIG_SCSI_AHCI=y
|
CONFIG_SCSI_AHCI=y
|
||||||
CONFIG_AHCI_PCI=y
|
CONFIG_AHCI_PCI=y
|
||||||
@@ -67,4 +68,5 @@ CONFIG_TPM2_MMIO=y
|
|||||||
CONFIG_USB_EHCI_HCD=y
|
CONFIG_USB_EHCI_HCD=y
|
||||||
CONFIG_USB_EHCI_PCI=y
|
CONFIG_USB_EHCI_PCI=y
|
||||||
CONFIG_TPM=y
|
CONFIG_TPM=y
|
||||||
|
CONFIG_UTHREAD=y
|
||||||
CONFIG_UNIT_TEST=y
|
CONFIG_UNIT_TEST=y
|
||||||
|
@@ -99,6 +99,7 @@ CONFIG_CMD_CBFS=y
|
|||||||
CONFIG_CMD_CRAMFS=y
|
CONFIG_CMD_CRAMFS=y
|
||||||
CONFIG_CMD_EXT4_WRITE=y
|
CONFIG_CMD_EXT4_WRITE=y
|
||||||
CONFIG_CMD_MTDPARTS=y
|
CONFIG_CMD_MTDPARTS=y
|
||||||
|
CONFIG_CMD_SPAWN=y
|
||||||
CONFIG_MAC_PARTITION=y
|
CONFIG_MAC_PARTITION=y
|
||||||
CONFIG_AMIGA_PARTITION=y
|
CONFIG_AMIGA_PARTITION=y
|
||||||
CONFIG_OF_CONTROL=y
|
CONFIG_OF_CONTROL=y
|
||||||
@@ -275,6 +276,7 @@ CONFIG_TPM=y
|
|||||||
CONFIG_ERRNO_STR=y
|
CONFIG_ERRNO_STR=y
|
||||||
CONFIG_GETOPT=y
|
CONFIG_GETOPT=y
|
||||||
CONFIG_TEST_FDTDEC=y
|
CONFIG_TEST_FDTDEC=y
|
||||||
|
CONFIG_UTHREAD=y
|
||||||
CONFIG_UNIT_TEST=y
|
CONFIG_UNIT_TEST=y
|
||||||
CONFIG_UT_TIME=y
|
CONFIG_UT_TIME=y
|
||||||
CONFIG_UT_DM=y
|
CONFIG_UT_DM=y
|
||||||
|
@@ -143,6 +143,7 @@ CONFIG_CMD_EXT4_WRITE=y
|
|||||||
CONFIG_CMD_SQUASHFS=y
|
CONFIG_CMD_SQUASHFS=y
|
||||||
CONFIG_CMD_MTDPARTS=y
|
CONFIG_CMD_MTDPARTS=y
|
||||||
CONFIG_CMD_STACKPROTECTOR_TEST=y
|
CONFIG_CMD_STACKPROTECTOR_TEST=y
|
||||||
|
CONFIG_CMD_SPAWN=y
|
||||||
CONFIG_MAC_PARTITION=y
|
CONFIG_MAC_PARTITION=y
|
||||||
CONFIG_OF_CONTROL=y
|
CONFIG_OF_CONTROL=y
|
||||||
CONFIG_OF_LIVE=y
|
CONFIG_OF_LIVE=y
|
||||||
@@ -364,6 +365,7 @@ CONFIG_TPM=y
|
|||||||
CONFIG_ERRNO_STR=y
|
CONFIG_ERRNO_STR=y
|
||||||
CONFIG_GETOPT=y
|
CONFIG_GETOPT=y
|
||||||
CONFIG_TEST_FDTDEC=y
|
CONFIG_TEST_FDTDEC=y
|
||||||
|
CONFIG_UTHREAD=y
|
||||||
CONFIG_UNIT_TEST=y
|
CONFIG_UNIT_TEST=y
|
||||||
CONFIG_UT_TIME=y
|
CONFIG_UT_TIME=y
|
||||||
CONFIG_UT_DM=y
|
CONFIG_UT_DM=y
|
||||||
|
@@ -25,6 +25,8 @@ U-Boot API documentation
|
|||||||
rng
|
rng
|
||||||
sandbox
|
sandbox
|
||||||
serial
|
serial
|
||||||
|
setjmp
|
||||||
sysreset
|
sysreset
|
||||||
timer
|
timer
|
||||||
unicode
|
unicode
|
||||||
|
uthread
|
||||||
|
20
doc/api/setjmp.rst
Normal file
20
doc/api/setjmp.rst
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
.. SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
Long jump API
|
||||||
|
=============
|
||||||
|
|
||||||
|
.. kernel-doc:: include/setjmp.h
|
||||||
|
:doc: Overview
|
||||||
|
|
||||||
|
.. kernel-doc:: include/setjmp.h
|
||||||
|
:internal:
|
||||||
|
|
||||||
|
Example
|
||||||
|
-------
|
||||||
|
|
||||||
|
Here is an example showing how to use the a long jump functions and
|
||||||
|
initjmp() in particular:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../test/lib/initjmp.c
|
||||||
|
:language: c
|
||||||
|
:linenos:
|
19
doc/api/uthread.rst
Normal file
19
doc/api/uthread.rst
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
.. SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
Uthread API
|
||||||
|
===========
|
||||||
|
|
||||||
|
.. kernel-doc:: include/uthread.h
|
||||||
|
:doc: Overview
|
||||||
|
|
||||||
|
.. kernel-doc:: include/uthread.h
|
||||||
|
:internal:
|
||||||
|
|
||||||
|
Example
|
||||||
|
-------
|
||||||
|
|
||||||
|
Here is an example of how to use this API:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../test/lib/uthread.c
|
||||||
|
:language: c
|
||||||
|
:linenos:
|
@@ -9,6 +9,7 @@
|
|||||||
#define LOG_CATEGORY UCLASS_USB
|
#define LOG_CATEGORY UCLASS_USB
|
||||||
|
|
||||||
#include <bootdev.h>
|
#include <bootdev.h>
|
||||||
|
#include <uthread.h>
|
||||||
#include <dm.h>
|
#include <dm.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <log.h>
|
#include <log.h>
|
||||||
@@ -17,6 +18,7 @@
|
|||||||
#include <dm/device-internal.h>
|
#include <dm/device-internal.h>
|
||||||
#include <dm/lists.h>
|
#include <dm/lists.h>
|
||||||
#include <dm/uclass-internal.h>
|
#include <dm/uclass-internal.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
static bool asynch_allowed;
|
static bool asynch_allowed;
|
||||||
|
|
||||||
@@ -172,6 +174,10 @@ int usb_get_max_xfer_size(struct usb_device *udev, size_t *size)
|
|||||||
return ops->get_max_xfer_size(bus, size);
|
return ops->get_max_xfer_size(bus, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if CONFIG_IS_ENABLED(UTHREAD)
|
||||||
|
static struct uthread_mutex mutex = UTHREAD_MUTEX_INITIALIZER;
|
||||||
|
#endif
|
||||||
|
|
||||||
int usb_stop(void)
|
int usb_stop(void)
|
||||||
{
|
{
|
||||||
struct udevice *bus;
|
struct udevice *bus;
|
||||||
@@ -180,10 +186,14 @@ int usb_stop(void)
|
|||||||
struct usb_uclass_priv *uc_priv;
|
struct usb_uclass_priv *uc_priv;
|
||||||
int err = 0, ret;
|
int err = 0, ret;
|
||||||
|
|
||||||
|
uthread_mutex_lock(&mutex);
|
||||||
|
|
||||||
/* De-activate any devices that have been activated */
|
/* De-activate any devices that have been activated */
|
||||||
ret = uclass_get(UCLASS_USB, &uc);
|
ret = uclass_get(UCLASS_USB, &uc);
|
||||||
if (ret)
|
if (ret) {
|
||||||
|
uthread_mutex_unlock(&mutex);
|
||||||
return ret;
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
uc_priv = uclass_get_priv(uc);
|
uc_priv = uclass_get_priv(uc);
|
||||||
|
|
||||||
@@ -218,28 +228,23 @@ int usb_stop(void)
|
|||||||
uc_priv->companion_device_count = 0;
|
uc_priv->companion_device_count = 0;
|
||||||
usb_started = 0;
|
usb_started = 0;
|
||||||
|
|
||||||
|
uthread_mutex_unlock(&mutex);
|
||||||
|
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void usb_scan_bus(struct udevice *bus, bool recurse)
|
static void _usb_scan_bus(void *arg)
|
||||||
{
|
{
|
||||||
|
struct udevice *bus = (struct udevice *)arg;
|
||||||
struct usb_bus_priv *priv;
|
struct usb_bus_priv *priv;
|
||||||
struct udevice *dev;
|
struct udevice *dev;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
priv = dev_get_uclass_priv(bus);
|
priv = dev_get_uclass_priv(bus);
|
||||||
|
|
||||||
assert(recurse); /* TODO: Support non-recusive */
|
|
||||||
|
|
||||||
printf("scanning bus %s for devices... ", bus->name);
|
|
||||||
debug("\n");
|
|
||||||
ret = usb_scan_device(bus, 0, USB_SPEED_FULL, &dev);
|
ret = usb_scan_device(bus, 0, USB_SPEED_FULL, &dev);
|
||||||
if (ret)
|
if (ret)
|
||||||
printf("failed, error %d\n", ret);
|
printf("Scanning bus %s failed, error %d\n", bus->name, ret);
|
||||||
else if (priv->next_addr == 0)
|
|
||||||
printf("No USB Device found\n");
|
|
||||||
else
|
|
||||||
printf("%d USB Device(s) found\n", priv->next_addr);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void remove_inactive_children(struct uclass *uc, struct udevice *bus)
|
static void remove_inactive_children(struct uclass *uc, struct udevice *bus)
|
||||||
@@ -287,65 +292,128 @@ static int usb_probe_companion(struct udevice *bus)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void _usb_init_bus(void *arg)
|
||||||
|
{
|
||||||
|
struct udevice *bus = (struct udevice *)arg;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
/* init low_level USB */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* For Sandbox, we need scan the device tree each time when we
|
||||||
|
* start the USB stack, in order to re-create the emulated USB
|
||||||
|
* devices and bind drivers for them before we actually do the
|
||||||
|
* driver probe.
|
||||||
|
*
|
||||||
|
* For USB onboard HUB, we need to do some non-trivial init
|
||||||
|
* like enabling a power regulator, before enumeration.
|
||||||
|
*/
|
||||||
|
if (IS_ENABLED(CONFIG_SANDBOX) ||
|
||||||
|
IS_ENABLED(CONFIG_USB_ONBOARD_HUB)) {
|
||||||
|
ret = dm_scan_fdt_dev(bus);
|
||||||
|
if (ret) {
|
||||||
|
printf("Bus %s: USB device scan from fdt failed (%d)\n",
|
||||||
|
bus->name, ret);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = device_probe(bus);
|
||||||
|
if (ret == -ENODEV) { /* No such device. */
|
||||||
|
printf("Bus %s: Port not available.\n", bus->name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret) { /* Other error. */
|
||||||
|
printf("Bus %s: probe failed, error %d\n", bus->name, ret);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
usb_probe_companion(bus);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int nthr;
|
||||||
|
static int grp_id;
|
||||||
|
|
||||||
|
static void usb_init_bus(struct udevice *bus)
|
||||||
|
{
|
||||||
|
if (!grp_id)
|
||||||
|
grp_id = uthread_grp_new_id();
|
||||||
|
if (!uthread_create(NULL, _usb_init_bus, (void *)bus, 0, grp_id))
|
||||||
|
nthr++;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void usb_scan_bus(struct udevice *bus, bool recurse)
|
||||||
|
{
|
||||||
|
if (!grp_id)
|
||||||
|
grp_id = uthread_grp_new_id();
|
||||||
|
if (!uthread_create(NULL, _usb_scan_bus, (void *)bus, 0, grp_id))
|
||||||
|
nthr++;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void usb_report_devices(struct uclass *uc)
|
||||||
|
{
|
||||||
|
struct usb_bus_priv *priv;
|
||||||
|
struct udevice *bus;
|
||||||
|
|
||||||
|
uclass_foreach_dev(bus, uc) {
|
||||||
|
if (!device_active(bus))
|
||||||
|
continue;
|
||||||
|
priv = dev_get_uclass_priv(bus);
|
||||||
|
printf("Bus %s: ", bus->name);
|
||||||
|
if (priv->next_addr == 0)
|
||||||
|
printf("No USB Device found\n");
|
||||||
|
else
|
||||||
|
printf("%d USB Device(s) found\n", priv->next_addr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void run_threads(void)
|
||||||
|
{
|
||||||
|
#if CONFIG_IS_ENABLED(UTHREAD)
|
||||||
|
if (!nthr)
|
||||||
|
return;
|
||||||
|
while (!uthread_grp_done(grp_id))
|
||||||
|
uthread_schedule();
|
||||||
|
nthr = 0;
|
||||||
|
grp_id = 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
int usb_init(void)
|
int usb_init(void)
|
||||||
{
|
{
|
||||||
int controllers_initialized = 0;
|
int controllers_initialized = 0;
|
||||||
|
unsigned long t0 = timer_get_us();
|
||||||
struct usb_uclass_priv *uc_priv;
|
struct usb_uclass_priv *uc_priv;
|
||||||
struct usb_bus_priv *priv;
|
struct usb_bus_priv *priv;
|
||||||
struct udevice *bus;
|
struct udevice *bus;
|
||||||
struct uclass *uc;
|
struct uclass *uc;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
|
uthread_mutex_lock(&mutex);
|
||||||
|
|
||||||
|
if (usb_started) {
|
||||||
|
ret = 0;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
asynch_allowed = 1;
|
asynch_allowed = 1;
|
||||||
|
|
||||||
ret = uclass_get(UCLASS_USB, &uc);
|
ret = uclass_get(UCLASS_USB, &uc);
|
||||||
if (ret)
|
if (ret)
|
||||||
return ret;
|
goto out;
|
||||||
|
|
||||||
uc_priv = uclass_get_priv(uc);
|
uc_priv = uclass_get_priv(uc);
|
||||||
|
|
||||||
uclass_foreach_dev(bus, uc) {
|
uclass_foreach_dev(bus, uc) {
|
||||||
/* init low_level USB */
|
usb_init_bus(bus);
|
||||||
printf("Bus %s: ", bus->name);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* For Sandbox, we need scan the device tree each time when we
|
|
||||||
* start the USB stack, in order to re-create the emulated USB
|
|
||||||
* devices and bind drivers for them before we actually do the
|
|
||||||
* driver probe.
|
|
||||||
*
|
|
||||||
* For USB onboard HUB, we need to do some non-trivial init
|
|
||||||
* like enabling a power regulator, before enumeration.
|
|
||||||
*/
|
|
||||||
if (IS_ENABLED(CONFIG_SANDBOX) ||
|
|
||||||
IS_ENABLED(CONFIG_USB_ONBOARD_HUB)) {
|
|
||||||
ret = dm_scan_fdt_dev(bus);
|
|
||||||
if (ret) {
|
|
||||||
printf("USB device scan from fdt failed (%d)", ret);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = device_probe(bus);
|
|
||||||
if (ret == -ENODEV) { /* No such device. */
|
|
||||||
puts("Port not available.\n");
|
|
||||||
controllers_initialized++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ret) { /* Other error. */
|
|
||||||
printf("probe failed, error %d\n", ret);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = usb_probe_companion(bus);
|
|
||||||
if (ret)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
controllers_initialized++;
|
|
||||||
usb_started = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (CONFIG_IS_ENABLED(UTHREAD))
|
||||||
|
run_threads();
|
||||||
|
|
||||||
|
usb_started = true;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* lowlevel init done, now scan the bus for devices i.e. search HUBs
|
* lowlevel init done, now scan the bus for devices i.e. search HUBs
|
||||||
* and configure them, first scan primary controllers.
|
* and configure them, first scan primary controllers.
|
||||||
@@ -354,11 +422,16 @@ int usb_init(void)
|
|||||||
if (!device_active(bus))
|
if (!device_active(bus))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
controllers_initialized++;
|
||||||
|
|
||||||
priv = dev_get_uclass_priv(bus);
|
priv = dev_get_uclass_priv(bus);
|
||||||
if (!priv->companion)
|
if (!priv->companion)
|
||||||
usb_scan_bus(bus, true);
|
usb_scan_bus(bus, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (CONFIG_IS_ENABLED(UTHREAD))
|
||||||
|
run_threads();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Now that the primary controllers have been scanned and have handed
|
* Now that the primary controllers have been scanned and have handed
|
||||||
* over any devices they do not understand to their companions, scan
|
* over any devices they do not understand to their companions, scan
|
||||||
@@ -375,21 +448,34 @@ int usb_init(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
debug("scan end\n");
|
if (CONFIG_IS_ENABLED(UTHREAD))
|
||||||
|
run_threads();
|
||||||
|
|
||||||
|
usb_report_devices(uc);
|
||||||
|
|
||||||
/* Remove any devices that were not found on this scan */
|
/* Remove any devices that were not found on this scan */
|
||||||
remove_inactive_children(uc, bus);
|
remove_inactive_children(uc, bus);
|
||||||
|
|
||||||
ret = uclass_get(UCLASS_USB_HUB, &uc);
|
ret = uclass_get(UCLASS_USB_HUB, &uc);
|
||||||
if (ret)
|
if (ret)
|
||||||
return ret;
|
goto out;
|
||||||
|
|
||||||
remove_inactive_children(uc, bus);
|
remove_inactive_children(uc, bus);
|
||||||
|
|
||||||
/* if we were not able to find at least one working bus, bail out */
|
/* if we were not able to find at least one working bus, bail out */
|
||||||
if (controllers_initialized == 0)
|
if (controllers_initialized == 0)
|
||||||
printf("No USB controllers found\n");
|
printf("No USB controllers found\n");
|
||||||
|
|
||||||
|
debug("USB initialized in %ld ms\n",
|
||||||
|
(timer_get_us() - t0) / 1000);
|
||||||
|
|
||||||
|
uthread_mutex_unlock(&mutex);
|
||||||
|
|
||||||
return usb_started ? 0 : -ENOENT;
|
return usb_started ? 0 : -ENOENT;
|
||||||
|
out:
|
||||||
|
uthread_mutex_unlock(&mutex);
|
||||||
|
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
int usb_setup_ehci_gadget(struct ehci_ctrl **ctlrp)
|
int usb_setup_ehci_gadget(struct ehci_ctrl **ctlrp)
|
||||||
|
@@ -3,12 +3,27 @@
|
|||||||
#ifndef _SETJMP_H_
|
#ifndef _SETJMP_H_
|
||||||
#define _SETJMP_H_ 1
|
#define _SETJMP_H_ 1
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DOC: Overview
|
||||||
|
*
|
||||||
|
* The long jump API allows to perform nonlocal gotos, that is jump from one
|
||||||
|
* function to another typically further down in the stack, while properly
|
||||||
|
* restoring the stack's state (unwinding). The two functions needed to do this
|
||||||
|
* are setjmp() and longjmp().
|
||||||
|
*
|
||||||
|
* In addition to these two standard POSIX.1-2001/C89 functions, a third one is
|
||||||
|
* present in U-Boot: initjmp(). It is an extension which allows to implement
|
||||||
|
* user-mode threads.
|
||||||
|
*/
|
||||||
|
|
||||||
#ifdef CONFIG_HAVE_SETJMP
|
#ifdef CONFIG_HAVE_SETJMP
|
||||||
#include <asm/setjmp.h>
|
#include <asm/setjmp.h>
|
||||||
#else
|
#else
|
||||||
struct jmp_buf_data {
|
struct jmp_buf_data {
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
#include <linux/compiler_attributes.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* typedef jmp_buf - information needed to restore a calling environment
|
* typedef jmp_buf - information needed to restore a calling environment
|
||||||
@@ -37,4 +52,21 @@ int setjmp(jmp_buf env);
|
|||||||
*/
|
*/
|
||||||
void longjmp(jmp_buf env, int val);
|
void longjmp(jmp_buf env, int val);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* initjmp() - prepare for a long jump to a given function with a given stack
|
||||||
|
*
|
||||||
|
* This function sets up a jump buffer for later use with longjmp(). It allows
|
||||||
|
* to branch to a specific function with a specific stack. Please note that
|
||||||
|
* @func MUST NOT return. It shall typically restore the main stack and resume
|
||||||
|
* execution by doing a long jump to a jump buffer initialized by setjmp()
|
||||||
|
* before the long jump. initjmp() allows to implement multithreading.
|
||||||
|
*
|
||||||
|
* @env: jump buffer
|
||||||
|
* @func: function to be called on longjmp(), MUST NOT RETURN
|
||||||
|
* @stack_base: the stack to be used by @func (lower address)
|
||||||
|
* @stack_sz: the stack size in bytes
|
||||||
|
*/
|
||||||
|
int initjmp(jmp_buf env, void __noreturn (*func)(void), void *stack_base,
|
||||||
|
size_t stack_sz);
|
||||||
|
|
||||||
#endif /* _SETJMP_H_ */
|
#endif /* _SETJMP_H_ */
|
||||||
|
@@ -3,6 +3,8 @@
|
|||||||
#ifndef _U_BOOT_SCHEDULE_H
|
#ifndef _U_BOOT_SCHEDULE_H
|
||||||
#define _U_BOOT_SCHEDULE_H
|
#define _U_BOOT_SCHEDULE_H
|
||||||
|
|
||||||
|
#include <uthread.h>
|
||||||
|
|
||||||
#if CONFIG_IS_ENABLED(CYCLIC)
|
#if CONFIG_IS_ENABLED(CYCLIC)
|
||||||
/**
|
/**
|
||||||
* schedule() - Schedule all potentially waiting tasks
|
* schedule() - Schedule all potentially waiting tasks
|
||||||
@@ -17,6 +19,7 @@ void schedule(void);
|
|||||||
|
|
||||||
static inline void schedule(void)
|
static inline void schedule(void)
|
||||||
{
|
{
|
||||||
|
uthread_schedule();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
183
include/uthread.h
Normal file
183
include/uthread.h
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
/* SPDX-License-Identifier: GPL-2.0+ */
|
||||||
|
/*
|
||||||
|
* Copyright 2025 Linaro Limited
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/list.h>
|
||||||
|
#include <linux/types.h>
|
||||||
|
#include <setjmp.h>
|
||||||
|
|
||||||
|
#ifndef _UTHREAD_H_
|
||||||
|
#define _UTHREAD_H_
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DOC: Overview
|
||||||
|
*
|
||||||
|
* The uthread framework is a basic task scheduler that allows to run functions
|
||||||
|
* "in parallel" on a single CPU core. The scheduling is cooperative, not
|
||||||
|
* preemptive -- meaning that context switches from one task to another task is
|
||||||
|
* voluntary, via a call to uthread_schedule(). This characteristic makes thread
|
||||||
|
* synchronization much easier, because a thread cannot be interrupted in the
|
||||||
|
* middle of a critical section (reading from or writing to shared state, for
|
||||||
|
* instance).
|
||||||
|
*
|
||||||
|
* CONFIG_UTHREAD in lib/Kconfig enables the uthread framework. When disabled,
|
||||||
|
* the uthread_create() and uthread_schedule() functions may still be used so
|
||||||
|
* that code differences between uthreads enabled and disabled can be reduced to
|
||||||
|
* a minimum.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* struct uthread - a thread object
|
||||||
|
*
|
||||||
|
* @fn: thread entry point
|
||||||
|
* @arg: argument passed to the entry point when the thread is started
|
||||||
|
* @ctx: context to resume execution of this thread (via longjmp())
|
||||||
|
* @stack: initial stack pointer for the thread
|
||||||
|
* @done: true once @fn has returned, false otherwise
|
||||||
|
* @grp_id: user-supplied identifier for this thread and possibly others. A
|
||||||
|
* thread can belong to zero or one group (not more), and a group may contain
|
||||||
|
* any number of threads.
|
||||||
|
* @list: link in the global scheduler list
|
||||||
|
*/
|
||||||
|
struct uthread {
|
||||||
|
void (*fn)(void *arg);
|
||||||
|
void *arg;
|
||||||
|
jmp_buf ctx;
|
||||||
|
void *stack;
|
||||||
|
bool done;
|
||||||
|
unsigned int grp_id;
|
||||||
|
struct list_head list;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal state of a struct uthread_mutex
|
||||||
|
*/
|
||||||
|
enum uthread_mutex_state {
|
||||||
|
UTHREAD_MUTEX_UNLOCKED = 0,
|
||||||
|
UTHREAD_MUTEX_LOCKED = 1
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uthread mutex
|
||||||
|
*/
|
||||||
|
struct uthread_mutex {
|
||||||
|
enum uthread_mutex_state state;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define UTHREAD_MUTEX_INITIALIZER { .state = UTHREAD_MUTEX_UNLOCKED }
|
||||||
|
|
||||||
|
#ifdef CONFIG_UTHREAD
|
||||||
|
|
||||||
|
/**
|
||||||
|
* uthread_create() - Create a uthread object and make it ready for execution
|
||||||
|
*
|
||||||
|
* Threads are automatically deleted when they return from their entry point.
|
||||||
|
*
|
||||||
|
* @uthr: a pointer to a user-allocated uthread structure to store information
|
||||||
|
* about the new thread, or NULL to let the framework allocate and manage its
|
||||||
|
* own structure.
|
||||||
|
* @fn: the thread's entry point
|
||||||
|
* @arg: argument passed to the thread's entry point
|
||||||
|
* @stack_sz: stack size for the new thread (in bytes). The stack is allocated
|
||||||
|
* on the heap.
|
||||||
|
* @grp_id: an optional thread group ID that the new thread should belong to
|
||||||
|
* (zero for no group)
|
||||||
|
*/
|
||||||
|
int uthread_create(struct uthread *uthr, void (*fn)(void *), void *arg,
|
||||||
|
size_t stack_sz, unsigned int grp_id);
|
||||||
|
/**
|
||||||
|
* uthread_schedule() - yield the CPU to the next runnable thread
|
||||||
|
*
|
||||||
|
* This function is called either by the main thread or any secondary thread
|
||||||
|
* (that is, any thread created via uthread_create()) to switch execution to
|
||||||
|
* the next runnable thread.
|
||||||
|
*
|
||||||
|
* Return: true if a thread was scheduled, false if no runnable thread was found
|
||||||
|
*/
|
||||||
|
bool uthread_schedule(void);
|
||||||
|
/**
|
||||||
|
* uthread_grp_new_id() - return a new ID for a thread group
|
||||||
|
*
|
||||||
|
* Return: the new thread group ID
|
||||||
|
*/
|
||||||
|
unsigned int uthread_grp_new_id(void);
|
||||||
|
/**
|
||||||
|
* uthread_grp_done() - test if all threads in a group are done
|
||||||
|
*
|
||||||
|
* @grp_id: the ID of the thread group that should be considered
|
||||||
|
* Return: false if the group contains at least one runnable thread (i.e., one
|
||||||
|
* thread which entry point has not returned yet), true otherwise
|
||||||
|
*/
|
||||||
|
bool uthread_grp_done(unsigned int grp_id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* uthread_mutex_lock() - lock a mutex
|
||||||
|
*
|
||||||
|
* If the cwmutexlock is available (i.e., not owned by any other thread), then
|
||||||
|
* it is locked for use by the current thread. Otherwise the current thread
|
||||||
|
* blocks: it enters a wait loop by scheduling other threads until the mutex
|
||||||
|
* becomes unlocked.
|
||||||
|
*
|
||||||
|
* @mutex: pointer to the mutex to lock
|
||||||
|
* Return: 0 on success, in which case the lock is owned by the calling thread.
|
||||||
|
* != 0 otherwise (the lock is not owned by the calling thread).
|
||||||
|
*/
|
||||||
|
int uthread_mutex_lock(struct uthread_mutex *mutex);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* uthread_mutex_trylock() - lock a mutex if not currently locked
|
||||||
|
*
|
||||||
|
* Similar to uthread_mutex_lock() except return immediately if the mutex is
|
||||||
|
* locked already.
|
||||||
|
*
|
||||||
|
* @mutex: pointer to the mutex to lock
|
||||||
|
* Return: 0 on success, in which case the lock is owned by the calling thread.
|
||||||
|
* EBUSY if the mutex is already locked by another thread. Any other non-zero
|
||||||
|
* value on error.
|
||||||
|
*/
|
||||||
|
int uthread_mutex_trylock(struct uthread_mutex *mutex);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* uthread_mutex_unlock() - unlock a mutex
|
||||||
|
*
|
||||||
|
* The mutex is assumed to be owned by the calling thread on entry. On exit, it
|
||||||
|
* is unlocked.
|
||||||
|
*
|
||||||
|
* @mutex: pointer to the mutex to unlock
|
||||||
|
* Return: 0 on success, != 0 on error
|
||||||
|
*/
|
||||||
|
int uthread_mutex_unlock(struct uthread_mutex *mutex);
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
static inline int uthread_create(struct uthread *uthr, void (*fn)(void *),
|
||||||
|
void *arg, size_t stack_sz,
|
||||||
|
unsigned int grp_id)
|
||||||
|
{
|
||||||
|
fn(arg);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool uthread_schedule(void)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline unsigned int uthread_grp_new_id(void)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool uthread_grp_done(unsigned int grp_id)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* These are macros for convenience on the caller side */
|
||||||
|
#define uthread_mutex_lock(_mutex) ({ 0; })
|
||||||
|
#define uthread_mutex_trylock(_mutex) ({ 0 })
|
||||||
|
#define uthread_mutex_unlock(_mutex) ({ 0; })
|
||||||
|
|
||||||
|
#endif /* CONFIG_UTHREAD */
|
||||||
|
#endif /* _UTHREAD_H_ */
|
21
lib/Kconfig
21
lib/Kconfig
@@ -1258,6 +1258,27 @@ config PHANDLE_CHECK_SEQ
|
|||||||
enable this config option to distinguish them using
|
enable this config option to distinguish them using
|
||||||
phandles in fdtdec_get_alias_seq() function.
|
phandles in fdtdec_get_alias_seq() function.
|
||||||
|
|
||||||
|
config UTHREAD
|
||||||
|
bool "Enable thread support"
|
||||||
|
depends on HAVE_INITJMP
|
||||||
|
help
|
||||||
|
Implement a simple form of cooperative multi-tasking based on
|
||||||
|
context-switching via initjmp(), setjmp() and longjmp(). The
|
||||||
|
uthread_ interface enables the main thread of execution to create
|
||||||
|
one or more secondary threads and schedule them until they all have
|
||||||
|
returned. At any point a thread may suspend its execution and
|
||||||
|
schedule another thread, which allows for the efficient multiplexing
|
||||||
|
of leghthy operations.
|
||||||
|
|
||||||
|
config UTHREAD_STACK_SIZE
|
||||||
|
int "Default uthread stack size"
|
||||||
|
depends on UTHREAD
|
||||||
|
default 32768
|
||||||
|
help
|
||||||
|
The default stack size for uthreads. Each uthread has its own stack.
|
||||||
|
When the stack_sz argument to uthread_create() is zero then this
|
||||||
|
value is used.
|
||||||
|
|
||||||
endmenu
|
endmenu
|
||||||
|
|
||||||
source "lib/fwu_updates/Kconfig"
|
source "lib/fwu_updates/Kconfig"
|
||||||
|
@@ -159,6 +159,8 @@ obj-$(CONFIG_LIB_ELF) += elf.o
|
|||||||
|
|
||||||
obj-$(CONFIG_$(PHASE_)SEMIHOSTING) += semihosting.o
|
obj-$(CONFIG_$(PHASE_)SEMIHOSTING) += semihosting.o
|
||||||
|
|
||||||
|
obj-$(CONFIG_UTHREAD) += uthread.o
|
||||||
|
|
||||||
#
|
#
|
||||||
# Build a fast OID lookup registry from include/linux/oid_registry.h
|
# Build a fast OID lookup registry from include/linux/oid_registry.h
|
||||||
#
|
#
|
||||||
|
@@ -17,6 +17,7 @@
|
|||||||
#include <asm/global_data.h>
|
#include <asm/global_data.h>
|
||||||
#include <asm/io.h>
|
#include <asm/io.h>
|
||||||
#include <linux/delay.h>
|
#include <linux/delay.h>
|
||||||
|
#include <uthread.h>
|
||||||
|
|
||||||
#ifndef CFG_WD_PERIOD
|
#ifndef CFG_WD_PERIOD
|
||||||
# define CFG_WD_PERIOD (10 * 1000 * 1000) /* 10 seconds default */
|
# define CFG_WD_PERIOD (10 * 1000 * 1000) /* 10 seconds default */
|
||||||
@@ -197,7 +198,13 @@ void udelay(unsigned long usec)
|
|||||||
do {
|
do {
|
||||||
schedule();
|
schedule();
|
||||||
kv = usec > CFG_WD_PERIOD ? CFG_WD_PERIOD : usec;
|
kv = usec > CFG_WD_PERIOD ? CFG_WD_PERIOD : usec;
|
||||||
__udelay(kv);
|
if (CONFIG_IS_ENABLED(UTHREAD)) {
|
||||||
|
ulong t0 = timer_get_us();
|
||||||
|
while (timer_get_us() - t0 < kv)
|
||||||
|
uthread_schedule();
|
||||||
|
} else {
|
||||||
|
__udelay(kv);
|
||||||
|
}
|
||||||
usec -= kv;
|
usec -= kv;
|
||||||
} while(usec);
|
} while(usec);
|
||||||
}
|
}
|
||||||
|
165
lib/uthread.c
Normal file
165
lib/uthread.c
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-only
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 Ahmad Fatoum, Pengutronix
|
||||||
|
* Copyright (C) 2025 Linaro Limited
|
||||||
|
*
|
||||||
|
* An implementation of cooperative multi-tasking inspired from barebox threads
|
||||||
|
* https://github.com/barebox/barebox/blob/master/common/bthread.c
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <compiler.h>
|
||||||
|
#include <linux/errno.h>
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/list.h>
|
||||||
|
#include <malloc.h>
|
||||||
|
#include <setjmp.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <uthread.h>
|
||||||
|
|
||||||
|
static struct uthread main_thread = {
|
||||||
|
.list = LIST_HEAD_INIT(main_thread.list),
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct uthread *current = &main_thread;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* uthread_trampoline() - Call the current thread's entry point then resume the
|
||||||
|
* main thread.
|
||||||
|
*
|
||||||
|
* This is a helper function which is used as the @func argument to the
|
||||||
|
* initjmp() function, and ultimately invoked via setjmp(). It does not return
|
||||||
|
* but instead longjmp()'s back to the main thread.
|
||||||
|
*/
|
||||||
|
static void __noreturn uthread_trampoline(void)
|
||||||
|
{
|
||||||
|
struct uthread *curr = current;
|
||||||
|
|
||||||
|
curr->fn(curr->arg);
|
||||||
|
curr->done = true;
|
||||||
|
current = &main_thread;
|
||||||
|
longjmp(current->ctx, 1);
|
||||||
|
/* Not reached */
|
||||||
|
while (true)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* uthread_free() - Free memory used by a uthread object.
|
||||||
|
*/
|
||||||
|
static void uthread_free(struct uthread *uthread)
|
||||||
|
{
|
||||||
|
if (!uthread)
|
||||||
|
return;
|
||||||
|
free(uthread->stack);
|
||||||
|
free(uthread);
|
||||||
|
}
|
||||||
|
|
||||||
|
int uthread_create(struct uthread *uthr, void (*fn)(void *), void *arg,
|
||||||
|
size_t stack_sz, unsigned int grp_id)
|
||||||
|
{
|
||||||
|
bool user_allocated = false;
|
||||||
|
|
||||||
|
if (!stack_sz)
|
||||||
|
stack_sz = CONFIG_UTHREAD_STACK_SIZE;
|
||||||
|
|
||||||
|
if (uthr) {
|
||||||
|
user_allocated = true;
|
||||||
|
} else {
|
||||||
|
uthr = calloc(1, sizeof(*uthr));
|
||||||
|
if (!uthr)
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
uthr->stack = memalign(16, stack_sz);
|
||||||
|
if (!uthr->stack)
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
uthr->fn = fn;
|
||||||
|
uthr->arg = arg;
|
||||||
|
uthr->grp_id = grp_id;
|
||||||
|
|
||||||
|
list_add_tail(&uthr->list, ¤t->list);
|
||||||
|
|
||||||
|
initjmp(uthr->ctx, uthread_trampoline, uthr->stack, stack_sz);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
err:
|
||||||
|
if (!user_allocated)
|
||||||
|
free(uthr);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* uthread_resume() - switch execution to a given thread
|
||||||
|
*
|
||||||
|
* @uthread: the thread object that should be resumed
|
||||||
|
*/
|
||||||
|
static void uthread_resume(struct uthread *uthread)
|
||||||
|
{
|
||||||
|
if (!setjmp(current->ctx)) {
|
||||||
|
current = uthread;
|
||||||
|
longjmp(uthread->ctx, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool uthread_schedule(void)
|
||||||
|
{
|
||||||
|
struct uthread *next;
|
||||||
|
struct uthread *tmp;
|
||||||
|
|
||||||
|
list_for_each_entry_safe(next, tmp, ¤t->list, list) {
|
||||||
|
if (!next->done) {
|
||||||
|
uthread_resume(next);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
/* Found a 'done' thread, free its resources */
|
||||||
|
list_del(&next->list);
|
||||||
|
uthread_free(next);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int uthread_grp_new_id(void)
|
||||||
|
{
|
||||||
|
static unsigned int id;
|
||||||
|
|
||||||
|
return ++id;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool uthread_grp_done(unsigned int grp_id)
|
||||||
|
{
|
||||||
|
struct uthread *next;
|
||||||
|
|
||||||
|
list_for_each_entry(next, &main_thread.list, list) {
|
||||||
|
if (next->grp_id == grp_id && !next->done)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int uthread_mutex_lock(struct uthread_mutex *mutex)
|
||||||
|
{
|
||||||
|
while (mutex->state == UTHREAD_MUTEX_LOCKED)
|
||||||
|
uthread_schedule();
|
||||||
|
|
||||||
|
mutex->state = UTHREAD_MUTEX_LOCKED;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int uthread_mutex_trylock(struct uthread_mutex *mutex)
|
||||||
|
{
|
||||||
|
if (mutex->state == UTHREAD_MUTEX_UNLOCKED) {
|
||||||
|
mutex->state = UTHREAD_MUTEX_LOCKED;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -EBUSY;
|
||||||
|
}
|
||||||
|
|
||||||
|
int uthread_mutex_unlock(struct uthread_mutex *mutex)
|
||||||
|
{
|
||||||
|
mutex->state = UTHREAD_MUTEX_UNLOCKED;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
@@ -392,8 +392,7 @@ static int bootdev_test_hunter(struct unit_test_state *uts)
|
|||||||
ut_assert_console_end();
|
ut_assert_console_end();
|
||||||
|
|
||||||
ut_assertok(bootdev_hunt("usb1", false));
|
ut_assertok(bootdev_hunt("usb1", false));
|
||||||
ut_assert_nextline(
|
ut_assert_skip_to_line("Bus usb@1: 5 USB Device(s) found");
|
||||||
"Bus usb@1: scanning bus usb@1 for devices... 5 USB Device(s) found");
|
|
||||||
ut_assert_console_end();
|
ut_assert_console_end();
|
||||||
|
|
||||||
/* USB is 7th in the list, so bit 8 */
|
/* USB is 7th in the list, so bit 8 */
|
||||||
@@ -448,8 +447,7 @@ static int bootdev_test_cmd_hunt(struct unit_test_state *uts)
|
|||||||
ut_assert_nextline("scanning bus for devices...");
|
ut_assert_nextline("scanning bus for devices...");
|
||||||
ut_assert_skip_to_line("Hunting with: spi_flash");
|
ut_assert_skip_to_line("Hunting with: spi_flash");
|
||||||
ut_assert_nextline("Hunting with: usb");
|
ut_assert_nextline("Hunting with: usb");
|
||||||
ut_assert_nextline(
|
ut_assert_skip_to_line("Bus usb@1: 5 USB Device(s) found");
|
||||||
"Bus usb@1: scanning bus usb@1 for devices... 5 USB Device(s) found");
|
|
||||||
ut_assert_nextline("Hunting with: virtio");
|
ut_assert_nextline("Hunting with: virtio");
|
||||||
ut_assert_console_end();
|
ut_assert_console_end();
|
||||||
|
|
||||||
@@ -551,8 +549,7 @@ static int bootdev_test_hunt_prio(struct unit_test_state *uts)
|
|||||||
ut_assertok(bootdev_hunt_prio(BOOTDEVP_5_SCAN_SLOW, true));
|
ut_assertok(bootdev_hunt_prio(BOOTDEVP_5_SCAN_SLOW, true));
|
||||||
ut_assert_nextline("Hunting with: ide");
|
ut_assert_nextline("Hunting with: ide");
|
||||||
ut_assert_nextline("Hunting with: usb");
|
ut_assert_nextline("Hunting with: usb");
|
||||||
ut_assert_nextline(
|
ut_assert_skip_to_line("Bus usb@1: 5 USB Device(s) found");
|
||||||
"Bus usb@1: scanning bus usb@1 for devices... 5 USB Device(s) found");
|
|
||||||
ut_assert_console_end();
|
ut_assert_console_end();
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
@@ -604,7 +601,7 @@ static int bootdev_test_hunt_label(struct unit_test_state *uts)
|
|||||||
ut_assertnonnull(dev);
|
ut_assertnonnull(dev);
|
||||||
ut_asserteq_str("usb_mass_storage.lun0.bootdev", dev->name);
|
ut_asserteq_str("usb_mass_storage.lun0.bootdev", dev->name);
|
||||||
ut_asserteq(BOOTFLOW_METHF_SINGLE_UCLASS, mflags);
|
ut_asserteq(BOOTFLOW_METHF_SINGLE_UCLASS, mflags);
|
||||||
ut_assert_nextlinen("Bus usb@1: scanning bus usb@1");
|
ut_assert_nextline("Bus usb@1: 5 USB Device(s) found");
|
||||||
ut_assert_console_end();
|
ut_assert_console_end();
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@@ -1290,7 +1290,7 @@ static int bootflow_efi(struct unit_test_state *uts)
|
|||||||
|
|
||||||
ut_assertok(run_command("bootflow scan", 0));
|
ut_assertok(run_command("bootflow scan", 0));
|
||||||
ut_assert_skip_to_line(
|
ut_assert_skip_to_line(
|
||||||
"Bus usb@1: scanning bus usb@1 for devices... 5 USB Device(s) found");
|
"Bus usb@1: 5 USB Device(s) found");
|
||||||
|
|
||||||
ut_assertok(run_command("bootflow list", 0));
|
ut_assertok(run_command("bootflow list", 0));
|
||||||
|
|
||||||
|
@@ -39,3 +39,4 @@ obj-$(CONFIG_CMD_WGET) += wget.o
|
|||||||
endif
|
endif
|
||||||
obj-$(CONFIG_ARM_FFA_TRANSPORT) += armffa.o
|
obj-$(CONFIG_ARM_FFA_TRANSPORT) += armffa.o
|
||||||
endif
|
endif
|
||||||
|
obj-$(CONFIG_CMD_SPAWN) += spawn.o
|
||||||
|
32
test/cmd/spawn.c
Normal file
32
test/cmd/spawn.c
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
/*
|
||||||
|
* Tests for spawn and wait commands
|
||||||
|
*
|
||||||
|
* Copyright 2025, Linaro Ltd.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <command.h>
|
||||||
|
#include <test/cmd.h>
|
||||||
|
#include <test/test.h>
|
||||||
|
#include <test/ut.h>
|
||||||
|
|
||||||
|
static int test_cmd_spawn(struct unit_test_state *uts)
|
||||||
|
{
|
||||||
|
ut_assertok(run_command("wait; spawn sleep 2; setenv j ${job_id}; "
|
||||||
|
"spawn setenv spawned true; "
|
||||||
|
"setenv jj ${job_id}; wait; "
|
||||||
|
"echo ${j} ${jj} ${spawned}", 0));
|
||||||
|
console_record_readline(uts->actual_str, sizeof(uts->actual_str));
|
||||||
|
ut_asserteq_ptr(uts->actual_str,
|
||||||
|
strstr(uts->actual_str, "1 2 true"));
|
||||||
|
|
||||||
|
ut_assertok(run_command("spawn true; wait; setenv t $?; spawn false; "
|
||||||
|
"wait; setenv f $?; wait; echo $t $f $?", 0));
|
||||||
|
console_record_readline(uts->actual_str, sizeof(uts->actual_str));
|
||||||
|
ut_asserteq_ptr(uts->actual_str,
|
||||||
|
strstr(uts->actual_str, "0 1 0"));
|
||||||
|
ut_assert_console_end();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
CMD_TEST(test_cmd_spawn, UTF_CONSOLE);
|
@@ -15,6 +15,7 @@ obj-$(CONFIG_SANDBOX) += kconfig.o
|
|||||||
obj-y += lmb.o
|
obj-y += lmb.o
|
||||||
obj-$(CONFIG_HAVE_SETJMP) += longjmp.o
|
obj-$(CONFIG_HAVE_SETJMP) += longjmp.o
|
||||||
obj-$(CONFIG_SANDBOX) += membuf.o
|
obj-$(CONFIG_SANDBOX) += membuf.o
|
||||||
|
obj-$(CONFIG_HAVE_INITJMP) += initjmp.o
|
||||||
obj-$(CONFIG_CONSOLE_RECORD) += test_print.o
|
obj-$(CONFIG_CONSOLE_RECORD) += test_print.o
|
||||||
obj-$(CONFIG_SSCANF) += sscanf.o
|
obj-$(CONFIG_SSCANF) += sscanf.o
|
||||||
obj-$(CONFIG_$(PHASE_)STRTO) += str.o
|
obj-$(CONFIG_$(PHASE_)STRTO) += str.o
|
||||||
@@ -31,6 +32,7 @@ obj-$(CONFIG_CRC8) += test_crc8.o
|
|||||||
obj-$(CONFIG_UT_LIB_CRYPT) += test_crypt.o
|
obj-$(CONFIG_UT_LIB_CRYPT) += test_crypt.o
|
||||||
obj-$(CONFIG_UT_TIME) += time.o
|
obj-$(CONFIG_UT_TIME) += time.o
|
||||||
obj-$(CONFIG_$(PHASE_)UT_UNICODE) += unicode.o
|
obj-$(CONFIG_$(PHASE_)UT_UNICODE) += unicode.o
|
||||||
|
obj-$(CONFIG_UTHREAD) += uthread.o
|
||||||
obj-$(CONFIG_LIB_UUID) += uuid.o
|
obj-$(CONFIG_LIB_UUID) += uuid.o
|
||||||
else
|
else
|
||||||
obj-$(CONFIG_SANDBOX) += kconfig_spl.o
|
obj-$(CONFIG_SANDBOX) += kconfig_spl.o
|
||||||
|
73
test/lib/initjmp.c
Normal file
73
test/lib/initjmp.c
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
/*
|
||||||
|
* Copyright 2025 Linaro Limited
|
||||||
|
*
|
||||||
|
* Unit test for initjmp()
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <compiler.h>
|
||||||
|
#include <setjmp.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <test/lib.h>
|
||||||
|
#include <test/ut.h>
|
||||||
|
#include <vsprintf.h>
|
||||||
|
|
||||||
|
static bool ep_entered;
|
||||||
|
static jmp_buf return_buf;
|
||||||
|
|
||||||
|
static void __noreturn entrypoint(void)
|
||||||
|
{
|
||||||
|
ep_entered = true;
|
||||||
|
|
||||||
|
/* Jump back to the main routine */
|
||||||
|
longjmp(return_buf, 1);
|
||||||
|
|
||||||
|
/* Not reached */
|
||||||
|
panic("longjmp failed\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static int lib_initjmp(struct unit_test_state *uts)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
void *stack;
|
||||||
|
jmp_buf buf;
|
||||||
|
/* Arbitrary but smaller values (< page size?) fail on SANDBOX */
|
||||||
|
size_t stack_sz = 8192;
|
||||||
|
|
||||||
|
(void)entrypoint;
|
||||||
|
|
||||||
|
ep_entered = false;
|
||||||
|
|
||||||
|
stack = malloc(stack_sz);
|
||||||
|
ut_assertnonnull(stack);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Prepare return_buf so that entrypoint may jump back just after the
|
||||||
|
* if()
|
||||||
|
*/
|
||||||
|
if (!setjmp(return_buf)) {
|
||||||
|
/* return_buf initialized, entrypoint not yet called */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Prepare another jump buffer to jump into entrypoint with the
|
||||||
|
* given stack
|
||||||
|
*/
|
||||||
|
ret = initjmp(buf, entrypoint, stack, stack_sz);
|
||||||
|
ut_assertok(ret);
|
||||||
|
|
||||||
|
/* Jump into entrypoint */
|
||||||
|
longjmp(buf, 1);
|
||||||
|
/*
|
||||||
|
* Not reached since entrypoint is expected to branch after
|
||||||
|
* the if()
|
||||||
|
*/
|
||||||
|
ut_assert(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
ut_assert(ep_entered);
|
||||||
|
|
||||||
|
free(stack);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
LIB_TEST(lib_initjmp, 0);
|
146
test/lib/uthread.c
Normal file
146
test/lib/uthread.c
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0+
|
||||||
|
/*
|
||||||
|
* Copyright 2025 Linaro Limited
|
||||||
|
*
|
||||||
|
* Unit test for uthread
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <test/lib.h>
|
||||||
|
#include <test/ut.h>
|
||||||
|
#include <uthread.h>
|
||||||
|
|
||||||
|
static int count;
|
||||||
|
|
||||||
|
/* A thread entry point */
|
||||||
|
static void worker(void *arg)
|
||||||
|
{
|
||||||
|
int loops = (int)(unsigned long)arg;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < loops; i++) {
|
||||||
|
count++;
|
||||||
|
uthread_schedule();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* uthread() - testing the uthread API
|
||||||
|
*
|
||||||
|
* This function creates two threads with the same entry point. The first one
|
||||||
|
* receives 5 as an argument, the second one receives 10. The number indicates
|
||||||
|
* the number of time the worker thread should loop on uthread_schedule()
|
||||||
|
* before returning. The workers increment a global counter each time they loop.
|
||||||
|
* As a result the main thread knows how many times it should call
|
||||||
|
* uthread_schedule() to let the two threads proceed, and it also knows which
|
||||||
|
* value the counter should have at any moment.
|
||||||
|
*/
|
||||||
|
static int uthread(struct unit_test_state *uts)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
int id1, id2;
|
||||||
|
|
||||||
|
count = 0;
|
||||||
|
id1 = uthread_grp_new_id();
|
||||||
|
ut_assert(id1 != 0);
|
||||||
|
id2 = uthread_grp_new_id();
|
||||||
|
ut_assert(id2 != 0);
|
||||||
|
ut_assert(id1 != id2);
|
||||||
|
ut_assertok(uthread_create(NULL, worker, (void *)5, 0, id1));
|
||||||
|
ut_assertok(uthread_create(NULL, worker, (void *)10, 0, 0));
|
||||||
|
/*
|
||||||
|
* The first call is expected to schedule the first worker, which will
|
||||||
|
* schedule the second one, which will schedule back to the main thread
|
||||||
|
* (here). Therefore count should be 2.
|
||||||
|
*/
|
||||||
|
ut_assert(uthread_schedule());
|
||||||
|
ut_asserteq(2, count);
|
||||||
|
ut_assert(!uthread_grp_done(id1));
|
||||||
|
/* Four more calls should bring the count to 10 */
|
||||||
|
for (i = 0; i < 4; i++) {
|
||||||
|
ut_assert(!uthread_grp_done(id1));
|
||||||
|
ut_assert(uthread_schedule());
|
||||||
|
}
|
||||||
|
ut_asserteq(10, count);
|
||||||
|
/* This one allows the first worker to exit */
|
||||||
|
ut_assert(uthread_schedule());
|
||||||
|
/* At this point there should be no runnable thread in group 'id1' */
|
||||||
|
ut_assert(uthread_grp_done(id1));
|
||||||
|
/* Five more calls for the second worker to finish incrementing */
|
||||||
|
for (i = 0; i < 5; i++)
|
||||||
|
ut_assert(uthread_schedule());
|
||||||
|
ut_asserteq(15, count);
|
||||||
|
/* Plus one call to let the second worker return from its entry point */
|
||||||
|
ut_assert(uthread_schedule());
|
||||||
|
/* Now both tasks should be done, schedule should return false */
|
||||||
|
ut_assert(!uthread_schedule());
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
LIB_TEST(uthread, 0);
|
||||||
|
|
||||||
|
struct mw_args {
|
||||||
|
struct unit_test_state *uts;
|
||||||
|
struct uthread_mutex *m;
|
||||||
|
int flag;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int mutex_worker_ret;
|
||||||
|
|
||||||
|
static int _mutex_worker(struct mw_args *args)
|
||||||
|
{
|
||||||
|
struct unit_test_state *uts = args->uts;
|
||||||
|
|
||||||
|
ut_asserteq(-EBUSY, uthread_mutex_trylock(args->m));
|
||||||
|
ut_assertok(uthread_mutex_lock(args->m));
|
||||||
|
args->flag = 1;
|
||||||
|
ut_assertok(uthread_mutex_unlock(args->m));
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mutex_worker(void *arg)
|
||||||
|
{
|
||||||
|
mutex_worker_ret = _mutex_worker((struct mw_args *)arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* thread_mutex() - testing uthread mutex operations
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
static int uthread_mutex(struct unit_test_state *uts)
|
||||||
|
{
|
||||||
|
struct uthread_mutex m = UTHREAD_MUTEX_INITIALIZER;
|
||||||
|
struct mw_args args = { .uts = uts, .m = &m, .flag = 0 };
|
||||||
|
int id;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
id = uthread_grp_new_id();
|
||||||
|
ut_assert(id != 0);
|
||||||
|
/* Take the mutex */
|
||||||
|
ut_assertok(uthread_mutex_lock(&m));
|
||||||
|
/* Start a thread */
|
||||||
|
ut_assertok(uthread_create(NULL, mutex_worker, (void *)&args, 0,
|
||||||
|
id));
|
||||||
|
/* Let the thread run for a bit */
|
||||||
|
for (i = 0; i < 100; i++)
|
||||||
|
ut_assert(uthread_schedule());
|
||||||
|
/* Thread should not have set the flag due to the mutex */
|
||||||
|
ut_asserteq(0, args.flag);
|
||||||
|
/* Release the mutex */
|
||||||
|
ut_assertok(uthread_mutex_unlock(&m));
|
||||||
|
/* Schedule the thread until it is done */
|
||||||
|
while (uthread_schedule())
|
||||||
|
;
|
||||||
|
/* Now the flag should be set */
|
||||||
|
ut_asserteq(1, args.flag);
|
||||||
|
/* And the mutex should be available */
|
||||||
|
ut_assertok(uthread_mutex_trylock(&m));
|
||||||
|
ut_assertok(uthread_mutex_unlock(&m));
|
||||||
|
|
||||||
|
/* Of course no error are expected from the thread routine */
|
||||||
|
ut_assertok(mutex_worker_ret);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
LIB_TEST(uthread_mutex, 0);
|
Reference in New Issue
Block a user