From 5be38d0702440c2b3ccb08a207caa4e5fe8baa93 Mon Sep 17 00:00:00 2001 From: Bananymous Date: Wed, 10 Apr 2024 03:05:27 +0300 Subject: [PATCH] Kernel: My AML parser can now enable ACPI mode on QEMU! --- kernel/include/kernel/ACPI/ACPI.h | 6 + kernel/include/kernel/ACPI/AML/Field.h | 13 + kernel/include/kernel/ACPI/AML/Method.h | 17 +- kernel/include/kernel/ACPI/AML/Namespace.h | 3 + kernel/include/kernel/ACPI/AML/ParseContext.h | 1 + kernel/kernel/ACPI/ACPI.cpp | 60 +++++ kernel/kernel/ACPI/AML/Field.cpp | 222 +++++++++++++++--- kernel/kernel/ACPI/AML/Node.cpp | 2 +- kernel/kernel/kernel.cpp | 5 +- 9 files changed, 295 insertions(+), 34 deletions(-) diff --git a/kernel/include/kernel/ACPI/ACPI.h b/kernel/include/kernel/ACPI/ACPI.h index 323677af55..06fc2811ce 100644 --- a/kernel/include/kernel/ACPI/ACPI.h +++ b/kernel/include/kernel/ACPI/ACPI.h @@ -16,6 +16,12 @@ namespace Kernel::ACPI const SDTHeader* get_header(BAN::StringView signature, uint32_t index); + // mode + // 0: PIC + // 1: APIC + // 2: SAPIC + BAN::ErrorOr enter_acpi_mode(uint8_t mode); + private: ACPI() = default; BAN::ErrorOr initialize_impl(); diff --git a/kernel/include/kernel/ACPI/AML/Field.h b/kernel/include/kernel/ACPI/AML/Field.h index ba78731f9b..9367393e3f 100644 --- a/kernel/include/kernel/ACPI/AML/Field.h +++ b/kernel/include/kernel/ACPI/AML/Field.h @@ -54,8 +54,21 @@ namespace Kernel::ACPI::AML {} BAN::RefPtr evaluate() override; + bool store(BAN::RefPtr source) override; void debug_print(int indent) const override; + + private: + struct AccessType + { + uint64_t offset; + uint64_t mask; + uint32_t access_size; + uint32_t shift; + }; + BAN::Optional determine_access_type() const; + BAN::Optional read_field(uint64_t offset, uint32_t access_size) const; + bool write_field(uint64_t offset, uint32_t access_size, uint64_t value) const; }; struct Field diff --git a/kernel/include/kernel/ACPI/AML/Method.h b/kernel/include/kernel/ACPI/AML/Method.h index 4bb565629e..d5715efe6a 100644 --- a/kernel/include/kernel/ACPI/AML/Method.h +++ b/kernel/include/kernel/ACPI/AML/Method.h @@ -13,6 +13,7 @@ namespace Kernel::ACPI::AML { using Arguments = BAN::Array, 7>; + Mutex mutex; uint8_t arg_count; bool serialized; uint8_t sync_level; @@ -68,17 +69,23 @@ namespace Kernel::ACPI::AML return ParseResult::Success; } - BAN::Optional> evaluate(Arguments args) + BAN::Optional> evaluate(Arguments args, uint8_t old_sync_level = 0) { ParseContext context; context.aml_data = term_list; context.scope = scope; context.method_args = args; + context.sync_level = old_sync_level; for (auto& local : context.method_locals) local = MUST(BAN::RefPtr::create()); - BAN::Optional> return_value; + if (serialized) + { + mutex.lock(); + context.sync_level = BAN::Math::max(sync_level, old_sync_level); + } + BAN::Optional> return_value; while (context.aml_data.size() > 0) { auto parse_result = AML::parse_object(context); @@ -88,7 +95,10 @@ namespace Kernel::ACPI::AML break; } if (!parse_result.success()) + { + AML_ERROR("Method {} evaluate failed", scope); break; + } } while (!context.created_objects.empty()) @@ -97,6 +107,9 @@ namespace Kernel::ACPI::AML context.created_objects.pop_back(); } + if (serialized) + mutex.unlock(); + return return_value; } diff --git a/kernel/include/kernel/ACPI/AML/Namespace.h b/kernel/include/kernel/ACPI/AML/Namespace.h index c46a0840bd..21063d08a1 100644 --- a/kernel/include/kernel/ACPI/AML/Namespace.h +++ b/kernel/include/kernel/ACPI/AML/Namespace.h @@ -1,12 +1,15 @@ #pragma once #include +#include namespace Kernel::ACPI::AML { struct Namespace : public AML::Scope { + Mutex global_lock; + static BAN::RefPtr root_namespace(); Namespace(NameSeg name) : AML::Scope(Node::Type::Namespace, name) {} diff --git a/kernel/include/kernel/ACPI/AML/ParseContext.h b/kernel/include/kernel/ACPI/AML/ParseContext.h index 1c74e9b84c..3440ad5bba 100644 --- a/kernel/include/kernel/ACPI/AML/ParseContext.h +++ b/kernel/include/kernel/ACPI/AML/ParseContext.h @@ -20,6 +20,7 @@ namespace Kernel::ACPI::AML // we don't really need large contiguous memory BAN::LinkedList created_objects; + uint8_t sync_level { 0 }; BAN::Array, 7> method_args; BAN::Array, 8> method_locals; }; diff --git a/kernel/kernel/ACPI/ACPI.cpp b/kernel/kernel/ACPI/ACPI.cpp index 5628951045..86bf9d6490 100644 --- a/kernel/kernel/ACPI/ACPI.cpp +++ b/kernel/kernel/ACPI/ACPI.cpp @@ -2,6 +2,9 @@ #include #include #include +#include +#include +#include #include #include @@ -249,4 +252,61 @@ namespace Kernel::ACPI return nullptr; } + BAN::ErrorOr ACPI::enter_acpi_mode(uint8_t mode) + { + if (!m_namespace) + { + dwarnln("ACPI namespace not initialized"); + return BAN::Error::from_errno(EFAULT); + } + + // Evaluate \\_SB._INI + auto _sb_ini = m_namespace->find_object({}, AML::NameString("\\_SB._INI")); + if (_sb_ini && _sb_ini->type == AML::Node::Type::Method) + { + auto* method = static_cast(_sb_ini.ptr()); + if (method->arg_count != 0) + { + dwarnln("Method \\_SB._INI has {} arguments, expected 0", method->arg_count); + return BAN::Error::from_errno(EINVAL); + } + method->evaluate({}); + } + + // Initialize devices + auto _sb = m_namespace->find_object({}, AML::NameString("\\_SB")); + if (_sb && _sb->is_scope()) + { + auto* scope = static_cast(_sb.ptr()); + for (auto& [name, object] : scope->objects) + { + if (object->type == AML::Node::Type::Device) + { + auto* device = static_cast(object.ptr()); + device->initialize(); + } + } + } + + // Evaluate \\_PIC (mode) + auto _pic = m_namespace->find_object({}, AML::NameString("\\_PIC")); + if (_pic && _pic->type == AML::Node::Type::Method) + { + auto* method = static_cast(_pic.ptr()); + if (method->arg_count != 1) + { + dwarnln("Method \\_PIC has {} arguments, expected 1", method->arg_count); + return BAN::Error::from_errno(EINVAL); + } + + AML::Method::Arguments args; + args[0] = MUST(BAN::RefPtr::create(MUST(BAN::RefPtr::create(mode)))); + method->evaluate(args); + } + + dprintln("Entered ACPI mode"); + + return {}; + } + } diff --git a/kernel/kernel/ACPI/AML/Field.cpp b/kernel/kernel/ACPI/AML/Field.cpp index 9bce4fb8e6..8d59f97a7c 100644 --- a/kernel/kernel/ACPI/AML/Field.cpp +++ b/kernel/kernel/ACPI/AML/Field.cpp @@ -1,5 +1,8 @@ +#include #include #include +#include +#include namespace Kernel::ACPI { @@ -134,10 +137,9 @@ namespace Kernel::ACPI return ParseResult::Success; } - BAN::RefPtr AML::FieldElement::evaluate() - { - // Field LockRule only applies to modifying the field, not reading it + BAN::Optional AML::FieldElement::determine_access_type() const + { uint32_t access_size = 0; switch (access_rules.access_type) { @@ -155,51 +157,213 @@ namespace Kernel::ACPI access_size = 8; break; case FieldRules::AccessType::Buffer: - AML_TODO("FieldElement evaluate with access type Buffer"); + AML_TODO("FieldElement with access type Buffer"); return {}; } + uint64_t byte_offset = op_region->region_offset + (bit_offset / 8); + if (auto rem = byte_offset % access_size) + byte_offset -= rem; + + if ((bit_offset % access_size) + bit_count > access_size * 8) + { + AML_ERROR("FieldElement over multiple access sizes"); + return {}; + } + + if (byte_offset + access_size > op_region->region_offset + op_region->region_length) + { + AML_ERROR("FieldElement out of bounds"); + return {}; + } + + uint32_t shift = bit_offset % access_size; + uint64_t mask = ((uint64_t)1 << bit_count) - 1; + + return AccessType { + .offset = byte_offset, + .mask = mask, + .access_size = access_size, + .shift = shift + }; + } + + BAN::Optional AML::FieldElement::read_field(uint64_t access_offset, uint32_t access_size) const + { switch (op_region->region_space) { case OpRegion::RegionSpace::SystemMemory: { - uint64_t byte_offset = op_region->region_offset + (bit_offset / 8); - if (auto rem = byte_offset % access_size) - byte_offset -= rem; - - if ((bit_offset % access_size) + bit_count > access_size * 8) - { - AML_ERROR("FieldElement evaluate over multiple access sizes"); - return {}; - } - - if (byte_offset + access_size > op_region->region_offset + op_region->region_length) - { - AML_ERROR("FieldElement evaluate out of bounds"); - return {}; - } - uint64_t result = 0; - PageTable::with_fast_page(byte_offset & PAGE_ADDR_MASK, [&] { + PageTable::with_fast_page(access_offset & PAGE_ADDR_MASK, [&] { switch (access_size) { - case 1: result = PageTable::fast_page_as_sized ((byte_offset % PAGE_SIZE) / access_size); break; - case 2: result = PageTable::fast_page_as_sized((byte_offset % PAGE_SIZE) / access_size); break; - case 4: result = PageTable::fast_page_as_sized((byte_offset % PAGE_SIZE) / access_size); break; - case 8: result = PageTable::fast_page_as_sized((byte_offset % PAGE_SIZE) / access_size); break; + case 1: result = PageTable::fast_page_as_sized ((access_offset % PAGE_SIZE) / access_size); break; + case 2: result = PageTable::fast_page_as_sized((access_offset % PAGE_SIZE) / access_size); break; + case 4: result = PageTable::fast_page_as_sized((access_offset % PAGE_SIZE) / access_size); break; + case 8: result = PageTable::fast_page_as_sized((access_offset % PAGE_SIZE) / access_size); break; } }); + return result; + } + case OpRegion::RegionSpace::SystemIO: + { + uint64_t result = 0; + switch (access_size) + { + case 1: result = IO::inb(access_offset); break; + case 2: result = IO::inw(access_offset); break; + case 4: result = IO::inl(access_offset); break; + default: + AML_ERROR("FieldElement read_field (SystemIO) with access size {}", access_size); + return {}; + } + return result; + } + case OpRegion::RegionSpace::PCIConfig: + { + // https://uefi.org/htmlspecs/ACPI_Spec_6_4_html/05_ACPI_Software_Programming_Model/ACPI_Software_Programming_Model.html#address-space-format + // PCI configuration space is confined to segment 0, bus 0 - result >>= bit_offset % access_size; - result &= ((uint64_t)1 << bit_count) - 1; - return MUST(BAN::RefPtr::create(result)); + uint16_t device = (access_offset >> 32) & 0xFFFF; + uint16_t function = (access_offset >> 16) & 0xFFFF; + uint16_t offset = access_offset & 0xFFFF; + + uint64_t result = 0; + switch (access_size) + { + case 1: result = PCI::PCIManager::read_config_byte(0, device, function, offset); break; + case 2: result = PCI::PCIManager::read_config_word(0, device, function, offset); break; + case 4: result = PCI::PCIManager::read_config_dword(0, device, function, offset); break; + default: + AML_ERROR("FieldElement read_field (PCIConfig) with access size {}", access_size); + return {}; + } + return result; } default: - AML_TODO("FieldElement evaluate with region space {}", static_cast(op_region->region_space)); + AML_TODO("FieldElement read_field with region space {}", static_cast(op_region->region_space)); return {}; } } + bool AML::FieldElement::write_field(uint64_t access_offset, uint32_t access_size, uint64_t value) const + { + switch (op_region->region_space) + { + case OpRegion::RegionSpace::SystemMemory: + { + PageTable::with_fast_page(access_offset & PAGE_ADDR_MASK, [&] { + switch (access_size) + { + case 1: PageTable::fast_page_as_sized ((access_offset % PAGE_SIZE) / access_size) = value; break; + case 2: PageTable::fast_page_as_sized((access_offset % PAGE_SIZE) / access_size) = value; break; + case 4: PageTable::fast_page_as_sized((access_offset % PAGE_SIZE) / access_size) = value; break; + case 8: PageTable::fast_page_as_sized((access_offset % PAGE_SIZE) / access_size) = value; break; + } + }); + return true; + } + case OpRegion::RegionSpace::SystemIO: + { + switch (access_size) + { + case 1: IO::outb(access_offset, value); break; + case 2: IO::outw(access_offset, value); break; + case 4: IO::outl(access_offset, value); break; + default: + AML_ERROR("FieldElement write_field (SystemIO) with access size {}", access_size); + return false; + } + return true; + } + case OpRegion::RegionSpace::PCIConfig: + { + // https://uefi.org/htmlspecs/ACPI_Spec_6_4_html/05_ACPI_Software_Programming_Model/ACPI_Software_Programming_Model.html#address-space-format + // PCI configuration space is confined to segment 0, bus 0 + + uint16_t device = (access_offset >> 32) & 0xFFFF; + uint16_t function = (access_offset >> 16) & 0xFFFF; + uint16_t offset = access_offset & 0xFFFF; + + switch (access_size) + { + case 1: PCI::PCIManager::write_config_byte(0, device, function, offset, value); break; + case 2: PCI::PCIManager::write_config_word(0, device, function, offset, value); break; + case 4: PCI::PCIManager::write_config_dword(0, device, function, offset, value); break; + default: + AML_ERROR("FieldElement write_field (PCIConfig) with access size {}", access_size); + return false; + } + return true; + } + default: + AML_TODO("FieldElement write_field with region space {}", static_cast(op_region->region_space)); + return false; + } + } + + BAN::RefPtr AML::FieldElement::evaluate() + { + // Field LockRule only applies to modifying the field, not reading it + + auto access_type = determine_access_type(); + if (!access_type.has_value()) + return {}; + + auto result = read_field(access_type->offset, access_type->access_size); + if (!result.has_value()) + return {}; + + return MUST(BAN::RefPtr::create((result.value() >> access_type->shift) & access_type->mask)); + } + + bool AML::FieldElement::store(BAN::RefPtr source) + { + auto source_integer = source->as_integer(); + if (!source_integer.has_value()) + { + AML_TODO("FieldElement store with non-integer source, type {}", static_cast(source->type)); + return false; + } + + auto access_type = determine_access_type(); + if (!access_type.has_value()) + return false; + + if (access_rules.lock_rule == FieldRules::LockRule::Lock) + Namespace::root_namespace()->global_lock.lock(); + BAN::ScopeGuard unlock_guard([&] { + if (access_rules.lock_rule == FieldRules::LockRule::Lock) + Namespace::root_namespace()->global_lock.unlock(); + }); + + uint64_t to_write = 0; + switch (access_rules.update_rule) + { + case FieldRules::UpdateRule::Preserve: + { + auto read_result = read_field(access_type->offset, access_type->access_size); + if (!read_result.has_value()) + return false; + to_write = read_result.value(); + to_write &= ~(access_type->mask << access_type->shift); + to_write |= (source_integer.value() & access_type->mask) << access_type->shift; + break; + } + case FieldRules::UpdateRule::WriteAsOnes: + to_write = ~(access_type->mask << access_type->shift); + to_write |= (source_integer.value() & access_type->mask) << access_type->shift; + break; + case FieldRules::UpdateRule::WriteAsZeros: + to_write = 0; + to_write |= (source_integer.value() & access_type->mask) << access_type->shift; + break; + } + + return write_field(access_type->offset, access_type->access_size, to_write); + } + void AML::FieldElement::debug_print(int indent) const { AML_DEBUG_PRINT_INDENT(indent); diff --git a/kernel/kernel/ACPI/AML/Node.cpp b/kernel/kernel/ACPI/AML/Node.cpp index f3c7956473..cf6e4e8cb3 100644 --- a/kernel/kernel/ACPI/AML/Node.cpp +++ b/kernel/kernel/ACPI/AML/Node.cpp @@ -183,7 +183,7 @@ namespace Kernel::ACPI args[i] = MUST(BAN::RefPtr::create(arg.node())); } - auto result = method->evaluate(args); + auto result = method->evaluate(args, context.sync_level); if (!result.has_value()) { AML_ERROR("Failed to evaluate {}", name_string.value()); diff --git a/kernel/kernel/kernel.cpp b/kernel/kernel/kernel.cpp index a4a47697c3..03f4c2ee84 100644 --- a/kernel/kernel/kernel.cpp +++ b/kernel/kernel/kernel.cpp @@ -154,6 +154,9 @@ extern "C" void kernel_main(uint32_t boot_magic, uint32_t boot_info) dprintln("Virtual TTY initialized"); } + if (ACPI::ACPI::get().enter_acpi_mode(InterruptController::get().is_using_apic()).is_error()) + dprintln("Failed to enter ACPI mode"); + Random::initialize(); dprintln("RNG initialized"); @@ -174,8 +177,6 @@ static void init2(void*) dprintln("Scheduler started"); - InterruptController::get().enter_acpi_mode(); - auto console = MUST(DevFileSystem::get().root_inode()->find_inode(cmdline.console)); ASSERT(console->is_tty()); ((TTY*)console.ptr())->set_as_current();