596 lines
16 KiB
C++
596 lines
16 KiB
C++
#include <BAN/Bitcast.h>
|
|
|
|
#include <kernel/ACPI/AML/Namespace.h>
|
|
#include <kernel/ACPI/AML/Node.h>
|
|
#include <kernel/ACPI/Headers.h>
|
|
|
|
#define STA_PRESENT 0x01
|
|
#define STA_FUNCTIONAL 0x08
|
|
|
|
#include <ctype.h>
|
|
|
|
namespace Kernel::ACPI::AML
|
|
{
|
|
|
|
static Namespace s_root_namespace;
|
|
|
|
static constexpr BAN::StringView s_supported_osi_strings[] {
|
|
"Windows 2000"_sv,
|
|
"Windows 2001"_sv,
|
|
"Windows 2001 SP1"_sv,
|
|
"Windows 2001.1"_sv,
|
|
"Windows 2001 SP2"_sv,
|
|
"Windows 2001.1 SP1"_sv,
|
|
"Windows 2006.1"_sv,
|
|
"Windows 2006 SP1"_sv,
|
|
"Windows 2006 SP2"_sv,
|
|
"Windows 2009"_sv,
|
|
"Windows 2012"_sv,
|
|
"Windows 2013"_sv,
|
|
"Windows 2015"_sv,
|
|
"Windows 2016"_sv,
|
|
"Windows 2017"_sv,
|
|
"Windows 2017.2"_sv,
|
|
"Windows 2018"_sv,
|
|
"Windows 2018.2"_sv,
|
|
"Windows 2019"_sv,
|
|
"Extended Address Space Descriptor"_sv,
|
|
// just to pass osi test from uACPI :D
|
|
"AnotherTestString"_sv,
|
|
};
|
|
|
|
Namespace::~Namespace()
|
|
{
|
|
for (auto& [_, reference] : m_named_objects)
|
|
if (--reference->ref_count == 0)
|
|
delete reference;
|
|
}
|
|
|
|
BAN::ErrorOr<void> Namespace::prepare_root_namespace()
|
|
{
|
|
// https://uefi.org/htmlspecs/ACPI_Spec_6_4_html/05_ACPI_Software_Programming_Model/ACPI_Software_Programming_Model.html?highlight=predefined#predefined-root-namespaces
|
|
|
|
const auto add_predefined_root_namespace =
|
|
[](const char* name) -> BAN::ErrorOr<void>
|
|
{
|
|
Node predefined {};
|
|
predefined.type = Node::Type::PredefinedScope;
|
|
TRY(s_root_namespace.add_named_object({}, TRY(NameString::from_string(name)), BAN::move(predefined)));
|
|
return {};
|
|
};
|
|
|
|
TRY(add_predefined_root_namespace("\\"));
|
|
TRY(add_predefined_root_namespace("\\_GPE"));
|
|
TRY(add_predefined_root_namespace("\\_PR_"));
|
|
TRY(add_predefined_root_namespace("\\_SB_"));
|
|
TRY(add_predefined_root_namespace("\\_SI_"));
|
|
TRY(add_predefined_root_namespace("\\_TZ_"));
|
|
|
|
{
|
|
Node revision;
|
|
revision.type = Node::Type::Integer;
|
|
revision.as.integer.value = 2;
|
|
TRY(s_root_namespace.add_named_object({}, TRY(NameString::from_string("_REV")), BAN::move(revision)));
|
|
}
|
|
|
|
{
|
|
auto osi_string = TRY(NameString::from_string("\\_OSI"));
|
|
|
|
Node method {};
|
|
method.type = Node::Type::Method;
|
|
new (method.as.method.storage) Kernel::Mutex();
|
|
method.as.method.arg_count = 1;
|
|
method.as.method.override_func =
|
|
[](const BAN::Array<Reference*, 7>& args) -> BAN::ErrorOr<Node>
|
|
{
|
|
ASSERT(args[0]);
|
|
|
|
if (args[0]->node.type != Node::Type::String)
|
|
{
|
|
dwarnln("_OSI called with {}", args[0]->node);
|
|
return BAN::Error::from_errno(EINVAL);
|
|
}
|
|
|
|
Node result {};
|
|
result.type = Node::Type::Integer;
|
|
result.as.integer.value = 0;
|
|
|
|
const auto arg0 = args[0]->node.as.str_buf->as_sv();
|
|
for (auto supported : s_supported_osi_strings)
|
|
{
|
|
if (supported != arg0)
|
|
continue;
|
|
result.as.integer.value = 0xFFFFFFFFFFFFFFFF;
|
|
break;
|
|
}
|
|
|
|
return result;
|
|
};
|
|
|
|
TRY(s_root_namespace.add_named_object({}, osi_string, BAN::move(method)));
|
|
}
|
|
|
|
{
|
|
auto gl_string = TRY(NameString::from_string("\\_GL_"));
|
|
|
|
Node mutex {};
|
|
mutex.type = Node::Type::Mutex;
|
|
mutex.as.mutex = new Mutex();
|
|
mutex.as.mutex->ref_count = 1;
|
|
mutex.as.mutex->sync_level = 0;
|
|
mutex.as.mutex->global_lock = true;
|
|
|
|
TRY(s_root_namespace.add_named_object({}, gl_string, BAN::move(mutex)));
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
Namespace& Namespace::root_namespace()
|
|
{
|
|
return s_root_namespace;
|
|
}
|
|
|
|
BAN::ErrorOr<uint64_t> Namespace::evaluate_sta(const Scope& scope)
|
|
{
|
|
auto [child_path, child_ref] = TRY(find_named_object(scope, TRY(NameString::from_string("_STA"_sv))));
|
|
if (child_ref == nullptr)
|
|
return 0x0F;
|
|
return TRY(convert_node(TRY(evaluate_node(child_path, child_ref->node)), ConvInteger, sizeof(uint64_t))).as.integer.value;
|
|
}
|
|
|
|
BAN::ErrorOr<void> Namespace::evaluate_ini(const Scope& scope)
|
|
{
|
|
auto [child_path, child_ref] = TRY(find_named_object(scope, TRY(NameString::from_string("_INI"_sv))));
|
|
if (child_ref == nullptr)
|
|
return {};
|
|
TRY(evaluate_node(child_path, child_ref->node));
|
|
return {};
|
|
}
|
|
|
|
BAN::ErrorOr<void> Namespace::post_load_initialize()
|
|
{
|
|
auto [sb_path, sb_ref] = TRY(find_named_object({}, TRY(NameString::from_string("\\_SB_"_sv))));
|
|
if (sb_ref != nullptr)
|
|
TRY(evaluate_ini(sb_path));
|
|
|
|
BAN::Vector<Scope> to_init;
|
|
TRY(to_init.push_back({}));
|
|
|
|
while (!to_init.empty())
|
|
{
|
|
BAN::Vector<Scope> to_init_next;
|
|
|
|
for (const Scope& current : to_init)
|
|
{
|
|
TRY(for_each_child(current,
|
|
[&](const Scope& child_path, Reference* child_ref) -> BAN::Iteration
|
|
{
|
|
if (m_aliases.contains(child_path))
|
|
return BAN::Iteration::Continue;
|
|
|
|
switch (child_ref->node.type)
|
|
{
|
|
case Node::Type::Device:
|
|
case Node::Type::Processor:
|
|
case Node::Type::ThermalZone:
|
|
case Node::Type::PredefinedScope:
|
|
break;
|
|
default:
|
|
return BAN::Iteration::Continue;
|
|
}
|
|
|
|
(void)for_each_child(current,
|
|
[&](const Scope& opregion_path, Reference* opregion_ref) -> BAN::Iteration
|
|
{
|
|
if (opregion_ref->node.type == Node::Type::OpRegion)
|
|
(void)opregion_call_reg(opregion_path, opregion_ref->node);
|
|
return BAN::Iteration::Continue;
|
|
}
|
|
);
|
|
|
|
auto sta_ret = evaluate_sta(child_path);
|
|
if (sta_ret.is_error())
|
|
return BAN::Iteration::Continue;
|
|
|
|
if (sta_ret.value() & STA_PRESENT)
|
|
(void)evaluate_ini(child_path);
|
|
|
|
if ((sta_ret.value() & STA_PRESENT) || (sta_ret.value() & STA_FUNCTIONAL))
|
|
{
|
|
auto child_path_copy = child_path.copy();
|
|
if (!child_path_copy.is_error())
|
|
(void)to_init_next.push_back(child_path_copy.release_value());
|
|
}
|
|
|
|
return BAN::Iteration::Continue;
|
|
}
|
|
));
|
|
}
|
|
|
|
to_init = BAN::move(to_init_next);
|
|
}
|
|
|
|
m_has_initialized_namespace = true;
|
|
|
|
if (auto ret = initialize_op_regions(); ret.is_error())
|
|
dwarnln("Failed to initialize all opregions: {}", ret.error());
|
|
|
|
return {};
|
|
}
|
|
|
|
BAN::ErrorOr<void> Namespace::initialize_op_regions()
|
|
{
|
|
for (const auto& [obj_path, obj_ref] : m_named_objects)
|
|
{
|
|
if (obj_ref->node.type != Node::Type::OpRegion)
|
|
continue;
|
|
// FIXME: if _REG adds stuff to namespace, iterators are invalidated
|
|
(void)opregion_call_reg(obj_path, obj_ref->node);
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
BAN::ErrorOr<void> Namespace::opregion_call_reg(const Scope& scope, const Node& opregion)
|
|
{
|
|
ASSERT(opregion.type == Node::Type::OpRegion);
|
|
|
|
const auto address_space = opregion.as.opregion.address_space;
|
|
if (address_space == GAS::AddressSpaceID::SystemIO || address_space == GAS::AddressSpaceID::SystemMemory)
|
|
return {};
|
|
const uint32_t address_space_u32 = static_cast<uint32_t>(address_space);
|
|
if (address_space_u32 >= 32)
|
|
return {};
|
|
const uint32_t mask = static_cast<uint32_t>(1) << address_space_u32;
|
|
|
|
auto parent = TRY(scope.copy());
|
|
parent.parts.pop_back();
|
|
|
|
auto it = m_called_reg_bitmaps.find(parent);
|
|
if (it != m_called_reg_bitmaps.end())
|
|
if (it->value & mask)
|
|
return {};
|
|
|
|
auto [reg_path, reg_obj] = TRY(find_named_object(parent, TRY(NameString::from_string("_REG"_sv)), true));
|
|
if (reg_obj != nullptr)
|
|
{
|
|
if (reg_obj->node.type != Node::Type::Method)
|
|
dwarnln("{} is not an method", reg_path);
|
|
else
|
|
{
|
|
if (reg_obj->node.as.method.arg_count != 2)
|
|
dwarnln("{} takes {} arguments", reg_obj->node.as.method.arg_count);
|
|
else
|
|
{
|
|
Reference arg0;
|
|
arg0.node.type = Node::Type::Integer;
|
|
arg0.node.as.integer.value = address_space_u32;
|
|
arg0.ref_count = 2; // evaluate should not delete
|
|
|
|
Reference arg1;
|
|
arg1.node.type = Node::Type::Integer;
|
|
arg1.node.as.integer.value = 1;
|
|
arg1.ref_count = 2; // evaluate should not delete
|
|
|
|
BAN::Array<Reference*, 7> args(nullptr);
|
|
args[0] = &arg0;
|
|
args[1] = &arg1;
|
|
|
|
if (auto ret = method_call(reg_path, reg_obj->node, BAN::move(args)); ret.is_error())
|
|
dwarnln("Failed to evaluate {}: {}", reg_path, ret.error());
|
|
}
|
|
}
|
|
}
|
|
|
|
if (it != m_called_reg_bitmaps.end())
|
|
it->value |= mask;
|
|
else
|
|
TRY(m_called_reg_bitmaps.insert(BAN::move(parent), mask));
|
|
|
|
return {};
|
|
}
|
|
|
|
BAN::ErrorOr<Scope> Namespace::resolve_path(const Scope& scope, const NameString& name_string)
|
|
{
|
|
Scope resolved_path;
|
|
|
|
if (name_string.base == NameString::base_root)
|
|
{
|
|
TRY(resolved_path.parts.reserve(name_string.parts.size()));
|
|
for (uint32_t part : name_string.parts)
|
|
TRY(resolved_path.parts.push_back(part));
|
|
}
|
|
else
|
|
{
|
|
const uint32_t scope_segment_count =
|
|
(name_string.base < scope.parts.size())
|
|
? scope.parts.size() - name_string.base
|
|
: 0;
|
|
|
|
TRY(resolved_path.parts.reserve(scope_segment_count + name_string.parts.size()));
|
|
for (size_t i = 0; i < scope_segment_count; i++)
|
|
TRY(resolved_path.parts.push_back(scope.parts[i]));
|
|
for (uint32_t part : name_string.parts)
|
|
TRY(resolved_path.parts.push_back(part));
|
|
}
|
|
|
|
return resolved_path;
|
|
}
|
|
|
|
BAN::ErrorOr<Scope> Namespace::add_named_object(const Scope& scope, const NameString& name_string, Node&& node)
|
|
{
|
|
dprintln_if(AML_DUMP_FUNCTION_CALLS, "add_named_object('{}', '{}', {})", scope, name_string, node);
|
|
|
|
auto resolved_path = TRY(resolve_path(scope, name_string));
|
|
if (m_named_objects.contains(resolved_path))
|
|
return Scope();
|
|
|
|
auto* reference = new Reference();
|
|
if (reference == nullptr)
|
|
return BAN::Error::from_errno(ENOMEM);
|
|
reference->node = BAN::move(node);
|
|
reference->ref_count = 1;
|
|
|
|
TRY(m_named_objects.insert(TRY(resolved_path.copy()), reference));
|
|
|
|
if (m_has_initialized_namespace && reference->node.type == Node::Type::OpRegion)
|
|
(void)opregion_call_reg(resolved_path, reference->node);
|
|
|
|
return resolved_path;
|
|
}
|
|
|
|
BAN::ErrorOr<Scope> Namespace::add_alias(const Scope& scope, const NameString& name_string, Reference* reference)
|
|
{
|
|
dprintln_if(AML_DUMP_FUNCTION_CALLS, "add_alias('{}', '{}', {})", scope, name_string, reference->node);
|
|
|
|
auto resolved_path = TRY(resolve_path(scope, name_string));
|
|
if (m_named_objects.contains(resolved_path))
|
|
return Scope();
|
|
|
|
ASSERT(reference->ref_count >= 1);
|
|
reference->ref_count++;
|
|
|
|
TRY(m_named_objects.insert(TRY(resolved_path.copy()), reference));
|
|
TRY(m_aliases.insert(TRY(resolved_path.copy())));
|
|
|
|
return resolved_path;
|
|
}
|
|
|
|
BAN::ErrorOr<void> Namespace::remove_named_object(const Scope& absolute_path)
|
|
{
|
|
dprintln_if(AML_DUMP_FUNCTION_CALLS, "remove_named_object('{}')", absolute_path);
|
|
|
|
auto it = m_named_objects.find(absolute_path);
|
|
if (it == m_named_objects.end())
|
|
return BAN::Error::from_errno(ENOENT);
|
|
if (--it->value->ref_count == 0)
|
|
delete it->value;
|
|
m_named_objects.remove(it);
|
|
return {};
|
|
}
|
|
|
|
BAN::ErrorOr<Namespace::FindResult> Namespace::find_named_object(const Scope& scope, const NameString& name_string, bool force_absolute)
|
|
{
|
|
dprintln_if(AML_DUMP_FUNCTION_CALLS, "find_named_object('{}', '{}')", scope, name_string);
|
|
|
|
if (force_absolute || name_string.base != 0)
|
|
{
|
|
// Absolute path
|
|
|
|
auto resolved_path = TRY(resolve_path(scope, name_string));
|
|
auto it = m_named_objects.find(resolved_path);
|
|
if (it != m_named_objects.end()) {
|
|
return FindResult {
|
|
.path = BAN::move(resolved_path),
|
|
.node = it->value,
|
|
};
|
|
}
|
|
|
|
return FindResult {
|
|
.path = {},
|
|
.node = nullptr,
|
|
};
|
|
}
|
|
|
|
// Relative path
|
|
|
|
Scope path_guess;
|
|
TRY(path_guess.parts.reserve(scope.parts.size() + name_string.parts.size()));
|
|
for (const auto& part : scope.parts)
|
|
TRY(path_guess.parts.push_back(part));
|
|
for (const auto& part : name_string.parts)
|
|
TRY(path_guess.parts.push_back(part));
|
|
|
|
auto it = m_named_objects.find(path_guess);
|
|
if (it != m_named_objects.end()) {
|
|
return FindResult {
|
|
.path = BAN::move(path_guess),
|
|
.node = it->value,
|
|
};
|
|
}
|
|
|
|
for (size_t i = 0; i < scope.parts.size(); i++)
|
|
{
|
|
path_guess.parts.remove(scope.parts.size() - i - 1);
|
|
auto it = m_named_objects.find(path_guess);
|
|
if (it != m_named_objects.end()) {
|
|
return FindResult {
|
|
.path = BAN::move(path_guess),
|
|
.node = it->value,
|
|
};
|
|
}
|
|
}
|
|
|
|
return FindResult {
|
|
.path = {},
|
|
.node = nullptr,
|
|
};
|
|
}
|
|
|
|
BAN::ErrorOr<void> Namespace::for_each_child(const Scope& scope, const BAN::Function<BAN::Iteration(BAN::StringView, Reference*)>& callback)
|
|
{
|
|
for (const auto& [obj_path, obj_ref] : m_named_objects)
|
|
{
|
|
if (obj_path.parts.size() != scope.parts.size() + 1)
|
|
continue;
|
|
|
|
bool match = true;
|
|
for (size_t i = 0; i < scope.parts.size() && match; i++)
|
|
match = obj_path.parts[i] == scope.parts[i];
|
|
if (!match)
|
|
continue;
|
|
|
|
auto name = BAN::StringView(reinterpret_cast<const char*>(&obj_path.parts.back()), 4);
|
|
auto iteration = callback(name, obj_ref);
|
|
if (iteration == BAN::Iteration::Break)
|
|
break;
|
|
ASSERT(iteration == BAN::Iteration::Continue);
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
BAN::ErrorOr<void> Namespace::for_each_child(const Scope& scope, const BAN::Function<BAN::Iteration(const Scope&, Reference*)>& callback)
|
|
{
|
|
for (const auto& [obj_path, obj_ref] : m_named_objects)
|
|
{
|
|
if (obj_path.parts.size() != scope.parts.size() + 1)
|
|
continue;
|
|
|
|
bool match = true;
|
|
for (size_t i = 0; i < scope.parts.size() && match; i++)
|
|
match = obj_path.parts[i] == scope.parts[i];
|
|
if (!match)
|
|
continue;
|
|
|
|
auto iteration = callback(obj_path, obj_ref);
|
|
if (iteration == BAN::Iteration::Break)
|
|
break;
|
|
ASSERT(iteration == BAN::Iteration::Continue);
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
static bool is_valid_eisa_id(BAN::StringView eisa_id)
|
|
{
|
|
if (eisa_id.size() != 7)
|
|
return false;
|
|
for (size_t i = 0; i < 3; i++)
|
|
if (!isupper(eisa_id[i]))
|
|
return false;
|
|
for (size_t i = 3; i < 7; i++)
|
|
if (!isxdigit(eisa_id[i]))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
static uint32_t encode_eisa_id(BAN::StringView eisa_id)
|
|
{
|
|
constexpr auto char_to_hex =
|
|
[](char ch) -> uint32_t
|
|
{
|
|
if (isdigit(ch))
|
|
return ch - '0';
|
|
return tolower(ch) - 'a' + 10;
|
|
};
|
|
|
|
uint16_t code = 0;
|
|
code |= (eisa_id[2] - 0x40) << 0;
|
|
code |= (eisa_id[1] - 0x40) << 5;
|
|
code |= (eisa_id[0] - 0x40) << 10;
|
|
|
|
uint32_t encoded = 0;
|
|
encoded |= code >> 8;
|
|
encoded |= (code & 0xFF) << 8;
|
|
encoded |= (char_to_hex(eisa_id[3]) << 20) | (char_to_hex(eisa_id[4]) << 16);
|
|
encoded |= (char_to_hex(eisa_id[5]) << 28) | (char_to_hex(eisa_id[6]) << 24);
|
|
return encoded;
|
|
}
|
|
|
|
BAN::ErrorOr<BAN::Vector<Scope>> Namespace::find_device_with_eisa_id(BAN::StringView eisa_id)
|
|
{
|
|
if (!is_valid_eisa_id(eisa_id))
|
|
{
|
|
dwarnln("Invalid EISA id '{}'", eisa_id);
|
|
return BAN::Error::from_errno(EINVAL);
|
|
}
|
|
|
|
const uint32_t encoded = encode_eisa_id(eisa_id);
|
|
|
|
BAN::Vector<Scope> result;
|
|
|
|
for (const auto& [obj_path, obj_ref] : m_named_objects)
|
|
{
|
|
if (obj_ref->node.type != Node::Type::Device)
|
|
continue;
|
|
|
|
auto [_, hid] = TRY(find_named_object(obj_path, TRY(NameString::from_string("_HID"_sv)), true));
|
|
if (hid == nullptr)
|
|
continue;
|
|
|
|
uint32_t device_hid = 0;
|
|
if (hid->node.type == Node::Type::Integer)
|
|
device_hid = hid->node.as.integer.value;
|
|
else if (hid->node.type == Node::Type::String && is_valid_eisa_id(hid->node.as.str_buf->as_sv()))
|
|
device_hid = encode_eisa_id(hid->node.as.str_buf->as_sv());
|
|
else
|
|
continue;
|
|
|
|
if (device_hid != encoded)
|
|
continue;
|
|
|
|
TRY(result.push_back(TRY(obj_path.copy())));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
BAN::ErrorOr<Node> Namespace::evaluate(BAN::StringView path)
|
|
{
|
|
Scope root_scope;
|
|
auto name_string = TRY(NameString::from_string(path));
|
|
auto [object_path, object] = TRY(find_named_object(root_scope, name_string));
|
|
if (object == nullptr)
|
|
return BAN::Error::from_errno(ENOENT);
|
|
return evaluate_node(object_path, object->node);
|
|
}
|
|
|
|
BAN::ErrorOr<void> Namespace::parse(BAN::ConstByteSpan aml_data)
|
|
{
|
|
if (aml_data.size() < sizeof(SDTHeader))
|
|
return BAN::Error::from_errno(EINVAL);
|
|
|
|
const auto& sdt_header = aml_data.as<const SDTHeader>();
|
|
if (aml_data.size() < sdt_header.length)
|
|
return BAN::Error::from_errno(EINVAL);
|
|
|
|
aml_data = aml_data.slice(sizeof(SDTHeader));
|
|
|
|
ParseContext context;
|
|
context.aml_data = aml_data;
|
|
TRY(context.allocate_locals());
|
|
|
|
while (!context.aml_data.empty())
|
|
{
|
|
auto parse_result = parse_node_or_execution_flow(context);
|
|
if (parse_result.is_error())
|
|
{
|
|
dwarnln("Failed to parse root namespace: {}", parse_result.error());
|
|
return parse_result.release_error();
|
|
}
|
|
|
|
auto [execution_flow, node] = parse_result.release_value();
|
|
if (execution_flow == ExecutionFlow::Normal)
|
|
continue;
|
|
if (execution_flow == ExecutionFlow::Return)
|
|
break;
|
|
dwarnln("Root namespace got execution flow {}", static_cast<int>(execution_flow));
|
|
return BAN::Error::from_errno(EINVAL);
|
|
}
|
|
|
|
return {};
|
|
}
|
|
}
|