Kernel: Rework AML namespace and object hierarchy

Remove tree-like structure from AML. This allows more spec compliant
parsing of named objects inside not yet declared devices.

This also allows AML to be run thread safely. All object adds/removes
are now guarded by a mutex.
This commit is contained in:
Bananymous 2024-04-16 16:23:12 +03:00
parent b6587b32b9
commit f1b2d7530d
7 changed files with 179 additions and 143 deletions

View File

@ -42,11 +42,13 @@ namespace Kernel::ACPI::AML
AML_DEBUG_PRINT("Device "); AML_DEBUG_PRINT("Device ");
name.debug_print(); name.debug_print();
AML_DEBUG_PRINTLN(" {"); AML_DEBUG_PRINTLN(" {");
for (const auto& [name, object] : objects) Namespace::root_namespace()->for_each_child(scope,
{ [&](const auto&, const auto& child)
object->debug_print(indent + 1); {
AML_DEBUG_PRINTLN(""); child->debug_print(indent + 1);
} AML_DEBUG_PRINTLN("");
}
);
AML_DEBUG_PRINT_INDENT(indent); AML_DEBUG_PRINT_INDENT(indent);
AML_DEBUG_PRINT("}"); AML_DEBUG_PRINT("}");
} }

View File

@ -59,7 +59,7 @@ namespace Kernel::ACPI::AML
if (!method_scope.has_value()) if (!method_scope.has_value())
return ParseResult::Failure; return ParseResult::Failure;
method->term_list = method_pkg.value(); method->term_list = method_pkg.value();
method->scope = method_scope.release_value(); method->scope = AML::NameString(method_scope.release_value());
#if AML_DEBUG_LEVEL >= 2 #if AML_DEBUG_LEVEL >= 2
method->debug_print(0); method->debug_print(0);

View File

@ -36,6 +36,14 @@ namespace Kernel::ACPI::AML
aml_data = aml_data.slice(4); aml_data = aml_data.slice(4);
} }
BAN::StringView sv() const
{
size_t len = 4;
while (len > 0 && chars[len - 1] == '_')
len--;
return BAN::StringView(chars, len);
}
static BAN::Optional<NameSeg> parse(BAN::ConstByteSpan& aml_data) static BAN::Optional<NameSeg> parse(BAN::ConstByteSpan& aml_data)
{ {
if (aml_data.size() < 4) if (aml_data.size() < 4)

View File

@ -1,5 +1,6 @@
#pragma once #pragma once
#include <BAN/HashMap.h>
#include <kernel/ACPI/AML/Scope.h> #include <kernel/ACPI/AML/Scope.h>
#include <kernel/ACPI/Headers.h> #include <kernel/ACPI/Headers.h>
#include <kernel/Lock/Mutex.h> #include <kernel/Lock/Mutex.h>
@ -11,12 +12,34 @@ namespace Kernel::ACPI::AML
{ {
static BAN::RefPtr<AML::Namespace> root_namespace(); static BAN::RefPtr<AML::Namespace> root_namespace();
template<typename F>
static void for_each_child(const AML::NameString& scope, const F& callback)
{
auto canonical_path = root_namespace()->resolve_path({}, scope);
ASSERT(canonical_path.has_value());
for (auto& [path, child] : root_namespace()->m_objects)
{
if (path.size() < canonical_path->size() + 1)
continue;
if (path[canonical_path->size()] != '.')
continue;
if (path.sv().substring(0, canonical_path->size()) != canonical_path->sv())
continue;
if (path.sv().substring(canonical_path->size() + 1).contains('.'))
continue;
callback(path, child);
}
}
Namespace(NameSeg name) : AML::Scope(Node::Type::Namespace, name) {} Namespace(NameSeg name) : AML::Scope(Node::Type::Namespace, name) {}
static BAN::RefPtr<AML::Namespace> create_root_namespace(); static BAN::RefPtr<AML::Namespace> create_root_namespace();
bool parse(const SDTHeader& header); bool parse(const SDTHeader& header);
BAN::Optional<AML::NameString> resolve_path(const AML::NameString& relative_base, const AML::NameString& relative_path); void debug_print(int indent) const override;
BAN::Optional<BAN::String> resolve_path(const AML::NameString& relative_base, const AML::NameString& relative_path, bool allow_nonexistent = false);
// Find an object in the namespace. Returns nullptr if the object is not found. // Find an object in the namespace. Returns nullptr if the object is not found.
BAN::RefPtr<NamedObject> find_object(const AML::NameString& relative_base, const AML::NameString& relative_path); BAN::RefPtr<NamedObject> find_object(const AML::NameString& relative_base, const AML::NameString& relative_path);
@ -26,6 +49,10 @@ namespace Kernel::ACPI::AML
// Remove an object from the namespace. Returns false if the object could not be removed. // Remove an object from the namespace. Returns false if the object could not be removed.
bool remove_named_object(const AML::NameString& absolute_path); bool remove_named_object(const AML::NameString& absolute_path);
private:
BAN::HashMap<BAN::String, BAN::RefPtr<NamedObject>> m_objects;
mutable Mutex m_object_mutex;
}; };
} }

View File

@ -1,6 +1,5 @@
#pragma once #pragma once
#include <BAN/HashMap.h>
#include <kernel/ACPI/AML/NamedObject.h> #include <kernel/ACPI/AML/NamedObject.h>
#include <kernel/ACPI/AML/Names.h> #include <kernel/ACPI/AML/Names.h>
@ -9,7 +8,6 @@ namespace Kernel::ACPI::AML
struct Scope : public AML::NamedObject struct Scope : public AML::NamedObject
{ {
BAN::HashMap<NameSeg, BAN::RefPtr<NamedObject>> objects;
AML::NameString scope; AML::NameString scope;
Scope(Node::Type type, NameSeg name) Scope(Node::Type type, NameSeg name)
@ -19,7 +17,6 @@ namespace Kernel::ACPI::AML
virtual bool is_scope() const override { return true; } virtual bool is_scope() const override { return true; }
static ParseResult parse(ParseContext& context); static ParseResult parse(ParseContext& context);
virtual void debug_print(int indent) const override;
protected: protected:
ParseResult enter_context_and_parse_term_list(ParseContext& outer_context, const AML::NameString& name, BAN::ConstByteSpan aml_data); ParseResult enter_context_and_parse_term_list(ParseContext& outer_context, const AML::NameString& name, BAN::ConstByteSpan aml_data);

View File

@ -1,8 +1,10 @@
#include <kernel/ACPI/AML/Device.h>
#include <kernel/ACPI/AML/Integer.h> #include <kernel/ACPI/AML/Integer.h>
#include <kernel/ACPI/AML/Method.h> #include <kernel/ACPI/AML/Method.h>
#include <kernel/ACPI/AML/Namespace.h> #include <kernel/ACPI/AML/Namespace.h>
#include <kernel/ACPI/AML/ParseContext.h> #include <kernel/ACPI/AML/ParseContext.h>
#include <kernel/ACPI/AML/Region.h> #include <kernel/ACPI/AML/Region.h>
#include <kernel/Lock/LockGuard.h>
namespace Kernel::ACPI namespace Kernel::ACPI
{ {
@ -20,16 +22,33 @@ namespace Kernel::ACPI
return s_root_namespace; return s_root_namespace;
} }
BAN::Optional<AML::NameString> AML::Namespace::resolve_path(const AML::NameString& relative_base, const AML::NameString& relative_path) void AML::Namespace::debug_print(int indent) const
{ {
LockGuard _(m_object_mutex);
AML_DEBUG_PRINT_INDENT(indent);
AML_DEBUG_PRINTLN("Namespace {} {", name);
for (auto& [path, child] : m_objects)
{
child->debug_print(indent + 1);
AML_DEBUG_PRINTLN("");
}
AML_DEBUG_PRINT_INDENT(indent);
AML_DEBUG_PRINT("}");
}
BAN::Optional<BAN::String> AML::Namespace::resolve_path(const AML::NameString& relative_base, const AML::NameString& relative_path, bool allow_nonexistent)
{
LockGuard _(m_object_mutex);
// Base must be non-empty absolute path // Base must be non-empty absolute path
ASSERT(relative_base.prefix == "\\"sv || relative_base.path.empty()); ASSERT(relative_base.prefix == "\\"sv || relative_base.path.empty());
// Do absolute path lookup // Do absolute path lookup
if (!relative_path.prefix.empty() || relative_path.path.size() != 1) if (!relative_path.prefix.empty() || relative_path.path.size() != 1 || allow_nonexistent)
{ {
AML::NameString absolute_path; BAN::String absolute_path;
MUST(absolute_path.prefix.push_back('\\')); MUST(absolute_path.push_back('\\'));
// Resolve root and parent references // Resolve root and parent references
if (relative_path.prefix == "\\"sv) if (relative_path.prefix == "\\"sv)
@ -42,158 +61,132 @@ namespace Kernel::ACPI
return {}; return {};
} }
for (size_t i = 0; i < relative_base.path.size() - relative_path.prefix.size(); i++) for (size_t i = 0; i < relative_base.path.size() - relative_path.prefix.size(); i++)
MUST(absolute_path.path.push_back(relative_base.path[i])); {
MUST(absolute_path.append(relative_base.path[i].sv()));
MUST(absolute_path.push_back('.'));
}
} }
// Append relative path // Append relative path
for (const auto& seg : relative_path.path) for (const auto& seg : relative_path.path)
MUST(absolute_path.path.push_back(seg));
// Validate path
BAN::RefPtr<AML::NamedObject> current_node = this;
for (const auto& seg : absolute_path.path)
{ {
if (!current_node->is_scope()) MUST(absolute_path.append(seg.sv()));
return {}; MUST(absolute_path.push_back('.'));
auto* current_scope = static_cast<AML::Scope*>(current_node.ptr());
auto it = current_scope->objects.find(seg);
if (it == current_scope->objects.end())
return {};
current_node = it->value;
} }
return absolute_path;
if (absolute_path.back() == '.')
absolute_path.pop_back();
if (allow_nonexistent || m_objects.contains(absolute_path))
return absolute_path;
return {};
} }
// Resolve with namespace search rules (ACPI Spec 6.4 - Section 5.3) // Resolve with namespace search rules (ACPI Spec 6.4 - Section 5.3)
AML::NameString last_match_path;
AML::NameSeg target_seg = relative_path.path.back(); AML::NameSeg target_seg = relative_path.path.back();
BAN::RefPtr<AML::Scope> current_scope = this; BAN::String last_match_path;
AML::NameString current_path; BAN::String current_path;
MUST(current_path.push_back('\\'));
// Check root namespace // Check root namespace
{ {
// If scope contains object with the same name as the segment, update last match BAN::String tmp;
if (current_scope->objects.contains(target_seg)) MUST(tmp.append(current_path));
{ MUST(tmp.append(target_seg.sv()));
last_match_path = current_path; if (m_objects.contains(tmp))
MUST(last_match_path.path.push_back(target_seg)); last_match_path = BAN::move(tmp);
}
} }
// Check base base path // Check base base path
for (const auto& seg : relative_base.path) for (const auto& seg : relative_base.path)
{ {
auto next_node = current_scope->objects[seg]; MUST(current_path.append(seg.sv()));
ASSERT(next_node && next_node->is_scope()); MUST(current_path.push_back('.'));
current_scope = static_cast<AML::Scope*>(next_node.ptr()); BAN::String tmp;
MUST(current_path.path.push_back(seg)); MUST(tmp.append(current_path));
MUST(tmp.append(target_seg.sv()));
// If scope contains object with the same name as the segment, update last match if (m_objects.contains(tmp))
if (current_scope->objects.contains(target_seg)) last_match_path = BAN::move(tmp);
{
last_match_path = current_path;
MUST(last_match_path.path.push_back(target_seg));
}
} }
if (!last_match_path.path.empty()) if (!last_match_path.empty())
{
MUST(last_match_path.prefix.push_back('\\'));
return last_match_path; return last_match_path;
}
return {}; return {};
} }
BAN::RefPtr<AML::NamedObject> AML::Namespace::find_object(const AML::NameString& relative_base, const AML::NameString& relative_path) BAN::RefPtr<AML::NamedObject> AML::Namespace::find_object(const AML::NameString& relative_base, const AML::NameString& relative_path)
{ {
LockGuard _(m_object_mutex);
auto canonical_path = resolve_path(relative_base, relative_path); auto canonical_path = resolve_path(relative_base, relative_path);
if (!canonical_path.has_value()) if (!canonical_path.has_value())
return nullptr; return nullptr;
if (canonical_path->path.empty())
return this;
BAN::RefPtr<NamedObject> node = this; auto it = m_objects.find(canonical_path.value());
for (const auto& seg : canonical_path->path) if (it == m_objects.end())
{ return {};
// Resolve path validates that all nodes are scopes return it->value;
ASSERT(node->is_scope());
node = static_cast<Scope*>(node.ptr())->objects[seg];
}
return node;
} }
bool AML::Namespace::add_named_object(ParseContext& parse_context, const AML::NameString& object_path, BAN::RefPtr<NamedObject> object) bool AML::Namespace::add_named_object(ParseContext& parse_context, const AML::NameString& object_path, BAN::RefPtr<NamedObject> object)
{ {
LockGuard _(m_object_mutex);
ASSERT(!object_path.path.empty()); ASSERT(!object_path.path.empty());
ASSERT(object_path.path.back() == object->name); ASSERT(object_path.path.back() == object->name);
auto parent_path = object_path; auto canonical_path = resolve_path(parse_context.scope, object_path, true);
parent_path.path.pop_back(); ASSERT(canonical_path.has_value());
auto parent_object = find_object(parse_context.scope, parent_path); if (canonical_path->empty())
if (!parent_object)
{ {
AML_ERROR("Parent object not found"); AML_ERROR("Trying to add root namespace");
return false; return false;
} }
if (!parent_object->is_scope()) if (m_objects.contains(canonical_path.value()))
{ {
AML_ERROR("Parent object is not a scope"); AML_ERROR("Object '{}' already exists", canonical_path.value());
return false; return false;
} }
auto* parent_scope = static_cast<Scope*>(parent_object.ptr()); auto canonical_scope = AML::NameString(canonical_path.value());
if (parent_scope->objects.contains(object->name))
{
AML_ERROR("Object already exists");
return false;
}
object->parent = parent_scope; MUST(m_objects.insert(canonical_path.value(), object));
MUST(parent_scope->objects.insert(object->name, object));
auto canonical_scope = resolve_path(parse_context.scope, object_path);
ASSERT(canonical_scope.has_value());
if (object->is_scope()) if (object->is_scope())
{ {
auto* scope = static_cast<Scope*>(object.ptr()); auto* scope = static_cast<Scope*>(object.ptr());
scope->scope = canonical_scope.value(); scope->scope = canonical_scope;
} }
MUST(parse_context.created_objects.push_back(BAN::move(canonical_scope.release_value())));
MUST(parse_context.created_objects.push_back(canonical_scope));
return true; return true;
} }
bool AML::Namespace::remove_named_object(const AML::NameString& absolute_path) bool AML::Namespace::remove_named_object(const AML::NameString& absolute_path)
{ {
auto object = find_object({}, absolute_path); LockGuard _(m_object_mutex);
if (!object)
auto canonical_path = resolve_path({}, absolute_path);
if (!canonical_path.has_value())
{ {
AML_ERROR("Object {} not found", absolute_path); AML_ERROR("Trying to delete non-existent object '{}'", absolute_path);
return false; return false;
} }
if (object.ptr() == this) if (canonical_path->empty())
{ {
AML_ERROR("Trying to remove root object"); AML_ERROR("Trying to remove root namespace");
return false; return false;
} }
auto parent = object->parent; ASSERT(m_objects.contains(canonical_path.value()));
ASSERT(parent->is_scope()); m_objects.remove(canonical_path.value());
auto* parent_scope = static_cast<Scope*>(parent.ptr());
parent_scope->objects.remove(object->name);
return true; return true;
} }
@ -202,6 +195,8 @@ namespace Kernel::ACPI
{ {
ASSERT(!s_root_namespace); ASSERT(!s_root_namespace);
s_root_namespace = MUST(BAN::RefPtr<Namespace>::create(NameSeg("\\"sv))); s_root_namespace = MUST(BAN::RefPtr<Namespace>::create(NameSeg("\\"sv)));
s_root_namespace->scope = AML::NameString("\\"sv);
MUST(s_root_namespace->m_objects.insert("\\"sv, s_root_namespace));
Integer::Constants::Zero = MUST(BAN::RefPtr<Integer>::create(0, true)); Integer::Constants::Zero = MUST(BAN::RefPtr<Integer>::create(0, true));
Integer::Constants::One = MUST(BAN::RefPtr<Integer>::create(1, true)); Integer::Constants::One = MUST(BAN::RefPtr<Integer>::create(1, true));
@ -212,7 +207,7 @@ namespace Kernel::ACPI
// Add predefined namespaces // Add predefined namespaces
#define ADD_PREDEFIED_NAMESPACE(NAME) \ #define ADD_PREDEFIED_NAMESPACE(NAME) \
ASSERT(s_root_namespace->add_named_object(context, AML::NameString("\\" NAME), MUST(BAN::RefPtr<AML::Namespace>::create(NameSeg(NAME))))); ASSERT(s_root_namespace->add_named_object(context, AML::NameString("\\" NAME), MUST(BAN::RefPtr<AML::Device>::create(NameSeg(NAME)))));
ADD_PREDEFIED_NAMESPACE("_GPE"sv); ADD_PREDEFIED_NAMESPACE("_GPE"sv);
ADD_PREDEFIED_NAMESPACE("_PR"sv); ADD_PREDEFIED_NAMESPACE("_PR"sv);
ADD_PREDEFIED_NAMESPACE("_SB"sv); ADD_PREDEFIED_NAMESPACE("_SB"sv);

View File

@ -23,12 +23,12 @@ namespace Kernel::ACPI
auto named_object = Namespace::root_namespace()->find_object(context.scope, name_string.value()); auto named_object = Namespace::root_namespace()->find_object(context.scope, name_string.value());
if (!named_object) if (!named_object)
{ {
AML_ERROR("Scope name {} not found in namespace", name_string.value()); AML_ERROR("Scope '{}' not found in namespace", name_string.value());
return ParseResult::Failure; return ParseResult::Failure;
} }
if (!named_object->is_scope()) if (!named_object->is_scope())
{ {
AML_ERROR("Scope name {} does not name a namespace", name_string.value()); AML_ERROR("Scope '{}' does not name a namespace", name_string.value());
return ParseResult::Failure; return ParseResult::Failure;
} }
@ -43,7 +43,7 @@ namespace Kernel::ACPI
return ParseResult::Failure; return ParseResult::Failure;
ParseContext scope_context; ParseContext scope_context;
scope_context.scope = resolved_scope.release_value(); scope_context.scope = AML::NameString(resolved_scope.release_value());
scope_context.aml_data = aml_data; scope_context.aml_data = aml_data;
scope_context.method_args = outer_context.method_args; scope_context.method_args = outer_context.method_args;
while (scope_context.aml_data.size() > 0) while (scope_context.aml_data.size() > 0)
@ -64,19 +64,27 @@ namespace Kernel::ACPI
return ParseResult::Success; return ParseResult::Success;
} }
void AML::Scope::debug_print(int indent) const static BAN::Optional<uint64_t> evaluate_or_invoke(BAN::RefPtr<AML::Node> object)
{ {
AML_DEBUG_PRINT_INDENT(indent); if (object->type != AML::Node::Type::Method)
AML_DEBUG_PRINT("Scope "); return object->as_integer();
name.debug_print();
AML_DEBUG_PRINTLN(" {"); auto* method = static_cast<AML::Method*>(object.ptr());
for (const auto& [name, object] : objects) if (method->arg_count != 0)
{ {
object->debug_print(indent + 1); AML_ERROR("Method has {} arguments, expected 0", method->arg_count);
AML_DEBUG_PRINTLN(""); return {};
} }
AML_DEBUG_PRINT_INDENT(indent);
AML_DEBUG_PRINT("}"); BAN::Vector<uint8_t> sync_stack;
auto result = method->evaluate({}, sync_stack);
if (!result.has_value())
{
AML_ERROR("Failed to evaluate method");
return {};
}
return result.value() ? result.value()->as_integer() : BAN::Optional<uint64_t>();
} }
bool AML::initialize_scope(BAN::RefPtr<AML::Scope> scope) bool AML::initialize_scope(BAN::RefPtr<AML::Scope> scope)
@ -88,61 +96,60 @@ namespace Kernel::ACPI
bool run_ini = true; bool run_ini = true;
bool init_children = true; bool init_children = true;
auto it = scope->objects.find(NameSeg("_STA"sv)); if (auto sta = Namespace::root_namespace()->find_object(scope->scope, AML::NameString("_STA"sv)))
if (scope->type != AML::Node::Type::Namespace && it != scope->objects.end() && it->value->type == Node::Type::Method)
{ {
auto* method = static_cast<Method*>(it->value.ptr()); auto result = evaluate_or_invoke(sta);
if (method->arg_count != 0)
{
AML_ERROR("Method {}._STA has {} arguments, expected 0", scope->scope, method->arg_count);
return false;
}
BAN::Vector<uint8_t> sync_stack;
auto result = method->evaluate({}, sync_stack);
if (!result.has_value()) if (!result.has_value())
{
AML_ERROR("Failed to evaluate {}._STA, ignoring device", scope->scope);
return true;
}
auto result_value = result.has_value() ? result.value()->as_integer() : BAN::Optional<uint64_t>();
if (!result_value.has_value())
{ {
AML_ERROR("Failed to evaluate {}._STA, return value could not be resolved to integer", scope->scope); AML_ERROR("Failed to evaluate {}._STA, return value could not be resolved to integer", scope->scope);
AML_ERROR(" Return value: ");
result.value()->debug_print(0);
return false; return false;
} }
run_ini = (result_value.value() & 0x01);
init_children = run_ini || (result_value.value() & 0x02); run_ini = (result.value() & 0x01);
init_children = run_ini || (result.value() & 0x02);
} }
if (run_ini) if (run_ini)
{ {
auto it = scope->objects.find(NameSeg("_STA"sv)); auto ini = Namespace::root_namespace()->find_object(scope->scope, AML::NameString("_INI"sv));
if (it != scope->objects.end() && it->value->type == Node::Type::Method) if (ini)
{ {
auto* method = static_cast<Method*>(it->value.ptr()); if (ini->type != AML::Node::Type::Method)
{
AML_ERROR("Object {}._INI is not a method", scope->scope);
return false;
}
auto* method = static_cast<Method*>(ini.ptr());
if (method->arg_count != 0) if (method->arg_count != 0)
{ {
AML_ERROR("Method {}._INI has {} arguments, expected 0", scope->scope, method->arg_count); AML_ERROR("Method {}._INI has {} arguments, expected 0", scope->scope, method->arg_count);
return false; return false;
} }
BAN::Vector<uint8_t> sync_stack; BAN::Vector<uint8_t> sync_stack;
method->evaluate({}, sync_stack); auto result = method->evaluate({}, sync_stack);
if (!result.has_value())
{
AML_ERROR("Failed to evaluate {}._INI, ignoring device", scope->scope);
return true;
}
} }
} }
bool success = true; bool success = true;
if (init_children) if (init_children)
{ {
for (auto& [_, child] : scope->objects) Namespace::root_namespace()->for_each_child(scope->scope,
{ [&](const auto&, auto& child)
if (!child->is_scope()) {
continue; if (!child->is_scope())
auto* child_scope = static_cast<Scope*>(child.ptr()); return;
if (!initialize_scope(child_scope)) auto* child_scope = static_cast<Scope*>(child.ptr());
success = false; if (!initialize_scope(child_scope))
} success = false;
}
);
} }
return success; return success;
} }