blob: 4c651a6fcecd298bde6900ca9eeb12d647b43d96 [file] [log] [blame]
/*
* Copyright 2019 The Hafnium Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "hf/arch/vm/power_mgmt.h"
#include "hf/arch/mm.h"
#include "hf/spinlock.h"
#include "test/hftest.h"
struct cpu_start_state {
void (*entry)(uintptr_t arg);
uintreg_t arg;
struct spinlock lock;
};
static noreturn void cpu_entry(uintptr_t arg)
{
struct cpu_start_state *s = (struct cpu_start_state *)arg;
struct cpu_start_state s_copy;
/*
* Initialize memory and enable caching. Must be the first thing we do.
*/
hftest_mm_vcpu_init();
/* Make a copy of the cpu_start_state struct. */
s_copy = *s;
/* Inform cpu_start() that the state struct memory can now be freed. */
sl_unlock(&s->lock);
/* Call the given entry function with the given argument. */
s_copy.entry(s_copy.arg);
/* If the entry function returns, turn off the CPU. */
arch_cpu_stop();
}
bool hftest_cpu_start(uintptr_t id, void *stack, size_t stack_size,
void (*entry)(uintptr_t arg), uintptr_t arg)
{
struct cpu_start_state s;
struct arch_cpu_start_state s_arch;
/*
* Config for arch_cpu_start() which will start a new CPU and
* immediately jump to cpu_entry(). This function must guarantee that
* the state struct is not be freed until cpu_entry() is called.
*/
s_arch.initial_sp = (uintptr_t)stack + stack_size;
s_arch.entry = cpu_entry;
s_arch.arg = (uintptr_t)&s;
/*
* Flush the `cpu_start_state` struct because the new CPU will be
* started without caching enabled and will need the data early on.
* Write back is all that is really needed so flushing will definitely
* get the job done.
*/
arch_mm_flush_dcache(&s_arch, sizeof(s_arch));
if ((s_arch.initial_sp % STACK_ALIGN) != 0) {
HFTEST_FAIL(true,
"Stack pointer of new vCPU not properly aligned.");
}
/*
* Config for cpu_entry(). Its job is to initialize memory and call the
* provided entry point with the provided argument.
*/
s.entry = entry;
s.arg = arg;
sl_init(&s.lock);
/*
* Lock the cpu_start_state struct which will be unlocked once
* cpu_entry() does not need its content anymore. This simultaneously
* protects the arch_cpu_start_state struct which must not be freed
* before cpu_entry() is called.
*/
sl_lock(&s.lock);
/* Try to start the given CPU. */
if (!arch_cpu_start(id, &s_arch)) {
return false;
}
/*
* Wait until cpu_entry() unlocks the cpu_start_state lock before
* freeing stack memory.
*/
sl_lock(&s.lock);
return true;
}