/* * 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 STATIC_IF_TEST
#include <kern/assert.h>
#include <kern/locks.h>
#include <kern/startup.h>
#include <machine/static_if.h>
#include <machine/machine_routines.h>
extern struct static_if_entry __static_if_entries[]
__SECTION_START_SYM(STATIC_IF_SEGMENT, STATIC_IF_SECTION);
extern struct static_if_entry __static_if_entries_end[]
__SECTION_END_SYM(STATIC_IF_SEGMENT, STATIC_IF_SECTION);
extern static_if_initializer __static_if_initializer_entries[]
__SECTION_START_SYM(STATIC_IF_SEGMENT, STATIC_IFINIT_SECTION);
extern static_if_initializer __static_if_initializer_entries_end[]
__SECTION_END_SYM(STATIC_IF_SEGMENT, STATIC_IFINIT_SECTION);
/* libhwtrace knows about this contract */
SECURITY_READ_ONLY_LATE(static_if_key_t) static_if_modified_keys;
SECURITY_READ_ONLY_LATE(uint32_t) static_if_abi = STATIC_IF_ABI_CURRENT;
#endif /* STATIC_IF_TEST */
#pragma mark boot-arg parsing
/*
* On SPTM targets we can't use PE_parse_boot_argn() because it isn't part
* of __BOOT_TEXT, so we need to roll our own.
*
* We can't use TUNABLES() yet either because they won't have been initialized.
*/
__attribute__((always_inline))
static inline bool
isargsep(char c)
{
return c == ' ' || c == '\0' || c == '\t';
}
__attribute__((always_inline))
static const char *
skip_seps(const char *s)
{
while (*s && isargsep(*s)) {
s++;
}
return s;
}
__attribute__((always_inline))
static const char *
skip_to_sep(const char *s)
{
while (!isargsep(*s)) {
s++;
}
return s;
}
__attribute__((always_inline))
static inline bool
skip_prefix(const char *args, const char *key, const char **argsout)
{
while (*key) {
if (*args != *key) {
return false;
}
args++;
key++;
}
*argsout = args;
return true;
}
__attribute__((always_inline))
static inline const char *
get_val(const char *s, uint64_t *val)
{
uint64_t radix = 10;
uint64_t v;
int sign = 1;
if (isargsep(*s)) {
/* "... key ..." is the same as "... key=1 ..." */
*val = 1;
return s;
}
if (*s != '=') {
/* if not followed by a = then this is garbage */
return s;
}
s++;
if (*s == '-') {
sign = -1;
s++;
}
if (isargsep(*s)) {
/* "... key=- ..." is malfomed */
return s;
}
v = (*s++ - '0');
if (v == 0) {
switch (*s) {
case 'x':
radix = 16;
s++;
break;
case 'b':
radix = 2;
s++;
break;
case '0' ... '7':
radix = 8;
break;
default:
if (!isargsep(*s)) {
return s;
}
break;
}
} else if (v > radix) {
return s;
}
for (;;) {
if (*s >= '0' && *s <= '9' - (10 - radix)) {
v = v * radix + *s - '0';
} else if (radix == 16 && *s >= 'a' && *s <= 'f') {
v = v * radix + 10 + *s - 'a';
} else if (radix == 16 && *s >= 'A' && *s <= 'F') {
v = v * radix + 10 + *s - 'A';
} else {
if (isargsep(*s)) {
*val = v * sign;
}
return s;
}
s++;
}
}
MARK_AS_FIXUP_TEXT uint64_t
static_if_boot_arg_uint64(const char *args, const char *key, uint64_t defval)
{
uint64_t ret = defval;
args = skip_seps(args);
while (*args) {
if (*args == '-' && skip_prefix(args + 1, key, &args)) {
if (isargsep(*args)) {
ret = TRUE;
}
} else if (skip_prefix(args, key, &args)) {
args = get_val(args, &ret);
}
args = skip_to_sep(args);
args = skip_seps(args);
}
return ret;
}
#pragma mark patching
#ifndef STATIC_IF_TEST
/*
* static_if() is implemented using keys, which are data structures
* of type `struct static_if_key`.
*
* These come in two concrete variants:
* - struct static_if_key_true, for which the key starts enabled/true,
* - struct static_if_key_false, for which the key starts disabled/false.
*
* Usage of static_if() and its variants use the following pattern:
* (a) a result variable is initialized to 0 (resp 1),
* (b) an asm goto() statement might jump to a label or fall through depending on
* the state of the key (implemented with STATIC_IF_{NOP,JUMP}),
* (c) in the fall through code, the variable is set to 1 (resp 0).
*
* As a result these macros implement a boolean return that depend on whether
* their assembly is currently a nop (in which case it will return the value
* from (c)) or a branch (in which case it will return the value from (a)).
*
* STATIC_IF_NOP() and STATIC_IF_ENTRY() are machine dependent macros that emit
* either a nop or a branch instruction, and generate a `struct static_if_entry`
* which denote where this patchable instruction lives, and for which key.
*
* static_if_init() will run early and chain all these entries onto their key,
* in order to enable static_if_key_{enable,disable} to be able to quickly patch
* these instructions between nops and jumps.
*/
__attribute__((always_inline))
static inline void *
__static_if_entry_next(static_if_entry_t sie)
{
return (void *)(sie->sie_link & ~3ul);
}
__attribute__((always_inline))
static inline bool
__static_if_is_jump(static_if_entry_t sie)
{
return sie->sie_link & 1;
}
MARK_AS_FIXUP_TEXT void
static_if_init(const char *args)
{
struct static_if_entry *sie = __static_if_entries_end;
unsigned long sie_flags;
static_if_key_t sie_key;
while (--sie >= __static_if_entries) {
sie_flags = sie->sie_link & 3ul;
sie_key = __static_if_entry_next(sie);
sie->sie_link = (vm_offset_t)sie_key->sik_entries_head | sie_flags;
sie_key->sik_entries_head = sie;
sie_key->sik_entries_count++;
}
for (static_if_initializer *f = __static_if_initializer_entries;
f < __static_if_initializer_entries_end; f++) {
(*f)(args);
}
ml_static_if_flush_icache();
}
MARK_AS_FIXUP_TEXT void
__static_if_key_delta(static_if_key_t key, int delta)
{
/*
* On SPTM configuration, static_if_init() is called by
* arm_static_if_init() during the XNU fixup phase,
* before the XNU kernel text is retyped to SPTM_XNU_CODE
* and can't be modified anymore.
*
* For other platforms, this is called from kernel_startup_bootstrap()
*/
if (startup_phase >= STARTUP_SUB_TUNABLES) {
panic("static_if_key_{enable,disable} called too late");
}
bool was_enabled = (key->sik_enable_count >= 0);
/*
* Remember modified keys.
*/
if (!key->sik_modified) {
key->sik_modified_next = static_if_modified_keys;
static_if_modified_keys = key;
key->sik_modified = true;
}
key->sik_enable_count += delta;
if (was_enabled != (key->sik_enable_count >= 0)) {
static_if_entry_t sie = key->sik_entries_head;
bool init_enabled = key->sik_init_value;
while (sie) {
ml_static_if_entry_patch(sie,
(was_enabled == init_enabled) ^
__static_if_is_jump(sie));
sie = __static_if_entry_next(sie);
}
}
}
#endif /* STATIC_IF_TEST */