/*
 * Copyright (c) 2000-2020 Apple Inc. All rights reserved.
 *
 * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
 *
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. The rights granted to you under the License
 * may not be used to create, or enable the creation or redistribution of,
 * unlawful or unlicensed copies of an Apple operating system, or to
 * circumvent, violate, or enable the circumvention or violation of, any
 * terms of an Apple operating system software license agreement.
 *
 * Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this file.
 *
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 *
 * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
 */
/*
 * @OSF_COPYRIGHT@
 */

#ifdef  XNU_KERNEL_PRIVATE

#ifndef _KERN_STARTUP_H_
#define _KERN_STARTUP_H_

#include <stdbool.h>
#include <stdint.h>

#include <libkern/section_keywords.h>

__BEGIN_DECLS

#pragma GCC visibility push(hidden)

/*!
 * @enum startup_subsystem_id_t
 *
 * @abstract
 * Represents a stage of kernel intialization, ubnd allows for subsystems
 * to register initializers for a specific stage.
 *
 * @discussion
 * Documentation of each subsystem initialization sequence exists in
 * @file doc/startup.md.
 */
__enum_decl(startup_subsystem_id_t, uint32_t, {
	STARTUP_SUB_NONE = 0,         /**< reserved for the startup subsystem  */

	STARTUP_SUB_TUNABLES,         /**< support for the tunables subsystem  */
	STARTUP_SUB_LOCKS_EARLY,      /**< early locking, before zalloc        */
	STARTUP_SUB_KPRINTF,          /**< kprintf initialization              */

	STARTUP_SUB_PMAP_STEAL,       /**< to perform various pmap carveouts   */
	STARTUP_SUB_VM_KERNEL,        /**< once the kernel VM is ready         */
	STARTUP_SUB_KMEM,             /**< once kmem is ready                  */
	STARTUP_SUB_KMEM_ALLOC,       /**< once kmem_alloc is ready            */
	STARTUP_SUB_ZALLOC,           /**< initialize zalloc and kalloc        */
	STARTUP_SUB_PERCPU,           /**< initialize the percpu subsystem     */
	STARTUP_SUB_LOCKS,            /**< various subsystem locks             */

	STARTUP_SUB_CODESIGNING,      /**< codesigning subsystem               */
	STARTUP_SUB_OSLOG,            /**< oslog and kernel loggging           */
	STARTUP_SUB_MACH_IPC,         /**< Mach IPC                            */
	STARTUP_SUB_SYSCTL,           /**< registers sysctls                   */
	STARTUP_SUB_EARLY_BOOT,       /**< interrupts/premption are turned on  */

	STARTUP_SUB_LOCKDOWN = ~0u,   /**< reserved for the startup subsystem  */
});

/*!
 * Stores the last subsystem to have been fully initialized;
 */
extern startup_subsystem_id_t startup_phase;

/*!
 * @enum startup_debug_t
 *
 * @abstract
 * Flags set in the @c startup_debug global to configure startup debugging.
 */
__options_decl(startup_debug_t, uint32_t, {
	STARTUP_DEBUG_NONE    = 0x00000000,
	STARTUP_DEBUG_VERBOSE = 0x00000001,
});

#if DEBUG || DEVELOPMENT
extern startup_debug_t startup_debug;
#else
#define startup_debug  STARTUP_DEBUG_NONE
#endif

/*!
 * @enum startup_rank
 *
 * @abstract
 * Specifies in which rank a given initializer runs within a given section
 * to register initializers for a specific rank within the subsystem.
 *
 * @description
 * A startup function, declared with @c STARTUP or @c STARTUP_ARG, can specify
 * an rank within the subsystem they initialize.
 *
 * @c STARTUP_RANK_NTH(n) will let callbacks be run at stage @c n (0-based).
 *
 * @c STARTUP_RANK_FIRST, @c STARTUP_RANK_SECOND, @c STARTUP_RANK_THIRD and
 * @c STARTUP_RANK_FOURTH are given as conveniency names for these.
 *
 * @c STARTUP_RANK_MIDDLE is a reserved value that will let startup functions
 * run after all the @c STARTUP_RANK_NTH(n) ones have.
 *
 * @c STARTUP_RANK_NTH_LATE_NTH(n) will let callbacks be run then in @c n rank
 * after the @c STARTUP_RANK_MIDDLE ones (0-based).
 *
 * @c STARTUP_RANK_LAST callbacks will run absolutely last after everything
 * else did for this subsystem.
 */
__enum_decl(startup_rank_t, uint32_t, {
#define STARTUP_RANK_NTH(n) \
	(enum startup_rank)(n)
	STARTUP_RANK_FIRST          = 0,
	STARTUP_RANK_SECOND         = 1,
	STARTUP_RANK_THIRD          = 2,
	STARTUP_RANK_FOURTH         = 3,

	STARTUP_RANK_MIDDLE         = 0x7fffffff,

#define STARTUP_RANK_LATE_NTH(n) \
	(enum startup_rank)(STARTUP_RANK_MIDDLE + 1 + (n))

	STARTUP_RANK_LAST           = 0xffffffff,
});

#if KASAN
/*
 * The use of weird sections that get unmapped confuse the hell out of kasan,
 * so for KASAN leave things in regular __TEXT/__DATA segments
 */
#define STARTUP_CODE_SEGSECT "__TEXT,__text"
#define STARTUP_DATA_SEGSECT "__DATA,__init"
#define STARTUP_HOOK_SEGMENT "__DATA"
#define STARTUP_HOOK_SECTION "__init_entry_set"
#elif defined(__x86_64__)
/* Intel doesn't have a __BOOTDATA but doesn't protect __KLD */
#define STARTUP_CODE_SEGSECT "__TEXT,__text"
#define STARTUP_DATA_SEGSECT "__KLDDATA,__init"
#define STARTUP_HOOK_SEGMENT "__KLDDATA"
#define STARTUP_HOOK_SECTION "__init_entry_set"
#else
/* arm protects __KLD early, so use __BOOTDATA for data */
#define STARTUP_CODE_SEGSECT "__TEXT,__text"
#define STARTUP_DATA_SEGSECT "__BOOTDATA,__init"
#define STARTUP_HOOK_SEGMENT "__BOOTDATA"
#define STARTUP_HOOK_SECTION "__init_entry_set"
#endif

/*!
 * @macro __startup_func
 *
 * @abstract
 * Attribute to place on functions used only during the kernel startup phase.
 *
 * @description
 * Code marked with this attribute will be unmapped after kernel lockdown.
 */
#define __startup_func \
	__PLACE_IN_SECTION(STARTUP_CODE_SEGSECT) \
	__attribute__((cold, visibility("hidden")))

/*!
 * @macro __startup_data
 *
 * @abstract
 * Attribute to place on globals used during the kernel startup phase.
 *
 * @description
 * Data marked with this attribute will be unmapped after kernel lockdown.
 */
#define __startup_data \
	__PLACE_IN_SECTION(STARTUP_DATA_SEGSECT)

/*!
 * @macro STARTUP
 *
 * @abstract
 * Declares a kernel startup callback.
 */
#define STARTUP(subsystem, rank, func) \
	__STARTUP(func, __LINE__, subsystem, rank, func)

/*!
 * @macro STARTUP_ARG
 *
 * @abstract
 * Declares a kernel startup callback that takes an argument.
 */
#define STARTUP_ARG(subsystem, rank, func, arg) \
	__STARTUP_ARG(func, __LINE__, subsystem, rank, func, arg)

/*!
 * @macro TUNABLE
 *
 * @abstract
 * Declares a read-only kernel tunable that is read from a boot-arg with
 * a default value, without further processing.
 *
 * @param type_t
 * Should be an integer type or bool.
 *
 * @param var
 * The name of the C variable to use for storage.
 *
 * @param key
 * The name of the boot-arg to parse for initialization
 *
 * @param default_value
 * The default value for the tunable if the boot-arg is absent.
 */
#define TUNABLE(type_t, var, key, default_value) \
	SECURITY_READ_ONLY_LATE(type_t) var = default_value; \
	__TUNABLE(type_t, var, key)

/*!
 * @macro TUNABLE_WRITEABLE
 *
 * @abstract
 * Declares a writeable kernel tunable that is read from a boot-arg with
 * a default value, without further processing.
 *
 * @param type_t
 * Should be an integer type or bool.
 *
 * @param var
 * The name of the C variable to use for storage.
 *
 * @param key
 * The name of the boot-arg to parse for initialization
 *
 * @param default_value
 * The default value for the tunable if the boot-arg is absent.
 */
#define TUNABLE_WRITEABLE(type_t, var, key, default_value) \
	type_t var = default_value; \
	__TUNABLE(type_t, var, key)

#pragma mark - internals

#define __TUNABLE(type_t, var, key) \
	static __startup_data char __startup_TUNABLES_name_ ## var[] = key; \
	static __startup_data struct startup_tunable_spec \
	__startup_TUNABLES_spec_ ## var = { \
	    .name = __startup_TUNABLES_name_ ## var, \
	    .var_addr = (void *)&var, \
	    .var_len = sizeof(type_t), \
	    .var_is_bool = __builtin_types_compatible_p(bool, type_t), \
	}; \
	__STARTUP_ARG(var, __LINE__, TUNABLES, STARTUP_RANK_FIRST, \
	    kernel_startup_tunable_init, &__startup_TUNABLES_spec_ ## var)

#ifdef __cplusplus
#define __STARTUP_FUNC_CAST(func, a) \
	    (void(*)(const void *))func
#else
#define __STARTUP_FUNC_CAST(func, a) \
	    (typeof(func(a))(*)(const void *))func
#endif


#define __STARTUP1(name, line, subsystem, rank, func, a, b) \
	__PLACE_IN_SECTION(STARTUP_HOOK_SEGMENT "," STARTUP_HOOK_SECTION) \
	static const struct startup_entry \
	__startup_ ## subsystem ## _entry_ ## name ## _ ## line = { \
	    STARTUP_SUB_ ## subsystem, \
	    rank, __STARTUP_FUNC_CAST(func, a), b, \
	}

#define __STARTUP(name, line, subsystem, rank, func) \
	__STARTUP1(name, line, subsystem, rank, func, , NULL)

#define __STARTUP_ARG(name, line, subsystem, rank, func, arg) \
	__STARTUP1(name, line, subsystem, rank, func, arg, arg)

struct startup_entry {
	startup_subsystem_id_t subsystem;
	startup_rank_t         rank;
	void                 (*func)(const void *);
	const void            *arg;
};

struct startup_tunable_spec {
	const char *name;
	void       *var_addr;
	int         var_len;
	bool        var_is_bool;
};

/*
 * Kernel and machine startup declarations
 */

/* Initialize kernel */
extern void kernel_startup_bootstrap(void);
extern void kernel_startup_initialize_upto(startup_subsystem_id_t upto);
extern void kernel_startup_tunable_init(const struct startup_tunable_spec *);
extern void kernel_bootstrap(void);

/* Initialize machine dependent stuff */
extern void machine_init(void);

extern void slave_main(void *machine_param);

/*
 * The following must be implemented in machine dependent code.
 */

/* Slave cpu initialization */
extern void slave_machine_init(void *machine_param);

/* Device subystem initialization */
extern void device_service_create(void);

#ifdef  MACH_BSD

/* BSD subsystem initialization */
extern void bsd_init(void);

#endif  /* MACH_BSD */

#pragma GCC visibility pop

__END_DECLS

#endif  /* _KERN_STARTUP_H_ */

#endif  /* XNU_KERNEL_PRIVATE */
