blob: e83c257e5d529b5269f5b6a3c2374d9147fc5edb [file] [log] [blame]
// 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);
}