Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

kernel: introduce k_smp_cpu_start() and k_smp_cpu_resume() #64755

Merged
merged 12 commits into from
Jan 17, 2024
Merged
2 changes: 2 additions & 0 deletions arch/x86/core/intel64/cpu.c
Original file line number Diff line number Diff line change
Expand Up @@ -208,4 +208,6 @@ FUNC_NORETURN void z_x86_cpu_init(struct x86_cpuboot *cpuboot)
/* Enter kernel, never return */
cpuboot->ready++;
cpuboot->fn(cpuboot->arg);

CODE_UNREACHABLE; /* LCOV_EXCL_LINE */
}
11 changes: 11 additions & 0 deletions boards/x86/qemu_x86/qemu_x86_64.dts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@

#include "qemu_x86.dts"

/ {
cpus {
cpu@1 {
device_type = "cpu";
compatible = "intel,x86";
d-cache-line-size = <64>;
reg = <1>;
};
};
};

&pcie0 {
smbus0: smbus0 {
compatible = "intel,pch-smbus";
Expand Down
11 changes: 11 additions & 0 deletions boards/x86/qemu_x86/qemu_x86_64_nokpti.dts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,14 @@
*/

#include "qemu_x86.dts"

/ {
cpus {
cpu@1 {
device_type = "cpu";
compatible = "intel,x86";
d-cache-line-size = <64>;
reg = <1>;
};
};
};
1 change: 0 additions & 1 deletion include/zephyr/kernel/internal/smp.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,5 @@ void z_smp_thread_swap(void);

void z_init_cpu(int id);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about those internal APIs? I see they are still being used in SOF.

void z_sched_ipi(void);
void z_smp_start_cpu(int id);

#endif
35 changes: 35 additions & 0 deletions include/zephyr/kernel/smp.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright (c) 2023 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/

#ifndef ZEPHYR_INCLUDE_KERNEL_SMP_H_
#define ZEPHYR_INCLUDE_KERNEL_SMP_H_

typedef void (*smp_init_fn)(void *arg);

/**
* @brief Start a CPU.
*
* This routine is used to manually start the CPU specified
* by @a id. It may be called to restart a CPU that had been
* stopped or powered down, as well as some other scenario.
* After the CPU has finished initialization, the CPU will be
* ready to participate in thread scheduling and execution.
*
* @note This function must not be used on currently running
* CPU. The target CPU must be in off state, or in
* certain architectural state(s) where the CPU is
* permitted to go through the power up process.
* Detection of such state(s) must be provided by
* the platform layers.
*
* @param id ID of target CPU.
* @param fn Function to be called before letting scheduler
* run.
* @param arg Argument to @a fn.
*/
void k_smp_cpu_start(int id, smp_init_fn fn, void *arg);

#endif /* ZEPHYR_INCLUDE_KERNEL_SMP_H_ */
5 changes: 3 additions & 2 deletions kernel/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -1177,8 +1177,9 @@ config SMP_BOOT_DELAY
depends on SMP
help
By default Zephyr will boot all available CPUs during start up.
Select this option to skip this and allow architecture code boot
secondary CPUs at a later time.
Select this option to skip this and allow custom code
(architecture/SoC/board/application) to boot secondary CPUs at
a later time.

config MP_NUM_CPUS
int "Number of CPUs/cores [DEPRECATED]"
Expand Down
43 changes: 37 additions & 6 deletions kernel/smp.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include <zephyr/kernel.h>
#include <zephyr/kernel_structs.h>
#include <zephyr/kernel/smp.h>
#include <zephyr/spinlock.h>
#include <kswap.h>
#include <kernel_internal.h>
Expand All @@ -12,6 +13,23 @@ static atomic_t global_lock;
static atomic_t cpu_start_flag;
static atomic_t ready_flag;

/**
* Struct holding the function to be called before handing off
* to schedule and its argument.
*/
static struct cpu_start_cb {
/**
* Function to be called before handing off to scheduler.
* Can be NULL.
*/
smp_init_fn fn;

/** Argument to @ref cpu_start_fn.fn. */
void *arg;
} cpu_start_fn;

static struct k_spinlock cpu_start_lock;

unsigned int z_smp_global_lock(void)
{
unsigned int key = arch_irq_lock();
Expand Down Expand Up @@ -80,35 +98,48 @@ void z_smp_thread_swap(void)
static inline FUNC_NORETURN void smp_init_top(void *arg)
{
struct k_thread dummy_thread;
struct cpu_start_cb *csc = arg;

(void)atomic_set(&ready_flag, 1);

wait_for_start_signal(arg);
wait_for_start_signal(&cpu_start_flag);
z_dummy_thread_init(&dummy_thread);
#ifdef CONFIG_SYS_CLOCK_EXISTS
smp_timer_init();
#endif

/* Do additional initialization steps if needed. */
if ((csc != NULL) && (csc->fn != NULL)) {
csc->fn(csc->arg);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dcpleung isn't this racy in general? On all CPUs csc points to cpu_start_fn, right? And the CPU gets here after signalling the initiator CPU, the one that started this one. So the initiator CPU can go ahead and start yet another CPU potentially with different parameters. So far that shouldn't be a problem for SOF, because there .fn is the same for all and .arg is always NULL, but in general this seems wrong?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, there is potential for conflicts here. So the caller must take precautions to avoid that situation. Or we can implement a per-CPU data struct (but this is going to take up some space).

}

z_swap_unlocked();

CODE_UNREACHABLE; /* LCOV_EXCL_LINE */
}

static void start_cpu(int id, atomic_t *start_flag)
static void start_cpu(int id, struct cpu_start_cb *csc)
{
z_init_cpu(id);
(void)atomic_clear(&ready_flag);
arch_start_cpu(id, z_interrupt_stacks[id], CONFIG_ISR_STACK_SIZE,
smp_init_top, start_flag);
smp_init_top, csc);
while (!atomic_get(&ready_flag)) {
local_delay();
}
}

void z_smp_start_cpu(int id)
void k_smp_cpu_start(int id, smp_init_fn fn, void *arg)
{
k_spinlock_key_t key = k_spin_lock(&cpu_start_lock);

cpu_start_fn.fn = fn;
cpu_start_fn.arg = arg;

(void)atomic_set(&cpu_start_flag, 1); /* async, don't care */
start_cpu(id, &cpu_start_flag);
start_cpu(id, &cpu_start_fn);

k_spin_unlock(&cpu_start_lock, key);
}

void z_smp_init(void)
Expand All @@ -118,7 +149,7 @@ void z_smp_init(void)
unsigned int num_cpus = arch_num_cpus();

for (int i = 1; i < num_cpus; i++) {
start_cpu(i, &cpu_start_flag);
start_cpu(i, NULL);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Passing the NULL here requires you to add tests in a bunch of spaces in smp_init_top() to handle the NULL pointer. Since the cpu_start_fn struct is static and available, can't you just set it up (a NULL function pointer and reinit_timer==true) to do what you want?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is done so that if k_smp_cpu_start() is not used at all, the garbage collector can remove the function and the static struct from the final binary... to save a few bytes.

}
(void)atomic_set(&cpu_start_flag, 1);
}
Expand Down
3 changes: 2 additions & 1 deletion tests/boards/intel_adsp/smoke/src/cpus.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*/
#include <stdlib.h>
#include <zephyr/kernel.h>
#include <zephyr/kernel/smp.h>
#include <zephyr/ztest.h>
#include <zephyr/cache.h>

Expand Down Expand Up @@ -188,7 +189,7 @@ static void halt_and_restart(int cpu)
k_msleep(50);
}

z_smp_start_cpu(cpu);
k_smp_cpu_start(cpu, NULL, NULL);

/* Startup can be slow */
k_msleep(50);
Expand Down
5 changes: 2 additions & 3 deletions tests/boards/intel_adsp/smoke/src/smpboot.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*/

#include <zephyr/kernel.h>
#include <zephyr/kernel/smp.h>
#include <zephyr/ztest.h>
#include "tests.h"

Expand All @@ -25,8 +26,6 @@ volatile bool mp_flag;
struct k_thread cpu_thr;
K_THREAD_STACK_DEFINE(thr_stack, STACKSZ);

extern void z_smp_start_cpu(int id);

static void thread_fn(void *a, void *b, void *c)
{
int cpuid = (int) a;
Expand Down Expand Up @@ -62,7 +61,7 @@ ZTEST(intel_adsp_boot, test_1st_smp_boot_delay)
zassert_false(mp_flag, "cpu %d must not be running yet", i);

/* Start the second CPU */
z_smp_start_cpu(i);
k_smp_cpu_start(i, NULL, NULL);

/* Verify the thread ran */
k_busy_wait(CPU_START_DELAY);
Expand Down
9 changes: 4 additions & 5 deletions tests/kernel/smp_boot_delay/src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*/

#include <zephyr/kernel.h>
#include <zephyr/kernel/smp.h>
#include <zephyr/ztest.h>

/* Experimentally 10ms is enough time to get the second CPU to run on
Expand All @@ -11,11 +12,10 @@
#define CPU_START_DELAY 10000

/* IPIs happen much faster than CPU startup */
#define CPU_IPI_DELAY 100
#define CPU_IPI_DELAY 1000

BUILD_ASSERT(CONFIG_SMP);
BUILD_ASSERT(CONFIG_SMP_BOOT_DELAY);
BUILD_ASSERT(CONFIG_KERNEL_COHERENCE);
BUILD_ASSERT(CONFIG_MP_MAX_NUM_CPUS > 1);

#define STACKSZ 2048
Expand All @@ -26,8 +26,6 @@ volatile bool mp_flag;
struct k_thread cpu1_thr;
K_THREAD_STACK_DEFINE(thr_stack, STACKSZ);

extern void z_smp_start_cpu(int id);

static void thread_fn(void *a, void *b, void *c)
{
mp_flag = true;
Expand All @@ -48,13 +46,14 @@ ZTEST(smp_boot_delay, test_smp_boot_delay)
zassert_false(mp_flag, "CPU1 must not be running yet");

/* Start the second CPU */
z_smp_start_cpu(1);
k_smp_cpu_start(1, NULL, NULL);

/* Verify the thread ran */
k_busy_wait(CPU_START_DELAY);
zassert_true(mp_flag, "CPU1 did not start");

k_thread_abort(&cpu1_thr);
k_thread_join(&cpu1_thr, K_FOREVER);

/* Spawn the same thread to do the same thing, but this time
* expect that the thread is going to run synchronously on the
Expand Down
8 changes: 4 additions & 4 deletions tests/kernel/smp_boot_delay/testcase.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@ tests:
tags:
- kernel
- smp
platform_allow: intel_adsp_cavs25
platform_allow: intel_adsp_cavs25 qemu_x86_64
integration_platforms:
- intel_adsp_cavs25
- qemu_x86_64
kernel.multiprocessing.smp_boot_delay.minimallibc:
filter: CONFIG_MINIMAL_LIBC_SUPPORTED
tags:
- kernel
- smp
- libc
platform_allow: intel_adsp_cavs25
platform_allow: intel_adsp_cavs25 qemu_x86_64
integration_platforms:
- intel_adsp_cavs25
- qemu_x86_64
extra_configs:
- CONFIG_MINIMAL_LIBC=y