From 4e364bd2f6621f1e97b59216ef903ff766c52e40 Mon Sep 17 00:00:00 2001 From: Bananymous Date: Wed, 18 Dec 2024 04:05:06 +0200 Subject: [PATCH] Kernel: Add support for ACPI Control Method Batteries The implementation is kinda weird but it exposes some battery information to userspace! --- kernel/CMakeLists.txt | 1 + kernel/include/kernel/ACPI/ACPI.h | 2 + kernel/include/kernel/ACPI/BatterySystem.h | 26 ++++ kernel/kernel/ACPI/ACPI.cpp | 8 ++ kernel/kernel/ACPI/BatterySystem.cpp | 150 +++++++++++++++++++++ kernel/kernel/kernel.cpp | 3 + 6 files changed, 190 insertions(+) create mode 100644 kernel/include/kernel/ACPI/BatterySystem.h create mode 100644 kernel/kernel/ACPI/BatterySystem.cpp diff --git a/kernel/CMakeLists.txt b/kernel/CMakeLists.txt index ed7ef9a8..17f27a5f 100644 --- a/kernel/CMakeLists.txt +++ b/kernel/CMakeLists.txt @@ -1,6 +1,7 @@ set(KERNEL_SOURCES font/prefs.psf.o kernel/ACPI/ACPI.cpp + kernel/ACPI/BatterySystem.cpp kernel/ACPI/AML/Namespace.cpp kernel/ACPI/AML/Node.cpp kernel/ACPI/AML/OpRegion.cpp diff --git a/kernel/include/kernel/ACPI/ACPI.h b/kernel/include/kernel/ACPI/ACPI.h index 04a13544..dc1efe7f 100644 --- a/kernel/include/kernel/ACPI/ACPI.h +++ b/kernel/include/kernel/ACPI/ACPI.h @@ -28,6 +28,8 @@ namespace Kernel::ACPI // 2: SAPIC BAN::ErrorOr enter_acpi_mode(uint8_t mode); + BAN::ErrorOr initialize_acpi_devices(); + BAN::ErrorOr poweroff(); BAN::ErrorOr reset(); diff --git a/kernel/include/kernel/ACPI/BatterySystem.h b/kernel/include/kernel/ACPI/BatterySystem.h new file mode 100644 index 00000000..5a5429b2 --- /dev/null +++ b/kernel/include/kernel/ACPI/BatterySystem.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include + +namespace Kernel::ACPI +{ + + class BatterySystem + { + BAN_NON_COPYABLE(BatterySystem); + BAN_NON_MOVABLE(BatterySystem); + public: + static BAN::ErrorOr initialize(AML::Namespace& acpi_namespace); + + private: + BatterySystem(AML::Namespace&); + + BAN::ErrorOr initialize_impl(); + + private: + AML::Namespace& m_acpi_namespace; + BAN::RefPtr m_directory; + }; + +} diff --git a/kernel/kernel/ACPI/ACPI.cpp b/kernel/kernel/ACPI/ACPI.cpp index ae681734..80d2fd26 100644 --- a/kernel/kernel/ACPI/ACPI.cpp +++ b/kernel/kernel/ACPI/ACPI.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -774,6 +775,13 @@ acpi_release_global_lock: return {}; } + BAN::ErrorOr ACPI::initialize_acpi_devices() + { + ASSERT(m_namespace); + TRY(BatterySystem::initialize(*m_namespace)); + return {}; + } + void ACPI::acpi_event_task() { auto get_fixed_event = diff --git a/kernel/kernel/ACPI/BatterySystem.cpp b/kernel/kernel/ACPI/BatterySystem.cpp new file mode 100644 index 00000000..922a8f19 --- /dev/null +++ b/kernel/kernel/ACPI/BatterySystem.cpp @@ -0,0 +1,150 @@ +#include +#include +#include + +namespace Kernel::ACPI +{ + + static BAN::UniqPtr s_instance; + + class BatteryInfoInode final : public TmpInode + { + public: + static BAN::ErrorOr> create_new( + AML::Namespace& acpi_namespace, + const AML::Scope& battery_path, + BAN::StringView method, + size_t index, + mode_t mode, uid_t uid, gid_t gid) + { + auto inode_info = create_inode_info(mode | Mode::IFREG, uid, gid); + auto ino = TRY(DevFileSystem::get().allocate_inode(inode_info)); + + auto battery_path_copy = TRY(battery_path.copy()); + auto method_copy = TRY(AML::NameString::from_string(method)); + + auto* inode_ptr = new BatteryInfoInode(acpi_namespace, BAN::move(battery_path_copy), BAN::move(method_copy), index, ino, inode_info); + if (inode_ptr == nullptr) + return BAN::Error::from_errno(ENOMEM); + return BAN::RefPtr::adopt(inode_ptr); + } + + protected: + BAN::ErrorOr read_impl(off_t offset, BAN::ByteSpan buffer) override + { + if (offset < 0) + return BAN::Error::from_errno(EINVAL); + + if (SystemTimer::get().ms_since_boot() > m_last_read_ms + 1000) + { + auto [method_path, method_ref] = TRY(m_acpi_namespace.find_named_object(m_battery_path, m_method_name)); + if (method_ref == nullptr) + return BAN::Error::from_errno(EFAULT); + + auto result = TRY(AML::method_call(method_path, method_ref->node, {})); + if (result.type != AML::Node::Type::Package || result.as.package->num_elements < m_result_index) + return BAN::Error::from_errno(EFAULT); + + auto& target_elem = result.as.package->elements[m_result_index]; + if (!target_elem.resolved || !target_elem.value.node) + return BAN::Error::from_errno(EFAULT); + + auto target_conv = AML::convert_node(TRY(target_elem.value.node->copy()), AML::ConvInteger, sizeof(uint64_t)); + if (target_conv.is_error()) + return BAN::Error::from_errno(EFAULT); + + m_last_read_ms = SystemTimer::get().ms_since_boot(); + m_last_value = target_conv.value().as.integer.value; + } + + auto target_str = TRY(BAN::String::formatted("{}", m_last_value)); + + if (static_cast(offset) >= target_str.size()) + return 0; + + const size_t ncopy = BAN::Math::min(buffer.size(), target_str.size() - offset); + memcpy(buffer.data(), target_str.data() + offset, ncopy); + return ncopy; + } + + BAN::ErrorOr write_impl(off_t, BAN::ConstByteSpan) override { return BAN::Error::from_errno(EINVAL); } + BAN::ErrorOr truncate_impl(size_t) override { return BAN::Error::from_errno(EINVAL); } + + bool can_read_impl() const override { return true; } + bool can_write_impl() const override { return false; } + bool has_error_impl() const override { return false; } + + private: + BatteryInfoInode(AML::Namespace& acpi_namespace, AML::Scope&& battery_path, AML::NameString&& method, size_t index, ino_t ino, const TmpInodeInfo& info) + : TmpInode(DevFileSystem::get(), ino, info) + , m_acpi_namespace(acpi_namespace) + , m_battery_path(BAN::move(battery_path)) + , m_method_name(BAN::move(method)) + , m_result_index(index) + { } + + private: + AML::Namespace& m_acpi_namespace; + AML::Scope m_battery_path; + AML::NameString m_method_name; + size_t m_result_index; + + uint64_t m_last_read_ms = 0; + uint64_t m_last_value = 0; + }; + + BAN::ErrorOr BatterySystem::initialize(AML::Namespace& acpi_namespace) + { + ASSERT(!s_instance); + + auto* battery_system = new BatterySystem(acpi_namespace); + if (battery_system == nullptr) + return BAN::Error::from_errno(ENOMEM); + s_instance = BAN::UniqPtr::adopt(battery_system); + + TRY(s_instance->initialize_impl()); + + return {}; + } + + BatterySystem::BatterySystem(AML::Namespace& acpi_namespace) + : m_acpi_namespace(acpi_namespace) + { } + + BAN::ErrorOr BatterySystem::initialize_impl() + { + auto base_inode = TRY(TmpDirectoryInode::create_new(DevFileSystem::get(), 0555, 0, 0, static_cast(*DevFileSystem::get().root_inode()))); + DevFileSystem::get().add_inode("batteries", base_inode); + + auto batteries = TRY(m_acpi_namespace.find_device_with_eisa_id("PNP0C0A"_sv)); + for (const auto& battery : batteries) + { + auto [_1, bif_ref] = TRY(m_acpi_namespace.find_named_object(battery, TRY(AML::NameString::from_string("_BIF"_sv)))); + if (!bif_ref || bif_ref->node.type != AML::Node::Type::Method || bif_ref->node.as.method.arg_count != 0) + { + dwarnln("Battery {} does not have _BIF or it is invalid", battery); + continue; + } + + auto [_2, bst_ref] = TRY(m_acpi_namespace.find_named_object(battery, TRY(AML::NameString::from_string("_BST"_sv)))); + if (!bst_ref || bst_ref->node.type != AML::Node::Type::Method || bst_ref->node.as.method.arg_count != 0) + { + dwarnln("Battery {} does not have _BST or it is invalid", battery); + continue; + } + + auto battery_name = BAN::StringView(reinterpret_cast(&battery.parts.back()), 4); + auto battery_inode = TRY(TmpDirectoryInode::create_new(DevFileSystem::get(), 0555, 0, 0, *base_inode)); + TRY(base_inode->link_inode(*battery_inode, battery_name)); + + auto cap_full_inode = TRY(BatteryInfoInode::create_new(m_acpi_namespace, battery, "_BIF"_sv, 2, 0444, 0, 0)); + TRY(battery_inode->link_inode(*cap_full_inode, "capacity_full"_sv)); + + auto cap_now_inode = TRY(BatteryInfoInode::create_new(m_acpi_namespace, battery, "_BST"_sv, 2, 0444, 0, 0)); + TRY(battery_inode->link_inode(*cap_now_inode, "capacity_now"_sv)); + } + + return {}; + } + +} diff --git a/kernel/kernel/kernel.cpp b/kernel/kernel/kernel.cpp index 4b951702..21184da4 100644 --- a/kernel/kernel/kernel.cpp +++ b/kernel/kernel/kernel.cpp @@ -231,6 +231,9 @@ static void init2(void*) if (!cmdline.disable_acpi && ACPI::ACPI::get().enter_acpi_mode(InterruptController::get().is_using_apic()).is_error()) dprintln("Failed to enter ACPI mode"); + if (auto ret = ACPI::ACPI::get().initialize_acpi_devices(); ret.is_error()) + dwarnln("Could not initialize ACPI devices: {}", ret.error()); + DevFileSystem::get().initialize_device_updater(); #if 0