/*
 * Copyright (c) 2021 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@
 */

#ifndef _MACHINE_STATIC_IF_H
#define _MACHINE_STATIC_IF_H

#include <sys/cdefs.h>
#include <stdbool.h>
#include <stdint.h>
#include <mach/kern_return.h>
#include <libkern/section_keywords.h>

typedef const struct static_if_entry *static_if_entry_t;

typedef struct static_if_key {
	int16_t                 sik_enable_count;
	bool                    sik_init_value;
	bool                    sik_modified;
	uint32_t                sik_entries_count;
	static_if_entry_t       sik_entries_head;
	struct static_if_key   *sik_modified_next;
} *static_if_key_t;

#if defined (__x86_64__)
#include "x86_64/static_if.h"
#elif defined (__arm__)
#include "arm/static_if.h"
#elif defined (__arm64__)
#include "arm64/static_if.h"
#else
#error architecture not supported
#endif

__BEGIN_DECLS
#pragma GCC visibility push(hidden)

/*
 * Declare/define a static jump key
 *
 * STATIC_IF_KEY_{DECLARE,DEFINE}_TRUE
 *     the static jump key initial enablement count is positive,
 *     and the key enabled.
 *
 * STATIC_IF_KEY_{DECLARE,DEFINE}_FALSE
 *     the static jump key initial enablement count is negative,
 *     and the key disabled.
 *
 * Enablement counts can be manipulated with @c static_if_key_{enable,disable}.
 */
#define STATIC_IF_KEY_DECLARE_TRUE(name) \
	extern struct static_if_key_true name##_jump_key

#define STATIC_IF_KEY_DEFINE_TRUE(name) \
	__security_const_late \
	__used struct static_if_key_true name##_jump_key = { \
	        .key.sik_init_value = true, \
	        .key.sik_enable_count = 0, \
	}

#define STATIC_IF_KEY_DECLARE_FALSE(name) \
	extern struct static_if_key_false name##_jump_key

#define STATIC_IF_KEY_DEFINE_FALSE(name) \
	__security_const_late \
	__used struct static_if_key_false name##_jump_key = { \
	        .key.sik_init_value = false, \
	        .key.sik_enable_count = -1, \
	}

/*!
 * @macro probable_static_if() / improbable_static_if()
 *
 * @brief
 * Returns whether the static if class is true or not,
 * encoding it in code rather than loading globals.
 *
 * @discussion
 * Static "ifs" are meant to provide extremely low overhead
 * enablement or disablement of features based on TEXT patching.
 *
 * It is meant for debugging features, or checkers that happen
 * on really hot paths where loading a global will affect
 * performance in meaningful ways (lock checks, preemption
 * disablement tracking, etc...).
 *
 * @c probable_static_if() versus @c improbable_static_if()
 * affects which direction will take a branch:
 *
 *   key value             |   TRUE   |  FALSE   |    outlining
 *   ----------------------+----------+----------+----------------
 *   probable_static_if    |  nop     |  branch  |  "FALSE" case
 *   improbable_static_if  |  branch  |  nop     |  "TRUE"  case
 *
 * @c static_if() will not outline any code, and will codegen a "nop"
 * for the initial value of the key.
 *
 * There usually is a STARTUP hook that pairs with those
 * static if domains that will toggle enablement based
 * on boot-args or various configurations.
 */
#define static_if(n)  ({                                        \
	__label__ __l;                                          \
	int __result = !__static_if_key_init_value(n);          \
	STATIC_IF_NOP(n, __l);                                  \
	__result = __static_if_key_init_value(n);               \
__l:                                                            \
	__result;                                               \
})

#define probable_static_if(n)  __probable(({                    \
	__label__ __l;                                          \
	int __result = 0;                                       \
	if (__static_if_key_init_value(n)) {                    \
	        STATIC_IF_NOP(n, __l);                          \
	} else {                                                \
	        STATIC_IF_BRANCH(n, __l);                       \
	}                                                       \
	__result = 1;                                           \
__l:                                                            \
	__result;                                               \
}))

#define improbable_static_if(n)  __improbable(({                \
	__label__ __l;                                          \
	int __result = 1;                                       \
	if (__static_if_key_init_value(n)) {                    \
	        STATIC_IF_BRANCH(n, __l);                       \
	} else {                                                \
	        STATIC_IF_NOP(n, __l);                          \
	}                                                       \
	__result = 0;                                           \
__l:                                                            \
	__result;                                               \
}))


/*!
 * @function static_if_key_enable()
 *
 * @brief
 * Increases the key enablement count.
 *
 * @discussion
 * The key becomes disabled when its enablement count becomes positive.
 * This function can only be called from the context of a STATIC_IF_INIT()
 * callout.
 */
#define static_if_key_enable(n) \
	__static_if_key_delta(&n##_jump_key.key, 1)

/*!
 * @function static_if_key_disable()
 *
 * @brief
 * Decreases the key enablement count.
 *
 * @discussion
 * The key becomes disabled when its enablement count becomes negative.
 * This function can only be called from the context of a STATIC_IF_INIT()
 * callout.
 */
#define static_if_key_disable(n) \
	__static_if_key_delta(&n##_jump_key.key, -1)

/*!
 * @brief
 * Marker for functions used to setup static_if() blocks during boot.
 */
#define __static_if_init_func MARK_AS_FIXUP_TEXT

/*!
 * @macro STATIC_IF_INIT
 *
 * @brief
 * Register a function to setup a static if direction.
 *
 * @discussion
 * This code runs extremly early during boot and it can only rely
 * on extremly basic notions such as boot-args or system registers.
 *
 * Code running during this call must be marked with __static_if_init_func.
 */
#define STATIC_IF_INIT(func) \
	__PLACE_IN_SECTION(STATIC_IF_SEGMENT "," STATIC_IFINIT_SECTION) \
	static static_if_initializer __static_if__ ## func = func

/*!
 * @function static_if_boot_arg_uint64()
 *
 * @brief
 * Parses a boot-arg within a STATIC_IF_INIT() function.
 *
 * @discussion
 * PE_parse_boot_argn() can't be used that early on SPTM devices,
 * and TUNABLES() aren't parsed yet.
 */
extern uint64_t static_if_boot_arg_uint64(
	const char             *args,
	const char             *name,
	uint64_t                defval);


#pragma mark implementation details

#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 STATIC_IF_SEGMENT       "__DATA_CONST"
#elif defined(__x86_64__)
/* Intel doesn't have a __BOOTDATA but doesn't protect __KLD */
#define STATIC_IF_SEGMENT       "__KLDDATA"
#else
/* arm protects __KLD early, so use __BOOTDATA for data */
#define STATIC_IF_SEGMENT       "__BOOTDATA"
#endif
#define STATIC_IF_SECTION       "__static_if"
#define STATIC_IFINIT_SECTION   "__static_ifinit"
#define STATIC_IF_SEGSECT       STATIC_IF_SEGMENT "," STATIC_IF_SECTION

typedef void (*static_if_initializer)(const char *boot_args);

struct static_if_key_true {
	struct static_if_key    key;
#if __cplusplus
	static const bool       init_value = true;
#endif
};

struct static_if_key_false {
	struct static_if_key    key;
#if __cplusplus
	static const bool       init_value = false;
#endif
};

#if __cplusplus
#define __static_if_key_init_value(n)  decltype(n##_jump_key)::init_value
#else
#define __static_if_key_init_value(n)  _Generic(n##_jump_key, \
	struct static_if_key_true: 1, \
	struct static_if_key_false: 0)
#endif

extern void __static_if_key_delta(
	static_if_key_t         key,
	int                     delta);

extern static_if_key_t static_if_modified_keys;

#define STATIC_IF_ABI_V1       1
#define STATIC_IF_ABI_CURRENT  STATIC_IF_ABI_V1

extern uint32_t static_if_abi;

#if MACH_KERNEL_PRIVATE

__attribute__((always_inline))
static inline unsigned long
__static_if_entry_patch_point(static_if_entry_t sie)
{
#if STATIC_IF_RELATIVE
	return (unsigned long)&sie->sie_base + (unsigned long)(long)sie->sie_base;
#else
	return (unsigned long)sie->sie_base;
#endif
}

extern void ml_static_if_entry_patch(
	static_if_entry_t     ent,
	int                     branch);

extern void ml_static_if_flush_icache(void);

extern void static_if_init(const char *args);

#endif /* MACH_KERNEL_PRIVATE */

#pragma GCC visibility pop
__END_DECLS

#endif /* _MACHINE_STATIC_IF_H */
