| // 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 <assert.h> |
| #include <errno.h> |
| #include <inttypes.h> |
| #include <limits.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <threads.h> |
| |
| #include <hw/inout.h> |
| #include <zircon/assert.h> |
| #include <zircon/process.h> |
| #include <zircon/syscalls.h> |
| #include <zxcpp/new.h> |
| #include <fbl/auto_lock.h> |
| #include <fbl/intrusive_hash_table.h> |
| #include <fbl/unique_ptr.h> |
| |
| #if !defined(__x86_64__) && !defined(__x86__) |
| #error "Unsupported architecture" |
| #endif |
| |
| #include "acpi.h" |
| |
| __WEAK zx_handle_t root_resource_handle; |
| |
| #define _COMPONENT ACPI_OS_SERVICES |
| ACPI_MODULE_NAME ("oszircon") |
| |
| #define LOCAL_TRACE 0 |
| |
| #define TRACEF(str, x...) do { printf("%s:%d: " str, __FUNCTION__, __LINE__, ## x); } while (0) |
| #define LTRACEF(x...) do { if (LOCAL_TRACE) { TRACEF(x); } } while (0) |
| |
| /* Data used for implementing AcpiOsExecute and |
| * AcpiOsWaitEventsComplete */ |
| static mtx_t os_execute_lock = MTX_INIT; |
| static cnd_t os_execute_cond; |
| static int os_execute_tasks = 0; |
| |
| class AcpiOsMappingNode : |
| public fbl::SinglyLinkedListable<fbl::unique_ptr<AcpiOsMappingNode>> { |
| public: |
| using HashTable = |
| fbl::HashTable<uintptr_t, fbl::unique_ptr<AcpiOsMappingNode>>; |
| |
| // @param vaddr Virtual address returned to ACPI, used as key to the hashtable. |
| // @param vaddr_actual Actual virtual address of the mapping. May be different than |
| // vaddr if it is unaligned. |
| // @param length Length of the mapping |
| // @param vmo_handle Handle to the mapped VMO |
| AcpiOsMappingNode(uintptr_t vaddr, uintptr_t vaddr_actual, |
| size_t length, zx_handle_t vmo_handle); |
| ~AcpiOsMappingNode(); |
| |
| // Trait implementation for fbl::HashTable |
| uintptr_t GetKey() const { return vaddr_; } |
| static size_t GetHash(uintptr_t key) { return key; } |
| |
| private: |
| uintptr_t vaddr_; |
| uintptr_t vaddr_actual_; |
| size_t length_; |
| zx_handle_t vmo_handle_; |
| }; |
| |
| fbl::Mutex os_mapping_lock; |
| |
| AcpiOsMappingNode::HashTable os_mapping_tbl; |
| |
| const size_t PCIE_MAX_DEVICES_PER_BUS = 32; |
| const size_t PCIE_MAX_FUNCTIONS_PER_DEVICE = 8; |
| |
| AcpiOsMappingNode::AcpiOsMappingNode(uintptr_t vaddr, uintptr_t vaddr_actual, |
| size_t length, zx_handle_t vmo_handle) |
| : vaddr_(vaddr), vaddr_actual_(vaddr_actual), |
| length_(length), vmo_handle_(vmo_handle) { |
| } |
| |
| AcpiOsMappingNode::~AcpiOsMappingNode() { |
| zx_vmar_unmap(zx_vmar_root_self(), (uintptr_t)vaddr_actual_, length_); |
| zx_handle_close(vmo_handle_); |
| } |
| |
| static zx_status_t mmap_physical(zx_paddr_t phys, size_t size, uint32_t cache_policy, |
| zx_handle_t* out_vmo, zx_vaddr_t* out_vaddr) { |
| zx_handle_t vmo; |
| zx_vaddr_t vaddr; |
| zx_status_t st = zx_vmo_create_physical(root_resource_handle, phys, size, &vmo); |
| if (st != ZX_OK) { |
| return st; |
| } |
| st = zx_vmo_set_cache_policy(vmo, cache_policy); |
| if (st != ZX_OK) { |
| zx_handle_close(vmo); |
| return st; |
| } |
| st = zx_vmar_map(zx_vmar_root_self(), 0, vmo, 0, size, |
| ZX_VM_FLAG_PERM_READ | ZX_VM_FLAG_PERM_WRITE | ZX_VM_FLAG_MAP_RANGE, |
| &vaddr); |
| if (st != ZX_OK) { |
| zx_handle_close(vmo); |
| return st; |
| } else { |
| *out_vmo = vmo; |
| *out_vaddr = vaddr; |
| return ZX_OK; |
| } |
| } |
| |
| static ACPI_STATUS thrd_status_to_acpi_status(int status) { |
| switch (status) { |
| case thrd_success: return AE_OK; |
| case thrd_nomem: return AE_NO_MEMORY; |
| case thrd_timedout: return AE_TIME; |
| default: return AE_ERROR; |
| } |
| } |
| |
| /** |
| * @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() { |
| ACPI_STATUS status = thrd_status_to_acpi_status( |
| cnd_init(&os_execute_cond)); |
| if (status != AE_OK) { |
| return status; |
| } |
| /* TODO(teisenbe): be less permissive */ |
| zx_mmap_device_io(root_resource_handle, 0, 65536); |
| 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() { |
| cnd_destroy(&os_execute_cond); |
| |
| return AE_OK; |
| } |
| |
| /** |
| * @brief Obtain the Root ACPI table pointer (RSDP). |
| * |
| * @return The physical address of the RSDP |
| */ |
| ACPI_PHYSICAL_ADDRESS AcpiOsGetRootPointer() { |
| ACPI_PHYSICAL_ADDRESS TableAddress = 0; |
| ACPI_STATUS status = AcpiFindRootPointer(&TableAddress); |
| |
| uint32_t uefi_rsdp = (uint32_t)zx_acpi_uefi_rsdp(root_resource_handle); |
| if (uefi_rsdp != 0) { |
| return uefi_rsdp; |
| } |
| |
| 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; |
| } |
| |
| // If we decide to make use of a more Zircon specific cache mechanism, |
| // remove the ACPI_USE_LOCAL_CACHE define from the header and implement these |
| // functions. |
| #if 0 |
| /** |
| * @brief Create a memory cache object. |
| * |
| * @param CacheName An ASCII identfier for the cache. |
| * @param ObjectSize The size of each object in the cache. |
| * @param MaxDepth Maximum number of objects in the cache. |
| * @param ReturnCache Where a pointer to the cache object is returned. |
| * |
| * @return AE_OK The cache was successfully created. |
| * @return AE_BAD_PARAMETER The ReturnCache pointer is NULL or ObjectSize < 16. |
| * @return AE_NO_MEMORY Insufficient dynamic memory to complete the operation. |
| */ |
| ACPI_STATUS AcpiOsCreateCache( |
| char *CacheName, |
| UINT16 ObjectSize, |
| UINT16 MaxDepth, |
| ACPI_CACHE_T **ReturnCache) { |
| PANIC_UNIMPLEMENTED; |
| return AE_NO_MEMORY; |
| } |
| |
| /** |
| * @brief Delete a memory cache object. |
| * |
| * @param Cache The cache object to be deleted. |
| * |
| * @return AE_OK The cache was successfully deleted. |
| * @return AE_BAD_PARAMETER The Cache pointer is NULL. |
| */ |
| ACPI_STATUS AcpiOsDeleteCache(ACPI_CACHE_T *Cache) { |
| PANIC_UNIMPLEMENTED; |
| return AE_OK; |
| } |
| |
| /** |
| * @brief Free all objects currently within a cache object. |
| * |
| * @param Cache The cache object to purge. |
| * |
| * @return AE_OK The cache was successfully purged. |
| * @return AE_BAD_PARAMETER The Cache pointer is NULL. |
| */ |
| ACPI_STATUS AcpiOsPurgeCache(ACPI_CACHE_T *Cache) { |
| PANIC_UNIMPLEMENTED; |
| return AE_OK; |
| } |
| |
| |
| /** |
| * @brief Acquire an object from a cache. |
| * |
| * @param Cache The cache object from which to acquire an object. |
| * |
| * @return A pointer to a cache object. NULL if the object could not be |
| * acquired. |
| */ |
| void *AcpiOsAcquireObject(ACPI_CACHE_T *Cache) { |
| PANIC_UNIMPLEMENTED; |
| return NULL; |
| } |
| |
| /** |
| * @brief Release an object to a cache. |
| * |
| * @param Cache The cache object to which the object will be released. |
| * @param Object The object to be released. |
| * |
| * @return AE_OK The object was successfully released. |
| * @return AE_BAD_PARAMETER The Cache or Object pointer is NULL. |
| */ |
| ACPI_STATUS AcpiOsReleaseObject(ACPI_CACHE_T *Cache, void *Object) { |
| PANIC_UNIMPLEMENTED; |
| return AE_OK; |
| } |
| #endif |
| |
| /** |
| * @brief Map physical memory into the caller's address space. |
| * |
| * @param PhysicalAddress A full physical address of the memory to be mapped |
| * into the caller's address space |
| * @param Length The amount of memory to mapped starting at the given physical |
| * address |
| * |
| * @return Logical pointer to the mapped memory. A NULL pointer indicated failures. |
| */ |
| void *AcpiOsMapMemory( |
| ACPI_PHYSICAL_ADDRESS PhysicalAddress, |
| ACPI_SIZE Length) { |
| |
| fbl::AutoLock lock(&os_mapping_lock); |
| |
| // Caution: PhysicalAddress might not be page-aligned, Length might not |
| // be a page multiple. |
| |
| ACPI_PHYSICAL_ADDRESS aligned_address = PhysicalAddress & ~(PAGE_SIZE - 1); |
| ACPI_PHYSICAL_ADDRESS end = (PhysicalAddress + Length + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1); |
| |
| uintptr_t vaddr; |
| size_t length = end - aligned_address; |
| zx_handle_t vmo; |
| zx_status_t status = mmap_physical(aligned_address, end - aligned_address, |
| ZX_CACHE_POLICY_CACHED, &vmo, &vaddr); |
| if (status != ZX_OK) { |
| return NULL; |
| } |
| |
| void* out_addr = (void*)(vaddr + (PhysicalAddress - aligned_address)); |
| fbl::unique_ptr<AcpiOsMappingNode> mn( |
| new AcpiOsMappingNode(reinterpret_cast<uintptr_t>(out_addr), |
| vaddr, length, vmo)); |
| os_mapping_tbl.insert(fbl::move(mn)); |
| |
| return out_addr; |
| } |
| |
| /** |
| * @brief Remove a physical to logical memory mapping. |
| * |
| * @param LogicalAddress The logical address that was returned from a previous |
| * call to AcpiOsMapMemory. |
| * @param Length The amount of memory that was mapped. This value must be |
| * identical to the value used in the call to AcpiOsMapMemory. |
| */ |
| void AcpiOsUnmapMemory(void *LogicalAddress, ACPI_SIZE Length) { |
| fbl::AutoLock lock(&os_mapping_lock); |
| fbl::unique_ptr<AcpiOsMappingNode> mn = os_mapping_tbl.erase((uintptr_t)LogicalAddress); |
| if (mn == NULL) { |
| printf("AcpiOsUnmapMemory nonexisting mapping %p\n", LogicalAddress); |
| } |
| } |
| |
| /** |
| * @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(); |
| } |
| |
| /* Structures used for implementing AcpiOsExecute and |
| * AcpiOsWaitEventsComplete */ |
| struct acpi_os_task_ctx { |
| ACPI_OSD_EXEC_CALLBACK func; |
| void *ctx; |
| }; |
| |
| static int acpi_os_task(void *raw_ctx) { |
| struct acpi_os_task_ctx *ctx = (struct acpi_os_task_ctx*)raw_ctx; |
| |
| ctx->func(ctx->ctx); |
| |
| mtx_lock(&os_execute_lock); |
| os_execute_tasks--; |
| if (os_execute_tasks == 0) { |
| cnd_broadcast(&os_execute_cond); |
| } |
| mtx_unlock(&os_execute_lock); |
| |
| free(ctx); |
| return 0; |
| } |
| |
| /** |
| * @brief Schedule a procedure for deferred execution. |
| * |
| * @param Type Type of the callback function. |
| * @param Function Address of the procedure to execute. |
| * @param Context A context value to be passed to the called procedure. |
| * |
| * @return AE_OK The procedure was successfully queued for execution. |
| * @return AE_BAD_PARAMETER The Type is invalid or the Function pointer |
| * is NULL. |
| */ |
| ACPI_STATUS AcpiOsExecute( |
| ACPI_EXECUTE_TYPE Type, |
| ACPI_OSD_EXEC_CALLBACK Function, |
| void *Context) { |
| |
| if (Function == NULL) { |
| return AE_BAD_PARAMETER; |
| } |
| |
| switch (Type) { |
| case OSL_GLOBAL_LOCK_HANDLER: |
| case OSL_NOTIFY_HANDLER: |
| case OSL_GPE_HANDLER: |
| case OSL_DEBUGGER_MAIN_THREAD: |
| case OSL_DEBUGGER_EXEC_THREAD: |
| case OSL_EC_POLL_HANDLER: |
| case OSL_EC_BURST_HANDLER: break; |
| default: return AE_BAD_PARAMETER; |
| } |
| |
| struct acpi_os_task_ctx *ctx = (struct acpi_os_task_ctx*)malloc(sizeof(*ctx)); |
| if (!ctx) { |
| return AE_NO_MEMORY; |
| } |
| ctx->func = Function; |
| ctx->ctx = Context; |
| |
| mtx_lock(&os_execute_lock); |
| os_execute_tasks++; |
| mtx_unlock(&os_execute_lock); |
| |
| // TODO(teisenbe): Instead of spawning a thread each time for this, |
| // we should back this with a thread pool. |
| thrd_t thread; |
| ACPI_STATUS status = thrd_status_to_acpi_status( |
| thrd_create(&thread, acpi_os_task, ctx)); |
| if (status != AE_OK) { |
| free(ctx); |
| mtx_lock(&os_execute_lock); |
| os_execute_tasks--; |
| if (os_execute_tasks == 0) { |
| cnd_broadcast(&os_execute_cond); |
| } |
| mtx_unlock(&os_execute_lock); |
| return status; |
| } |
| |
| thrd_detach(thread); |
| return AE_OK; |
| } |
| |
| /** |
| * @brief Wait for completion of asynchronous events. |
| * |
| * This function blocks until all asynchronous events initiated by |
| * AcpiOsExecute have completed. |
| */ |
| void AcpiOsWaitEventsComplete(void) { |
| mtx_lock(&os_execute_lock); |
| while (os_execute_tasks > 0) { |
| cnd_wait(&os_execute_cond, &os_execute_lock); |
| } |
| mtx_unlock(&os_execute_lock); |
| } |
| |
| /** |
| * @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 Create a semaphore. |
| * |
| * @param MaxUnits The maximum number of units this semaphore will be required |
| * to accept |
| * @param InitialUnits The initial number of units to be assigned to the |
| * semaphore. |
| * @param OutHandle A pointer to a locaton where a handle to the semaphore is |
| * to be returned. |
| * |
| * @return AE_OK The semaphore was successfully created. |
| * @return AE_BAD_PARAMETER The InitialUnits is invalid or the OutHandle |
| * pointer is NULL. |
| * @return AE_NO_MEMORY Insufficient memory to create the semaphore. |
| */ |
| ACPI_STATUS AcpiOsCreateSemaphore( |
| UINT32 MaxUnits, |
| UINT32 InitialUnits, |
| ACPI_SEMAPHORE *OutHandle) { |
| sem_t *sem = (sem_t*)malloc(sizeof(sem_t)); |
| if (!sem) { |
| return AE_NO_MEMORY; |
| } |
| if (sem_init(sem, 0, InitialUnits) < 0) { |
| free(sem); |
| return AE_ERROR; |
| } |
| *OutHandle = sem; |
| return AE_OK; |
| } |
| |
| /** |
| * @brief Delete a semaphore. |
| * |
| * @param Handle A handle to a semaphore objected that was returned by a |
| * previous call to AcpiOsCreateSemaphore. |
| * |
| * @return AE_OK The semaphore was successfully deleted. |
| * @return AE_BAD_PARAMETER The Handle is invalid. |
| */ |
| ACPI_STATUS AcpiOsDeleteSemaphore(ACPI_SEMAPHORE Handle) { |
| free(Handle); |
| return AE_OK; |
| } |
| |
| /** |
| * @brief Wait for units from a semaphore. |
| * |
| * @param Handle A handle to a semaphore objected that was returned by a |
| * previous call to AcpiOsCreateSemaphore. |
| * @param Units The number of units the caller is requesting. |
| * @param Timeout How long the caller is willing to wait for the requested |
| * units, in milliseconds. A value of -1 indicates that the caller |
| * is willing to wait forever. Timeout may be 0. |
| * |
| * @return AE_OK The requested units were successfully received. |
| * @return AE_BAD_PARAMETER The Handle is invalid. |
| * @return AE_TIME The units could not be acquired within the specified time. |
| */ |
| ACPI_STATUS AcpiOsWaitSemaphore( |
| ACPI_SEMAPHORE Handle, |
| UINT32 Units, |
| UINT16 Timeout) { |
| |
| if (Timeout == UINT16_MAX) { |
| if (sem_wait(Handle) < 0) { |
| ZX_ASSERT_MSG(false, "sem_wait failed %d", errno); |
| } |
| return AE_OK; |
| } |
| |
| zx_time_t now = zx_time_get(ZX_CLOCK_UTC); |
| struct timespec then = { |
| .tv_sec = static_cast<time_t>(now / ZX_SEC(1)), |
| .tv_nsec = static_cast<long>(now % ZX_SEC(1)), |
| }; |
| then.tv_nsec += ZX_MSEC(Timeout); |
| if (then.tv_nsec > static_cast<long>(ZX_SEC(1))) { |
| then.tv_sec += then.tv_nsec / ZX_SEC(1); |
| then.tv_nsec %= ZX_SEC(1); |
| } |
| |
| if (sem_timedwait(Handle, &then) < 0) { |
| ZX_ASSERT_MSG(errno == ETIMEDOUT, "sem_timedwait failed unexpectedly %d", errno); |
| return AE_TIME; |
| } |
| return AE_OK; |
| } |
| |
| /** |
| * @brief Send units to a semaphore. |
| * |
| * @param Handle A handle to a semaphore objected that was returned by a |
| * previous call to AcpiOsCreateSemaphore. |
| * @param Units The number of units to send to the semaphore. |
| * |
| * @return AE_OK The semaphore was successfully signaled. |
| * @return AE_BAD_PARAMETER The Handle is invalid. |
| */ |
| ACPI_STATUS AcpiOsSignalSemaphore( |
| ACPI_SEMAPHORE Handle, |
| UINT32 Units) { |
| // TODO: Implement support for Units > 1 |
| assert(Units == 1); |
| |
| sem_post(Handle); |
| return AE_OK; |
| } |
| |
| /** |
| * @brief Create a spin lock. |
| * |
| * @param OutHandle A pointer to a locaton where a handle to the lock is |
| * to be returned. |
| * |
| * @return AE_OK The lock was successfully created. |
| * @return AE_BAD_PARAMETER The OutHandle pointer is NULL. |
| * @return AE_NO_MEMORY Insufficient memory to create the lock. |
| */ |
| ACPI_STATUS AcpiOsCreateLock(ACPI_SPINLOCK *OutHandle) { |
| // Since we don't have a notion of interrupt contex in usermode, just make |
| // these mutexes. |
| mtx_t* lock = (mtx_t*)malloc(sizeof(mtx_t)); |
| if (!lock) { |
| return AE_NO_MEMORY; |
| } |
| |
| ACPI_STATUS status = thrd_status_to_acpi_status( |
| mtx_init(lock, mtx_plain)); |
| if (status != AE_OK) { |
| return status; |
| } |
| *OutHandle = lock; |
| return AE_OK; |
| } |
| |
| /** |
| * @brief Delete a spin lock. |
| * |
| * @param Handle A handle to a lock objected that was returned by a |
| * previous call to AcpiOsCreateLock. |
| * |
| * @return AE_OK The lock was successfully deleted. |
| * @return AE_BAD_PARAMETER The Handle is invalid. |
| */ |
| void AcpiOsDeleteLock(ACPI_SPINLOCK Handle) { |
| mtx_destroy(Handle); |
| free(Handle); |
| } |
| |
| /** |
| * @brief Acquire a spin lock. |
| * |
| * @param Handle A handle to a lock objected that was returned by a |
| * previous call to AcpiOsCreateLock. |
| * |
| * @return Platform-dependent CPU flags. To be used when the lock is released. |
| */ |
| ACPI_CPU_FLAGS AcpiOsAcquireLock(ACPI_SPINLOCK Handle) { |
| mtx_lock(Handle); |
| return 0; |
| } |
| |
| /** |
| * @brief Release a spin lock. |
| * |
| * @param Handle A handle to a lock objected that was returned by a |
| * previous call to AcpiOsCreateLock. |
| * @param Flags CPU Flags that were returned from AcpiOsAcquireLock. |
| */ |
| void AcpiOsReleaseLock(ACPI_SPINLOCK Handle, ACPI_CPU_FLAGS Flags) { |
| mtx_unlock(Handle); |
| } |
| |
| // Wrapper structs for interfacing between our interrupt handler convention and |
| // ACPICA's |
| struct acpi_irq_thread_arg { |
| ACPI_OSD_HANDLER handler; |
| zx_handle_t irq_handle; |
| void *context; |
| }; |
| static int acpi_irq_thread(void *arg) { |
| struct acpi_irq_thread_arg *real_arg = (struct acpi_irq_thread_arg *)arg; |
| while (1) { |
| zx_status_t status = zx_interrupt_wait(real_arg->irq_handle); |
| if (status != ZX_OK) { |
| continue; |
| } |
| |
| // TODO: Should we do something with the return value from the handler? |
| real_arg->handler(real_arg->context); |
| |
| zx_interrupt_complete(real_arg->irq_handle); |
| } |
| return 0; |
| } |
| |
| /** |
| * @brief Install a handler for a hardware interrupt. |
| * |
| * @param InterruptLevel Interrupt level that the handler will service. |
| * @param Handler Address of the handler. |
| * @param Context A context value that is passed to the handler when the |
| * interrupt is dispatched. |
| * |
| * @return AE_OK The handler was successfully installed. |
| * @return AE_BAD_PARAMETER The InterruptNumber is invalid or the Handler |
| * pointer is NULL. |
| * @return AE_ALREADY_EXISTS A handler for this interrupt level is already |
| * installed. |
| */ |
| ACPI_STATUS AcpiOsInstallInterruptHandler( |
| UINT32 InterruptLevel, |
| ACPI_OSD_HANDLER Handler, |
| void *Context) { |
| // Note that InterruptLevel here is ISA IRQs (or global of the legacy PIC |
| // does't exist), not system exceptions. |
| |
| // TODO: Clean this up to be less x86 centric. |
| |
| if (InterruptLevel == 0) { |
| /* Some buggy firmware fails to populate the SCI_INT field of the FADT |
| * properly. 0 is a known bad value, since the legacy PIT uses it and |
| * cannot be remapped. Just lie and say we installed a handler; this |
| * system will just never receive an SCI. If we return an error here, |
| * ACPI init will fail completely, and the system will be unusable. */ |
| return AE_OK; |
| } |
| |
| assert(InterruptLevel == 0x9); // SCI |
| |
| struct acpi_irq_thread_arg *arg = (struct acpi_irq_thread_arg*)malloc(sizeof(*arg)); |
| if (!arg) { |
| return AE_NO_MEMORY; |
| } |
| |
| zx_handle_t handle; |
| zx_status_t status = zx_interrupt_create(root_resource_handle, InterruptLevel, |
| ZX_FLAG_REMAP_IRQ, &handle); |
| if (status != ZX_OK) { |
| free(arg); |
| return AE_ERROR; |
| } |
| |
| arg->handler = Handler; |
| arg->context = Context; |
| arg->irq_handle = handle; |
| |
| thrd_t thread; |
| int ret = thrd_create(&thread, acpi_irq_thread, arg); |
| if (ret != 0) { |
| free(arg); |
| return AE_ERROR; |
| } |
| thrd_detach(thread); |
| |
| return AE_OK; |
| } |
| |
| /** |
| * @brief Remove an interrupt handler. |
| * |
| * @param InterruptNumber Interrupt number that the handler is currently |
| * servicing. |
| * @param Handler Address of the handler that was previously installed. |
| * |
| * @return AE_OK The handler was successfully removed. |
| * @return AE_BAD_PARAMETER The InterruptNumber is invalid, the Handler |
| * pointer is NULL, or the Handler address is no the same as the one |
| * currently installed. |
| * @return AE_NOT_EXIST There is no handler installed for this interrupt level. |
| */ |
| ACPI_STATUS AcpiOsRemoveInterruptHandler( |
| UINT32 InterruptNumber, |
| ACPI_OSD_HANDLER Handler) { |
| assert(false); |
| return AE_NOT_EXIST; |
| } |
| |
| /** |
| * @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) { |
| assert(false); |
| 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) { |
| assert(false); |
| return AE_OK; |
| } |
| |
| /** |
| * @brief Read a value from an input port. |
| * |
| * @param Address Hardware I/O port address to be read. |
| * @param Value A pointer to a location where the data is to be returned. |
| * @param Width The port width in bits, either 8, 16, or 32. |
| * |
| * @return Exception code that indicates success or reason for failure. |
| */ |
| ACPI_STATUS AcpiOsReadPort( |
| ACPI_IO_ADDRESS Address, |
| UINT32 *Value, |
| UINT32 Width) { |
| if (Address > 0xffff) { |
| return AE_BAD_PARAMETER; |
| } |
| |
| switch (Width) { |
| case 8: |
| *Value = inp((uint16_t)Address); |
| break; |
| case 16: |
| *Value = inpw((uint16_t)Address); |
| break; |
| case 32: |
| *Value = inpd((uint16_t)Address); |
| break; |
| default: |
| return AE_BAD_PARAMETER; |
| } |
| return AE_OK; |
| } |
| |
| /** |
| * @brief Write a value to an output port. |
| * |
| * @param Address Hardware I/O port address where data is to be written. |
| * @param Value The value to be written. |
| * @param Width The port width in bits, either 8, 16, or 32. |
| * |
| * @return Exception code that indicates success or reason for failure. |
| */ |
| ACPI_STATUS AcpiOsWritePort( |
| ACPI_IO_ADDRESS Address, |
| UINT32 Value, |
| UINT32 Width) { |
| if (Address > 0xffff) { |
| return AE_BAD_PARAMETER; |
| } |
| |
| switch (Width) { |
| case 8: |
| outp((uint16_t)Address, (uint8_t)Value); |
| break; |
| case 16: |
| outpw((uint16_t)Address, (uint16_t)Value); |
| break; |
| case 32: |
| outpd((uint16_t)Address, (uint32_t)Value); |
| break; |
| default: |
| return AE_BAD_PARAMETER; |
| } |
| return AE_OK; |
| } |
| |
| /** |
| * @brief Read/Write a value from a PCI configuration register. |
| * |
| * @param PciId The full PCI configuration space address, consisting of a |
| * segment number, bus number, device number, and function number. |
| * @param Register The PCI register address to be read from. |
| * @param Value A pointer to a location where the data is to be returned. |
| * @param Width The register width in bits, either 8, 16, 32, or 64. |
| * @param Write Write or Read. |
| * |
| * @return Exception code that indicates success or reason for failure. |
| */ |
| static ACPI_STATUS AcpiOsReadWritePciConfiguration( |
| ACPI_PCI_ID *PciId, |
| UINT32 Register, |
| UINT64 *Value, |
| UINT32 Width, |
| bool Write) { |
| |
| if (LOCAL_TRACE) { |
| printf("ACPIOS: %s PCI Config %x:%x:%x:%x register %#x width %u\n", |
| Write ? "write" : "read" ,PciId->Segment, PciId->Bus, PciId->Device, PciId->Function, Register, Width); |
| } |
| |
| // Only segment 0 is supported for now |
| if (PciId->Segment != 0) { |
| printf("ACPIOS: read/write config, segment != 0 not supported.\n"); |
| return AE_ERROR; |
| } |
| |
| // Check bounds of device and function offsets |
| if (PciId->Device >= PCIE_MAX_DEVICES_PER_BUS |
| || PciId->Function >= PCIE_MAX_FUNCTIONS_PER_DEVICE) { |
| printf("ACPIOS: device out of reasonable bounds.\n"); |
| return AE_ERROR; |
| } |
| |
| // PCI config only supports up to 32 bit values |
| if (Write && (*Value > UINT_MAX)) { |
| printf("ACPIOS: read/write config, Value passed does not fit confg registers.\n"); |
| } |
| |
| // Clear higher bits before a read |
| if (!Write) { |
| *Value = 0; |
| } |
| |
| #if __x86_64__ |
| uint8_t bus = static_cast<uint8_t>(PciId->Bus); |
| uint8_t dev = static_cast<uint8_t>(PciId->Device); |
| uint8_t func = static_cast<uint8_t>(PciId->Function); |
| uint8_t offset = static_cast<uint8_t>(Register); |
| uint8_t width = static_cast<uint8_t>(Width); |
| uint32_t val = *Value & 0xFFFFFFFF; // PIO access can only be 32 bits |
| zx_status_t status = zx_pci_cfg_pio_rw(root_resource_handle, bus, dev, func, offset, |
| &val, width, Write); |
| |
| *Value = val; |
| |
| #ifdef ACPI_DEBUG_OUTPUT |
| if (status != ZX_OK) { |
| printf("ACPIOS: pci rw error: %d\n", status); |
| } |
| #endif // ACPI_DEBUG_OUTPUT |
| return (status == ZX_OK) ? AE_OK : AE_ERROR; |
| #endif // __x86_64__ |
| |
| return AE_NOT_IMPLEMENTED; |
| } |
| /** |
| * @brief Read a value from a PCI configuration register. |
| * |
| * @param PciId The full PCI configuration space address, consisting of a |
| * segment number, bus number, device number, and function number. |
| * @param Register The PCI register address to be read from. |
| * @param Value A pointer to a location where the data is to be returned. |
| * @param Width The register width in bits, either 8, 16, 32, or 64. |
| * |
| * @return Exception code that indicates success or reason for failure. |
| */ |
| ACPI_STATUS AcpiOsReadPciConfiguration( |
| ACPI_PCI_ID *PciId, |
| UINT32 Register, |
| UINT64 *Value, |
| UINT32 Width) { |
| |
| return AcpiOsReadWritePciConfiguration(PciId, Register, Value, Width, false); |
| } |
| |
| /** |
| * @brief Write a value to a PCI configuration register. |
| * |
| * @param PciId The full PCI configuration space address, consisting of a |
| * segment number, bus number, device number, and function number. |
| * @param Register The PCI register address to be written to. |
| * @param Value Data to be written. |
| * @param Width The register width in bits, either 8, 16, or 32. |
| * |
| * @return Exception code that indicates success or reason for failure. |
| */ |
| ACPI_STATUS AcpiOsWritePciConfiguration( |
| ACPI_PCI_ID *PciId, |
| UINT32 Register, |
| UINT64 Value, |
| UINT32 Width) { |
| |
| return AcpiOsReadWritePciConfiguration(PciId, Register, &Value, Width, true); |
| } |
| |
| /** |
| * @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() { |
| assert(false); |
| return 0; |
| } |
| |
| /** |
| * @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) { |
| assert(false); |
| return AE_OK; |
| } |
| |
| /* @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) |
| { |
| ACPI_TABLE_FACS *table = (ACPI_TABLE_FACS*)FacsPtr; |
| uint32_t old_val, new_val, test_val; |
| do { |
| old_val = test_val = 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->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) |
| { |
| ACPI_TABLE_FACS *table = (ACPI_TABLE_FACS*)FacsPtr; |
| uint32_t old_val, new_val, test_val; |
| do { |
| old_val = test_val = table->GlobalLock; |
| new_val = old_val & ~(ACPI_GLOCK_PENDING | ACPI_GLOCK_OWNED); |
| __atomic_compare_exchange_n(&table->GlobalLock, &old_val, new_val, false, |
| __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); |
| } while (old_val != test_val); |
| |
| return !!(old_val & ACPI_GLOCK_PENDING); |
| } |