blob: 7ef723a0e222219ff541a06222fdac67cbc02551 [file] [log] [blame]
// Copyright 2022 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 <acpica/acpi.h>
#include <fbl/intrusive_double_list.h>
/* Structures used for implementing AcpiOsExecute and
* AcpiOsWaitEventsComplete */
struct AcpiOsTaskCtx : public fbl::DoublyLinkedListable<std::unique_ptr<AcpiOsTaskCtx>> {
ACPI_OSD_EXEC_CALLBACK func;
void* ctx;
};
/* Thread function for implementing AcpiOsExecute */
static int AcpiOsExecuteTask(void* arg);
/* Data used for implementing AcpiOsExecute and
* AcpiOsWaitEventsComplete */
static struct {
thrd_t thread;
cnd_t cond;
cnd_t idle_cond;
mtx_t lock = MTX_INIT;
bool shutdown = false;
bool idle = true;
fbl::DoublyLinkedList<std::unique_ptr<AcpiOsTaskCtx>> tasks;
} os_execute_state;
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;
}
}
ACPI_STATUS AcpiTaskThreadStart() {
ACPI_STATUS status = thrd_status_to_acpi_status(cnd_init(&os_execute_state.cond));
if (status != AE_OK) {
return status;
}
status = thrd_status_to_acpi_status(cnd_init(&os_execute_state.idle_cond));
if (status != AE_OK) {
cnd_destroy(&os_execute_state.cond);
return status;
}
status = thrd_status_to_acpi_status(
thrd_create_with_name(&os_execute_state.thread, AcpiOsExecuteTask, nullptr, "acpi_os_task"));
if (status != AE_OK) {
return status;
}
return AE_OK;
}
ACPI_STATUS AcpiTaskThreadTerminate() {
mtx_lock(&os_execute_state.lock);
os_execute_state.shutdown = true;
mtx_unlock(&os_execute_state.lock);
cnd_broadcast(&os_execute_state.cond);
thrd_join(os_execute_state.thread, nullptr);
cnd_destroy(&os_execute_state.cond);
cnd_destroy(&os_execute_state.idle_cond);
return AE_OK;
}
static int AcpiOsExecuteTask(void* arg) {
while (1) {
std::unique_ptr<AcpiOsTaskCtx> task;
mtx_lock(&os_execute_state.lock);
while ((task = os_execute_state.tasks.pop_front()) == nullptr) {
os_execute_state.idle = true;
// If anything is waiting for the queue to empty, notify it.
cnd_signal(&os_execute_state.idle_cond);
// If we're waiting to shutdown, do it now that there's no more work
if (os_execute_state.shutdown) {
mtx_unlock(&os_execute_state.lock);
return 0;
}
cnd_wait(&os_execute_state.cond, &os_execute_state.lock);
}
os_execute_state.idle = false;
mtx_unlock(&os_execute_state.lock);
task->func(task->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;
}
fbl::AllocChecker ac;
std::unique_ptr<AcpiOsTaskCtx> task(new (&ac) AcpiOsTaskCtx);
if (!ac.check()) {
return AE_NO_MEMORY;
}
task->func = Function;
task->ctx = Context;
mtx_lock(&os_execute_state.lock);
os_execute_state.tasks.push_back(std::move(task));
mtx_unlock(&os_execute_state.lock);
cnd_signal(&os_execute_state.cond);
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_state.lock);
while (!os_execute_state.idle) {
cnd_wait(&os_execute_state.idle_cond, &os_execute_state.lock);
}
mtx_unlock(&os_execute_state.lock);
}