0,0 → 1,764 |
/******************************************************************************* |
* |
* Module Name: dbexec - debugger control method execution |
* |
******************************************************************************/ |
|
/* |
* Copyright (C) 2000 - 2015, Intel Corp. |
* All rights reserved. |
* |
* Redistribution and use in source and binary forms, with or without |
* modification, are permitted provided that the following conditions |
* are met: |
* 1. Redistributions of source code must retain the above copyright |
* notice, this list of conditions, and the following disclaimer, |
* without modification. |
* 2. Redistributions in binary form must reproduce at minimum a disclaimer |
* substantially similar to the "NO WARRANTY" disclaimer below |
* ("Disclaimer") and any redistribution must be conditioned upon |
* including a substantially similar Disclaimer requirement for further |
* binary redistribution. |
* 3. Neither the names of the above-listed copyright holders nor the names |
* of any contributors may be used to endorse or promote products derived |
* from this software without specific prior written permission. |
* |
* Alternatively, this software may be distributed under the terms of the |
* GNU General Public License ("GPL") version 2 as published by the Free |
* Software Foundation. |
* |
* NO WARRANTY |
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR |
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
* HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, |
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING |
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
* POSSIBILITY OF SUCH DAMAGES. |
*/ |
|
#include <acpi/acpi.h> |
#include "accommon.h" |
#include "acdebug.h" |
#include "acnamesp.h" |
|
#define _COMPONENT ACPI_CA_DEBUGGER |
ACPI_MODULE_NAME("dbexec") |
|
static struct acpi_db_method_info acpi_gbl_db_method_info; |
|
/* Local prototypes */ |
|
static acpi_status |
acpi_db_execute_method(struct acpi_db_method_info *info, |
struct acpi_buffer *return_obj); |
|
static acpi_status acpi_db_execute_setup(struct acpi_db_method_info *info); |
|
static u32 acpi_db_get_outstanding_allocations(void); |
|
static void ACPI_SYSTEM_XFACE acpi_db_method_thread(void *context); |
|
static acpi_status |
acpi_db_execution_walk(acpi_handle obj_handle, |
u32 nesting_level, void *context, void **return_value); |
|
/******************************************************************************* |
* |
* FUNCTION: acpi_db_delete_objects |
* |
* PARAMETERS: count - Count of objects in the list |
* objects - Array of ACPI_OBJECTs to be deleted |
* |
* RETURN: None |
* |
* DESCRIPTION: Delete a list of ACPI_OBJECTS. Handles packages and nested |
* packages via recursion. |
* |
******************************************************************************/ |
|
void acpi_db_delete_objects(u32 count, union acpi_object *objects) |
{ |
u32 i; |
|
for (i = 0; i < count; i++) { |
switch (objects[i].type) { |
case ACPI_TYPE_BUFFER: |
|
ACPI_FREE(objects[i].buffer.pointer); |
break; |
|
case ACPI_TYPE_PACKAGE: |
|
/* Recursive call to delete package elements */ |
|
acpi_db_delete_objects(objects[i].package.count, |
objects[i].package.elements); |
|
/* Free the elements array */ |
|
ACPI_FREE(objects[i].package.elements); |
break; |
|
default: |
|
break; |
} |
} |
} |
|
/******************************************************************************* |
* |
* FUNCTION: acpi_db_execute_method |
* |
* PARAMETERS: info - Valid info segment |
* return_obj - Where to put return object |
* |
* RETURN: Status |
* |
* DESCRIPTION: Execute a control method. |
* |
******************************************************************************/ |
|
static acpi_status |
acpi_db_execute_method(struct acpi_db_method_info *info, |
struct acpi_buffer *return_obj) |
{ |
acpi_status status; |
struct acpi_object_list param_objects; |
union acpi_object params[ACPI_DEBUGGER_MAX_ARGS + 1]; |
u32 i; |
|
ACPI_FUNCTION_TRACE(db_execute_method); |
|
if (acpi_gbl_db_output_to_file && !acpi_dbg_level) { |
acpi_os_printf("Warning: debug output is not enabled!\n"); |
} |
|
param_objects.count = 0; |
param_objects.pointer = NULL; |
|
/* Pass through any command-line arguments */ |
|
if (info->args && info->args[0]) { |
|
/* Get arguments passed on the command line */ |
|
for (i = 0; (info->args[i] && *(info->args[i])); i++) { |
|
/* Convert input string (token) to an actual union acpi_object */ |
|
status = acpi_db_convert_to_object(info->types[i], |
info->args[i], |
¶ms[i]); |
if (ACPI_FAILURE(status)) { |
ACPI_EXCEPTION((AE_INFO, status, |
"While parsing method arguments")); |
goto cleanup; |
} |
} |
|
param_objects.count = i; |
param_objects.pointer = params; |
} |
|
/* Prepare for a return object of arbitrary size */ |
|
return_obj->pointer = acpi_gbl_db_buffer; |
return_obj->length = ACPI_DEBUG_BUFFER_SIZE; |
|
/* Do the actual method execution */ |
|
acpi_gbl_method_executing = TRUE; |
status = acpi_evaluate_object(NULL, info->pathname, |
¶m_objects, return_obj); |
|
acpi_gbl_cm_single_step = FALSE; |
acpi_gbl_method_executing = FALSE; |
|
if (ACPI_FAILURE(status)) { |
ACPI_EXCEPTION((AE_INFO, status, |
"while executing %s from debugger", |
info->pathname)); |
|
if (status == AE_BUFFER_OVERFLOW) { |
ACPI_ERROR((AE_INFO, |
"Possible overflow of internal debugger " |
"buffer (size 0x%X needed 0x%X)", |
ACPI_DEBUG_BUFFER_SIZE, |
(u32)return_obj->length)); |
} |
} |
|
cleanup: |
acpi_db_delete_objects(param_objects.count, params); |
return_ACPI_STATUS(status); |
} |
|
/******************************************************************************* |
* |
* FUNCTION: acpi_db_execute_setup |
* |
* PARAMETERS: info - Valid method info |
* |
* RETURN: None |
* |
* DESCRIPTION: Setup info segment prior to method execution |
* |
******************************************************************************/ |
|
static acpi_status acpi_db_execute_setup(struct acpi_db_method_info *info) |
{ |
acpi_status status; |
|
ACPI_FUNCTION_NAME(db_execute_setup); |
|
/* Catenate the current scope to the supplied name */ |
|
info->pathname[0] = 0; |
if ((info->name[0] != '\\') && (info->name[0] != '/')) { |
if (acpi_ut_safe_strcat(info->pathname, sizeof(info->pathname), |
acpi_gbl_db_scope_buf)) { |
status = AE_BUFFER_OVERFLOW; |
goto error_exit; |
} |
} |
|
if (acpi_ut_safe_strcat(info->pathname, sizeof(info->pathname), |
info->name)) { |
status = AE_BUFFER_OVERFLOW; |
goto error_exit; |
} |
|
acpi_db_prep_namestring(info->pathname); |
|
acpi_db_set_output_destination(ACPI_DB_DUPLICATE_OUTPUT); |
acpi_os_printf("Evaluating %s\n", info->pathname); |
|
if (info->flags & EX_SINGLE_STEP) { |
acpi_gbl_cm_single_step = TRUE; |
acpi_db_set_output_destination(ACPI_DB_CONSOLE_OUTPUT); |
} |
|
else { |
/* No single step, allow redirection to a file */ |
|
acpi_db_set_output_destination(ACPI_DB_REDIRECTABLE_OUTPUT); |
} |
|
return (AE_OK); |
|
error_exit: |
|
ACPI_EXCEPTION((AE_INFO, status, "During setup for method execution")); |
return (status); |
} |
|
#ifdef ACPI_DBG_TRACK_ALLOCATIONS |
u32 acpi_db_get_cache_info(struct acpi_memory_list *cache) |
{ |
|
return (cache->total_allocated - cache->total_freed - |
cache->current_depth); |
} |
#endif |
|
/******************************************************************************* |
* |
* FUNCTION: acpi_db_get_outstanding_allocations |
* |
* PARAMETERS: None |
* |
* RETURN: Current global allocation count minus cache entries |
* |
* DESCRIPTION: Determine the current number of "outstanding" allocations -- |
* those allocations that have not been freed and also are not |
* in one of the various object caches. |
* |
******************************************************************************/ |
|
static u32 acpi_db_get_outstanding_allocations(void) |
{ |
u32 outstanding = 0; |
|
#ifdef ACPI_DBG_TRACK_ALLOCATIONS |
|
outstanding += acpi_db_get_cache_info(acpi_gbl_state_cache); |
outstanding += acpi_db_get_cache_info(acpi_gbl_ps_node_cache); |
outstanding += acpi_db_get_cache_info(acpi_gbl_ps_node_ext_cache); |
outstanding += acpi_db_get_cache_info(acpi_gbl_operand_cache); |
#endif |
|
return (outstanding); |
} |
|
/******************************************************************************* |
* |
* FUNCTION: acpi_db_execution_walk |
* |
* PARAMETERS: WALK_CALLBACK |
* |
* RETURN: Status |
* |
* DESCRIPTION: Execute a control method. Name is relative to the current |
* scope. |
* |
******************************************************************************/ |
|
static acpi_status |
acpi_db_execution_walk(acpi_handle obj_handle, |
u32 nesting_level, void *context, void **return_value) |
{ |
union acpi_operand_object *obj_desc; |
struct acpi_namespace_node *node = |
(struct acpi_namespace_node *)obj_handle; |
struct acpi_buffer return_obj; |
acpi_status status; |
|
obj_desc = acpi_ns_get_attached_object(node); |
if (obj_desc->method.param_count) { |
return (AE_OK); |
} |
|
return_obj.pointer = NULL; |
return_obj.length = ACPI_ALLOCATE_BUFFER; |
|
acpi_ns_print_node_pathname(node, "Evaluating"); |
|
/* Do the actual method execution */ |
|
acpi_os_printf("\n"); |
acpi_gbl_method_executing = TRUE; |
|
status = acpi_evaluate_object(node, NULL, NULL, &return_obj); |
|
acpi_os_printf("Evaluation of [%4.4s] returned %s\n", |
acpi_ut_get_node_name(node), |
acpi_format_exception(status)); |
|
acpi_gbl_method_executing = FALSE; |
return (AE_OK); |
} |
|
/******************************************************************************* |
* |
* FUNCTION: acpi_db_execute |
* |
* PARAMETERS: name - Name of method to execute |
* args - Parameters to the method |
* Types - |
* flags - single step/no single step |
* |
* RETURN: None |
* |
* DESCRIPTION: Execute a control method. Name is relative to the current |
* scope. |
* |
******************************************************************************/ |
|
void |
acpi_db_execute(char *name, char **args, acpi_object_type * types, u32 flags) |
{ |
acpi_status status; |
struct acpi_buffer return_obj; |
char *name_string; |
|
#ifdef ACPI_DEBUG_OUTPUT |
u32 previous_allocations; |
u32 allocations; |
#endif |
|
/* |
* Allow one execution to be performed by debugger or single step |
* execution will be dead locked by the interpreter mutexes. |
*/ |
if (acpi_gbl_method_executing) { |
acpi_os_printf("Only one debugger execution is allowed.\n"); |
return; |
} |
#ifdef ACPI_DEBUG_OUTPUT |
/* Memory allocation tracking */ |
|
previous_allocations = acpi_db_get_outstanding_allocations(); |
#endif |
|
if (*name == '*') { |
(void)acpi_walk_namespace(ACPI_TYPE_METHOD, ACPI_ROOT_OBJECT, |
ACPI_UINT32_MAX, |
acpi_db_execution_walk, NULL, NULL, |
NULL); |
return; |
} else { |
name_string = ACPI_ALLOCATE(strlen(name) + 1); |
if (!name_string) { |
return; |
} |
|
memset(&acpi_gbl_db_method_info, 0, |
sizeof(struct acpi_db_method_info)); |
|
strcpy(name_string, name); |
acpi_ut_strupr(name_string); |
acpi_gbl_db_method_info.name = name_string; |
acpi_gbl_db_method_info.args = args; |
acpi_gbl_db_method_info.types = types; |
acpi_gbl_db_method_info.flags = flags; |
|
return_obj.pointer = NULL; |
return_obj.length = ACPI_ALLOCATE_BUFFER; |
|
status = acpi_db_execute_setup(&acpi_gbl_db_method_info); |
if (ACPI_FAILURE(status)) { |
ACPI_FREE(name_string); |
return; |
} |
|
/* Get the NS node, determines existence also */ |
|
status = acpi_get_handle(NULL, acpi_gbl_db_method_info.pathname, |
&acpi_gbl_db_method_info.method); |
if (ACPI_SUCCESS(status)) { |
status = |
acpi_db_execute_method(&acpi_gbl_db_method_info, |
&return_obj); |
} |
ACPI_FREE(name_string); |
} |
|
/* |
* Allow any handlers in separate threads to complete. |
* (Such as Notify handlers invoked from AML executed above). |
*/ |
acpi_os_sleep((u64)10); |
|
#ifdef ACPI_DEBUG_OUTPUT |
|
/* Memory allocation tracking */ |
|
allocations = |
acpi_db_get_outstanding_allocations() - previous_allocations; |
|
acpi_db_set_output_destination(ACPI_DB_DUPLICATE_OUTPUT); |
|
if (allocations > 0) { |
acpi_os_printf |
("0x%X Outstanding allocations after evaluation of %s\n", |
allocations, acpi_gbl_db_method_info.pathname); |
} |
#endif |
|
if (ACPI_FAILURE(status)) { |
acpi_os_printf("Evaluation of %s failed with status %s\n", |
acpi_gbl_db_method_info.pathname, |
acpi_format_exception(status)); |
} else { |
/* Display a return object, if any */ |
|
if (return_obj.length) { |
acpi_os_printf("Evaluation of %s returned object %p, " |
"external buffer length %X\n", |
acpi_gbl_db_method_info.pathname, |
return_obj.pointer, |
(u32)return_obj.length); |
|
acpi_db_dump_external_object(return_obj.pointer, 1); |
|
/* Dump a _PLD buffer if present */ |
|
if (ACPI_COMPARE_NAME |
((ACPI_CAST_PTR |
(struct acpi_namespace_node, |
acpi_gbl_db_method_info.method)->name.ascii), |
METHOD_NAME__PLD)) { |
acpi_db_dump_pld_buffer(return_obj.pointer); |
} |
} else { |
acpi_os_printf |
("No object was returned from evaluation of %s\n", |
acpi_gbl_db_method_info.pathname); |
} |
} |
|
acpi_db_set_output_destination(ACPI_DB_CONSOLE_OUTPUT); |
} |
|
/******************************************************************************* |
* |
* FUNCTION: acpi_db_method_thread |
* |
* PARAMETERS: context - Execution info segment |
* |
* RETURN: None |
* |
* DESCRIPTION: Debugger execute thread. Waits for a command line, then |
* simply dispatches it. |
* |
******************************************************************************/ |
|
static void ACPI_SYSTEM_XFACE acpi_db_method_thread(void *context) |
{ |
acpi_status status; |
struct acpi_db_method_info *info = context; |
struct acpi_db_method_info local_info; |
u32 i; |
u8 allow; |
struct acpi_buffer return_obj; |
|
/* |
* acpi_gbl_db_method_info.Arguments will be passed as method arguments. |
* Prevent acpi_gbl_db_method_info from being modified by multiple threads |
* concurrently. |
* |
* Note: The arguments we are passing are used by the ASL test suite |
* (aslts). Do not change them without updating the tests. |
*/ |
(void)acpi_os_wait_semaphore(info->info_gate, 1, ACPI_WAIT_FOREVER); |
|
if (info->init_args) { |
acpi_db_uint32_to_hex_string(info->num_created, |
info->index_of_thread_str); |
acpi_db_uint32_to_hex_string((u32)acpi_os_get_thread_id(), |
info->id_of_thread_str); |
} |
|
if (info->threads && (info->num_created < info->num_threads)) { |
info->threads[info->num_created++] = acpi_os_get_thread_id(); |
} |
|
local_info = *info; |
local_info.args = local_info.arguments; |
local_info.arguments[0] = local_info.num_threads_str; |
local_info.arguments[1] = local_info.id_of_thread_str; |
local_info.arguments[2] = local_info.index_of_thread_str; |
local_info.arguments[3] = NULL; |
|
local_info.types = local_info.arg_types; |
|
(void)acpi_os_signal_semaphore(info->info_gate, 1); |
|
for (i = 0; i < info->num_loops; i++) { |
status = acpi_db_execute_method(&local_info, &return_obj); |
if (ACPI_FAILURE(status)) { |
acpi_os_printf |
("%s During evaluation of %s at iteration %X\n", |
acpi_format_exception(status), info->pathname, i); |
if (status == AE_ABORT_METHOD) { |
break; |
} |
} |
#if 0 |
if ((i % 100) == 0) { |
acpi_os_printf("%u loops, Thread 0x%x\n", |
i, acpi_os_get_thread_id()); |
} |
|
if (return_obj.length) { |
acpi_os_printf |
("Evaluation of %s returned object %p Buflen %X\n", |
info->pathname, return_obj.pointer, |
(u32)return_obj.length); |
acpi_db_dump_external_object(return_obj.pointer, 1); |
} |
#endif |
} |
|
/* Signal our completion */ |
|
allow = 0; |
(void)acpi_os_wait_semaphore(info->thread_complete_gate, |
1, ACPI_WAIT_FOREVER); |
info->num_completed++; |
|
if (info->num_completed == info->num_threads) { |
|
/* Do signal for main thread once only */ |
allow = 1; |
} |
|
(void)acpi_os_signal_semaphore(info->thread_complete_gate, 1); |
|
if (allow) { |
status = acpi_os_signal_semaphore(info->main_thread_gate, 1); |
if (ACPI_FAILURE(status)) { |
acpi_os_printf |
("Could not signal debugger thread sync semaphore, %s\n", |
acpi_format_exception(status)); |
} |
} |
} |
|
/******************************************************************************* |
* |
* FUNCTION: acpi_db_create_execution_threads |
* |
* PARAMETERS: num_threads_arg - Number of threads to create |
* num_loops_arg - Loop count for the thread(s) |
* method_name_arg - Control method to execute |
* |
* RETURN: None |
* |
* DESCRIPTION: Create threads to execute method(s) |
* |
******************************************************************************/ |
|
void |
acpi_db_create_execution_threads(char *num_threads_arg, |
char *num_loops_arg, char *method_name_arg) |
{ |
acpi_status status; |
u32 num_threads; |
u32 num_loops; |
u32 i; |
u32 size; |
acpi_mutex main_thread_gate; |
acpi_mutex thread_complete_gate; |
acpi_mutex info_gate; |
|
/* Get the arguments */ |
|
num_threads = strtoul(num_threads_arg, NULL, 0); |
num_loops = strtoul(num_loops_arg, NULL, 0); |
|
if (!num_threads || !num_loops) { |
acpi_os_printf("Bad argument: Threads %X, Loops %X\n", |
num_threads, num_loops); |
return; |
} |
|
/* |
* Create the semaphore for synchronization of |
* the created threads with the main thread. |
*/ |
status = acpi_os_create_semaphore(1, 0, &main_thread_gate); |
if (ACPI_FAILURE(status)) { |
acpi_os_printf("Could not create semaphore for " |
"synchronization with the main thread, %s\n", |
acpi_format_exception(status)); |
return; |
} |
|
/* |
* Create the semaphore for synchronization |
* between the created threads. |
*/ |
status = acpi_os_create_semaphore(1, 1, &thread_complete_gate); |
if (ACPI_FAILURE(status)) { |
acpi_os_printf("Could not create semaphore for " |
"synchronization between the created threads, %s\n", |
acpi_format_exception(status)); |
|
(void)acpi_os_delete_semaphore(main_thread_gate); |
return; |
} |
|
status = acpi_os_create_semaphore(1, 1, &info_gate); |
if (ACPI_FAILURE(status)) { |
acpi_os_printf("Could not create semaphore for " |
"synchronization of AcpiGbl_DbMethodInfo, %s\n", |
acpi_format_exception(status)); |
|
(void)acpi_os_delete_semaphore(thread_complete_gate); |
(void)acpi_os_delete_semaphore(main_thread_gate); |
return; |
} |
|
memset(&acpi_gbl_db_method_info, 0, sizeof(struct acpi_db_method_info)); |
|
/* Array to store IDs of threads */ |
|
acpi_gbl_db_method_info.num_threads = num_threads; |
size = sizeof(acpi_thread_id) * acpi_gbl_db_method_info.num_threads; |
|
acpi_gbl_db_method_info.threads = acpi_os_allocate(size); |
if (acpi_gbl_db_method_info.threads == NULL) { |
acpi_os_printf("No memory for thread IDs array\n"); |
(void)acpi_os_delete_semaphore(main_thread_gate); |
(void)acpi_os_delete_semaphore(thread_complete_gate); |
(void)acpi_os_delete_semaphore(info_gate); |
return; |
} |
memset(acpi_gbl_db_method_info.threads, 0, size); |
|
/* Setup the context to be passed to each thread */ |
|
acpi_gbl_db_method_info.name = method_name_arg; |
acpi_gbl_db_method_info.flags = 0; |
acpi_gbl_db_method_info.num_loops = num_loops; |
acpi_gbl_db_method_info.main_thread_gate = main_thread_gate; |
acpi_gbl_db_method_info.thread_complete_gate = thread_complete_gate; |
acpi_gbl_db_method_info.info_gate = info_gate; |
|
/* Init arguments to be passed to method */ |
|
acpi_gbl_db_method_info.init_args = 1; |
acpi_gbl_db_method_info.args = acpi_gbl_db_method_info.arguments; |
acpi_gbl_db_method_info.arguments[0] = |
acpi_gbl_db_method_info.num_threads_str; |
acpi_gbl_db_method_info.arguments[1] = |
acpi_gbl_db_method_info.id_of_thread_str; |
acpi_gbl_db_method_info.arguments[2] = |
acpi_gbl_db_method_info.index_of_thread_str; |
acpi_gbl_db_method_info.arguments[3] = NULL; |
|
acpi_gbl_db_method_info.types = acpi_gbl_db_method_info.arg_types; |
acpi_gbl_db_method_info.arg_types[0] = ACPI_TYPE_INTEGER; |
acpi_gbl_db_method_info.arg_types[1] = ACPI_TYPE_INTEGER; |
acpi_gbl_db_method_info.arg_types[2] = ACPI_TYPE_INTEGER; |
|
acpi_db_uint32_to_hex_string(num_threads, |
acpi_gbl_db_method_info.num_threads_str); |
|
status = acpi_db_execute_setup(&acpi_gbl_db_method_info); |
if (ACPI_FAILURE(status)) { |
goto cleanup_and_exit; |
} |
|
/* Get the NS node, determines existence also */ |
|
status = acpi_get_handle(NULL, acpi_gbl_db_method_info.pathname, |
&acpi_gbl_db_method_info.method); |
if (ACPI_FAILURE(status)) { |
acpi_os_printf("%s Could not get handle for %s\n", |
acpi_format_exception(status), |
acpi_gbl_db_method_info.pathname); |
goto cleanup_and_exit; |
} |
|
/* Create the threads */ |
|
acpi_os_printf("Creating %X threads to execute %X times each\n", |
num_threads, num_loops); |
|
for (i = 0; i < (num_threads); i++) { |
status = |
acpi_os_execute(OSL_DEBUGGER_EXEC_THREAD, |
acpi_db_method_thread, |
&acpi_gbl_db_method_info); |
if (ACPI_FAILURE(status)) { |
break; |
} |
} |
|
/* Wait for all threads to complete */ |
|
(void)acpi_os_wait_semaphore(main_thread_gate, 1, ACPI_WAIT_FOREVER); |
|
acpi_db_set_output_destination(ACPI_DB_DUPLICATE_OUTPUT); |
acpi_os_printf("All threads (%X) have completed\n", num_threads); |
acpi_db_set_output_destination(ACPI_DB_CONSOLE_OUTPUT); |
|
cleanup_and_exit: |
|
/* Cleanup and exit */ |
|
(void)acpi_os_delete_semaphore(main_thread_gate); |
(void)acpi_os_delete_semaphore(thread_complete_gate); |
(void)acpi_os_delete_semaphore(info_gate); |
|
acpi_os_free(acpi_gbl_db_method_info.threads); |
acpi_gbl_db_method_info.threads = NULL; |
} |