/* * Copyright 2003-2023, Haiku Inc. All rights reserved. * Distributed under the terms of the MIT License. * * Authors: * Axel Dörfler * Ingo Weinhold * François Revol * Ithamar R. Adema * * Copyright 2001, Travis Geiselbrecht. All rights reserved. * Distributed under the terms of the NewOS License. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "arch_int_gicv2.h" #include "soc.h" #include "soc_pxa.h" #include "soc_omap3.h" #include "soc_sun4i.h" #include "ARMVMTranslationMap.h" //#define TRACE_ARCH_INT #ifdef TRACE_ARCH_INT # define TRACE(x...) dprintf(x) #else # define TRACE(x...) ; #endif #define VECTORPAGE_SIZE 64 #define USER_VECTOR_ADDR_LOW 0x00000000 #define USER_VECTOR_ADDR_HIGH 0xffff0000 extern int _vectors_start; extern int _vectors_end; static area_id sUserVectorPageArea; static void *sUserVectorPageAddress; //static fdt_module_info *sFdtModule; // An iframe stack used in the early boot process when we don't have // threads yet. struct iframe_stack gBootFrameStack; void arch_int_enable_io_interrupt(int32 irq) { TRACE("arch_int_enable_io_interrupt(%" B_PRId32 ")\n", irq); InterruptController *ic = InterruptController::Get(); if (ic != NULL) ic->EnableInterrupt(irq); } void arch_int_disable_io_interrupt(int32 irq) { TRACE("arch_int_disable_io_interrupt(%" B_PRId32 ")\n", irq); InterruptController *ic = InterruptController::Get(); if (ic != NULL) ic->DisableInterrupt(irq); } /* arch_int_*_interrupts() and friends are in arch_asm.S */ int32 arch_int_assign_to_cpu(int32 irq, int32 cpu) { // Not yet supported. return 0; } static void print_iframe(const char *event, struct iframe *frame) { if (event) dprintf("Exception: %s\n", event); dprintf("R00=%08x R01=%08x R02=%08x R03=%08x\n" "R04=%08x R05=%08x R06=%08x R07=%08x\n", frame->r0, frame->r1, frame->r2, frame->r3, frame->r4, frame->r5, frame->r6, frame->r7); dprintf("R08=%08x R09=%08x R10=%08x R11=%08x\n" "R12=%08x SPs=%08x LRs=%08x PC =%08x\n", frame->r8, frame->r9, frame->r10, frame->r11, frame->r12, frame->svc_sp, frame->svc_lr, frame->pc); dprintf(" SPu=%08x LRu=%08x CPSR=%08x\n", frame->usr_sp, frame->usr_lr, frame->spsr); } extern "C" void arm_vector_init(void); status_t arch_int_init(kernel_args *args) { TRACE("arch_int_init\n"); // copy vector code to vector page memcpy((void*)USER_VECTOR_ADDR_HIGH, &_vectors_start, VECTORPAGE_SIZE); // initialize stack for vectors arm_vector_init(); // enable high vectors arm_set_sctlr(arm_get_sctlr() | SCTLR_HIGH_VECTORS); return B_OK; } status_t arch_int_init_post_vm(kernel_args *args) { TRACE("arch_int_init_post_vm\n"); sUserVectorPageAddress = (addr_t*)USER_VECTOR_ADDR_HIGH; sUserVectorPageArea = create_area("user_vectorpage", (void **)&sUserVectorPageAddress, B_EXACT_ADDRESS, B_PAGE_SIZE, B_ALREADY_WIRED, B_READ_AREA | B_EXECUTE_AREA); if (sUserVectorPageArea < 0) panic("user vector page @ %p could not be created (%x)!", sUserVectorPageAddress, sUserVectorPageArea); if (strncmp(args->arch_args.interrupt_controller.kind, INTC_KIND_GICV2, sizeof(args->arch_args.interrupt_controller.kind)) == 0) { InterruptController *ic = new(std::nothrow) GICv2InterruptController( args->arch_args.interrupt_controller.regs1.start, args->arch_args.interrupt_controller.regs2.start); if (ic == NULL) return B_NO_MEMORY; } else if (strncmp(args->arch_args.interrupt_controller.kind, INTC_KIND_OMAP3, sizeof(args->arch_args.interrupt_controller.kind)) == 0) { InterruptController *ic = new(std::nothrow) OMAP3InterruptController( args->arch_args.interrupt_controller.regs1.start); if (ic == NULL) return B_NO_MEMORY; } else if (strncmp(args->arch_args.interrupt_controller.kind, INTC_KIND_PXA, sizeof(args->arch_args.interrupt_controller.kind)) == 0) { InterruptController *ic = new(std::nothrow) PXAInterruptController( args->arch_args.interrupt_controller.regs1.start); if (ic == NULL) return B_NO_MEMORY; } else if (strncmp(args->arch_args.interrupt_controller.kind, INTC_KIND_SUN4I, sizeof(args->arch_args.interrupt_controller.kind)) == 0) { InterruptController *ic = new(std::nothrow) Sun4iInterruptController( args->arch_args.interrupt_controller.regs1.start); if (ic == NULL) return B_NO_MEMORY; } else { panic("No interrupt controllers found!\n"); } return B_OK; } status_t arch_int_init_io(kernel_args* args) { return B_OK; } status_t arch_int_init_post_device_manager(struct kernel_args *args) { return B_ENTRY_NOT_FOUND; } // Little helper class for handling the // iframe stack as used by KDL. class IFrameScope { public: IFrameScope(struct iframe *iframe) { fThread = thread_get_current_thread(); if (fThread) arm_push_iframe(&fThread->arch_info.iframes, iframe); else arm_push_iframe(&gBootFrameStack, iframe); } virtual ~IFrameScope() { // pop iframe if (fThread) arm_pop_iframe(&fThread->arch_info.iframes); else arm_pop_iframe(&gBootFrameStack); } private: Thread* fThread; }; extern "C" void arch_arm_undefined(struct iframe *iframe) { print_iframe("Undefined Instruction", iframe); IFrameScope scope(iframe); // push/pop iframe panic("not handled!"); } extern "C" void arch_arm_syscall(struct iframe *iframe) { #ifdef TRACE_ARCH_INT print_iframe("Software interrupt", iframe); #endif IFrameScope scope(iframe); uint32_t syscall = *(uint32_t *)(iframe->pc-4) & 0x00ffffff; TRACE("syscall number: %d\n", syscall); uint32_t args[20]; if (syscall < kSyscallCount) { TRACE("syscall(%s,%d)\n", kExtendedSyscallInfos[syscall].name, kExtendedSyscallInfos[syscall].parameter_count); int argSize = kSyscallInfos[syscall].parameter_size; memcpy(args, &iframe->r0, std::min(argSize, 4 * sizeof(uint32))); if (argSize > 4 * sizeof(uint32)) { status_t res = user_memcpy(&args[4], (void *)iframe->usr_sp, (argSize - 4 * sizeof(uint32))); if (res < B_OK) { dprintf("can't read syscall arguments on user stack\n"); iframe->r0 = res; return; } } } thread_get_current_thread()->arch_info.userFrame = iframe; thread_get_current_thread()->arch_info.oldR0 = iframe->r0; thread_at_kernel_entry(system_time()); enable_interrupts(); uint64 returnValue = 0; syscall_dispatcher(syscall, (void*)args, &returnValue); TRACE("returning %" B_PRId64 "\n", returnValue); iframe->r0 = returnValue; disable_interrupts(); atomic_and(&thread_get_current_thread()->flags, ~THREAD_FLAGS_SYSCALL_RESTARTED); if ((thread_get_current_thread()->flags & (THREAD_FLAGS_SIGNALS_PENDING | THREAD_FLAGS_DEBUG_THREAD | THREAD_FLAGS_TRAP_FOR_CORE_DUMP)) != 0) { enable_interrupts(); thread_at_kernel_exit(); } else { thread_at_kernel_exit_no_signals(); } if ((thread_get_current_thread()->flags & THREAD_FLAGS_RESTART_SYSCALL) != 0) { atomic_and(&thread_get_current_thread()->flags, ~THREAD_FLAGS_RESTART_SYSCALL); atomic_or(&thread_get_current_thread()->flags, THREAD_FLAGS_SYSCALL_RESTARTED); iframe->r0 = thread_get_current_thread()->arch_info.oldR0; iframe->pc -= 4; } thread_get_current_thread()->arch_info.userFrame = NULL; } static bool arch_arm_handle_access_flag_fault(addr_t far, uint32 fsr, bool isWrite, bool isExec) { VMAddressSpacePutter addressSpace; if (IS_KERNEL_ADDRESS(far)) addressSpace.SetTo(VMAddressSpace::GetKernel()); else if (IS_USER_ADDRESS(far)) addressSpace.SetTo(VMAddressSpace::GetCurrent()); if (!addressSpace.IsSet()) return false; ARMVMTranslationMap *map = (ARMVMTranslationMap *)addressSpace->TranslationMap(); if ((fsr & (FSR_FS_MASK | FSR_LPAE_MASK)) == FSR_FS_ACCESS_FLAG_FAULT) { phys_addr_t physAddr; uint32 pageFlags; map->QueryInterrupt(far, &physAddr, &pageFlags); if ((PAGE_PRESENT & pageFlags) == 0) return false; if ((pageFlags & PAGE_ACCESSED) == 0) { map->SetFlags(far, PAGE_ACCESSED); return true; } } if (isWrite && ((fsr & (FSR_FS_MASK | FSR_LPAE_MASK)) == FSR_FS_PERMISSION_FAULT_L2)) { phys_addr_t physAddr; uint32 pageFlags; map->QueryInterrupt(far, &physAddr, &pageFlags); if ((PAGE_PRESENT & pageFlags) == 0) return false; if (((pageFlags & B_KERNEL_WRITE_AREA) && ((pageFlags & PAGE_MODIFIED) == 0))) { map->SetFlags(far, PAGE_MODIFIED); return true; } } return false; } static void arch_arm_page_fault(struct iframe *frame, addr_t far, uint32 fsr, bool isWrite, bool isExec) { if (arch_arm_handle_access_flag_fault(far, fsr, isWrite, isExec)) return; Thread *thread = thread_get_current_thread(); bool isUser = (frame->spsr & CPSR_MODE_MASK) == CPSR_MODE_USR; addr_t newip = 0; #ifdef TRACE_ARCH_INT print_iframe("Page Fault", frame); dprintf("FAR: %08lx, FSR: %08x, isUser: %d, isWrite: %d, isExec: %d, thread: %s\n", far, fsr, isUser, isWrite, isExec, thread->name); #endif IFrameScope scope(frame); if (debug_debugger_running()) { // If this CPU or this thread has a fault handler, we're allowed to be // here. if (thread != NULL) { cpu_ent* cpu = &gCPU[smp_get_current_cpu()]; if (cpu->fault_handler != 0) { debug_set_page_fault_info(far, frame->pc, isWrite ? DEBUG_PAGE_FAULT_WRITE : 0); frame->svc_sp = cpu->fault_handler_stack_pointer; frame->pc = cpu->fault_handler; return; } if (thread->fault_handler != 0) { kprintf("ERROR: thread::fault_handler used in kernel " "debugger!\n"); debug_set_page_fault_info(far, frame->pc, isWrite ? DEBUG_PAGE_FAULT_WRITE : 0); frame->pc = reinterpret_cast(thread->fault_handler); return; } } // otherwise, not really panic("page fault in debugger without fault handler! Touching " "address %p from pc %p\n", (void *)far, (void *)frame->pc); return; } else if (isExec && !isUser && (far < KERNEL_BASE) && (((fsr & 0x060f) == FSR_FS_PERMISSION_FAULT_L1) || ((fsr & 0x060f) == FSR_FS_PERMISSION_FAULT_L2))) { panic("PXN violation trying to execute user-mapped address 0x%08" B_PRIxADDR " from kernel mode\n", far); } else if (!isExec && ((fsr & 0x060f) == FSR_FS_ALIGNMENT_FAULT)) { panic("unhandled alignment exception\n"); } else if ((fsr & 0x060f) == FSR_FS_ACCESS_FLAG_FAULT) { panic("unhandled access flag fault\n"); } else if ((frame->spsr & CPSR_I) != 0) { // interrupts disabled // If a page fault handler is installed, we're allowed to be here. // TODO: Now we are generally allowing user_memcpy() with interrupts // disabled, which in most cases is a bug. We should add some thread // flag allowing to explicitly indicate that this handling is desired. uintptr_t handler = reinterpret_cast(thread->fault_handler); if (thread && thread->fault_handler != 0) { if (frame->pc != handler) { frame->pc = handler; return; } // The fault happened at the fault handler address. This is a // certain infinite loop. panic("page fault, interrupts disabled, fault handler loop. " "Touching address %p from pc %p\n", (void*)far, (void*)frame->pc); } // If we are not running the kernel startup the page fault was not // allowed to happen and we must panic. panic("page fault, but interrupts were disabled. Touching address " "%p from pc %p\n", (void *)far, (void *)frame->pc); return; } else if (thread != NULL && thread->page_faults_allowed < 1) { panic("page fault not allowed at this place. Touching address " "%p from pc %p\n", (void *)far, (void *)frame->pc); return; } enable_interrupts(); vm_page_fault(far, frame->pc, isWrite, isExec, isUser, &newip); if (newip != 0) { // the page fault handler wants us to modify the iframe to set the // IP the cpu will return to to be this ip frame->pc = newip; } } extern "C" void arch_arm_data_abort(struct iframe *frame) { addr_t dfar = arm_get_dfar(); uint32 dfsr = arm_get_dfsr(); bool isWrite = (dfsr & FSR_WNR) == FSR_WNR; arch_arm_page_fault(frame, dfar, dfsr, isWrite, false); } extern "C" void arch_arm_prefetch_abort(struct iframe *frame) { addr_t ifar = arm_get_ifar(); uint32 ifsr = arm_get_ifsr(); arch_arm_page_fault(frame, ifar, ifsr, false, true); } extern "C" void arch_arm_irq(struct iframe *iframe) { IFrameScope scope(iframe); InterruptController *ic = InterruptController::Get(); if (ic != NULL) ic->HandleInterrupt(); Thread* thread = thread_get_current_thread(); cpu_status state = disable_interrupts(); if (thread->post_interrupt_callback != NULL) { void (*callback)(void*) = thread->post_interrupt_callback; void* data = thread->post_interrupt_data; thread->post_interrupt_callback = NULL; thread->post_interrupt_data = NULL; restore_interrupts(state); callback(data); } else if (thread->cpu->invoke_scheduler) { SpinLocker schedulerLocker(thread->scheduler_lock); scheduler_reschedule(B_THREAD_READY); schedulerLocker.Unlock(); restore_interrupts(state); } } extern "C" void arch_arm_fiq(struct iframe *iframe) { IFrameScope scope(iframe); panic("FIQ not implemented yet!"); }