Boot Linux faster!

Check our new training course

Boot Linux faster!

Check our new training course
and Creative Commons CC-BY-SA
lecture and lab materials

Bootlin logo

Elixir Cross Referencer

/*
 * Copyright (c) 2010-2015 Wind River Systems, Inc.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * @file
 * @brief Thread support primitives
 *
 * This module provides core thread related primitives for the IA-32
 * processor architecture.
 */

#include <kernel.h>
#include <ksched.h>
#include <arch/x86/mmustructs.h>

/* forward declaration */

/* Initial thread stack frame, such that everything is laid out as expected
 * for when z_swap() switches to it for the first time.
 */
struct _x86_initial_frame {
	u32_t swap_retval;
	u32_t ebp;
	u32_t ebx;
	u32_t esi;
	u32_t edi;
	void *thread_entry;
	u32_t eflags;
	k_thread_entry_t entry;
	void *p1;
	void *p2;
	void *p3;
};

#ifdef CONFIG_X86_USERSPACE
/* Nothing to do here if KPTI is enabled. We are in supervisor mode, so the
 * active PDPT is the kernel's page tables. If the incoming thread is in user
 * mode we are going to switch CR3 to the thread- specific tables when we go
 * through z_x86_trampoline_to_user.
 *
 * We don't need to update _main_tss either, privilege elevation always lands
 * on the trampoline stack and the irq/sycall code has to manually transition
 * off of it to the thread's kernel stack after switching page tables.
 */
#ifndef CONFIG_X86_KPTI
/* Change to new set of page tables. ONLY intended for use from
 * z_x88_swap_update_page_tables(). This changes CR3, no memory access
 * afterwards is legal unless it is known for sure that the relevant
 * mappings are identical wrt supervisor mode until we iret out.
 */
static inline void page_tables_set(struct x86_page_tables *ptables)
{
	__asm__ volatile("movl %0, %%cr3\n\t" : : "r" (ptables) : "memory");
}

/* Update the to the incoming thread's page table, and update the location
 * of the privilege elevation stack.
 *
 * May be called ONLY during context switch and when supervisor
 * threads drop synchronously to user mode. Hot code path!
 */
void z_x86_swap_update_page_tables(struct k_thread *incoming)
{
	struct x86_page_tables *ptables;

	/* If we're a user thread, we want the active page tables to
	 * be the per-thread instance.
	 *
	 * However, if we're a supervisor thread, use the master
	 * kernel page tables instead.
	 */
	if ((incoming->base.user_options & K_USER) != 0) {
		ptables = z_x86_thread_page_tables_get(incoming);

		/* In case of privilege elevation, use the incoming
		 * thread's kernel stack. This area starts immediately
		 * before the PDPT.
		 */
		_main_tss.esp0 = (uintptr_t)ptables;
	} else {
		ptables = &z_x86_kernel_ptables;
	}

	/* Check first that we actually need to do this, since setting
	 * CR3 involves an expensive full TLB flush.
	 */
	if (ptables != z_x86_page_tables_get()) {
		page_tables_set(ptables);
	}
}
#endif /* CONFIG_X86_KPTI */

static FUNC_NORETURN void drop_to_user(k_thread_entry_t user_entry,
				       void *p1, void *p2, void *p3)
{
	u32_t stack_end;

	/* Transition will reset stack pointer to initial, discarding
	 * any old context since this is a one-way operation
	 */
	stack_end = STACK_ROUND_DOWN(_current->stack_info.start +
				     _current->stack_info.size);

	z_x86_userspace_enter(user_entry, p1, p2, p3, stack_end,
			      _current->stack_info.start);
	CODE_UNREACHABLE;
}

FUNC_NORETURN void arch_user_mode_enter(k_thread_entry_t user_entry,
					void *p1, void *p2, void *p3)
{
	struct z_x86_thread_stack_header *header =
		(struct z_x86_thread_stack_header *)_current->stack_obj;

	/* Set up the kernel stack used during privilege elevation */
	z_x86_mmu_set_flags(&z_x86_kernel_ptables, &header->privilege_stack,
			    MMU_PAGE_SIZE, MMU_ENTRY_WRITE, Z_X86_MMU_RW,
			    true);

	/* Initialize per-thread page tables, since that wasn't done when
	 * the thread was initialized (K_USER was not set at creation time)
	 */
	z_x86_thread_pt_init(_current);

	/* Apply memory domain configuration, if assigned */
	if (_current->mem_domain_info.mem_domain != NULL) {
		z_x86_apply_mem_domain(z_x86_thread_page_tables_get(_current),
				       _current->mem_domain_info.mem_domain);
	}

#ifndef CONFIG_X86_KPTI
	/* We're synchronously dropping into user mode from a thread that
	 * used to be in supervisor mode. K_USER flag has now been set, but
	 * Need to swap from the kernel's page tables to the per-thread page
	 * tables.
	 *
	 * Safe to update page tables from here, all tables are identity-
	 * mapped and memory areas used before the ring 3 transition all
	 * have the same attributes wrt supervisor mode access.
	 */
	z_x86_swap_update_page_tables(_current);
#endif

	drop_to_user(user_entry, p1, p2, p3);
}

/* Implemented in userspace.S */
extern void z_x86_syscall_entry_stub(void);

/* Syscalls invoked by 'int 0x80'. Installed in the IDT at DPL=3 so that
 * userspace can invoke it.
 */
NANO_CPU_INT_REGISTER(z_x86_syscall_entry_stub, -1, -1, 0x80, 3);

#endif /* CONFIG_X86_USERSPACE */

#if defined(CONFIG_FLOAT) && defined(CONFIG_FP_SHARING)

extern int z_float_disable(struct k_thread *thread);

int arch_float_disable(struct k_thread *thread)
{
#if defined(CONFIG_LAZY_FP_SHARING)
	return z_float_disable(thread);
#else
	return -ENOSYS;
#endif /* CONFIG_LAZY_FP_SHARING */
}
#endif /* CONFIG_FLOAT && CONFIG_FP_SHARING */

void arch_new_thread(struct k_thread *thread, k_thread_stack_t *stack,
		     size_t stack_size, k_thread_entry_t entry,
		     void *parameter1, void *parameter2, void *parameter3,
		     int priority, unsigned int options)
{
	char *stack_buf;
	char *stack_high;
	struct _x86_initial_frame *initial_frame;
#if defined(CONFIG_X86_USERSPACE) || defined(CONFIG_X86_STACK_PROTECTION)
	struct z_x86_thread_stack_header *header =
		(struct z_x86_thread_stack_header *)stack;
#endif

	Z_ASSERT_VALID_PRIO(priority, entry);
	stack_buf = Z_THREAD_STACK_BUFFER(stack);
	z_new_thread_init(thread, stack_buf, stack_size, priority, options);

#ifdef CONFIG_X86_USERSPACE
	/* Set MMU properties for the privilege mode elevation stack.
	 * If we're not starting in user mode, this functions as a guard
	 * area.
	 */
	z_x86_mmu_set_flags(&z_x86_kernel_ptables, &header->privilege_stack,
		MMU_PAGE_SIZE,
		((options & K_USER) == 0U) ? MMU_ENTRY_READ : MMU_ENTRY_WRITE,
		Z_X86_MMU_RW, true);
#endif /* CONFIG_X86_USERSPACE */

#if CONFIG_X86_STACK_PROTECTION
	/* Set guard area to read-only to catch stack overflows */
	z_x86_mmu_set_flags(&z_x86_kernel_ptables, &header->guard_page,
			    MMU_PAGE_SIZE, MMU_ENTRY_READ, Z_X86_MMU_RW,
			    true);
#endif

	stack_high = (char *)STACK_ROUND_DOWN(stack_buf + stack_size);

	/* Create an initial context on the stack expected by z_swap() */
	initial_frame = (struct _x86_initial_frame *)
		(stack_high - sizeof(struct _x86_initial_frame));
	/* z_thread_entry() arguments */
	initial_frame->entry = entry;
	initial_frame->p1 = parameter1;
	initial_frame->p2 = parameter2;
	initial_frame->p3 = parameter3;
	initial_frame->eflags = EFLAGS_INITIAL;
#ifdef CONFIG_X86_USERSPACE
	if ((options & K_USER) != 0U) {
		z_x86_thread_pt_init(thread);
#ifdef _THREAD_WRAPPER_REQUIRED
		initial_frame->edi = (u32_t)drop_to_user;
		initial_frame->thread_entry = z_x86_thread_entry_wrapper;
#else
		initial_frame->thread_entry = drop_to_user;
#endif /* _THREAD_WRAPPER_REQUIRED */
	} else
#endif /* CONFIG_X86_USERSPACE */
	{
#ifdef _THREAD_WRAPPER_REQUIRED
		initial_frame->edi = (u32_t)z_thread_entry;
		initial_frame->thread_entry = z_x86_thread_entry_wrapper;
#else
		initial_frame->thread_entry = z_thread_entry;
#endif
	}
	/* Remaining _x86_initial_frame members can be garbage, z_thread_entry()
	 * doesn't care about their state when execution begins
	 */
	thread->callee_saved.esp = (unsigned long)initial_frame;

#if defined(CONFIG_LAZY_FP_SHARING)
	thread->arch.excNestCount = 0;
#endif /* CONFIG_LAZY_FP_SHARING */

	thread->arch.flags = 0;
}