diff --git a/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_interrupt_controller.hpp b/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_interrupt_controller.hpp index ce276c1a7..cf1761a5d 100644 --- a/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_interrupt_controller.hpp +++ b/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_interrupt_controller.hpp @@ -213,7 +213,7 @@ namespace ams::kern::arch::arm64 { this->gicc->eoir = irq; } - bool IsInterruptDefined(s32 irq) { + bool IsInterruptDefined(s32 irq) const { const s32 num_interrupts = std::min(32 + 32 * (this->gicd->typer & 0x1F), static_cast(NumInterrupts)); return (0 <= irq && irq < num_interrupts); } diff --git a/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_interrupt_manager.hpp b/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_interrupt_manager.hpp index 2ecf5ebd5..5fbccd4b8 100644 --- a/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_interrupt_manager.hpp +++ b/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_interrupt_manager.hpp @@ -67,10 +67,18 @@ namespace ams::kern::arch::arm64 { NOINLINE void Initialize(s32 core_id); NOINLINE void Finalize(s32 core_id); - bool IsInterruptDefined(s32 irq) { + bool IsInterruptDefined(s32 irq) const { return this->interrupt_controller.IsInterruptDefined(irq); } + bool IsGlobal(s32 irq) const { + return this->interrupt_controller.IsGlobal(irq); + } + + bool IsLocal(s32 irq) const { + return this->interrupt_controller.IsLocal(irq); + } + NOINLINE Result BindHandler(KInterruptHandler *handler, s32 irq, s32 core_id, s32 priority, bool manual_clear, bool level); NOINLINE Result UnbindHandler(s32 irq, s32 core); diff --git a/libraries/libmesosphere/include/mesosphere/kern_k_capabilities.hpp b/libraries/libmesosphere/include/mesosphere/kern_k_capabilities.hpp index 72d48666a..7aab28a84 100644 --- a/libraries/libmesosphere/include/mesosphere/kern_k_capabilities.hpp +++ b/libraries/libmesosphere/include/mesosphere/kern_k_capabilities.hpp @@ -230,7 +230,7 @@ namespace ams::kern { } } - bool SetInterruptAllowed(u32 id) { + bool SetInterruptPermitted(u32 id) { constexpr size_t BitsPerWord = BITSIZEOF(this->irq_access_flags[0]); if (id < BITSIZEOF(this->irq_access_flags)) { this->irq_access_flags[id / BitsPerWord] = (1ul << (id % BitsPerWord)); @@ -274,6 +274,15 @@ namespace ams::kern { } } + constexpr bool IsPermittedInterrupt(u32 id) const { + constexpr size_t BitsPerWord = BITSIZEOF(this->irq_access_flags[0]); + if (id < BITSIZEOF(this->irq_access_flags)) { + return (this->irq_access_flags[id / BitsPerWord] & (1ul << (id % BitsPerWord))) != 0; + } else { + return false; + } + } + /* TODO: Member functions. */ }; diff --git a/libraries/libmesosphere/include/mesosphere/kern_k_interrupt_event.hpp b/libraries/libmesosphere/include/mesosphere/kern_k_interrupt_event.hpp index 15f102087..326994b10 100644 --- a/libraries/libmesosphere/include/mesosphere/kern_k_interrupt_event.hpp +++ b/libraries/libmesosphere/include/mesosphere/kern_k_interrupt_event.hpp @@ -26,13 +26,40 @@ namespace ams::kern { class KInterruptEvent final : public KAutoObjectWithSlabHeapAndContainer { MESOSPHERE_AUTOOBJECT_TRAITS(KInterruptEvent, KReadableEvent); + private: + KInterruptEventTask *task; + s32 interrupt_id; + bool is_initialized; public: - /* TODO: This is a placeholder definition. */ + constexpr KInterruptEvent() : task(nullptr), interrupt_id(-1), is_initialized(false) { /* ... */ } + virtual ~KInterruptEvent() { /* ... */ } + + Result Initialize(int32_t interrupt_name, ams::svc::InterruptType type); + virtual void Finalize() override; + + virtual Result Reset() override; + + virtual bool IsInitialized() const override { return this->is_initialized; } + + static void PostDestroy(uintptr_t arg) { /* ... */ } + + constexpr s32 GetInterruptId() const { return this->interrupt_id; } }; class KInterruptEventTask : public KSlabAllocated, public KInterruptTask { + private: + KInterruptEvent *event; + s32 interrupt_id; public: - /* TODO: This is a placeholder definition. */ + constexpr KInterruptEventTask() : event(nullptr), interrupt_id(-1) { /* ... */ } + ~KInterruptEventTask() { /* ... */ } + + virtual KInterruptTask *OnInterrupt(s32 interrupt_id) override; + virtual void DoTask() override; + + void Unregister(); + public: + static Result Register(KInterruptEventTask **out, s32 interrupt_id, bool level, KInterruptEvent *event); }; } diff --git a/libraries/libmesosphere/include/mesosphere/kern_k_process.hpp b/libraries/libmesosphere/include/mesosphere/kern_k_process.hpp index a730aef4d..805f94650 100644 --- a/libraries/libmesosphere/include/mesosphere/kern_k_process.hpp +++ b/libraries/libmesosphere/include/mesosphere/kern_k_process.hpp @@ -147,6 +147,10 @@ namespace ams::kern { return this->is_suspended; } + constexpr bool IsPermittedInterrupt(int32_t interrupt_id) const { + return this->capabilities.IsPermittedInterrupt(interrupt_id); + } + bool EnterUserException(); bool LeaveUserException(); bool ReleaseUserException(KThread *thread); diff --git a/libraries/libmesosphere/source/kern_k_capabilities.cpp b/libraries/libmesosphere/source/kern_k_capabilities.cpp index 871721108..d54d3b5cc 100644 --- a/libraries/libmesosphere/source/kern_k_capabilities.cpp +++ b/libraries/libmesosphere/source/kern_k_capabilities.cpp @@ -164,7 +164,7 @@ namespace ams::kern { for (size_t i = 0; i < util::size(ids); i++) { if (ids[i] != PaddingInterruptId) { R_UNLESS(Kernel::GetInterruptManager().IsInterruptDefined(ids[i]), svc::ResultOutOfRange()); - R_UNLESS(this->SetInterruptAllowed(ids[i]), svc::ResultOutOfRange()); + R_UNLESS(this->SetInterruptPermitted(ids[i]), svc::ResultOutOfRange()); } } diff --git a/libraries/libmesosphere/source/kern_k_interrupt_event.cpp b/libraries/libmesosphere/source/kern_k_interrupt_event.cpp new file mode 100644 index 000000000..b8e24f699 --- /dev/null +++ b/libraries/libmesosphere/source/kern_k_interrupt_event.cpp @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +namespace ams::kern { + + namespace { + + constinit KLightLock g_interrupt_event_lock; + constinit KInterruptEventTask *g_interrupt_event_task_table[KInterruptController::NumInterrupts] = {}; + + } + + Result KInterruptEvent::Initialize(int32_t interrupt_name, ams::svc::InterruptType type) { + MESOSPHERE_ASSERT_THIS(); + + /* Set interrupt id. */ + this->interrupt_id = interrupt_name; + + /* Initialize readable event base. */ + KReadableEvent::Initialize(nullptr); + + /* Try to register the task. */ + R_TRY(KInterruptEventTask::Register(std::addressof(this->task), this->interrupt_id, type == ams::svc::InterruptType_Level, this)); + + /* Mark initialized. */ + this->is_initialized = true; + return ResultSuccess(); + } + + void KInterruptEvent::Finalize() { + MESOSPHERE_ASSERT_THIS(); + + MESOSPHERE_ASSERT(this->task != nullptr); + this->task->Unregister(); + } + + Result KInterruptEvent::Reset() { + MESOSPHERE_ASSERT_THIS(); + + /* Lock the task table. */ + KScopedLightLock lk(g_interrupt_event_lock); + + /* Clear the event. */ + R_TRY(KReadableEvent::Reset()); + + /* Clear the interrupt. */ + Kernel::GetInterruptManager().ClearInterrupt(this->interrupt_id); + + return ResultSuccess(); + } + + Result KInterruptEventTask::Register(KInterruptEventTask **out, s32 interrupt_id, bool level, KInterruptEvent *event) { + /* Verify the interrupt id is defined and global. */ + R_UNLESS(Kernel::GetInterruptManager().IsInterruptDefined(interrupt_id), svc::ResultOutOfRange()); + R_UNLESS(Kernel::GetInterruptManager().IsGlobal(interrupt_id), svc::ResultOutOfRange()); + + /* Lock the task table. */ + KScopedLightLock lk(g_interrupt_event_lock); + + /* Get a task for the id. */ + bool allocated = false; + KInterruptEventTask *task = g_interrupt_event_task_table[interrupt_id]; + if (task != nullptr) { + /* Check that there's not already an event for this task. */ + R_UNLESS(task->event == nullptr, svc::ResultBusy()); + } else { + /* Allocate a new task. */ + task = KInterruptEventTask::Allocate(); + R_UNLESS(task != nullptr, svc::ResultOutOfResource()); + + allocated = true; + } + + /* Register/bind the interrupt task. */ + { + /* Ensure that the task is cleaned up if anything goes wrong. */ + auto task_guard = SCOPE_GUARD { if (allocated) { KInterruptEventTask::Free(task); } }; + + /* Bind the interrupt handler. */ + R_TRY(Kernel::GetInterruptManager().BindHandler(task, interrupt_id, GetCurrentCoreId(), KInterruptController::PriorityLevel_High, true, level)); + + /* We successfully registered, so we don't need to free the task. */ + task_guard.Cancel(); + } + + /* Set the event. */ + task->event = event; + + /* If we allocated, set the event in the table. */ + if (allocated) { + task->interrupt_id = interrupt_id; + g_interrupt_event_task_table[interrupt_id] = task; + } + + /* Set the output. */ + *out = task; + return ResultSuccess(); + } + + void KInterruptEventTask::Unregister() { + MESOSPHERE_ASSERT_THIS(); + + /* Lock the task table. */ + KScopedLightLock lk(g_interrupt_event_lock); + + /* Ensure we can unregister. */ + MESOSPHERE_ABORT_UNLESS(g_interrupt_event_task_table[this->interrupt_id] == this); + MESOSPHERE_ABORT_UNLESS(this->event != nullptr); + this->event = nullptr; + + /* Unbind the interrupt. */ + Kernel::GetInterruptManager().UnbindHandler(this->interrupt_id, GetCurrentCoreId()); + } + + KInterruptTask *KInterruptEventTask::OnInterrupt(s32 interrupt_id) { + MESOSPHERE_ASSERT_THIS(); + + return this; + } + + void KInterruptEventTask::DoTask() { + MESOSPHERE_ASSERT_THIS(); + + /* Lock the task table. */ + KScopedLightLock lk(g_interrupt_event_lock); + + if (this->event != nullptr) { + this->event->Signal(); + } + } +} diff --git a/libraries/libmesosphere/source/svc/kern_svc_interrupt_event.cpp b/libraries/libmesosphere/source/svc/kern_svc_interrupt_event.cpp index f724e223e..bc9975019 100644 --- a/libraries/libmesosphere/source/svc/kern_svc_interrupt_event.cpp +++ b/libraries/libmesosphere/source/svc/kern_svc_interrupt_event.cpp @@ -21,20 +21,58 @@ namespace ams::kern::svc { namespace { + constexpr bool IsValidInterruptType(ams::svc::InterruptType type) { + switch (type) { + case ams::svc::InterruptType_Edge: + case ams::svc::InterruptType_Level: + return true; + default: + return false; + } + } + Result CreateInterruptEvent(ams::svc::Handle *out, int32_t interrupt_id, ams::svc::InterruptType type) { + /* Validate the type. */ + R_UNLESS(IsValidInterruptType(type), svc::ResultInvalidEnumValue()); + + /* Check whether the interrupt is allowed. */ + auto &process = GetCurrentProcess(); + R_UNLESS(process.IsPermittedInterrupt(interrupt_id), svc::ResultNotFound()); + + /* Get the current handle table. */ + auto &handle_table = process.GetHandleTable(); + + /* Create the interrupt event. */ + KInterruptEvent *event = KInterruptEvent::Create(); + R_UNLESS(event != nullptr, svc::ResultOutOfResource()); + + /* Ensure that we cleanup the event on exit, leaving the only reference as the one in the handle table. */ + ON_SCOPE_EXIT { event->Close(); }; + + /* Initialize the event. */ + R_TRY(event->Initialize(interrupt_id, type)); + + /* Register the event. */ + R_TRY(KInterruptEvent::Register(event)); + + /* Add the event to the handle table. */ + R_TRY(handle_table.Add(out, event)); + + return ResultSuccess(); + } } /* ============================= 64 ABI ============================= */ Result CreateInterruptEvent64(ams::svc::Handle *out_read_handle, int32_t interrupt_id, ams::svc::InterruptType interrupt_type) { - MESOSPHERE_PANIC("Stubbed SvcCreateInterruptEvent64 was called."); + return CreateInterruptEvent(out_read_handle, interrupt_id, interrupt_type); } /* ============================= 64From32 ABI ============================= */ Result CreateInterruptEvent64From32(ams::svc::Handle *out_read_handle, int32_t interrupt_id, ams::svc::InterruptType interrupt_type) { - MESOSPHERE_PANIC("Stubbed SvcCreateInterruptEvent64From32 was called."); + return CreateInterruptEvent(out_read_handle, interrupt_id, interrupt_type); } }