Commit b17629db authored by Rafael J. Wysocki's avatar Rafael J. Wysocki

Merge branch 'acpi-debug' into acpica

parents 74bf8efb 59adb398
...@@ -58,14 +58,25 @@ config ACPI_CCA_REQUIRED ...@@ -58,14 +58,25 @@ config ACPI_CCA_REQUIRED
bool bool
config ACPI_DEBUGGER config ACPI_DEBUGGER
bool "AML debugger interface (EXPERIMENTAL)" bool "AML debugger interface"
select ACPI_DEBUG select ACPI_DEBUG
help help
Enable in-kernel debugging of AML facilities: statistics, internal Enable in-kernel debugging of AML facilities: statistics,
object dump, single step control method execution. internal object dump, single step control method execution.
This is still under development, currently enabling this only This is still under development, currently enabling this only
results in the compilation of the ACPICA debugger files. results in the compilation of the ACPICA debugger files.
if ACPI_DEBUGGER
config ACPI_DEBUGGER_USER
tristate "Userspace debugger accessiblity"
depends on DEBUG_FS
help
Export /sys/kernel/debug/acpi/acpidbg for userspace utilities
to access the debugger functionalities.
endif
config ACPI_SLEEP config ACPI_SLEEP
bool bool
depends on SUSPEND || HIBERNATION depends on SUSPEND || HIBERNATION
......
...@@ -79,6 +79,7 @@ obj-$(CONFIG_ACPI_EC_DEBUGFS) += ec_sys.o ...@@ -79,6 +79,7 @@ obj-$(CONFIG_ACPI_EC_DEBUGFS) += ec_sys.o
obj-$(CONFIG_ACPI_CUSTOM_METHOD)+= custom_method.o obj-$(CONFIG_ACPI_CUSTOM_METHOD)+= custom_method.o
obj-$(CONFIG_ACPI_BGRT) += bgrt.o obj-$(CONFIG_ACPI_BGRT) += bgrt.o
obj-$(CONFIG_ACPI_CPPC_LIB) += cppc_acpi.o obj-$(CONFIG_ACPI_CPPC_LIB) += cppc_acpi.o
obj-$(CONFIG_ACPI_DEBUGGER_USER) += acpi_dbg.o
# processor has its own "processor." module_param namespace # processor has its own "processor." module_param namespace
processor-y := processor_driver.o processor-y := processor_driver.o
......
This diff is collapsed.
...@@ -80,9 +80,15 @@ struct acpi_db_execute_walk { ...@@ -80,9 +80,15 @@ struct acpi_db_execute_walk {
/* /*
* dbxface - external debugger interfaces * dbxface - external debugger interfaces
*/ */
acpi_status ACPI_DBR_DEPENDENT_RETURN_OK(acpi_status
acpi_db_single_step(struct acpi_walk_state *walk_state, acpi_db_single_step(struct acpi_walk_state
union acpi_parse_object *op, u32 op_type); *walk_state,
union acpi_parse_object *op,
u32 op_type))
ACPI_DBR_DEPENDENT_RETURN_VOID(void
acpi_db_signal_break_point(struct
acpi_walk_state
*walk_state))
/* /*
* dbcmds - debug commands and output routines * dbcmds - debug commands and output routines
...@@ -182,11 +188,15 @@ void acpi_db_display_method_info(union acpi_parse_object *op); ...@@ -182,11 +188,15 @@ void acpi_db_display_method_info(union acpi_parse_object *op);
void acpi_db_decode_and_display_object(char *target, char *output_type); void acpi_db_decode_and_display_object(char *target, char *output_type);
void ACPI_DBR_DEPENDENT_RETURN_VOID(void
acpi_db_display_result_object(union acpi_operand_object *obj_desc, acpi_db_display_result_object(union
struct acpi_walk_state *walk_state); acpi_operand_object
*obj_desc,
struct
acpi_walk_state
*walk_state))
acpi_status acpi_db_display_all_methods(char *display_count_arg); acpi_status acpi_db_display_all_methods(char *display_count_arg);
void acpi_db_display_arguments(void); void acpi_db_display_arguments(void);
...@@ -198,9 +208,13 @@ void acpi_db_display_calling_tree(void); ...@@ -198,9 +208,13 @@ void acpi_db_display_calling_tree(void);
void acpi_db_display_object_type(char *object_arg); void acpi_db_display_object_type(char *object_arg);
void ACPI_DBR_DEPENDENT_RETURN_VOID(void
acpi_db_display_argument_object(union acpi_operand_object *obj_desc, acpi_db_display_argument_object(union
struct acpi_walk_state *walk_state); acpi_operand_object
*obj_desc,
struct
acpi_walk_state
*walk_state))
/* /*
* dbexec - debugger control method execution * dbexec - debugger control method execution
...@@ -257,7 +271,7 @@ acpi_db_command_dispatch(char *input_buffer, ...@@ -257,7 +271,7 @@ acpi_db_command_dispatch(char *input_buffer,
void ACPI_SYSTEM_XFACE acpi_db_execute_thread(void *context); void ACPI_SYSTEM_XFACE acpi_db_execute_thread(void *context);
acpi_status acpi_db_user_commands(char prompt, union acpi_parse_object *op); acpi_status acpi_db_user_commands(void);
char *acpi_db_get_next_token(char *string, char *acpi_db_get_next_token(char *string,
char **next, acpi_object_type * return_type); char **next, acpi_object_type * return_type);
......
...@@ -326,7 +326,6 @@ ACPI_GLOBAL(struct acpi_external_file *, acpi_gbl_external_file_list); ...@@ -326,7 +326,6 @@ ACPI_GLOBAL(struct acpi_external_file *, acpi_gbl_external_file_list);
#ifdef ACPI_DEBUGGER #ifdef ACPI_DEBUGGER
ACPI_INIT_GLOBAL(u8, acpi_gbl_abort_method, FALSE); ACPI_INIT_GLOBAL(u8, acpi_gbl_abort_method, FALSE);
ACPI_INIT_GLOBAL(u8, acpi_gbl_method_executing, FALSE);
ACPI_INIT_GLOBAL(acpi_thread_id, acpi_gbl_db_thread_id, ACPI_INVALID_THREAD_ID); ACPI_INIT_GLOBAL(acpi_thread_id, acpi_gbl_db_thread_id, ACPI_INVALID_THREAD_ID);
ACPI_GLOBAL(u8, acpi_gbl_db_opt_no_ini_methods); ACPI_GLOBAL(u8, acpi_gbl_db_opt_no_ini_methods);
...@@ -345,7 +344,6 @@ ACPI_GLOBAL(acpi_object_type, acpi_gbl_db_arg_types[ACPI_DEBUGGER_MAX_ARGS]); ...@@ -345,7 +344,6 @@ ACPI_GLOBAL(acpi_object_type, acpi_gbl_db_arg_types[ACPI_DEBUGGER_MAX_ARGS]);
/* These buffers should all be the same size */ /* These buffers should all be the same size */
ACPI_GLOBAL(char, acpi_gbl_db_line_buf[ACPI_DB_LINE_BUFFER_SIZE]);
ACPI_GLOBAL(char, acpi_gbl_db_parsed_buf[ACPI_DB_LINE_BUFFER_SIZE]); ACPI_GLOBAL(char, acpi_gbl_db_parsed_buf[ACPI_DB_LINE_BUFFER_SIZE]);
ACPI_GLOBAL(char, acpi_gbl_db_scope_buf[ACPI_DB_LINE_BUFFER_SIZE]); ACPI_GLOBAL(char, acpi_gbl_db_scope_buf[ACPI_DB_LINE_BUFFER_SIZE]);
ACPI_GLOBAL(char, acpi_gbl_db_debug_filename[ACPI_DB_LINE_BUFFER_SIZE]); ACPI_GLOBAL(char, acpi_gbl_db_debug_filename[ACPI_DB_LINE_BUFFER_SIZE]);
...@@ -360,9 +358,6 @@ ACPI_GLOBAL(u16, acpi_gbl_node_type_count_misc); ...@@ -360,9 +358,6 @@ ACPI_GLOBAL(u16, acpi_gbl_node_type_count_misc);
ACPI_GLOBAL(u32, acpi_gbl_num_nodes); ACPI_GLOBAL(u32, acpi_gbl_num_nodes);
ACPI_GLOBAL(u32, acpi_gbl_num_objects); ACPI_GLOBAL(u32, acpi_gbl_num_objects);
ACPI_GLOBAL(acpi_mutex, acpi_gbl_db_command_ready);
ACPI_GLOBAL(acpi_mutex, acpi_gbl_db_command_complete);
#endif /* ACPI_DEBUGGER */ #endif /* ACPI_DEBUGGER */
/***************************************************************************** /*****************************************************************************
......
...@@ -400,17 +400,6 @@ ...@@ -400,17 +400,6 @@
#define ACPI_HW_OPTIONAL_FUNCTION(addr) NULL #define ACPI_HW_OPTIONAL_FUNCTION(addr) NULL
#endif #endif
/*
* Some code only gets executed when the debugger is built in.
* Note that this is entirely independent of whether the
* DEBUG_PRINT stuff (set by ACPI_DEBUG_OUTPUT) is on, or not.
*/
#ifdef ACPI_DEBUGGER
#define ACPI_DEBUGGER_EXEC(a) a
#else
#define ACPI_DEBUGGER_EXEC(a)
#endif
/* /*
* Macros used for ACPICA utilities only * Macros used for ACPICA utilities only
*/ */
......
...@@ -679,6 +679,12 @@ acpi_db_display_result_object(union acpi_operand_object *obj_desc, ...@@ -679,6 +679,12 @@ acpi_db_display_result_object(union acpi_operand_object *obj_desc,
struct acpi_walk_state *walk_state) struct acpi_walk_state *walk_state)
{ {
#ifndef ACPI_APPLICATION
if (acpi_gbl_db_thread_id != acpi_os_get_thread_id()) {
return;
}
#endif
/* Only display if single stepping */ /* Only display if single stepping */
if (!acpi_gbl_cm_single_step) { if (!acpi_gbl_cm_single_step) {
...@@ -708,6 +714,12 @@ acpi_db_display_argument_object(union acpi_operand_object *obj_desc, ...@@ -708,6 +714,12 @@ acpi_db_display_argument_object(union acpi_operand_object *obj_desc,
struct acpi_walk_state *walk_state) struct acpi_walk_state *walk_state)
{ {
#ifndef ACPI_APPLICATION
if (acpi_gbl_db_thread_id != acpi_os_get_thread_id()) {
return;
}
#endif
if (!acpi_gbl_cm_single_step) { if (!acpi_gbl_cm_single_step) {
return; return;
} }
......
...@@ -53,8 +53,6 @@ static u32 acpi_db_get_line(char *input_buffer); ...@@ -53,8 +53,6 @@ static u32 acpi_db_get_line(char *input_buffer);
static u32 acpi_db_match_command(char *user_command); static u32 acpi_db_match_command(char *user_command);
static void acpi_db_single_thread(void);
static void acpi_db_display_command_info(char *command, u8 display_all); static void acpi_db_display_command_info(char *command, u8 display_all);
static void acpi_db_display_help(char *command); static void acpi_db_display_help(char *command);
...@@ -1149,55 +1147,16 @@ acpi_db_command_dispatch(char *input_buffer, ...@@ -1149,55 +1147,16 @@ acpi_db_command_dispatch(char *input_buffer,
void ACPI_SYSTEM_XFACE acpi_db_execute_thread(void *context) void ACPI_SYSTEM_XFACE acpi_db_execute_thread(void *context)
{ {
acpi_status status = AE_OK;
acpi_status Mstatus;
while (status != AE_CTRL_TERMINATE && !acpi_gbl_db_terminate_loop) {
acpi_gbl_method_executing = FALSE;
acpi_gbl_step_to_next_call = FALSE;
Mstatus = acpi_os_acquire_mutex(acpi_gbl_db_command_ready,
ACPI_WAIT_FOREVER);
if (ACPI_FAILURE(Mstatus)) {
return;
}
status =
acpi_db_command_dispatch(acpi_gbl_db_line_buf, NULL, NULL);
acpi_os_release_mutex(acpi_gbl_db_command_complete); (void)acpi_db_user_commands();
}
acpi_gbl_db_threads_terminated = TRUE; acpi_gbl_db_threads_terminated = TRUE;
} }
/*******************************************************************************
*
* FUNCTION: acpi_db_single_thread
*
* PARAMETERS: None
*
* RETURN: None
*
* DESCRIPTION: Debugger execute thread. Waits for a command line, then
* simply dispatches it.
*
******************************************************************************/
static void acpi_db_single_thread(void)
{
acpi_gbl_method_executing = FALSE;
acpi_gbl_step_to_next_call = FALSE;
(void)acpi_db_command_dispatch(acpi_gbl_db_line_buf, NULL, NULL);
}
/******************************************************************************* /*******************************************************************************
* *
* FUNCTION: acpi_db_user_commands * FUNCTION: acpi_db_user_commands
* *
* PARAMETERS: prompt - User prompt (depends on mode) * PARAMETERS: None
* op - Current executing parse op
* *
* RETURN: None * RETURN: None
* *
...@@ -1206,7 +1165,7 @@ static void acpi_db_single_thread(void) ...@@ -1206,7 +1165,7 @@ static void acpi_db_single_thread(void)
* *
******************************************************************************/ ******************************************************************************/
acpi_status acpi_db_user_commands(char prompt, union acpi_parse_object *op) acpi_status acpi_db_user_commands(void)
{ {
acpi_status status = AE_OK; acpi_status status = AE_OK;
...@@ -1216,52 +1175,31 @@ acpi_status acpi_db_user_commands(char prompt, union acpi_parse_object *op) ...@@ -1216,52 +1175,31 @@ acpi_status acpi_db_user_commands(char prompt, union acpi_parse_object *op)
while (!acpi_gbl_db_terminate_loop) { while (!acpi_gbl_db_terminate_loop) {
/* Force output to console until a command is entered */ /* Wait the readiness of the command */
acpi_db_set_output_destination(ACPI_DB_CONSOLE_OUTPUT);
/* Different prompt if method is executing */
if (!acpi_gbl_method_executing) {
acpi_os_printf("%1c ", ACPI_DEBUGGER_COMMAND_PROMPT);
} else {
acpi_os_printf("%1c ", ACPI_DEBUGGER_EXECUTE_PROMPT);
}
/* Get the user input line */
status = acpi_os_get_line(acpi_gbl_db_line_buf, status = acpi_os_wait_command_ready();
ACPI_DB_LINE_BUFFER_SIZE, NULL);
if (ACPI_FAILURE(status)) { if (ACPI_FAILURE(status)) {
ACPI_EXCEPTION((AE_INFO, status, break;
"While parsing command line"));
return (status);
} }
/* Check for single or multithreaded debug */ /* Just call to the command line interpreter */
if (acpi_gbl_debugger_configuration & DEBUGGER_MULTI_THREADED) { acpi_gbl_method_executing = FALSE;
/* acpi_gbl_step_to_next_call = FALSE;
* Signal the debug thread that we have a command to execute,
* and wait for the command to complete.
*/
acpi_os_release_mutex(acpi_gbl_db_command_ready);
if (ACPI_FAILURE(status)) {
return (status);
}
status = (void)acpi_db_command_dispatch(acpi_gbl_db_line_buf, NULL,
acpi_os_acquire_mutex(acpi_gbl_db_command_complete, NULL);
ACPI_WAIT_FOREVER);
if (ACPI_FAILURE(status)) { /* Notify the completion of the command */
return (status);
}
} else {
/* Just call to the command line interpreter */
acpi_db_single_thread(); status = acpi_os_notify_command_complete();
if (ACPI_FAILURE(status)) {
break;
} }
} }
if (ACPI_FAILURE(status) && status != AE_CTRL_TERMINATE) {
ACPI_EXCEPTION((AE_INFO, status, "While parsing command line"));
}
return (status); return (status);
} }
...@@ -85,46 +85,21 @@ acpi_db_start_command(struct acpi_walk_state *walk_state, ...@@ -85,46 +85,21 @@ acpi_db_start_command(struct acpi_walk_state *walk_state,
acpi_gbl_method_executing = TRUE; acpi_gbl_method_executing = TRUE;
status = AE_CTRL_TRUE; status = AE_CTRL_TRUE;
while (status == AE_CTRL_TRUE) {
if (acpi_gbl_debugger_configuration == DEBUGGER_MULTI_THREADED) {
/* Handshake with the front-end that gets user command lines */
acpi_os_release_mutex(acpi_gbl_db_command_complete);
status =
acpi_os_acquire_mutex(acpi_gbl_db_command_ready,
ACPI_WAIT_FOREVER);
if (ACPI_FAILURE(status)) {
return (status);
}
} else {
/* Single threaded, we must get a command line ourselves */
/* Force output to console until a command is entered */
acpi_db_set_output_destination(ACPI_DB_CONSOLE_OUTPUT); while (status == AE_CTRL_TRUE) {
/* Different prompt if method is executing */ /* Notify the completion of the command */
if (!acpi_gbl_method_executing) { status = acpi_os_notify_command_complete();
acpi_os_printf("%1c ", if (ACPI_FAILURE(status)) {
ACPI_DEBUGGER_COMMAND_PROMPT); goto error_exit;
} else { }
acpi_os_printf("%1c ",
ACPI_DEBUGGER_EXECUTE_PROMPT);
}
/* Get the user input line */ /* Wait the readiness of the command */
status = acpi_os_get_line(acpi_gbl_db_line_buf, status = acpi_os_wait_command_ready();
ACPI_DB_LINE_BUFFER_SIZE, if (ACPI_FAILURE(status)) {
NULL); goto error_exit;
if (ACPI_FAILURE(status)) {
ACPI_EXCEPTION((AE_INFO, status,
"While parsing command line"));
return (status);
}
} }
status = status =
...@@ -134,9 +109,44 @@ acpi_db_start_command(struct acpi_walk_state *walk_state, ...@@ -134,9 +109,44 @@ acpi_db_start_command(struct acpi_walk_state *walk_state,
/* acpi_ut_acquire_mutex (ACPI_MTX_NAMESPACE); */ /* acpi_ut_acquire_mutex (ACPI_MTX_NAMESPACE); */
error_exit:
if (ACPI_FAILURE(status) && status != AE_CTRL_TERMINATE) {
ACPI_EXCEPTION((AE_INFO, status,
"While parsing/handling command line"));
}
return (status); return (status);
} }
/*******************************************************************************
*
* FUNCTION: acpi_db_signal_break_point
*
* PARAMETERS: walk_state - Current walk
*
* RETURN: Status
*
* DESCRIPTION: Called for AML_BREAK_POINT_OP
*
******************************************************************************/
void acpi_db_signal_break_point(struct acpi_walk_state *walk_state)
{
#ifndef ACPI_APPLICATION
if (acpi_gbl_db_thread_id != acpi_os_get_thread_id()) {
return;
}
#endif
/*
* Set the single-step flag. This will cause the debugger (if present)
* to break to the console within the AML debugger at the start of the
* next AML instruction.
*/
acpi_gbl_cm_single_step = TRUE;
acpi_os_printf("**break** Executed AML BreakPoint opcode\n");
}
/******************************************************************************* /*******************************************************************************
* *
* FUNCTION: acpi_db_single_step * FUNCTION: acpi_db_single_step
...@@ -420,15 +430,7 @@ acpi_status acpi_initialize_debugger(void) ...@@ -420,15 +430,7 @@ acpi_status acpi_initialize_debugger(void)
/* These were created with one unit, grab it */ /* These were created with one unit, grab it */
status = acpi_os_acquire_mutex(acpi_gbl_db_command_complete, status = acpi_os_initialize_command_signals();
ACPI_WAIT_FOREVER);
if (ACPI_FAILURE(status)) {
acpi_os_printf("Could not get debugger mutex\n");
return_ACPI_STATUS(status);
}
status = acpi_os_acquire_mutex(acpi_gbl_db_command_ready,
ACPI_WAIT_FOREVER);
if (ACPI_FAILURE(status)) { if (ACPI_FAILURE(status)) {
acpi_os_printf("Could not get debugger mutex\n"); acpi_os_printf("Could not get debugger mutex\n");
return_ACPI_STATUS(status); return_ACPI_STATUS(status);
...@@ -473,13 +475,14 @@ void acpi_terminate_debugger(void) ...@@ -473,13 +475,14 @@ void acpi_terminate_debugger(void)
acpi_gbl_db_terminate_loop = TRUE; acpi_gbl_db_terminate_loop = TRUE;
if (acpi_gbl_debugger_configuration & DEBUGGER_MULTI_THREADED) { if (acpi_gbl_debugger_configuration & DEBUGGER_MULTI_THREADED) {
acpi_os_release_mutex(acpi_gbl_db_command_ready);
/* Wait the AML Debugger threads */ /* Wait the AML Debugger threads */
while (!acpi_gbl_db_threads_terminated) { while (!acpi_gbl_db_threads_terminated) {
acpi_os_sleep(100); acpi_os_sleep(100);
} }
acpi_os_terminate_command_signals();
} }
if (acpi_gbl_db_buffer) { if (acpi_gbl_db_buffer) {
......
...@@ -47,6 +47,7 @@ ...@@ -47,6 +47,7 @@
#include "amlcode.h" #include "amlcode.h"
#include "acdispat.h" #include "acdispat.h"
#include "acinterp.h" #include "acinterp.h"
#include "acdebug.h"
#define _COMPONENT ACPI_DISPATCHER #define _COMPONENT ACPI_DISPATCHER
ACPI_MODULE_NAME("dscontrol") ACPI_MODULE_NAME("dscontrol")
...@@ -348,14 +349,7 @@ acpi_ds_exec_end_control_op(struct acpi_walk_state * walk_state, ...@@ -348,14 +349,7 @@ acpi_ds_exec_end_control_op(struct acpi_walk_state * walk_state,
case AML_BREAK_POINT_OP: case AML_BREAK_POINT_OP:
/* acpi_db_signal_break_point(walk_state);
* Set the single-step flag. This will cause the debugger (if present)
* to break to the console within the AML debugger at the start of the
* next AML instruction.
*/
ACPI_DEBUGGER_EXEC(acpi_gbl_cm_single_step = TRUE);
ACPI_DEBUGGER_EXEC(acpi_os_printf
("**break** Executed AML BreakPoint opcode\n"));
/* Call to the OSL in case OS wants a piece of the action */ /* Call to the OSL in case OS wants a piece of the action */
......
...@@ -605,8 +605,8 @@ acpi_ds_create_operand(struct acpi_walk_state *walk_state, ...@@ -605,8 +605,8 @@ acpi_ds_create_operand(struct acpi_walk_state *walk_state,
if (ACPI_FAILURE(status)) { if (ACPI_FAILURE(status)) {
return_ACPI_STATUS(status); return_ACPI_STATUS(status);
} }
ACPI_DEBUGGER_EXEC(acpi_db_display_argument_object
(obj_desc, walk_state)); acpi_db_display_argument_object(obj_desc, walk_state);
} else { } else {
/* Check for null name case */ /* Check for null name case */
...@@ -638,10 +638,11 @@ acpi_ds_create_operand(struct acpi_walk_state *walk_state, ...@@ -638,10 +638,11 @@ acpi_ds_create_operand(struct acpi_walk_state *walk_state,
ACPI_DEBUG_PRINT((ACPI_DB_DISPATCH, ACPI_DEBUG_PRINT((ACPI_DB_DISPATCH,
"Argument previously created, already stacked\n")); "Argument previously created, already stacked\n"));
ACPI_DEBUGGER_EXEC(acpi_db_display_argument_object acpi_db_display_argument_object(walk_state->
(walk_state-> operands[walk_state->
operands[walk_state->num_operands - num_operands -
1], walk_state)); 1],
walk_state);
/* /*
* Use value that was already previously returned * Use value that was already previously returned
...@@ -685,8 +686,7 @@ acpi_ds_create_operand(struct acpi_walk_state *walk_state, ...@@ -685,8 +686,7 @@ acpi_ds_create_operand(struct acpi_walk_state *walk_state,
return_ACPI_STATUS(status); return_ACPI_STATUS(status);
} }
ACPI_DEBUGGER_EXEC(acpi_db_display_argument_object acpi_db_display_argument_object(obj_desc, walk_state);
(obj_desc, walk_state));
} }
return_ACPI_STATUS(AE_OK); return_ACPI_STATUS(AE_OK);
......
...@@ -178,8 +178,7 @@ acpi_ds_get_predicate_value(struct acpi_walk_state *walk_state, ...@@ -178,8 +178,7 @@ acpi_ds_get_predicate_value(struct acpi_walk_state *walk_state,
/* Break to debugger to display result */ /* Break to debugger to display result */
ACPI_DEBUGGER_EXEC(acpi_db_display_result_object acpi_db_display_result_object(local_obj_desc, walk_state);
(local_obj_desc, walk_state));
/* /*
* Delete the predicate result object (we know that * Delete the predicate result object (we know that
...@@ -386,11 +385,10 @@ acpi_status acpi_ds_exec_end_op(struct acpi_walk_state *walk_state) ...@@ -386,11 +385,10 @@ acpi_status acpi_ds_exec_end_op(struct acpi_walk_state *walk_state)
/* Call debugger for single step support (DEBUG build only) */ /* Call debugger for single step support (DEBUG build only) */
ACPI_DEBUGGER_EXEC(status = status = acpi_db_single_step(walk_state, op, op_class);
acpi_db_single_step(walk_state, op, op_class)); if (ACPI_FAILURE(status)) {
ACPI_DEBUGGER_EXEC(if (ACPI_FAILURE(status)) { return_ACPI_STATUS(status);
return_ACPI_STATUS(status);} }
) ;
/* Decode the Opcode Class */ /* Decode the Opcode Class */
...@@ -728,8 +726,8 @@ acpi_status acpi_ds_exec_end_op(struct acpi_walk_state *walk_state) ...@@ -728,8 +726,8 @@ acpi_status acpi_ds_exec_end_op(struct acpi_walk_state *walk_state)
/* Break to debugger to display result */ /* Break to debugger to display result */
ACPI_DEBUGGER_EXEC(acpi_db_display_result_object acpi_db_display_result_object(walk_state->result_obj,
(walk_state->result_obj, walk_state)); walk_state);
/* /*
* Delete the result op if and only if: * Delete the result op if and only if:
......
...@@ -111,17 +111,6 @@ acpi_status acpi_ut_mutex_initialize(void) ...@@ -111,17 +111,6 @@ acpi_status acpi_ut_mutex_initialize(void)
if (ACPI_FAILURE(status)) { if (ACPI_FAILURE(status)) {
return_ACPI_STATUS(status); return_ACPI_STATUS(status);
} }
#ifdef ACPI_DEBUGGER
/* Debugger Support */
status = acpi_os_create_mutex(&acpi_gbl_db_command_ready);
if (ACPI_FAILURE(status)) {
return_ACPI_STATUS(status);
}
status = acpi_os_create_mutex(&acpi_gbl_db_command_complete);
#endif
return_ACPI_STATUS(status); return_ACPI_STATUS(status);
} }
...@@ -162,12 +151,6 @@ void acpi_ut_mutex_terminate(void) ...@@ -162,12 +151,6 @@ void acpi_ut_mutex_terminate(void)
/* Delete the reader/writer lock */ /* Delete the reader/writer lock */
acpi_ut_delete_rw_lock(&acpi_gbl_namespace_rw_lock); acpi_ut_delete_rw_lock(&acpi_gbl_namespace_rw_lock);
#ifdef ACPI_DEBUGGER
acpi_os_delete_mutex(acpi_gbl_db_command_ready);
acpi_os_delete_mutex(acpi_gbl_db_command_complete);
#endif
return_VOID; return_VOID;
} }
......
...@@ -1094,6 +1094,7 @@ static int __init acpi_init(void) ...@@ -1094,6 +1094,7 @@ static int __init acpi_init(void)
acpi_debugfs_init(); acpi_debugfs_init();
acpi_sleep_proc_init(); acpi_sleep_proc_init();
acpi_wakeup_device_init(); acpi_wakeup_device_init();
acpi_debugger_init();
return 0; return 0;
} }
......
...@@ -220,6 +220,7 @@ void acpi_os_printf(const char *fmt, ...) ...@@ -220,6 +220,7 @@ void acpi_os_printf(const char *fmt, ...)
acpi_os_vprintf(fmt, args); acpi_os_vprintf(fmt, args);
va_end(args); va_end(args);
} }
EXPORT_SYMBOL(acpi_os_printf);
void acpi_os_vprintf(const char *fmt, va_list args) void acpi_os_vprintf(const char *fmt, va_list args)
{ {
...@@ -234,7 +235,8 @@ void acpi_os_vprintf(const char *fmt, va_list args) ...@@ -234,7 +235,8 @@ void acpi_os_vprintf(const char *fmt, va_list args)
printk(KERN_CONT "%s", buffer); printk(KERN_CONT "%s", buffer);
} }
#else #else
printk(KERN_CONT "%s", buffer); if (acpi_debugger_write_log(buffer) < 0)
printk(KERN_CONT "%s", buffer);
#endif #endif
} }
...@@ -1101,6 +1103,200 @@ static void acpi_os_execute_deferred(struct work_struct *work) ...@@ -1101,6 +1103,200 @@ static void acpi_os_execute_deferred(struct work_struct *work)
kfree(dpc); kfree(dpc);
} }
#ifdef CONFIG_ACPI_DEBUGGER
static struct acpi_debugger acpi_debugger;
static bool acpi_debugger_initialized;
int acpi_register_debugger(struct module *owner,
const struct acpi_debugger_ops *ops)
{
int ret = 0;
mutex_lock(&acpi_debugger.lock);
if (acpi_debugger.ops) {
ret = -EBUSY;
goto err_lock;
}
acpi_debugger.owner = owner;
acpi_debugger.ops = ops;
err_lock:
mutex_unlock(&acpi_debugger.lock);
return ret;
}
EXPORT_SYMBOL(acpi_register_debugger);
void acpi_unregister_debugger(const struct acpi_debugger_ops *ops)
{
mutex_lock(&acpi_debugger.lock);
if (ops == acpi_debugger.ops) {
acpi_debugger.ops = NULL;
acpi_debugger.owner = NULL;
}
mutex_unlock(&acpi_debugger.lock);
}
EXPORT_SYMBOL(acpi_unregister_debugger);
int acpi_debugger_create_thread(acpi_osd_exec_callback function, void *context)
{
int ret;
int (*func)(acpi_osd_exec_callback, void *);
struct module *owner;
if (!acpi_debugger_initialized)
return -ENODEV;
mutex_lock(&acpi_debugger.lock);
if (!acpi_debugger.ops) {
ret = -ENODEV;
goto err_lock;
}
if (!try_module_get(acpi_debugger.owner)) {
ret = -ENODEV;
goto err_lock;
}
func = acpi_debugger.ops->create_thread;
owner = acpi_debugger.owner;
mutex_unlock(&acpi_debugger.lock);
ret = func(function, context);
mutex_lock(&acpi_debugger.lock);
module_put(owner);
err_lock:
mutex_unlock(&acpi_debugger.lock);
return ret;
}
ssize_t acpi_debugger_write_log(const char *msg)
{
ssize_t ret;
ssize_t (*func)(const char *);
struct module *owner;
if (!acpi_debugger_initialized)
return -ENODEV;
mutex_lock(&acpi_debugger.lock);
if (!acpi_debugger.ops) {
ret = -ENODEV;
goto err_lock;
}
if (!try_module_get(acpi_debugger.owner)) {
ret = -ENODEV;
goto err_lock;
}
func = acpi_debugger.ops->write_log;
owner = acpi_debugger.owner;
mutex_unlock(&acpi_debugger.lock);
ret = func(msg);
mutex_lock(&acpi_debugger.lock);
module_put(owner);
err_lock:
mutex_unlock(&acpi_debugger.lock);
return ret;
}
ssize_t acpi_debugger_read_cmd(char *buffer, size_t buffer_length)
{
ssize_t ret;
ssize_t (*func)(char *, size_t);
struct module *owner;
if (!acpi_debugger_initialized)
return -ENODEV;
mutex_lock(&acpi_debugger.lock);
if (!acpi_debugger.ops) {
ret = -ENODEV;
goto err_lock;
}
if (!try_module_get(acpi_debugger.owner)) {
ret = -ENODEV;
goto err_lock;
}
func = acpi_debugger.ops->read_cmd;
owner = acpi_debugger.owner;
mutex_unlock(&acpi_debugger.lock);
ret = func(buffer, buffer_length);
mutex_lock(&acpi_debugger.lock);
module_put(owner);
err_lock:
mutex_unlock(&acpi_debugger.lock);
return ret;
}
int acpi_debugger_wait_command_ready(void)
{
int ret;
int (*func)(bool, char *, size_t);
struct module *owner;
if (!acpi_debugger_initialized)
return -ENODEV;
mutex_lock(&acpi_debugger.lock);
if (!acpi_debugger.ops) {
ret = -ENODEV;
goto err_lock;
}
if (!try_module_get(acpi_debugger.owner)) {
ret = -ENODEV;
goto err_lock;
}
func = acpi_debugger.ops->wait_command_ready;
owner = acpi_debugger.owner;
mutex_unlock(&acpi_debugger.lock);
ret = func(acpi_gbl_method_executing,
acpi_gbl_db_line_buf, ACPI_DB_LINE_BUFFER_SIZE);
mutex_lock(&acpi_debugger.lock);
module_put(owner);
err_lock:
mutex_unlock(&acpi_debugger.lock);
return ret;
}
int acpi_debugger_notify_command_complete(void)
{
int ret;
int (*func)(void);
struct module *owner;
if (!acpi_debugger_initialized)
return -ENODEV;
mutex_lock(&acpi_debugger.lock);
if (!acpi_debugger.ops) {
ret = -ENODEV;
goto err_lock;
}
if (!try_module_get(acpi_debugger.owner)) {
ret = -ENODEV;
goto err_lock;
}
func = acpi_debugger.ops->notify_command_complete;
owner = acpi_debugger.owner;
mutex_unlock(&acpi_debugger.lock);
ret = func();
mutex_lock(&acpi_debugger.lock);
module_put(owner);
err_lock:
mutex_unlock(&acpi_debugger.lock);
return ret;
}
int __init acpi_debugger_init(void)
{
mutex_init(&acpi_debugger.lock);
acpi_debugger_initialized = true;
return 0;
}
#endif
/******************************************************************************* /*******************************************************************************
* *
* FUNCTION: acpi_os_execute * FUNCTION: acpi_os_execute
...@@ -1127,6 +1323,15 @@ acpi_status acpi_os_execute(acpi_execute_type type, ...@@ -1127,6 +1323,15 @@ acpi_status acpi_os_execute(acpi_execute_type type,
"Scheduling function [%p(%p)] for deferred execution.\n", "Scheduling function [%p(%p)] for deferred execution.\n",
function, context)); function, context));
if (type == OSL_DEBUGGER_MAIN_THREAD) {
ret = acpi_debugger_create_thread(function, context);
if (ret) {
pr_err("Call to kthread_create() failed.\n");
status = AE_ERROR;
}
goto out_thread;
}
/* /*
* Allocate/initialize DPC structure. Note that this memory will be * Allocate/initialize DPC structure. Note that this memory will be
* freed by the callee. The kernel handles the work_struct list in a * freed by the callee. The kernel handles the work_struct list in a
...@@ -1151,11 +1356,17 @@ acpi_status acpi_os_execute(acpi_execute_type type, ...@@ -1151,11 +1356,17 @@ acpi_status acpi_os_execute(acpi_execute_type type,
if (type == OSL_NOTIFY_HANDLER) { if (type == OSL_NOTIFY_HANDLER) {
queue = kacpi_notify_wq; queue = kacpi_notify_wq;
INIT_WORK(&dpc->work, acpi_os_execute_deferred); INIT_WORK(&dpc->work, acpi_os_execute_deferred);
} else { } else if (type == OSL_GPE_HANDLER) {
queue = kacpid_wq; queue = kacpid_wq;
INIT_WORK(&dpc->work, acpi_os_execute_deferred); INIT_WORK(&dpc->work, acpi_os_execute_deferred);
} else {
pr_err("Unsupported os_execute type %d.\n", type);
status = AE_ERROR;
} }
if (ACPI_FAILURE(status))
goto err_workqueue;
/* /*
* On some machines, a software-initiated SMI causes corruption unless * On some machines, a software-initiated SMI causes corruption unless
* the SMI runs on CPU 0. An SMI can be initiated by any AML, but * the SMI runs on CPU 0. An SMI can be initiated by any AML, but
...@@ -1164,13 +1375,15 @@ acpi_status acpi_os_execute(acpi_execute_type type, ...@@ -1164,13 +1375,15 @@ acpi_status acpi_os_execute(acpi_execute_type type,
* queueing on CPU 0. * queueing on CPU 0.
*/ */
ret = queue_work_on(0, queue, &dpc->work); ret = queue_work_on(0, queue, &dpc->work);
if (!ret) { if (!ret) {
printk(KERN_ERR PREFIX printk(KERN_ERR PREFIX
"Call to queue_work() failed.\n"); "Call to queue_work() failed.\n");
status = AE_ERROR; status = AE_ERROR;
kfree(dpc);
} }
err_workqueue:
if (ACPI_FAILURE(status))
kfree(dpc);
out_thread:
return status; return status;
} }
EXPORT_SYMBOL(acpi_os_execute); EXPORT_SYMBOL(acpi_os_execute);
...@@ -1358,10 +1571,39 @@ acpi_status acpi_os_get_line(char *buffer, u32 buffer_length, u32 *bytes_read) ...@@ -1358,10 +1571,39 @@ acpi_status acpi_os_get_line(char *buffer, u32 buffer_length, u32 *bytes_read)
chars = strlen(buffer) - 1; chars = strlen(buffer) - 1;
buffer[chars] = '\0'; buffer[chars] = '\0';
} }
#else
int ret;
ret = acpi_debugger_read_cmd(buffer, buffer_length);
if (ret < 0)
return AE_ERROR;
if (bytes_read)
*bytes_read = ret;
#endif #endif
return AE_OK; return AE_OK;
} }
EXPORT_SYMBOL(acpi_os_get_line);
acpi_status acpi_os_wait_command_ready(void)
{
int ret;
ret = acpi_debugger_wait_command_ready();
if (ret < 0)
return AE_ERROR;
return AE_OK;
}
acpi_status acpi_os_notify_command_complete(void)
{
int ret;
ret = acpi_debugger_notify_command_complete();
if (ret < 0)
return AE_ERROR;
return AE_OK;
}
acpi_status acpi_os_signal(u32 function, void *info) acpi_status acpi_os_signal(u32 function, void *info)
{ {
......
...@@ -349,12 +349,28 @@ void acpi_os_redirect_output(void *destination); ...@@ -349,12 +349,28 @@ void acpi_os_redirect_output(void *destination);
#endif #endif
/* /*
* Debug input * Debug IO
*/ */
#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_acpi_os_get_line #ifndef ACPI_USE_ALTERNATE_PROTOTYPE_acpi_os_get_line
acpi_status acpi_os_get_line(char *buffer, u32 buffer_length, u32 *bytes_read); acpi_status acpi_os_get_line(char *buffer, u32 buffer_length, u32 *bytes_read);
#endif #endif
#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_acpi_os_initialize_command_signals
acpi_status acpi_os_initialize_command_signals(void);
#endif
#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_acpi_os_terminate_command_signals
void acpi_os_terminate_command_signals(void);
#endif
#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_acpi_os_wait_command_ready
acpi_status acpi_os_wait_command_ready(void);
#endif
#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_acpi_os_notify_command_complete
acpi_status acpi_os_notify_command_complete(void);
#endif
/* /*
* Obtain ACPI table(s) * Obtain ACPI table(s)
*/ */
......
...@@ -263,6 +263,15 @@ ACPI_INIT_GLOBAL(u32, acpi_gbl_trace_dbg_layer, ACPI_TRACE_LAYER_DEFAULT); ...@@ -263,6 +263,15 @@ ACPI_INIT_GLOBAL(u32, acpi_gbl_trace_dbg_layer, ACPI_TRACE_LAYER_DEFAULT);
ACPI_INIT_GLOBAL(u32, acpi_dbg_level, ACPI_DEBUG_DEFAULT); ACPI_INIT_GLOBAL(u32, acpi_dbg_level, ACPI_DEBUG_DEFAULT);
ACPI_INIT_GLOBAL(u32, acpi_dbg_layer, 0); ACPI_INIT_GLOBAL(u32, acpi_dbg_layer, 0);
/*
* Debugger command handshake globals. Host OSes need to access these
* variables to implement their own command handshake mechanism.
*/
#ifdef ACPI_DEBUGGER
ACPI_INIT_GLOBAL(u8, acpi_gbl_method_executing, FALSE);
ACPI_GLOBAL(char, acpi_gbl_db_line_buf[ACPI_DB_LINE_BUFFER_SIZE]);
#endif
/* /*
* Other miscellaneous globals * Other miscellaneous globals
*/ */
...@@ -366,6 +375,29 @@ ACPI_GLOBAL(u8, acpi_gbl_system_awake_and_running); ...@@ -366,6 +375,29 @@ ACPI_GLOBAL(u8, acpi_gbl_system_awake_and_running);
#endif /* ACPI_APPLICATION */ #endif /* ACPI_APPLICATION */
/*
* Debugger prototypes
*
* All interfaces used by debugger will be configured
* out of the ACPICA build unless the ACPI_DEBUGGER
* flag is defined.
*/
#ifdef ACPI_DEBUGGER
#define ACPI_DBR_DEPENDENT_RETURN_OK(prototype) \
ACPI_EXTERNAL_RETURN_OK(prototype)
#define ACPI_DBR_DEPENDENT_RETURN_VOID(prototype) \
ACPI_EXTERNAL_RETURN_VOID(prototype)
#else
#define ACPI_DBR_DEPENDENT_RETURN_OK(prototype) \
static ACPI_INLINE prototype {return(AE_OK);}
#define ACPI_DBR_DEPENDENT_RETURN_VOID(prototype) \
static ACPI_INLINE prototype {return;}
#endif /* ACPI_DEBUGGER */
/***************************************************************************** /*****************************************************************************
* *
* ACPICA public interface prototypes * ACPICA public interface prototypes
...@@ -929,6 +961,8 @@ ACPI_EXTERNAL_RETURN_STATUS(acpi_status ...@@ -929,6 +961,8 @@ ACPI_EXTERNAL_RETURN_STATUS(acpi_status
void **data, void **data,
void (*callback)(void *))) void (*callback)(void *)))
void acpi_run_debugger(char *batch_buffer);
void acpi_set_debugger_thread_id(acpi_thread_id thread_id); void acpi_set_debugger_thread_id(acpi_thread_id thread_id);
#endif /* __ACXFACE_H__ */ #endif /* __ACXFACE_H__ */
...@@ -150,6 +150,8 @@ ...@@ -150,6 +150,8 @@
*/ */
#define ACPI_USE_ALTERNATE_PROTOTYPE_acpi_os_readable #define ACPI_USE_ALTERNATE_PROTOTYPE_acpi_os_readable
#define ACPI_USE_ALTERNATE_PROTOTYPE_acpi_os_writable #define ACPI_USE_ALTERNATE_PROTOTYPE_acpi_os_writable
#define ACPI_USE_ALTERNATE_PROTOTYPE_acpi_os_initialize_command_signals
#define ACPI_USE_ALTERNATE_PROTOTYPE_acpi_os_terminate_command_signals
/* /*
* OSL interfaces used by utilities * OSL interfaces used by utilities
......
...@@ -129,6 +129,15 @@ static inline u8 acpi_os_readable(void *pointer, acpi_size length) ...@@ -129,6 +129,15 @@ static inline u8 acpi_os_readable(void *pointer, acpi_size length)
return TRUE; return TRUE;
} }
static inline acpi_status acpi_os_initialize_command_signals(void)
{
return AE_OK;
}
static inline void acpi_os_terminate_command_signals(void)
{
}
/* /*
* OSL interfaces added by Linux * OSL interfaces added by Linux
*/ */
......
...@@ -37,6 +37,8 @@ ...@@ -37,6 +37,8 @@
#include <linux/list.h> #include <linux/list.h>
#include <linux/mod_devicetable.h> #include <linux/mod_devicetable.h>
#include <linux/dynamic_debug.h> #include <linux/dynamic_debug.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <acpi/acpi_bus.h> #include <acpi/acpi_bus.h>
#include <acpi/acpi_drivers.h> #include <acpi/acpi_drivers.h>
...@@ -119,6 +121,75 @@ typedef int (*acpi_tbl_table_handler)(struct acpi_table_header *table); ...@@ -119,6 +121,75 @@ typedef int (*acpi_tbl_table_handler)(struct acpi_table_header *table);
typedef int (*acpi_tbl_entry_handler)(struct acpi_subtable_header *header, typedef int (*acpi_tbl_entry_handler)(struct acpi_subtable_header *header,
const unsigned long end); const unsigned long end);
/* Debugger support */
struct acpi_debugger_ops {
int (*create_thread)(acpi_osd_exec_callback function, void *context);
ssize_t (*write_log)(const char *msg);
ssize_t (*read_cmd)(char *buffer, size_t length);
int (*wait_command_ready)(bool single_step, char *buffer, size_t length);
int (*notify_command_complete)(void);
};
struct acpi_debugger {
const struct acpi_debugger_ops *ops;
struct module *owner;
struct mutex lock;
};
#ifdef CONFIG_ACPI_DEBUGGER
int __init acpi_debugger_init(void);
int acpi_register_debugger(struct module *owner,
const struct acpi_debugger_ops *ops);
void acpi_unregister_debugger(const struct acpi_debugger_ops *ops);
int acpi_debugger_create_thread(acpi_osd_exec_callback function, void *context);
ssize_t acpi_debugger_write_log(const char *msg);
ssize_t acpi_debugger_read_cmd(char *buffer, size_t buffer_length);
int acpi_debugger_wait_command_ready(void);
int acpi_debugger_notify_command_complete(void);
#else
static inline int acpi_debugger_init(void)
{
return -ENODEV;
}
static inline int acpi_register_debugger(struct module *owner,
const struct acpi_debugger_ops *ops)
{
return -ENODEV;
}
static inline void acpi_unregister_debugger(const struct acpi_debugger_ops *ops)
{
}
static inline int acpi_debugger_create_thread(acpi_osd_exec_callback function,
void *context)
{
return -ENODEV;
}
static inline int acpi_debugger_write_log(const char *msg)
{
return -ENODEV;
}
static inline int acpi_debugger_read_cmd(char *buffer, u32 buffer_length)
{
return -ENODEV;
}
static inline int acpi_debugger_wait_command_ready(void)
{
return -ENODEV;
}
static inline int acpi_debugger_notify_command_complete(void)
{
return -ENODEV;
}
#endif
#ifdef CONFIG_ACPI_INITRD_TABLE_OVERRIDE #ifdef CONFIG_ACPI_INITRD_TABLE_OVERRIDE
void acpi_initrd_override(void *data, size_t size); void acpi_initrd_override(void *data, size_t size);
#else #else
......
...@@ -10,18 +10,18 @@ ...@@ -10,18 +10,18 @@
include ../../scripts/Makefile.include include ../../scripts/Makefile.include
all: acpidump ec all: acpidbg acpidump ec
clean: acpidump_clean ec_clean clean: acpidbg_clean acpidump_clean ec_clean
install: acpidump_install ec_install install: acpidbg_install acpidump_install ec_install
uninstall: acpidump_uninstall ec_uninstall uninstall: acpidbg_uninstall acpidump_uninstall ec_uninstall
acpidump ec: FORCE acpidbg acpidump ec: FORCE
$(call descend,tools/$@,all) $(call descend,tools/$@,all)
acpidump_clean ec_clean: acpidbg_clean acpidump_clean ec_clean:
$(call descend,tools/$(@:_clean=),clean) $(call descend,tools/$(@:_clean=),clean)
acpidump_install ec_install: acpidbg_install acpidump_install ec_install:
$(call descend,tools/$(@:_install=),install) $(call descend,tools/$(@:_install=),install)
acpidump_uninstall ec_uninstall: acpidbg_uninstall acpidump_uninstall ec_uninstall:
$(call descend,tools/$(@:_uninstall=),uninstall) $(call descend,tools/$(@:_uninstall=),uninstall)
.PHONY: FORCE .PHONY: FORCE
# tools/power/acpi/tools/acpidbg/Makefile - ACPI tool Makefile
#
# Copyright (c) 2015, Intel Corporation
# Author: Lv Zheng <lv.zheng@intel.com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; version 2
# of the License.
include ../../Makefile.config
TOOL = acpidbg
vpath %.c \
../../../../../drivers/acpi/acpica\
../../common\
../../os_specific/service_layers\
.
CFLAGS += -DACPI_APPLICATION -DACPI_SINGLE_THREAD -DACPI_DEBUGGER\
-I.\
-I../../../../../drivers/acpi/acpica\
-I../../../../../include
LDFLAGS += -lpthread
TOOL_OBJS = \
acpidbg.o
include ../../Makefile.rules
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment