| // Copyright 2016 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "zircon/system/ulib/acpica/osfuchsia.h" |
| |
| #include <zircon/assert.h> |
| #include <zircon/process.h> |
| #include <zircon/status.h> |
| #include <zircon/syscalls.h> |
| #include <zircon/time.h> |
| |
| #include <acpica/acpi.h> |
| |
| #if !defined(__x86_64__) && !defined(__aarch64__) |
| #error "Unsupported architecture" |
| #endif |
| |
| __WEAK zx_handle_t root_resource_handle; |
| |
| #define UNIMPLEMENTED() ZX_PANIC("%s unimplemented\n", __func__) |
| |
| /** |
| * @brief Initialize the OSL subsystem. |
| * |
| * This function allows the OSL to initialize itself. It is called during |
| * intiialization of the ACPICA subsystem. |
| * |
| * @return Initialization status |
| */ |
| ACPI_STATUS AcpiOsInitialize() { |
| auto status = AcpiTaskThreadStart(); |
| if (status != AE_OK) { |
| return status; |
| } |
| |
| status = AcpiIoPortSetup(); |
| if (status != AE_OK) { |
| return status; |
| } |
| return AE_OK; |
| } |
| |
| /** |
| * @brief Terminate the OSL subsystem. |
| * |
| * This function allows the OSL to cleanup and terminate. It is called during |
| * termination of the ACPICA subsystem. |
| * |
| * @return Termination status |
| */ |
| ACPI_STATUS AcpiOsTerminate() { |
| AcpiTaskThreadTerminate(); |
| |
| return AE_OK; |
| } |
| |
| /** |
| * @brief Obtain the Root ACPI table pointer (RSDP). |
| * |
| * @return The physical address of the RSDP |
| */ |
| ACPI_PHYSICAL_ADDRESS AcpiOsGetRootPointer() { |
| zx_paddr_t acpi_rsdp, smbios; |
| zx_status_t zx_status = zx_pc_firmware_tables(root_resource_handle, &acpi_rsdp, &smbios); |
| if (zx_status == ZX_OK && acpi_rsdp != 0) { |
| return acpi_rsdp; |
| } |
| |
| ACPI_PHYSICAL_ADDRESS TableAddress = 0; |
| ACPI_STATUS status = AcpiFindRootPointer(&TableAddress); |
| if (status != AE_OK) { |
| return 0; |
| } |
| return TableAddress; |
| } |
| |
| /** |
| * @brief Allow the host OS to override a predefined ACPI object. |
| * |
| * @param PredefinedObject A pointer to a predefind object (name and initial |
| * value) |
| * @param NewValue Where a new value for the predefined object is returned. |
| * NULL if there is no override for this object. |
| * |
| * @return Exception code that indicates success or reason for failure. |
| */ |
| ACPI_STATUS AcpiOsPredefinedOverride(const ACPI_PREDEFINED_NAMES* PredefinedObject, |
| ACPI_STRING* NewValue) { |
| *NewValue = NULL; |
| return AE_OK; |
| } |
| |
| /** |
| * @brief Allow the host OS to override a firmware ACPI table via a logical |
| * address. |
| * |
| * @param ExistingTable A pointer to the header of the existing ACPI table |
| * @param NewTable Where the pointer to the replacment table is returned. The |
| * OSL returns NULL if no replacement is provided. |
| * |
| * @return Exception code that indicates success or reason for failure. |
| */ |
| ACPI_STATUS AcpiOsTableOverride(ACPI_TABLE_HEADER* ExistingTable, ACPI_TABLE_HEADER** NewTable) { |
| *NewTable = NULL; |
| return AE_OK; |
| } |
| |
| /** |
| * @brief Allow the host OS to override a firmware ACPI table via a physical |
| * address. |
| * |
| * @param ExistingTable A pointer to the header of the existing ACPI table |
| * @param NewAddress Where the physical address of the replacment table is |
| * returned. The OSL returns NULL if no replacement is provided. |
| * @param NewLength Where the length of the replacement table is returned. |
| * |
| * @return Exception code that indicates success or reason for failure. |
| */ |
| ACPI_STATUS AcpiOsPhysicalTableOverride(ACPI_TABLE_HEADER* ExistingTable, |
| ACPI_PHYSICAL_ADDRESS* NewAddress, UINT32* NewTableLength) { |
| *NewAddress = 0; |
| return AE_OK; |
| } |
| |
| /** |
| * @brief Allocate memory from the dynamic memory pool. |
| * |
| * @param Size Amount of memory to allocate. |
| * |
| * @return A pointer to the allocated memory. A NULL pointer is returned on |
| * error. |
| */ |
| void* AcpiOsAllocate(ACPI_SIZE Size) { return malloc(Size); } |
| |
| /** |
| * @brief Free previously allocated memory. |
| * |
| * @param Memory A pointer to the memory to be freed. |
| */ |
| void AcpiOsFree(void* Memory) { free(Memory); } |
| |
| /** |
| * @brief Obtain the ID of the currently executing thread. |
| * |
| * @return A unique non-zero value that represents the ID of the currently |
| * executing thread. The value -1 is reserved and must not be returned |
| * by this interface. |
| */ |
| static_assert(sizeof(ACPI_THREAD_ID) >= sizeof(zx_handle_t), "tid size"); |
| ACPI_THREAD_ID AcpiOsGetThreadId() { return (uintptr_t)thrd_current(); } |
| |
| /** |
| * @brief Suspend the running task (course granularity). |
| * |
| * @param Milliseconds The amount of time to sleep, in milliseconds. |
| */ |
| void AcpiOsSleep(UINT64 Milliseconds) { |
| if (Milliseconds > UINT32_MAX) { |
| // If we're asked to sleep for a long time (>1.5 months), shorten it |
| Milliseconds = UINT32_MAX; |
| } |
| zx_nanosleep(zx_deadline_after(ZX_MSEC(Milliseconds))); |
| } |
| |
| /** |
| * @brief Wait for a short amount of time (fine granularity). |
| * |
| * Execution of the running thread is not suspended for this time. |
| * |
| * @param Microseconds The amount of time to delay, in microseconds. |
| */ |
| void AcpiOsStall(UINT32 Microseconds) { zx_nanosleep(zx_deadline_after(ZX_USEC(Microseconds))); } |
| |
| /** |
| * @brief Read a value from a memory location. |
| * |
| * @param Address Memory address to be read. |
| * @param Value A pointer to a location where the data is to be returned. |
| * @param Width The memory width in bits, either 8, 16, 32, or 64. |
| * |
| * @return Exception code that indicates success or reason for failure. |
| */ |
| ACPI_STATUS AcpiOsReadMemory(ACPI_PHYSICAL_ADDRESS Address, UINT64* Value, UINT32 Width) { |
| UNIMPLEMENTED(); |
| return AE_OK; |
| } |
| |
| /** |
| * @brief Write a value to a memory location. |
| * |
| * @param Address Memory address where data is to be written. |
| * @param Value Data to be written to the memory location. |
| * @param Width The memory width in bits, either 8, 16, 32, or 64. |
| * |
| * @return Exception code that indicates success or reason for failure. |
| */ |
| ACPI_STATUS AcpiOsWriteMemory(ACPI_PHYSICAL_ADDRESS Address, UINT64 Value, UINT32 Width) { |
| UNIMPLEMENTED(); |
| return AE_OK; |
| } |
| |
| /** |
| * @brief A hook before writing sleep registers to enter the sleep state. |
| * |
| * @param Which sleep state to enter |
| * @param Register A value |
| * @param Register B value |
| * |
| * @return AE_CTRL_TERMINATE to skip further sleep register writes, otherwise AE_OK |
| */ |
| |
| ACPI_STATUS AcpiOsEnterSleep(UINT8 SleepState, UINT32 RegaValue, UINT32 RegbValue) { |
| // The upstream ACPICA code expects that AcpiHwLegacySleep() or AcpiHwExtendedSleep() is invoked |
| // with interrupts disabled. It requires this because the last steps of going to sleep is writing |
| // to a few registers, flushing the caches (so we don't lose data if the caches are dropped), and |
| // then writing to a register to enter the sleep. If we were to take an interrupt after the cache |
| // flush but before entering sleep, we could have inconsistent memory after waking up. |
| |
| // In Fuchsia, ACPICA runs in usermode and we don't expose a mechanism for it to disable |
| // interrupts. For full shutdown (sleep state 5) this does not matter as any cache corruption |
| // will be trumped by full power loss. For any other S state transitions via AcpiHwLegacySleep() |
| // or AcpiHwExtendedSleep() we make a call to zx_system_powerctl to execute the necessary code in |
| // the kernel where interrupts can be disabled. This means that any call to this hook is from a |
| // function which we do not support for S state transitions so we should return an error. |
| if (SleepState == ACPI_STATE_S5) { |
| return (AE_OK); |
| } else { |
| return (AE_ERROR); |
| } |
| } |
| |
| /** |
| * @brief Formatted stream output. |
| * |
| * @param Format A standard print format string. |
| * @param ... |
| */ |
| void ACPI_INTERNAL_VAR_XFACE AcpiOsPrintf(const char* Format, ...) { |
| va_list argp; |
| va_start(argp, Format); |
| AcpiOsVprintf(Format, argp); |
| va_end(argp); |
| } |
| |
| /** |
| * @brief Formatted stream output. |
| * |
| * @param Format A standard print format string. |
| * @param Args A variable parameter list |
| */ |
| void AcpiOsVprintf(const char* Format, va_list Args) { |
| // Only implement if ACPI_DEBUG_OUTPUT is defined, otherwise this causes |
| // excess boot spew. |
| #ifdef ACPI_DEBUG_OUTPUT |
| vprintf(Format, Args); |
| #endif |
| } |
| |
| /** |
| * @brief Get current value of the system timer |
| * |
| * @return The current value of the system timer in 100-ns units. |
| */ |
| UINT64 AcpiOsGetTimer() { return zx_clock_get_monotonic() / 100; } |
| |
| /** |
| * @brief Break to the debugger or display a breakpoint message. |
| * |
| * @param Function Signal to be sent to the host operating system. Either |
| * ACPI_SIGNAL_FATAL or ACPI_SIGNAL_BREAKPOINT |
| * @param Info Data associated with the signal; type depends on signal type. |
| * |
| * @return Exception code that indicates success or reason for failure. |
| */ |
| ACPI_STATUS AcpiOsSignal(UINT32 Function, void* Info) { |
| UNIMPLEMENTED(); |
| return AE_OK; |
| } |
| |
| /* |
| * According to the the ACPI specification, section 5.2.10, the platform boot firmware aligns |
| * the FACS (Firmware ACPI Control Structure) on a 64-byte boundary anywhere within the system’s |
| * memory address space. This means we can assume the alignment when interacting with it. |
| * Specifically we need to be able to manipulate the GlobalLock contained in the FACS table with |
| * atomic operations, and these require aligned accesses. |
| * |
| * Although we know that the table will be aligned, to prevent the compiler from complaining we |
| * use a wrapper struct to set the alignment attribute. |
| */ |
| struct AlignedFacs { |
| ACPI_TABLE_FACS table; |
| } __attribute__((aligned(8))); |
| |
| /* Setting the alignment should not have changed the size. */ |
| static_assert(sizeof(AlignedFacs) == sizeof(ACPI_TABLE_FACS)); |
| |
| /* @brief Acquire the ACPI global lock |
| * |
| * Implementation for ACPI_ACQUIRE_GLOBAL_LOCK |
| * |
| * @param FacsPtr pointer to the FACS ACPI structure |
| * |
| * @return True if the lock was successfully acquired |
| */ |
| bool _acpica_acquire_global_lock(void* FacsPtr) { |
| ZX_DEBUG_ASSERT(reinterpret_cast<uintptr_t>(FacsPtr) % 8 == 0); |
| AlignedFacs* table = (AlignedFacs*)FacsPtr; |
| uint32_t old_val, new_val, test_val; |
| do { |
| old_val = test_val = table->table.GlobalLock; |
| new_val = old_val & ~ACPI_GLOCK_PENDING; |
| // If the lock is owned, we'll mark it pending |
| if (new_val & ACPI_GLOCK_OWNED) { |
| new_val |= ACPI_GLOCK_PENDING; |
| } |
| new_val |= ACPI_GLOCK_OWNED; |
| __atomic_compare_exchange_n(&table->table.GlobalLock, &old_val, new_val, false, |
| __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); |
| } while (old_val != test_val); |
| |
| /* If we're here, we either acquired the lock or marked it pending */ |
| return !(new_val & ACPI_GLOCK_PENDING); |
| } |
| |
| /* @brief Release the ACPI global lock |
| * |
| * Implementation for ACPI_RELEASE_GLOBAL_LOCK |
| * |
| * @param FacsPtr pointer to the FACS ACPI structure |
| * |
| * @return True if there is someone waiting to acquire the lock |
| */ |
| bool _acpica_release_global_lock(void* FacsPtr) { |
| // the FACS table is required to be 8 byte aligned, so sanity check with an assert but |
| // otherwise we can just treat it as being aligned. |
| ZX_DEBUG_ASSERT(reinterpret_cast<uintptr_t>(FacsPtr) % 8 == 0); |
| AlignedFacs* table = (AlignedFacs*)FacsPtr; |
| uint32_t old_val, new_val, test_val; |
| do { |
| old_val = test_val = table->table.GlobalLock; |
| new_val = old_val & ~(ACPI_GLOCK_PENDING | ACPI_GLOCK_OWNED); |
| __atomic_compare_exchange_n(&table->table.GlobalLock, &old_val, new_val, false, |
| __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); |
| } while (old_val != test_val); |
| |
| return !!(old_val & ACPI_GLOCK_PENDING); |
| } |