Skip to content
Permalink
0a798f6738
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
2048 lines (1690 sloc) 48.6 KB
/*
* Copyright (c) 2007-2017 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@
*/
#include <arm64/proc_reg.h>
#include <arm/machine_cpu.h>
#include <arm/cpu_internal.h>
#include <arm/cpuid.h>
#include <arm/io_map_entries.h>
#include <arm/cpu_data.h>
#include <arm/cpu_data_internal.h>
#include <arm/caches_internal.h>
#include <arm/misc_protos.h>
#include <arm/machdep_call.h>
#include <arm/rtclock.h>
#include <console/serial_protos.h>
#include <kern/machine.h>
#include <prng/random.h>
#include <kern/startup.h>
#include <kern/thread.h>
#include <mach/machine.h>
#include <machine/atomic.h>
#include <vm/pmap.h>
#include <vm/vm_page.h>
#include <sys/kdebug.h>
#include <kern/coalition.h>
#include <pexpert/device_tree.h>
#include <IOKit/IOPlatformExpert.h>
#include <libkern/section_keywords.h>
#if defined(KERNEL_INTEGRITY_KTRR)
#include <libkern/kernel_mach_header.h>
#endif
#if KPC
#include <kern/kpc.h>
#endif
static int max_cpus_initialized = 0;
#define MAX_CPUS_SET 0x1
#define MAX_CPUS_WAIT 0x2
uint32_t LockTimeOut;
uint32_t LockTimeOutUsec;
uint64_t MutexSpin;
boolean_t is_clock_configured = FALSE;
extern int mach_assert;
extern volatile uint32_t debug_enabled;
SECURITY_READ_ONLY_LATE(unsigned int) debug_boot_arg;
void machine_conf(void);
thread_t Idle_context(void);
static uint32_t cpu_phys_ids[MAX_CPUS] = {[0 ... MAX_CPUS - 1] = (uint32_t)-1};
static unsigned int avail_cpus = 0;
static int boot_cpu = -1;
static int max_cpu_number = 0;
cluster_type_t boot_cluster = CLUSTER_TYPE_SMP;
lockdown_handler_t lockdown_handler;
void *lockdown_this;
lck_mtx_t lockdown_handler_lck;
lck_grp_t *lockdown_handler_grp;
int lockdown_done;
void ml_lockdown_init(void);
void ml_lockdown_run_handler(void);
uint32_t get_arm_cpu_version(void);
void ml_cpu_signal(unsigned int cpu_id __unused)
{
panic("Platform does not support ACC Fast IPI");
}
void ml_cpu_signal_deferred_adjust_timer(uint64_t nanosecs) {
(void)nanosecs;
panic("Platform does not support ACC Fast IPI");
}
uint64_t ml_cpu_signal_deferred_get_timer() {
return 0;
}
void ml_cpu_signal_deferred(unsigned int cpu_id __unused)
{
panic("Platform does not support ACC Fast IPI deferral");
}
void ml_cpu_signal_retract(unsigned int cpu_id __unused)
{
panic("Platform does not support ACC Fast IPI retraction");
}
void machine_idle(void)
{
__asm__ volatile ("msr DAIFSet, %[mask]" ::[mask] "i" (DAIFSC_IRQF | DAIFSC_FIQF));
Idle_context();
__asm__ volatile ("msr DAIFClr, %[mask]" ::[mask] "i" (DAIFSC_IRQF | DAIFSC_FIQF));
}
void init_vfp(void)
{
return;
}
boolean_t get_vfp_enabled(void)
{
return TRUE;
}
void OSSynchronizeIO(void)
{
__builtin_arm_dsb(DSB_SY);
}
uint64_t get_aux_control(void)
{
uint64_t value;
MRS(value, "ACTLR_EL1");
return value;
}
uint64_t get_mmu_control(void)
{
uint64_t value;
MRS(value, "SCTLR_EL1");
return value;
}
uint64_t get_tcr(void)
{
uint64_t value;
MRS(value, "TCR_EL1");
return value;
}
boolean_t ml_get_interrupts_enabled(void)
{
uint64_t value;
MRS(value, "DAIF");
if (value & DAIF_IRQF)
return FALSE;
return TRUE;
}
pmap_paddr_t get_mmu_ttb(void)
{
pmap_paddr_t value;
MRS(value, "TTBR0_EL1");
return value;
}
MARK_AS_PMAP_TEXT
void set_mmu_ttb(pmap_paddr_t value)
{
__builtin_arm_dsb(DSB_ISH);
MSR("TTBR0_EL1", value);
__builtin_arm_isb(ISB_SY);
}
static uint32_t get_midr_el1(void)
{
uint64_t value;
MRS(value, "MIDR_EL1");
/* This is a 32-bit register. */
return (uint32_t) value;
}
uint32_t get_arm_cpu_version(void)
{
uint32_t value = get_midr_el1();
/* Compose the register values into 8 bits; variant[7:4], revision[3:0]. */
return ((value & MIDR_EL1_REV_MASK) >> MIDR_EL1_REV_SHIFT) | ((value & MIDR_EL1_VAR_MASK) >> (MIDR_EL1_VAR_SHIFT - 4));
}
/*
* user_cont_hwclock_allowed()
*
* Indicates whether we allow EL0 to read the physical timebase (CNTPCT_EL0)
* as a continuous time source (e.g. from mach_continuous_time)
*/
boolean_t user_cont_hwclock_allowed(void)
{
return FALSE;
}
/*
* user_timebase_allowed()
*
* Indicates whether we allow EL0 to read the physical timebase (CNTPCT_EL0).
*/
boolean_t user_timebase_allowed(void)
{
return TRUE;
}
boolean_t arm64_wfe_allowed(void)
{
return TRUE;
}
#if defined(KERNEL_INTEGRITY_KTRR)
uint64_t rorgn_begin __attribute__((section("__DATA, __const"))) = 0;
uint64_t rorgn_end __attribute__((section("__DATA, __const"))) = 0;
vm_offset_t amcc_base;
static void assert_unlocked(void);
static void assert_amcc_cache_disabled(void);
static void lock_amcc(void);
static void lock_mmu(uint64_t begin, uint64_t end);
void rorgn_stash_range(void)
{
#if DEVELOPMENT || DEBUG
boolean_t rorgn_disable = FALSE;
PE_parse_boot_argn("-unsafe_kernel_text", &rorgn_disable, sizeof(rorgn_disable));
if (rorgn_disable) {
/* take early out if boot arg present, don't query any machine registers to avoid
* dependency on amcc DT entry
*/
return;
}
#endif
/* Get the AMC values, and stash them into rorgn_begin, rorgn_end. */
#if defined(KERNEL_INTEGRITY_KTRR)
uint64_t soc_base = 0;
DTEntry entryP = NULL;
uintptr_t *reg_prop = NULL;
uint32_t prop_size = 0;
int rc;
soc_base = pe_arm_get_soc_base_phys();
rc = DTFindEntry("name", "mcc", &entryP);
assert(rc == kSuccess);
rc = DTGetProperty(entryP, "reg", (void **)&reg_prop, &prop_size);
assert(rc == kSuccess);
amcc_base = ml_io_map(soc_base + *reg_prop, *(reg_prop + 1));
#else
#error "KERNEL_INTEGRITY config error"
#endif
#if defined(KERNEL_INTEGRITY_KTRR)
assert(rRORGNENDADDR > rRORGNBASEADDR);
rorgn_begin = (rRORGNBASEADDR << ARM_PGSHIFT) + gPhysBase;
rorgn_end = (rRORGNENDADDR << ARM_PGSHIFT) + gPhysBase;
#else
#error KERNEL_INTEGRITY config error
#endif /* defined (KERNEL_INTEGRITY_KTRR) */
}
static void assert_unlocked() {
uint64_t ktrr_lock = 0;
uint32_t rorgn_lock = 0;
assert(amcc_base);
#if defined(KERNEL_INTEGRITY_KTRR)
rorgn_lock = rRORGNLOCK;
ktrr_lock = __builtin_arm_rsr64(ARM64_REG_KTRR_LOCK_EL1);
#else
#error KERNEL_INTEGRITY config error
#endif /* defined(KERNEL_INTEGRITY_KTRR) */
assert(!ktrr_lock);
assert(!rorgn_lock);
}
static void lock_amcc() {
#if defined(KERNEL_INTEGRITY_KTRR)
rRORGNLOCK = 1;
__builtin_arm_isb(ISB_SY);
#else
#error KERNEL_INTEGRITY config error
#endif
}
static void lock_mmu(uint64_t begin, uint64_t end) {
#if defined(KERNEL_INTEGRITY_KTRR)
__builtin_arm_wsr64(ARM64_REG_KTRR_LOWER_EL1, begin);
__builtin_arm_wsr64(ARM64_REG_KTRR_UPPER_EL1, end);
__builtin_arm_wsr64(ARM64_REG_KTRR_LOCK_EL1, 1ULL);
/* flush TLB */
__builtin_arm_isb(ISB_SY);
flush_mmu_tlb();
#else
#error KERNEL_INTEGRITY config error
#endif
}
static void assert_amcc_cache_disabled() {
#if defined(KERNEL_INTEGRITY_KTRR)
assert((rMCCGEN & 1) == 0); /* assert M$ disabled or LLC clean will be unreliable */
#else
#error KERNEL_INTEGRITY config error
#endif
}
/*
* void rorgn_lockdown(void)
*
* Lock the MMU and AMCC RORegion within lower and upper boundaries if not already locked
*
* [ ] - ensure this is being called ASAP on secondary CPUs: KTRR programming and lockdown handled in
* start.s:start_cpu() for subsequent wake/resume of all cores
*/
void rorgn_lockdown(void)
{
vm_offset_t ktrr_begin, ktrr_end;
unsigned long plt_segsz, last_segsz;
#if DEVELOPMENT || DEBUG
boolean_t ktrr_disable = FALSE;
PE_parse_boot_argn("-unsafe_kernel_text", &ktrr_disable, sizeof(ktrr_disable));
if (ktrr_disable) {
/*
* take early out if boot arg present, since we may not have amcc DT entry present
* we can't assert that iboot hasn't programmed the RO region lockdown registers
*/
goto out;
}
#endif /* DEVELOPMENT || DEBUG */
assert_unlocked();
/* [x] - Use final method of determining all kernel text range or expect crashes */
ktrr_begin = (uint64_t) getsegdatafromheader(&_mh_execute_header, "__PRELINK_TEXT", &plt_segsz);
assert(ktrr_begin && gVirtBase && gPhysBase);
ktrr_begin = kvtophys(ktrr_begin);
/* __LAST is not part of the MMU KTRR region (it is however part of the AMCC KTRR region) */
ktrr_end = (uint64_t) getsegdatafromheader(&_mh_execute_header, "__LAST", &last_segsz);
ktrr_end = (kvtophys(ktrr_end) - 1) & ~PAGE_MASK;
/* ensure that iboot and xnu agree on the ktrr range */
assert(rorgn_begin == ktrr_begin && rorgn_end == (ktrr_end + last_segsz));
/* assert that __LAST segment containing privileged insns is only a single page */
assert(last_segsz == PAGE_SIZE);
#if DEBUG
printf("KTRR Begin: %p End: %p, setting lockdown\n", (void *)ktrr_begin, (void *)ktrr_end);
#endif
/* [x] - ensure all in flight writes are flushed to AMCC before enabling RO Region Lock */
assert_amcc_cache_disabled();
CleanPoC_DcacheRegion_Force(phystokv(ktrr_begin),
(unsigned)((ktrr_end + last_segsz) - ktrr_begin + PAGE_MASK));
lock_amcc();
lock_mmu(ktrr_begin, ktrr_end);
#if DEVELOPMENT || DEBUG
out:
#endif
/* now we can run lockdown handler */
ml_lockdown_run_handler();
}
#endif /* defined(KERNEL_INTEGRITY_KTRR)*/
void
machine_startup(__unused boot_args * args)
{
int boot_arg;
#if MACH_KDP
if (PE_parse_boot_argn("debug", &debug_boot_arg, sizeof (debug_boot_arg)) &&
debug_enabled) {
if (debug_boot_arg & DB_HALT)
halt_in_debugger = 1;
if (debug_boot_arg & DB_NMI)
panicDebugging = TRUE;
} else {
debug_boot_arg = 0;
}
#endif
PE_parse_boot_argn("assert", &mach_assert, sizeof (mach_assert));
if (PE_parse_boot_argn("preempt", &boot_arg, sizeof (boot_arg))) {
default_preemption_rate = boot_arg;
}
if (PE_parse_boot_argn("bg_preempt", &boot_arg, sizeof (boot_arg))) {
default_bg_preemption_rate = boot_arg;
}
machine_conf();
/*
* Kick off the kernel bootstrap.
*/
kernel_bootstrap();
/* NOTREACHED */
}
void machine_lockdown_preflight(void)
{
#if CONFIG_KERNEL_INTEGRITY
#if defined(KERNEL_INTEGRITY_KTRR)
rorgn_stash_range();
#endif
#endif
}
void machine_lockdown(void)
{
#if CONFIG_KERNEL_INTEGRITY
#if KERNEL_INTEGRITY_WT
/* Watchtower
*
* Notify the monitor about the completion of early kernel bootstrap.
* From this point forward it will enforce the integrity of kernel text,
* rodata and page tables.
*/
#ifdef MONITOR
monitor_call(MONITOR_LOCKDOWN, 0, 0, 0);
#endif
#endif /* KERNEL_INTEGRITY_WT */
#if defined(KERNEL_INTEGRITY_KTRR)
/* KTRR
*
* Lock physical KTRR region. KTRR region is read-only. Memory outside
* the region is not executable at EL1.
*/
rorgn_lockdown();
#endif /* defined(KERNEL_INTEGRITY_KTRR)*/
#endif /* CONFIG_KERNEL_INTEGRITY */
}
char *
machine_boot_info(
__unused char *buf,
__unused vm_size_t size)
{
return (PE_boot_args());
}
void
machine_conf(void)
{
/*
* This is known to be inaccurate. mem_size should always be capped at 2 GB
*/
machine_info.memory_size = (uint32_t)mem_size;
}
void
machine_init(void)
{
debug_log_init();
clock_config();
is_clock_configured = TRUE;
if (debug_enabled)
pmap_map_globals();
}
void
slave_machine_init(__unused void *param)
{
cpu_machine_init(); /* Initialize the processor */
clock_init(); /* Init the clock */
}
/*
* Routine: machine_processor_shutdown
* Function:
*/
thread_t
machine_processor_shutdown(
__unused thread_t thread,
void (*doshutdown) (processor_t),
processor_t processor)
{
return (Shutdown_context(doshutdown, processor));
}
/*
* Routine: ml_init_max_cpus
* Function:
*/
void
ml_init_max_cpus(unsigned int max_cpus)
{
boolean_t current_state;
current_state = ml_set_interrupts_enabled(FALSE);
if (max_cpus_initialized != MAX_CPUS_SET) {
machine_info.max_cpus = max_cpus;
machine_info.physical_cpu_max = max_cpus;
machine_info.logical_cpu_max = max_cpus;
if (max_cpus_initialized == MAX_CPUS_WAIT)
thread_wakeup((event_t) & max_cpus_initialized);
max_cpus_initialized = MAX_CPUS_SET;
}
(void) ml_set_interrupts_enabled(current_state);
}
/*
* Routine: ml_get_max_cpus
* Function:
*/
unsigned int
ml_get_max_cpus(void)
{
boolean_t current_state;
current_state = ml_set_interrupts_enabled(FALSE);
if (max_cpus_initialized != MAX_CPUS_SET) {
max_cpus_initialized = MAX_CPUS_WAIT;
assert_wait((event_t) & max_cpus_initialized, THREAD_UNINT);
(void) thread_block(THREAD_CONTINUE_NULL);
}
(void) ml_set_interrupts_enabled(current_state);
return (machine_info.max_cpus);
}
/*
* Routine: ml_init_lock_timeout
* Function:
*/
void
ml_init_lock_timeout(void)
{
uint64_t abstime;
uint64_t mtxspin;
uint64_t default_timeout_ns = NSEC_PER_SEC>>2;
uint32_t slto;
if (PE_parse_boot_argn("slto_us", &slto, sizeof (slto)))
default_timeout_ns = slto * NSEC_PER_USEC;
nanoseconds_to_absolutetime(default_timeout_ns, &abstime);
LockTimeOutUsec = (uint32_t)(abstime / NSEC_PER_USEC);
LockTimeOut = (uint32_t)abstime;
if (PE_parse_boot_argn("mtxspin", &mtxspin, sizeof (mtxspin))) {
if (mtxspin > USEC_PER_SEC>>4)
mtxspin = USEC_PER_SEC>>4;
nanoseconds_to_absolutetime(mtxspin*NSEC_PER_USEC, &abstime);
} else {
nanoseconds_to_absolutetime(10*NSEC_PER_USEC, &abstime);
}
MutexSpin = abstime;
}
/*
* This is called from the machine-independent routine cpu_up()
* to perform machine-dependent info updates.
*/
void
ml_cpu_up(void)
{
hw_atomic_add(&machine_info.physical_cpu, 1);
hw_atomic_add(&machine_info.logical_cpu, 1);
}
/*
* This is called from the machine-independent routine cpu_down()
* to perform machine-dependent info updates.
*/
void
ml_cpu_down(void)
{
cpu_data_t *cpu_data_ptr;
hw_atomic_sub(&machine_info.physical_cpu, 1);
hw_atomic_sub(&machine_info.logical_cpu, 1);
/*
* If we want to deal with outstanding IPIs, we need to
* do relatively early in the processor_doshutdown path,
* as we pend decrementer interrupts using the IPI
* mechanism if we cannot immediately service them (if
* IRQ is masked). Do so now.
*
* We aren't on the interrupt stack here; would it make
* more sense to disable signaling and then enable
* interrupts? It might be a bit cleaner.
*/
cpu_data_ptr = getCpuDatap();
cpu_data_ptr->cpu_running = FALSE;
cpu_signal_handler_internal(TRUE);
}
/*
* Routine: ml_cpu_get_info
* Function:
*/
void
ml_cpu_get_info(ml_cpu_info_t * ml_cpu_info)
{
cache_info_t *cpuid_cache_info;
cpuid_cache_info = cache_info();
ml_cpu_info->vector_unit = 0;
ml_cpu_info->cache_line_size = cpuid_cache_info->c_linesz;
ml_cpu_info->l1_icache_size = cpuid_cache_info->c_isize;
ml_cpu_info->l1_dcache_size = cpuid_cache_info->c_dsize;
#if (__ARM_ARCH__ >= 7)
ml_cpu_info->l2_settings = 1;
ml_cpu_info->l2_cache_size = cpuid_cache_info->c_l2size;
#else
ml_cpu_info->l2_settings = 0;
ml_cpu_info->l2_cache_size = 0xFFFFFFFF;
#endif
ml_cpu_info->l3_settings = 0;
ml_cpu_info->l3_cache_size = 0xFFFFFFFF;
}
unsigned int
ml_get_machine_mem(void)
{
return (machine_info.memory_size);
}
__attribute__((noreturn))
void
halt_all_cpus(boolean_t reboot)
{
if (reboot) {
printf("MACH Reboot\n");
PEHaltRestart(kPERestartCPU);
} else {
printf("CPU halted\n");
PEHaltRestart(kPEHaltCPU);
}
while (1);
}
__attribute__((noreturn))
void
halt_cpu(void)
{
halt_all_cpus(FALSE);
}
/*
* Routine: machine_signal_idle
* Function:
*/
void
machine_signal_idle(
processor_t processor)
{
cpu_signal(processor_to_cpu_datap(processor), SIGPnop, (void *)NULL, (void *)NULL);
KERNEL_DEBUG_CONSTANT(MACHDBG_CODE(DBG_MACH_SCHED, MACH_REMOTE_AST), processor->cpu_id, 0 /* nop */, 0, 0, 0);
}
void
machine_signal_idle_deferred(
processor_t processor)
{
cpu_signal_deferred(processor_to_cpu_datap(processor));
KERNEL_DEBUG_CONSTANT(MACHDBG_CODE(DBG_MACH_SCHED, MACH_REMOTE_DEFERRED_AST), processor->cpu_id, 0 /* nop */, 0, 0, 0);
}
void
machine_signal_idle_cancel(
processor_t processor)
{
cpu_signal_cancel(processor_to_cpu_datap(processor));
KERNEL_DEBUG_CONSTANT(MACHDBG_CODE(DBG_MACH_SCHED, MACH_REMOTE_CANCEL_AST), processor->cpu_id, 0 /* nop */, 0, 0, 0);
}
/*
* Routine: ml_install_interrupt_handler
* Function: Initialize Interrupt Handler
*/
void
ml_install_interrupt_handler(
void *nub,
int source,
void *target,
IOInterruptHandler handler,
void *refCon)
{
cpu_data_t *cpu_data_ptr;
boolean_t current_state;
current_state = ml_set_interrupts_enabled(FALSE);
cpu_data_ptr = getCpuDatap();
cpu_data_ptr->interrupt_nub = nub;
cpu_data_ptr->interrupt_source = source;
cpu_data_ptr->interrupt_target = target;
cpu_data_ptr->interrupt_handler = handler;
cpu_data_ptr->interrupt_refCon = refCon;
cpu_data_ptr->interrupts_enabled = TRUE;
(void) ml_set_interrupts_enabled(current_state);
initialize_screen(NULL, kPEAcquireScreen);
}
/*
* Routine: ml_init_interrupt
* Function: Initialize Interrupts
*/
void
ml_init_interrupt(void)
{
}
/*
* Routine: ml_init_timebase
* Function: register and setup Timebase, Decremeter services
*/
void ml_init_timebase(
void *args,
tbd_ops_t tbd_funcs,
vm_offset_t int_address,
vm_offset_t int_value __unused)
{
cpu_data_t *cpu_data_ptr;
cpu_data_ptr = (cpu_data_t *)args;
if ((cpu_data_ptr == &BootCpuData)
&& (rtclock_timebase_func.tbd_fiq_handler == (void *)NULL)) {
rtclock_timebase_func = *tbd_funcs;
rtclock_timebase_addr = int_address;
}
}
void
ml_parse_cpu_topology(void)
{
DTEntry entry, child __unused;
OpaqueDTEntryIterator iter;
uint32_t cpu_boot_arg;
int err;
cpu_boot_arg = MAX_CPUS;
PE_parse_boot_argn("cpus", &cpu_boot_arg, sizeof(cpu_boot_arg));
err = DTLookupEntry(NULL, "/cpus", &entry);
assert(err == kSuccess);
err = DTInitEntryIterator(entry, &iter);
assert(err == kSuccess);
while (kSuccess == DTIterateEntries(&iter, &child)) {
unsigned int propSize;
void *prop = NULL;
int cpu_id = avail_cpus++;
if (kSuccess == DTGetProperty(child, "cpu-id", &prop, &propSize))
cpu_id = *((int32_t*)prop);
assert(cpu_id < MAX_CPUS);
assert(cpu_phys_ids[cpu_id] == (uint32_t)-1);
if (boot_cpu == -1) {
if (kSuccess != DTGetProperty(child, "state", &prop, &propSize))
panic("unable to retrieve state for cpu %d", cpu_id);
if (strncmp((char*)prop, "running", propSize) == 0) {
boot_cpu = cpu_id;
}
}
if (kSuccess != DTGetProperty(child, "reg", &prop, &propSize))
panic("unable to retrieve physical ID for cpu %d", cpu_id);
cpu_phys_ids[cpu_id] = *((uint32_t*)prop);
if ((cpu_id > max_cpu_number) && ((cpu_id == boot_cpu) || (avail_cpus <= cpu_boot_arg)))
max_cpu_number = cpu_id;
}
if (avail_cpus > cpu_boot_arg)
avail_cpus = cpu_boot_arg;
if (avail_cpus == 0)
panic("No cpus found!");
if (boot_cpu == -1)
panic("unable to determine boot cpu!");
}
unsigned int
ml_get_cpu_count(void)
{
return avail_cpus;
}
int
ml_get_boot_cpu_number(void)
{
return boot_cpu;
}
cluster_type_t
ml_get_boot_cluster(void)
{
return boot_cluster;
}
int
ml_get_cpu_number(uint32_t phys_id)
{
for (int log_id = 0; log_id <= ml_get_max_cpu_number(); ++log_id) {
if (cpu_phys_ids[log_id] == phys_id)
return log_id;
}
return -1;
}
int
ml_get_max_cpu_number(void)
{
return max_cpu_number;
}
void ml_lockdown_init() {
lockdown_handler_grp = lck_grp_alloc_init("lockdown_handler", NULL);
assert(lockdown_handler_grp != NULL);
lck_mtx_init(&lockdown_handler_lck, lockdown_handler_grp, NULL);
}
kern_return_t
ml_lockdown_handler_register(lockdown_handler_t f, void *this)
{
if (lockdown_handler || !f) {
return KERN_FAILURE;
}
lck_mtx_lock(&lockdown_handler_lck);
lockdown_handler = f;
lockdown_this = this;
#if !(defined(KERNEL_INTEGRITY_KTRR))
lockdown_done=1;
lockdown_handler(this);
#else
if (lockdown_done) {
lockdown_handler(this);
}
#endif
lck_mtx_unlock(&lockdown_handler_lck);
return KERN_SUCCESS;
}
void ml_lockdown_run_handler() {
lck_mtx_lock(&lockdown_handler_lck);
assert(!lockdown_done);
lockdown_done = 1;
if (lockdown_handler) {
lockdown_handler(lockdown_this);
}
lck_mtx_unlock(&lockdown_handler_lck);
}
kern_return_t
ml_processor_register(
ml_processor_info_t * in_processor_info,
processor_t * processor_out,
ipi_handler_t * ipi_handler)
{
cpu_data_t *this_cpu_datap;
processor_set_t pset;
boolean_t is_boot_cpu;
static unsigned int reg_cpu_count = 0;
if (in_processor_info->log_id > (uint32_t)ml_get_max_cpu_number())
return KERN_FAILURE;
if ((unsigned int)OSIncrementAtomic((SInt32*)&reg_cpu_count) >= avail_cpus)
return KERN_FAILURE;
if (in_processor_info->log_id != (uint32_t)ml_get_boot_cpu_number()) {
is_boot_cpu = FALSE;
this_cpu_datap = cpu_data_alloc(FALSE);
cpu_data_init(this_cpu_datap);
} else {
this_cpu_datap = &BootCpuData;
is_boot_cpu = TRUE;
}
assert(in_processor_info->log_id < MAX_CPUS);
this_cpu_datap->cpu_id = in_processor_info->cpu_id;
this_cpu_datap->cpu_chud = chudxnu_cpu_alloc(is_boot_cpu);
if (this_cpu_datap->cpu_chud == (void *)NULL)
goto processor_register_error;
this_cpu_datap->cpu_console_buf = console_cpu_alloc(is_boot_cpu);
if (this_cpu_datap->cpu_console_buf == (void *)(NULL))
goto processor_register_error;
if (!is_boot_cpu) {
this_cpu_datap->cpu_number = in_processor_info->log_id;
if (cpu_data_register(this_cpu_datap) != KERN_SUCCESS)
goto processor_register_error;
}
this_cpu_datap->cpu_idle_notify = (void *) in_processor_info->processor_idle;
this_cpu_datap->cpu_cache_dispatch = in_processor_info->platform_cache_dispatch;
nanoseconds_to_absolutetime((uint64_t) in_processor_info->powergate_latency, &this_cpu_datap->cpu_idle_latency);
this_cpu_datap->cpu_reset_assist = kvtophys(in_processor_info->powergate_stub_addr);
this_cpu_datap->idle_timer_notify = (void *) in_processor_info->idle_timer;
this_cpu_datap->idle_timer_refcon = in_processor_info->idle_timer_refcon;
this_cpu_datap->platform_error_handler = (void *) in_processor_info->platform_error_handler;
this_cpu_datap->cpu_regmap_paddr = in_processor_info->regmap_paddr;
this_cpu_datap->cpu_phys_id = in_processor_info->phys_id;
this_cpu_datap->cpu_l2_access_penalty = in_processor_info->l2_access_penalty;
this_cpu_datap->cpu_cluster_type = in_processor_info->cluster_type;
this_cpu_datap->cpu_cluster_id = in_processor_info->cluster_id;
this_cpu_datap->cpu_l2_id = in_processor_info->l2_cache_id;
this_cpu_datap->cpu_l2_size = in_processor_info->l2_cache_size;
this_cpu_datap->cpu_l3_id = in_processor_info->l3_cache_id;
this_cpu_datap->cpu_l3_size = in_processor_info->l3_cache_size;
this_cpu_datap->cluster_master = is_boot_cpu;
pset = pset_find(in_processor_info->cluster_id, processor_pset(master_processor));
assert(pset != NULL);
kprintf("%s>cpu_id %p cluster_id %d cpu_number %d is type %d\n", __FUNCTION__, in_processor_info->cpu_id, in_processor_info->cluster_id, this_cpu_datap->cpu_number, in_processor_info->cluster_type);
if (!is_boot_cpu) {
processor_init((struct processor *)this_cpu_datap->cpu_processor,
this_cpu_datap->cpu_number, pset);
if (this_cpu_datap->cpu_l2_access_penalty) {
/*
* Cores that have a non-zero L2 access penalty compared
* to the boot processor should be de-prioritized by the
* scheduler, so that threads use the cores with better L2
* preferentially.
*/
processor_set_primary(this_cpu_datap->cpu_processor,
master_processor);
}
}
*processor_out = this_cpu_datap->cpu_processor;
*ipi_handler = cpu_signal_handler;
if (in_processor_info->idle_tickle != (idle_tickle_t *) NULL)
*in_processor_info->idle_tickle = (idle_tickle_t) cpu_idle_tickle;
#if KPC
if (kpc_register_cpu(this_cpu_datap) != TRUE)
goto processor_register_error;
#endif
if (!is_boot_cpu) {
prng_cpu_init(this_cpu_datap->cpu_number);
// now let next CPU register itself
OSIncrementAtomic((SInt32*)&real_ncpus);
}
return KERN_SUCCESS;
processor_register_error:
#if KPC
kpc_unregister_cpu(this_cpu_datap);
#endif
if (this_cpu_datap->cpu_chud != (void *)NULL)
chudxnu_cpu_free(this_cpu_datap->cpu_chud);
if (!is_boot_cpu)
cpu_data_free(this_cpu_datap);
return KERN_FAILURE;
}
void
ml_init_arm_debug_interface(
void * in_cpu_datap,
vm_offset_t virt_address)
{
((cpu_data_t *)in_cpu_datap)->cpu_debug_interface_map = virt_address;
do_debugid();
}
/*
* Routine: init_ast_check
* Function:
*/
void
init_ast_check(
__unused processor_t processor)
{
}
/*
* Routine: cause_ast_check
* Function:
*/
void
cause_ast_check(
processor_t processor)
{
if (current_processor() != processor) {
cpu_signal(processor_to_cpu_datap(processor), SIGPast, (void *)NULL, (void *)NULL);
KERNEL_DEBUG_CONSTANT(MACHDBG_CODE(DBG_MACH_SCHED, MACH_REMOTE_AST), processor->cpu_id, 1 /* ast */, 0, 0, 0);
}
}
/*
* Routine: ml_at_interrupt_context
* Function: Check if running at interrupt context
*/
boolean_t
ml_at_interrupt_context(void)
{
unsigned int local;
vm_offset_t intstack_top_ptr;
intstack_top_ptr = getCpuDatap()->intstack_top;
return (((vm_offset_t)(&local) < intstack_top_ptr) && ((vm_offset_t)(&local) > (intstack_top_ptr - INTSTACK_SIZE)));
}
extern uint32_t cpu_idle_count;
void ml_get_power_state(boolean_t *icp, boolean_t *pidlep) {
*icp = ml_at_interrupt_context();
*pidlep = (cpu_idle_count == real_ncpus);
}
/*
* Routine: ml_cause_interrupt
* Function: Generate a fake interrupt
*/
void
ml_cause_interrupt(void)
{
return; /* BS_XXX */
}
/* Map memory map IO space */
vm_offset_t
ml_io_map(
vm_offset_t phys_addr,
vm_size_t size)
{
return (io_map(phys_addr, size, VM_WIMG_IO));
}
vm_offset_t
ml_io_map_wcomb(
vm_offset_t phys_addr,
vm_size_t size)
{
return (io_map(phys_addr, size, VM_WIMG_WCOMB));
}
/* boot memory allocation */
vm_offset_t
ml_static_malloc(
__unused vm_size_t size)
{
return ((vm_offset_t) NULL);
}
vm_map_address_t
ml_map_high_window(
vm_offset_t phys_addr,
vm_size_t len)
{
return pmap_map_high_window_bd(phys_addr, len, VM_PROT_READ | VM_PROT_WRITE);
}
vm_offset_t
ml_static_ptovirt(
vm_offset_t paddr)
{
return phystokv(paddr);
}
vm_offset_t
ml_static_vtop(
vm_offset_t vaddr)
{
if (((vm_address_t)(vaddr) - gVirtBase) >= gPhysSize)
panic("ml_static_ptovirt(): illegal vaddr: %p\n", (void*)vaddr);
return ((vm_address_t)(vaddr) - gVirtBase + gPhysBase);
}
kern_return_t
ml_static_protect(
vm_offset_t vaddr, /* kernel virtual address */
vm_size_t size,
vm_prot_t new_prot)
{
pt_entry_t arm_prot = 0;
pt_entry_t arm_block_prot = 0;
vm_offset_t vaddr_cur;
ppnum_t ppn;
kern_return_t result = KERN_SUCCESS;
if (vaddr < VM_MIN_KERNEL_ADDRESS) {
panic("ml_static_protect(): %p < %p", (void *) vaddr, (void *) VM_MIN_KERNEL_ADDRESS);
return KERN_FAILURE;
}
assert((vaddr & (PAGE_SIZE - 1)) == 0); /* must be page aligned */
if ((new_prot & VM_PROT_WRITE) && (new_prot & VM_PROT_EXECUTE)) {
panic("ml_static_protect(): WX request on %p", (void *) vaddr);
}
/* Set up the protection bits, and block bits so we can validate block mappings. */
if (new_prot & VM_PROT_WRITE) {
arm_prot |= ARM_PTE_AP(AP_RWNA);
arm_block_prot |= ARM_TTE_BLOCK_AP(AP_RWNA);
} else {
arm_prot |= ARM_PTE_AP(AP_RONA);
arm_block_prot |= ARM_TTE_BLOCK_AP(AP_RONA);
}
arm_prot |= ARM_PTE_NX;
arm_block_prot |= ARM_TTE_BLOCK_NX;
if (!(new_prot & VM_PROT_EXECUTE)) {
arm_prot |= ARM_PTE_PNX;
arm_block_prot |= ARM_TTE_BLOCK_PNX;
}
for (vaddr_cur = vaddr;
vaddr_cur < trunc_page_64(vaddr + size);
vaddr_cur += PAGE_SIZE) {
ppn = pmap_find_phys(kernel_pmap, vaddr_cur);
if (ppn != (vm_offset_t) NULL) {
#if __ARM64_TWO_LEVEL_PMAP__
tt_entry_t *tte2;
#else
tt_entry_t *tte1, *tte2;
#endif
pt_entry_t *pte_p;
pt_entry_t ptmp;
#if __ARM64_TWO_LEVEL_PMAP__
tte2 = &kernel_pmap->tte[(((vaddr_cur) & ARM_TT_L2_INDEX_MASK) >> ARM_TT_L2_SHIFT)];
#else
tte1 = &kernel_pmap->tte[(((vaddr_cur) & ARM_TT_L1_INDEX_MASK) >> ARM_TT_L1_SHIFT)];
tte2 = &((tt_entry_t*) phystokv((*tte1) & ARM_TTE_TABLE_MASK))[(((vaddr_cur) & ARM_TT_L2_INDEX_MASK) >> ARM_TT_L2_SHIFT)];
#endif
if (((*tte2) & ARM_TTE_TYPE_MASK) != ARM_TTE_TYPE_TABLE) {
if ((((*tte2) & ARM_TTE_TYPE_MASK) == ARM_TTE_TYPE_BLOCK) &&
((*tte2 & (ARM_TTE_BLOCK_NXMASK | ARM_TTE_BLOCK_PNXMASK | ARM_TTE_BLOCK_APMASK)) == arm_block_prot)) {
/*
* We can support ml_static_protect on a block mapping if the mapping already has
* the desired protections. We still want to run checks on a per-page basis.
*/
continue;
}
result = KERN_FAILURE;
break;
}
pte_p = (pt_entry_t *)&((tt_entry_t*)(phystokv((*tte2) & ARM_TTE_TABLE_MASK)))[(((vaddr_cur) & ARM_TT_L3_INDEX_MASK) >> ARM_TT_L3_SHIFT)];
ptmp = *pte_p;
if ((ptmp & ARM_PTE_HINT_MASK) && ((ptmp & (ARM_PTE_APMASK | ARM_PTE_PNXMASK | ARM_PTE_NXMASK)) != arm_prot)) {
/*
* The contiguous hint is similar to a block mapping for ml_static_protect; if the existing
* protections do not match the desired protections, then we will fail (as we cannot update
* this mapping without updating other mappings as well).
*/
result = KERN_FAILURE;
break;
}
__unreachable_ok_push
if (TEST_PAGE_RATIO_4) {
{
unsigned int i;
pt_entry_t *ptep_iter;
ptep_iter = pte_p;
for (i=0; i<4; i++, ptep_iter++) {
/* Note that there is a hole in the HINT sanity checking here. */
ptmp = *ptep_iter;
/* We only need to update the page tables if the protections do not match. */
if ((ptmp & (ARM_PTE_APMASK | ARM_PTE_PNXMASK | ARM_PTE_NXMASK)) != arm_prot) {
ptmp = (ptmp & ~(ARM_PTE_APMASK | ARM_PTE_PNXMASK | ARM_PTE_NXMASK)) | arm_prot;
*ptep_iter = ptmp;
}
}
}
#ifndef __ARM_L1_PTW__
FlushPoC_DcacheRegion( trunc_page_32(pte_p), 4*sizeof(*pte_p));
#endif
} else {
ptmp = *pte_p;
/* We only need to update the page tables if the protections do not match. */
if ((ptmp & (ARM_PTE_APMASK | ARM_PTE_PNXMASK | ARM_PTE_NXMASK)) != arm_prot) {
ptmp = (ptmp & ~(ARM_PTE_APMASK | ARM_PTE_PNXMASK | ARM_PTE_NXMASK)) | arm_prot;
*pte_p = ptmp;
}
#ifndef __ARM_L1_PTW__
FlushPoC_DcacheRegion( trunc_page_32(pte_p), sizeof(*pte_p));
#endif
}
__unreachable_ok_pop
}
}
if (vaddr_cur > vaddr) {
assert(((vaddr_cur - vaddr) & 0xFFFFFFFF00000000ULL) == 0);
flush_mmu_tlb_region(vaddr, (uint32_t)(vaddr_cur - vaddr));
}
return result;
}
/*
* Routine: ml_static_mfree
* Function:
*/
void
ml_static_mfree(
vm_offset_t vaddr,
vm_size_t size)
{
vm_offset_t vaddr_cur;
ppnum_t ppn;
uint32_t freed_pages = 0;
/* It is acceptable (if bad) to fail to free. */
if (vaddr < VM_MIN_KERNEL_ADDRESS)
return;
assert((vaddr & (PAGE_SIZE - 1)) == 0); /* must be page aligned */
for (vaddr_cur = vaddr;
vaddr_cur < trunc_page_64(vaddr + size);
vaddr_cur += PAGE_SIZE) {
ppn = pmap_find_phys(kernel_pmap, vaddr_cur);
if (ppn != (vm_offset_t) NULL) {
/*
* It is not acceptable to fail to update the protections on a page
* we will release to the VM. We need to either panic or continue.
* For now, we'll panic (to help flag if there is memory we can
* reclaim).
*/
if (ml_static_protect(vaddr_cur, PAGE_SIZE, VM_PROT_WRITE | VM_PROT_READ) != KERN_SUCCESS) {
panic("Failed ml_static_mfree on %p", (void *) vaddr_cur);
}
#if 0
/*
* Must NOT tear down the "V==P" mapping for vaddr_cur as the zone alias scheme
* relies on the persistence of these mappings for all time.
*/
// pmap_remove(kernel_pmap, (addr64_t) vaddr_cur, (addr64_t) (vaddr_cur + PAGE_SIZE));
#endif
vm_page_create(ppn, (ppn + 1));
freed_pages++;
}
}
vm_page_lockspin_queues();
vm_page_wire_count -= freed_pages;
vm_page_wire_count_initial -= freed_pages;
vm_page_unlock_queues();
#if DEBUG
kprintf("ml_static_mfree: Released 0x%x pages at VA %p, size:0x%llx, last ppn: 0x%x\n", freed_pages, (void *)vaddr, (uint64_t)size, ppn);
#endif
}
/* virtual to physical on wired pages */
vm_offset_t
ml_vtophys(vm_offset_t vaddr)
{
return kvtophys(vaddr);
}
/*
* Routine: ml_nofault_copy
* Function: Perform a physical mode copy if the source and destination have
* valid translations in the kernel pmap. If translations are present, they are
* assumed to be wired; e.g., no attempt is made to guarantee that the
* translations obtained remain valid for the duration of the copy process.
*/
vm_size_t
ml_nofault_copy(vm_offset_t virtsrc, vm_offset_t virtdst, vm_size_t size)
{
addr64_t cur_phys_dst, cur_phys_src;
vm_size_t count, nbytes = 0;
while (size > 0) {
if (!(cur_phys_src = kvtophys(virtsrc)))
break;
if (!(cur_phys_dst = kvtophys(virtdst)))
break;
if (!pmap_valid_address(trunc_page_64(cur_phys_dst)) ||
!pmap_valid_address(trunc_page_64(cur_phys_src)))
break;
count = PAGE_SIZE - (cur_phys_src & PAGE_MASK);
if (count > (PAGE_SIZE - (cur_phys_dst & PAGE_MASK)))
count = PAGE_SIZE - (cur_phys_dst & PAGE_MASK);
if (count > size)
count = size;
bcopy_phys(cur_phys_src, cur_phys_dst, count);
nbytes += count;
virtsrc += count;
virtdst += count;
size -= count;
}
return nbytes;
}
/*
* Routine: ml_validate_nofault
* Function: Validate that ths address range has a valid translations
* in the kernel pmap. If translations are present, they are
* assumed to be wired; i.e. no attempt is made to guarantee
* that the translation persist after the check.
* Returns: TRUE if the range is mapped and will not cause a fault,
* FALSE otherwise.
*/
boolean_t ml_validate_nofault(
vm_offset_t virtsrc, vm_size_t size)
{
addr64_t cur_phys_src;
uint32_t count;
while (size > 0) {
if (!(cur_phys_src = kvtophys(virtsrc)))
return FALSE;
if (!pmap_valid_address(trunc_page_64(cur_phys_src)))
return FALSE;
count = (uint32_t)(PAGE_SIZE - (cur_phys_src & PAGE_MASK));
if (count > size)
count = (uint32_t)size;
virtsrc += count;
size -= count;
}
return TRUE;
}
void
ml_get_bouncepool_info(vm_offset_t * phys_addr, vm_size_t * size)
{
*phys_addr = 0;
*size = 0;
}
void
active_rt_threads(__unused boolean_t active)
{
}
static void cpu_qos_cb_default(__unused int urgency, __unused uint64_t qos_param1, __unused uint64_t qos_param2) {
return;
}
cpu_qos_update_t cpu_qos_update = cpu_qos_cb_default;
void cpu_qos_update_register(cpu_qos_update_t cpu_qos_cb) {
if (cpu_qos_cb != NULL) {
cpu_qos_update = cpu_qos_cb;
} else {
cpu_qos_update = cpu_qos_cb_default;
}
}
void
thread_tell_urgency(int urgency, uint64_t rt_period, uint64_t rt_deadline, uint64_t sched_latency __unused, __unused thread_t nthread)
{
SCHED_DEBUG_PLATFORM_KERNEL_DEBUG_CONSTANT(MACHDBG_CODE(DBG_MACH_SCHED,MACH_URGENCY) | DBG_FUNC_START, urgency, rt_period, rt_deadline, sched_latency, 0);
cpu_qos_update(urgency, rt_period, rt_deadline);
SCHED_DEBUG_PLATFORM_KERNEL_DEBUG_CONSTANT(MACHDBG_CODE(DBG_MACH_SCHED,MACH_URGENCY) | DBG_FUNC_END, urgency, rt_period, rt_deadline, 0, 0);
}
void
machine_run_count(__unused uint32_t count)
{
}
processor_t
machine_choose_processor(__unused processor_set_t pset, processor_t processor)
{
return (processor);
}
vm_offset_t
ml_stack_remaining(void)
{
uintptr_t local = (uintptr_t) &local;
if (ml_at_interrupt_context()) {
return (local - (getCpuDatap()->intstack_top - INTSTACK_SIZE));
} else {
return (local - current_thread()->kernel_stack);
}
}
#if KASAN
vm_offset_t ml_stack_base(void);
vm_size_t ml_stack_size(void);
vm_offset_t
ml_stack_base(void)
{
if (ml_at_interrupt_context()) {
return getCpuDatap()->intstack_top - INTSTACK_SIZE;
} else {
return current_thread()->kernel_stack;
}
}
vm_size_t
ml_stack_size(void)
{
if (ml_at_interrupt_context()) {
return INTSTACK_SIZE;
} else {
return kernel_stack_size;
}
}
#endif
boolean_t machine_timeout_suspended(void) {
return FALSE;
}
kern_return_t
ml_interrupt_prewarm(__unused uint64_t deadline)
{
return KERN_FAILURE;
}
/*
* Assumes fiq, irq disabled.
*/
void
ml_set_decrementer(uint32_t dec_value)
{
cpu_data_t *cdp = getCpuDatap();
assert(ml_get_interrupts_enabled() == FALSE);
cdp->cpu_decrementer = dec_value;
if (cdp->cpu_set_decrementer_func) {
((void (*)(uint32_t))cdp->cpu_set_decrementer_func)(dec_value);
} else {
__asm__ volatile("msr CNTP_TVAL_EL0, %0" : : "r"((uint64_t)dec_value));
}
}
uint64_t ml_get_hwclock()
{
uint64_t timebase;
// ISB required by ARMV7C.b section B8.1.2 & ARMv8 section D6.1.2
// "Reads of CNTPCT[_EL0] can occur speculatively and out of order relative
// to other instructions executed on the same processor."
__asm__ volatile("isb\n"
"mrs %0, CNTPCT_EL0"
: "=r"(timebase));
return timebase;
}
uint64_t
ml_get_timebase()
{
return (ml_get_hwclock() + getCpuDatap()->cpu_base_timebase);
}
uint32_t
ml_get_decrementer()
{
cpu_data_t *cdp = getCpuDatap();
uint32_t dec;
assert(ml_get_interrupts_enabled() == FALSE);
if (cdp->cpu_get_decrementer_func) {
dec = ((uint32_t (*)(void))cdp->cpu_get_decrementer_func)();
} else {
uint64_t wide_val;
__asm__ volatile("mrs %0, CNTP_TVAL_EL0" : "=r"(wide_val));
dec = (uint32_t)wide_val;
assert(wide_val == (uint64_t)dec);
}
return dec;
}
boolean_t
ml_get_timer_pending()
{
uint64_t cntp_ctl;
__asm__ volatile("mrs %0, CNTP_CTL_EL0" : "=r"(cntp_ctl));
return ((cntp_ctl & CNTP_CTL_EL0_ISTATUS) != 0) ? TRUE : FALSE;
}
boolean_t
ml_wants_panic_trap_to_debugger(void)
{
boolean_t result = FALSE;
return result;
}
static void
cache_trap_error(thread_t thread, vm_map_address_t fault_addr)
{
mach_exception_data_type_t exc_data[2];
arm_saved_state_t *regs = get_user_regs(thread);
set_saved_state_far(regs, fault_addr);
exc_data[0] = KERN_INVALID_ADDRESS;
exc_data[1] = fault_addr;
exception_triage(EXC_BAD_ACCESS, exc_data, 2);
}
static void
cache_trap_recover()
{
vm_map_address_t fault_addr;
__asm__ volatile("mrs %0, FAR_EL1" : "=r"(fault_addr));
cache_trap_error(current_thread(), fault_addr);
}
static void
dcache_flush_trap(vm_map_address_t start, vm_map_size_t size)
{
vm_map_address_t end = start + size;
thread_t thread = current_thread();
vm_offset_t old_recover = thread->recover;
/* Check bounds */
if (task_has_64BitAddr(current_task())) {
if (end > MACH_VM_MAX_ADDRESS) {
cache_trap_error(thread, end & ((1 << ARM64_CLINE_SHIFT) - 1));
}
} else {
if (end > VM_MAX_ADDRESS) {
cache_trap_error(thread, end & ((1 << ARM64_CLINE_SHIFT) - 1));
}
}
if (start > end) {
cache_trap_error(thread, start & ((1 << ARM64_CLINE_SHIFT) - 1));
}
/* Set recovery function */
thread->recover = (vm_address_t)cache_trap_recover;
#if defined(APPLE_ARM64_ARCH_FAMILY)
/*
* We're coherent on Apple ARM64 CPUs, so this could be a nop. However,
* if the region given us is bad, it would be good to catch it and
* crash, ergo we still do the flush.
*/
assert((size & 0xFFFFFFFF00000000ULL) == 0);
FlushPoC_DcacheRegion(start, (uint32_t)size);
#else
#error "Make sure you don't need to xcall."
#endif
/* Restore recovery function */
thread->recover = old_recover;
/* Return (caller does exception return) */
}
static void
icache_invalidate_trap(vm_map_address_t start, vm_map_size_t size)
{
vm_map_address_t end = start + size;
thread_t thread = current_thread();
vm_offset_t old_recover = thread->recover;
/* Check bounds */
if (task_has_64BitAddr(current_task())) {
if (end > MACH_VM_MAX_ADDRESS) {
cache_trap_error(thread, end & ((1 << ARM64_CLINE_SHIFT) - 1));
}
} else {
if (end > VM_MAX_ADDRESS) {
cache_trap_error(thread, end & ((1 << ARM64_CLINE_SHIFT) - 1));
}
}
if (start > end) {
cache_trap_error(thread, start & ((1 << ARM64_CLINE_SHIFT) - 1));
}
/* Set recovery function */
thread->recover = (vm_address_t)cache_trap_recover;
#if defined(APPLE_ARM64_ARCH_FAMILY)
/* Clean dcache to unification, except we're coherent on Apple ARM64 CPUs */
#else
#error Make sure not cleaning is right for this platform!
#endif
/* Invalidate iCache to point of unification */
assert((size & 0xFFFFFFFF00000000ULL) == 0);
InvalidatePoU_IcacheRegion(start, (uint32_t)size);
/* Restore recovery function */
thread->recover = old_recover;
/* Return (caller does exception return) */
}
__attribute__((noreturn))
void
platform_syscall(arm_saved_state_t *state)
{
uint32_t code;
#define platform_syscall_kprintf(x...) /* kprintf("platform_syscall: " x) */
code = (uint32_t)get_saved_state_reg(state, 3);
switch (code) {
case 0:
/* I-Cache flush */
platform_syscall_kprintf("icache flush requested.\n");
icache_invalidate_trap(get_saved_state_reg(state, 0), get_saved_state_reg(state, 1));
break;
case 1:
/* D-Cache flush */
platform_syscall_kprintf("dcache flush requested.\n");
dcache_flush_trap(get_saved_state_reg(state, 0), get_saved_state_reg(state, 1));
break;
case 2:
/* set cthread */
platform_syscall_kprintf("set cthread self.\n");
thread_set_cthread_self(get_saved_state_reg(state, 0));
break;
case 3:
/* get cthread */
platform_syscall_kprintf("get cthread self.\n");
set_saved_state_reg(state, 0, thread_get_cthread_self());
break;
default:
platform_syscall_kprintf("unknown: %d\n", code);
break;
}
thread_exception_return();
}
static void
_enable_timebase_event_stream(uint32_t bit_index)
{
uint64_t cntkctl; /* One wants to use 32 bits, but "mrs" prefers it this way */
if (bit_index >= 64) {
panic("%s: invalid bit index (%u)", __FUNCTION__, bit_index);
}
__asm__ volatile ("mrs %0, CNTKCTL_EL1" : "=r"(cntkctl));
cntkctl |= (bit_index << CNTKCTL_EL1_EVENTI_SHIFT);
cntkctl |= CNTKCTL_EL1_EVNTEN;
cntkctl |= CNTKCTL_EL1_EVENTDIR; /* 1->0; why not? */
/*
* If the SOC supports it (and it isn't broken), enable
* EL0 access to the physical timebase register.
*/
if (user_timebase_allowed()) {
cntkctl |= CNTKCTL_EL1_PL0PCTEN;
}
__asm__ volatile ("msr CNTKCTL_EL1, %0" : : "r"(cntkctl));
}
/*
* Turn timer on, unmask that interrupt.
*/
static void
_enable_virtual_timer(void)
{
uint64_t cntvctl = CNTP_CTL_EL0_ENABLE; /* One wants to use 32 bits, but "mrs" prefers it this way */
__asm__ volatile ("msr CNTP_CTL_EL0, %0" : : "r"(cntvctl));
}
void
fiq_context_init(boolean_t enable_fiq __unused)
{
#if defined(APPLE_ARM64_ARCH_FAMILY)
/* Could fill in our own ops here, if we needed them */
uint64_t ticks_per_sec, ticks_per_event, events_per_sec;
uint32_t bit_index;
ticks_per_sec = gPEClockFrequencyInfo.timebase_frequency_hz;
#if defined(ARM_BOARD_WFE_TIMEOUT_NS)
events_per_sec = 1000000000 / ARM_BOARD_WFE_TIMEOUT_NS;
#else
/* Default to 1usec (or as close as we can get) */
events_per_sec = 1000000;
#endif
ticks_per_event = ticks_per_sec / events_per_sec;
bit_index = flsll(ticks_per_event) - 1; /* Highest bit set */
/* Round up to power of two */
if ((ticks_per_event & ((1 << bit_index) - 1)) != 0) {
bit_index++;
}
/*
* The timer can only trigger on rising or falling edge,
* not both; we don't care which we trigger on, but we
* do need to adjust which bit we are interested in to
* account for this.
*/
if (bit_index != 0)
bit_index--;
_enable_timebase_event_stream(bit_index);
#else
#error Need a board configuration.
#endif
/* Interrupts still disabled. */
assert(ml_get_interrupts_enabled() == FALSE);
_enable_virtual_timer();
}
/*
* ARM64_TODO: remove me (just a convenience while we don't have crashreporter)
*/
extern int copyinframe(vm_address_t, char *, boolean_t);
size_t _OSUserBacktrace(char *buffer, size_t bufsize);
size_t _OSUserBacktrace(char *buffer, size_t bufsize)
{
thread_t thread = current_thread();
boolean_t is64bit = thread_is_64bit(thread);
size_t trace_size_bytes = 0, lr_size;
vm_address_t frame_addr; // Should really by mach_vm_offset_t...
if (bufsize < 8) {
return 0;
}
if (get_threadtask(thread) == kernel_task) {
panic("%s: Should never be called from a kernel thread.", __FUNCTION__);
}
frame_addr = get_saved_state_fp(thread->machine.upcb);
if (is64bit) {
uint64_t frame[2];
lr_size = sizeof(frame[1]);
*((uint64_t*)buffer) = get_saved_state_pc(thread->machine.upcb);
trace_size_bytes = lr_size;
while (trace_size_bytes + lr_size < bufsize) {
if (!(frame_addr < VM_MIN_KERNEL_AND_KEXT_ADDRESS)) {
break;
}
if (0 != copyinframe(frame_addr, (char*)frame, TRUE)) {
break;
}
*((uint64_t*)(buffer + trace_size_bytes)) = frame[1]; /* lr */
frame_addr = frame[0];
trace_size_bytes += lr_size;
if (frame[0] == 0x0ULL) {
break;
}
}
} else {
uint32_t frame[2];
lr_size = sizeof(frame[1]);
*((uint32_t*)buffer) = (uint32_t)get_saved_state_pc(thread->machine.upcb);
trace_size_bytes = lr_size;
while (trace_size_bytes + lr_size < bufsize) {
if (!(frame_addr < VM_MIN_KERNEL_AND_KEXT_ADDRESS)) {
break;
}
if (0 != copyinframe(frame_addr, (char*)frame, FALSE)) {
break;
}
*((uint32_t*)(buffer + trace_size_bytes)) = frame[1]; /* lr */
frame_addr = frame[0];
trace_size_bytes += lr_size;
if (frame[0] == 0x0ULL) {
break;
}
}
}
return trace_size_bytes;
}
boolean_t
ml_delay_should_spin(uint64_t interval)
{
cpu_data_t *cdp = getCpuDatap();
if (cdp->cpu_idle_latency) {
return (interval < cdp->cpu_idle_latency) ? TRUE : FALSE;
} else {
/*
* Early boot, latency is unknown. Err on the side of blocking,
* which should always be safe, even if slow
*/
return FALSE;
}
}
boolean_t ml_thread_is64bit(thread_t thread) {
return (thread_is_64bit(thread));
}
void ml_timer_evaluate(void) {
}
boolean_t
ml_timer_forced_evaluation(void) {
return FALSE;
}
uint64_t
ml_energy_stat(thread_t t) {
return t->machine.energy_estimate_nj;
}
void
ml_gpu_stat_update(__unused uint64_t gpu_ns_delta) {
#if CONFIG_EMBEDDED
/*
* For now: update the resource coalition stats of the
* current thread's coalition
*/
task_coalition_update_gpu_stats(current_task(), gpu_ns_delta);
#endif
}
uint64_t
ml_gpu_stat(__unused thread_t t) {
return 0;
}
#if !CONFIG_SKIP_PRECISE_USER_KERNEL_TIME
static void
timer_state_event(boolean_t switch_to_kernel)
{
thread_t thread = current_thread();
if (!thread->precise_user_kernel_time) return;
processor_data_t *pd = &getCpuDatap()->cpu_processor->processor_data;
uint64_t now = ml_get_timebase();
timer_stop(pd->current_state, now);
pd->current_state = (switch_to_kernel) ? &pd->system_state : &pd->user_state;
timer_start(pd->current_state, now);
timer_stop(pd->thread_timer, now);
pd->thread_timer = (switch_to_kernel) ? &thread->system_timer : &thread->user_timer;
timer_start(pd->thread_timer, now);
}
void
timer_state_event_user_to_kernel(void)
{
timer_state_event(TRUE);
}
void
timer_state_event_kernel_to_user(void)
{
timer_state_event(FALSE);
}
#endif /* !CONFIG_SKIP_PRECISE_USER_KERNEL_TIME */
/*
* The following are required for parts of the kernel
* that cannot resolve these functions as inlines:
*/
extern thread_t current_act(void);
thread_t
current_act(void)
{
return current_thread_fast();
}
#undef current_thread
extern thread_t current_thread(void);
thread_t
current_thread(void)
{
return current_thread_fast();
}
typedef struct
{
ex_cb_t cb;
void *refcon;
}
ex_cb_info_t;
ex_cb_info_t ex_cb_info[EXCB_CLASS_MAX];
/*
* Callback registration
* Currently we support only one registered callback per class but
* it should be possible to support more callbacks
*/
kern_return_t ex_cb_register(
ex_cb_class_t cb_class,
ex_cb_t cb,
void *refcon)
{
ex_cb_info_t *pInfo = &ex_cb_info[cb_class];
if ((NULL == cb) || (cb_class >= EXCB_CLASS_MAX))
{
return KERN_INVALID_VALUE;
}
if (NULL == pInfo->cb)
{
pInfo->cb = cb;
pInfo->refcon = refcon;
return KERN_SUCCESS;
}
return KERN_FAILURE;
}
/*
* Called internally by platform kernel to invoke the registered callback for class
*/
ex_cb_action_t ex_cb_invoke(
ex_cb_class_t cb_class,
vm_offset_t far)
{
ex_cb_info_t *pInfo = &ex_cb_info[cb_class];
ex_cb_state_t state = {far};
if (cb_class >= EXCB_CLASS_MAX)
{
panic("Invalid exception callback class 0x%x\n", cb_class);
}
if (pInfo->cb)
{
return pInfo->cb(cb_class, pInfo->refcon, &state);
}
return EXCB_ACTION_NONE;
}