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 ");
name.debug_print();
AML_DEBUG_PRINTLN(" {");
for (const auto& [name, object] : objects)
{
object->debug_print(indent + 1);
AML_DEBUG_PRINTLN("");
}
Namespace::root_namespace()->for_each_child(scope,
[&](const auto&, const auto& child)
{
child->debug_print(indent + 1);
AML_DEBUG_PRINTLN("");
}
);
AML_DEBUG_PRINT_INDENT(indent);
AML_DEBUG_PRINT("}");
}

View File

@ -59,7 +59,7 @@ namespace Kernel::ACPI::AML
if (!method_scope.has_value())
return ParseResult::Failure;
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
method->debug_print(0);

View File

@ -36,6 +36,14 @@ namespace Kernel::ACPI::AML
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)
{
if (aml_data.size() < 4)

View File

@ -1,5 +1,6 @@
#pragma once
#include <BAN/HashMap.h>
#include <kernel/ACPI/AML/Scope.h>
#include <kernel/ACPI/Headers.h>
#include <kernel/Lock/Mutex.h>
@ -11,12 +12,34 @@ namespace Kernel::ACPI::AML
{
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) {}
static BAN::RefPtr<AML::Namespace> create_root_namespace();
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.
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.
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
#include <BAN/HashMap.h>
#include <kernel/ACPI/AML/NamedObject.h>
#include <kernel/ACPI/AML/Names.h>
@ -9,7 +8,6 @@ namespace Kernel::ACPI::AML
struct Scope : public AML::NamedObject
{
BAN::HashMap<NameSeg, BAN::RefPtr<NamedObject>> objects;
AML::NameString scope;
Scope(Node::Type type, NameSeg name)
@ -19,7 +17,6 @@ namespace Kernel::ACPI::AML
virtual bool is_scope() const override { return true; }
static ParseResult parse(ParseContext& context);
virtual void debug_print(int indent) const override;
protected:
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/Method.h>
#include <kernel/ACPI/AML/Namespace.h>
#include <kernel/ACPI/AML/ParseContext.h>
#include <kernel/ACPI/AML/Region.h>
#include <kernel/Lock/LockGuard.h>
namespace Kernel::ACPI
{
@ -20,16 +22,33 @@ namespace Kernel::ACPI
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
ASSERT(relative_base.prefix == "\\"sv || relative_base.path.empty());
// 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;
MUST(absolute_path.prefix.push_back('\\'));
BAN::String absolute_path;
MUST(absolute_path.push_back('\\'));
// Resolve root and parent references
if (relative_path.prefix == "\\"sv)
@ -42,158 +61,132 @@ namespace Kernel::ACPI
return {};
}
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
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())
return {};
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;
MUST(absolute_path.append(seg.sv()));
MUST(absolute_path.push_back('.'));
}
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)
AML::NameString last_match_path;
AML::NameSeg target_seg = relative_path.path.back();
BAN::RefPtr<AML::Scope> current_scope = this;
AML::NameString current_path;
BAN::String last_match_path;
BAN::String current_path;
MUST(current_path.push_back('\\'));
// Check root namespace
{
// If scope contains object with the same name as the segment, update last match
if (current_scope->objects.contains(target_seg))
{
last_match_path = current_path;
MUST(last_match_path.path.push_back(target_seg));
}
BAN::String tmp;
MUST(tmp.append(current_path));
MUST(tmp.append(target_seg.sv()));
if (m_objects.contains(tmp))
last_match_path = BAN::move(tmp);
}
// Check base base path
for (const auto& seg : relative_base.path)
{
auto next_node = current_scope->objects[seg];
ASSERT(next_node && next_node->is_scope());
MUST(current_path.append(seg.sv()));
MUST(current_path.push_back('.'));
current_scope = static_cast<AML::Scope*>(next_node.ptr());
MUST(current_path.path.push_back(seg));
// If scope contains object with the same name as the segment, update last match
if (current_scope->objects.contains(target_seg))
{
last_match_path = current_path;
MUST(last_match_path.path.push_back(target_seg));
}
BAN::String tmp;
MUST(tmp.append(current_path));
MUST(tmp.append(target_seg.sv()));
if (m_objects.contains(tmp))
last_match_path = BAN::move(tmp);
}
if (!last_match_path.path.empty())
{
MUST(last_match_path.prefix.push_back('\\'));
if (!last_match_path.empty())
return last_match_path;
}
return {};
}
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);
if (!canonical_path.has_value())
return nullptr;
if (canonical_path->path.empty())
return this;
BAN::RefPtr<NamedObject> node = this;
for (const auto& seg : canonical_path->path)
{
// Resolve path validates that all nodes are scopes
ASSERT(node->is_scope());
node = static_cast<Scope*>(node.ptr())->objects[seg];
}
return node;
auto it = m_objects.find(canonical_path.value());
if (it == m_objects.end())
return {};
return it->value;
}
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.back() == object->name);
auto parent_path = object_path;
parent_path.path.pop_back();
auto canonical_path = resolve_path(parse_context.scope, object_path, true);
ASSERT(canonical_path.has_value());
auto parent_object = find_object(parse_context.scope, parent_path);
if (!parent_object)
if (canonical_path->empty())
{
AML_ERROR("Parent object not found");
AML_ERROR("Trying to add root namespace");
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;
}
auto* parent_scope = static_cast<Scope*>(parent_object.ptr());
if (parent_scope->objects.contains(object->name))
{
AML_ERROR("Object already exists");
return false;
}
auto canonical_scope = AML::NameString(canonical_path.value());
object->parent = parent_scope;
MUST(parent_scope->objects.insert(object->name, object));
auto canonical_scope = resolve_path(parse_context.scope, object_path);
ASSERT(canonical_scope.has_value());
MUST(m_objects.insert(canonical_path.value(), object));
if (object->is_scope())
{
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;
}
bool AML::Namespace::remove_named_object(const AML::NameString& absolute_path)
{
auto object = find_object({}, absolute_path);
if (!object)
LockGuard _(m_object_mutex);
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;
}
if (object.ptr() == this)
if (canonical_path->empty())
{
AML_ERROR("Trying to remove root object");
AML_ERROR("Trying to remove root namespace");
return false;
}
auto parent = object->parent;
ASSERT(parent->is_scope());
auto* parent_scope = static_cast<Scope*>(parent.ptr());
parent_scope->objects.remove(object->name);
ASSERT(m_objects.contains(canonical_path.value()));
m_objects.remove(canonical_path.value());
return true;
}
@ -202,6 +195,8 @@ namespace Kernel::ACPI
{
ASSERT(!s_root_namespace);
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::One = MUST(BAN::RefPtr<Integer>::create(1, true));
@ -212,7 +207,7 @@ namespace Kernel::ACPI
// Add predefined namespaces
#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("_PR"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());
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;
}
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;
}
@ -43,7 +43,7 @@ namespace Kernel::ACPI
return ParseResult::Failure;
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.method_args = outer_context.method_args;
while (scope_context.aml_data.size() > 0)
@ -64,19 +64,27 @@ namespace Kernel::ACPI
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);
AML_DEBUG_PRINT("Scope ");
name.debug_print();
AML_DEBUG_PRINTLN(" {");
for (const auto& [name, object] : objects)
if (object->type != AML::Node::Type::Method)
return object->as_integer();
auto* method = static_cast<AML::Method*>(object.ptr());
if (method->arg_count != 0)
{
object->debug_print(indent + 1);
AML_DEBUG_PRINTLN("");
AML_ERROR("Method has {} arguments, expected 0", method->arg_count);
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)
@ -88,61 +96,60 @@ namespace Kernel::ACPI
bool run_ini = true;
bool init_children = true;
auto it = scope->objects.find(NameSeg("_STA"sv));
if (scope->type != AML::Node::Type::Namespace && it != scope->objects.end() && it->value->type == Node::Type::Method)
if (auto sta = Namespace::root_namespace()->find_object(scope->scope, AML::NameString("_STA"sv)))
{
auto* method = static_cast<Method*>(it->value.ptr());
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);
auto result = evaluate_or_invoke(sta);
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(" Return value: ");
result.value()->debug_print(0);
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)
{
auto it = scope->objects.find(NameSeg("_STA"sv));
if (it != scope->objects.end() && it->value->type == Node::Type::Method)
auto ini = Namespace::root_namespace()->find_object(scope->scope, AML::NameString("_INI"sv));
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)
{
AML_ERROR("Method {}._INI has {} arguments, expected 0", scope->scope, method->arg_count);
return false;
}
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;
if (init_children)
{
for (auto& [_, child] : scope->objects)
{
if (!child->is_scope())
continue;
auto* child_scope = static_cast<Scope*>(child.ptr());
if (!initialize_scope(child_scope))
success = false;
}
Namespace::root_namespace()->for_each_child(scope->scope,
[&](const auto&, auto& child)
{
if (!child->is_scope())
return;
auto* child_scope = static_cast<Scope*>(child.ptr());
if (!initialize_scope(child_scope))
success = false;
}
);
}
return success;
}