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:
@@ -392,8 +392,7 @@ static int bootdev_test_hunter(struct unit_test_state *uts)
|
||||
ut_assert_console_end();
|
||||
|
||||
ut_assertok(bootdev_hunt("usb1", false));
|
||||
ut_assert_nextline(
|
||||
"Bus usb@1: scanning bus usb@1 for devices... 5 USB Device(s) found");
|
||||
ut_assert_skip_to_line("Bus usb@1: 5 USB Device(s) found");
|
||||
ut_assert_console_end();
|
||||
|
||||
/* 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_skip_to_line("Hunting with: spi_flash");
|
||||
ut_assert_nextline("Hunting with: usb");
|
||||
ut_assert_nextline(
|
||||
"Bus usb@1: scanning bus usb@1 for devices... 5 USB Device(s) found");
|
||||
ut_assert_skip_to_line("Bus usb@1: 5 USB Device(s) found");
|
||||
ut_assert_nextline("Hunting with: virtio");
|
||||
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_assert_nextline("Hunting with: ide");
|
||||
ut_assert_nextline("Hunting with: usb");
|
||||
ut_assert_nextline(
|
||||
"Bus usb@1: scanning bus usb@1 for devices... 5 USB Device(s) found");
|
||||
ut_assert_skip_to_line("Bus usb@1: 5 USB Device(s) found");
|
||||
ut_assert_console_end();
|
||||
|
||||
return 0;
|
||||
@@ -604,7 +601,7 @@ static int bootdev_test_hunt_label(struct unit_test_state *uts)
|
||||
ut_assertnonnull(dev);
|
||||
ut_asserteq_str("usb_mass_storage.lun0.bootdev", dev->name);
|
||||
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();
|
||||
|
||||
return 0;
|
||||
|
@@ -1290,7 +1290,7 @@ static int bootflow_efi(struct unit_test_state *uts)
|
||||
|
||||
ut_assertok(run_command("bootflow scan", 0));
|
||||
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));
|
||||
|
||||
|
@@ -39,3 +39,4 @@ obj-$(CONFIG_CMD_WGET) += wget.o
|
||||
endif
|
||||
obj-$(CONFIG_ARM_FFA_TRANSPORT) += armffa.o
|
||||
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-$(CONFIG_HAVE_SETJMP) += longjmp.o
|
||||
obj-$(CONFIG_SANDBOX) += membuf.o
|
||||
obj-$(CONFIG_HAVE_INITJMP) += initjmp.o
|
||||
obj-$(CONFIG_CONSOLE_RECORD) += test_print.o
|
||||
obj-$(CONFIG_SSCANF) += sscanf.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_TIME) += time.o
|
||||
obj-$(CONFIG_$(PHASE_)UT_UNICODE) += unicode.o
|
||||
obj-$(CONFIG_UTHREAD) += uthread.o
|
||||
obj-$(CONFIG_LIB_UUID) += uuid.o
|
||||
else
|
||||
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