1045 lines · 27.3 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0/* * sysfs.c - ACPI sysfs interface to userspace. */ #define pr_fmt(fmt) "ACPI: " fmt #include <linux/acpi.h>#include <linux/bitmap.h>#include <linux/init.h>#include <linux/kernel.h>#include <linux/kstrtox.h>#include <linux/moduleparam.h> #include "internal.h" #ifdef CONFIG_ACPI_DEBUG/* * ACPI debug sysfs I/F, including: * /sys/module/acpi/parameters/debug_layer * /sys/module/acpi/parameters/debug_level * /sys/module/acpi/parameters/trace_method_name * /sys/module/acpi/parameters/trace_state * /sys/module/acpi/parameters/trace_debug_layer * /sys/module/acpi/parameters/trace_debug_level */ struct acpi_dlayer {	const char *name;	unsigned long value;};struct acpi_dlevel {	const char *name;	unsigned long value;};#define ACPI_DEBUG_INIT(v)	{ .name = #v, .value = v } static const struct acpi_dlayer acpi_debug_layers[] = {	ACPI_DEBUG_INIT(ACPI_UTILITIES),	ACPI_DEBUG_INIT(ACPI_HARDWARE),	ACPI_DEBUG_INIT(ACPI_EVENTS),	ACPI_DEBUG_INIT(ACPI_TABLES),	ACPI_DEBUG_INIT(ACPI_NAMESPACE),	ACPI_DEBUG_INIT(ACPI_PARSER),	ACPI_DEBUG_INIT(ACPI_DISPATCHER),	ACPI_DEBUG_INIT(ACPI_EXECUTER),	ACPI_DEBUG_INIT(ACPI_RESOURCES),	ACPI_DEBUG_INIT(ACPI_CA_DEBUGGER),	ACPI_DEBUG_INIT(ACPI_OS_SERVICES),	ACPI_DEBUG_INIT(ACPI_CA_DISASSEMBLER),	ACPI_DEBUG_INIT(ACPI_COMPILER),	ACPI_DEBUG_INIT(ACPI_TOOLS),}; static const struct acpi_dlevel acpi_debug_levels[] = {	ACPI_DEBUG_INIT(ACPI_LV_INIT),	ACPI_DEBUG_INIT(ACPI_LV_DEBUG_OBJECT),	ACPI_DEBUG_INIT(ACPI_LV_INFO),	ACPI_DEBUG_INIT(ACPI_LV_REPAIR),	ACPI_DEBUG_INIT(ACPI_LV_TRACE_POINT), 	ACPI_DEBUG_INIT(ACPI_LV_INIT_NAMES),	ACPI_DEBUG_INIT(ACPI_LV_PARSE),	ACPI_DEBUG_INIT(ACPI_LV_LOAD),	ACPI_DEBUG_INIT(ACPI_LV_DISPATCH),	ACPI_DEBUG_INIT(ACPI_LV_EXEC),	ACPI_DEBUG_INIT(ACPI_LV_NAMES),	ACPI_DEBUG_INIT(ACPI_LV_OPREGION),	ACPI_DEBUG_INIT(ACPI_LV_BFIELD),	ACPI_DEBUG_INIT(ACPI_LV_TABLES),	ACPI_DEBUG_INIT(ACPI_LV_VALUES),	ACPI_DEBUG_INIT(ACPI_LV_OBJECTS),	ACPI_DEBUG_INIT(ACPI_LV_RESOURCES),	ACPI_DEBUG_INIT(ACPI_LV_USER_REQUESTS),	ACPI_DEBUG_INIT(ACPI_LV_PACKAGE), 	ACPI_DEBUG_INIT(ACPI_LV_ALLOCATIONS),	ACPI_DEBUG_INIT(ACPI_LV_FUNCTIONS),	ACPI_DEBUG_INIT(ACPI_LV_OPTIMIZATIONS), 	ACPI_DEBUG_INIT(ACPI_LV_MUTEX),	ACPI_DEBUG_INIT(ACPI_LV_THREADS),	ACPI_DEBUG_INIT(ACPI_LV_IO),	ACPI_DEBUG_INIT(ACPI_LV_INTERRUPTS), 	ACPI_DEBUG_INIT(ACPI_LV_AML_DISASSEMBLE),	ACPI_DEBUG_INIT(ACPI_LV_VERBOSE_INFO),	ACPI_DEBUG_INIT(ACPI_LV_FULL_TABLES),	ACPI_DEBUG_INIT(ACPI_LV_EVENTS),}; static int param_get_debug_layer(char *buffer, const struct kernel_param *kp){	int result = 0;	int i; 	result = sprintf(buffer, "%-25s\tHex        SET\n", "Description"); 	for (i = 0; i < ARRAY_SIZE(acpi_debug_layers); i++) {		result += sprintf(buffer + result, "%-25s\t0x%08lX [%c]\n",				  acpi_debug_layers[i].name,				  acpi_debug_layers[i].value,				  (acpi_dbg_layer & acpi_debug_layers[i].value)				  ? '*' : ' ');	}	result +=	    sprintf(buffer + result, "%-25s\t0x%08X [%c]\n", "ACPI_ALL_DRIVERS",		    ACPI_ALL_DRIVERS,		    (acpi_dbg_layer & ACPI_ALL_DRIVERS) ==		    ACPI_ALL_DRIVERS ? '*' : (acpi_dbg_layer & ACPI_ALL_DRIVERS)		    == 0 ? ' ' : '-');	result +=	    sprintf(buffer + result,		    "--\ndebug_layer = 0x%08X ( * = enabled)\n",		    acpi_dbg_layer); 	return result;} static int param_get_debug_level(char *buffer, const struct kernel_param *kp){	int result = 0;	int i; 	result = sprintf(buffer, "%-25s\tHex        SET\n", "Description"); 	for (i = 0; i < ARRAY_SIZE(acpi_debug_levels); i++) {		result += sprintf(buffer + result, "%-25s\t0x%08lX [%c]\n",				  acpi_debug_levels[i].name,				  acpi_debug_levels[i].value,				  (acpi_dbg_level & acpi_debug_levels[i].value)				  ? '*' : ' ');	}	result +=	    sprintf(buffer + result, "--\ndebug_level = 0x%08X (* = enabled)\n",		    acpi_dbg_level); 	return result;} static const struct kernel_param_ops param_ops_debug_layer = {	.set = param_set_uint,	.get = param_get_debug_layer,}; static const struct kernel_param_ops param_ops_debug_level = {	.set = param_set_uint,	.get = param_get_debug_level,}; module_param_cb(debug_layer, &param_ops_debug_layer, &acpi_dbg_layer, 0644);module_param_cb(debug_level, &param_ops_debug_level, &acpi_dbg_level, 0644); static char trace_method_name[1024]; static int param_set_trace_method_name(const char *val,				       const struct kernel_param *kp){	u32 saved_flags = 0;	bool is_abs_path = true; 	if (*val != '\\')		is_abs_path = false; 	if ((is_abs_path && strlen(val) > 1023) ||	    (!is_abs_path && strlen(val) > 1022)) {		pr_err("%s: string parameter too long\n", kp->name);		return -ENOSPC;	} 	/*	 * It's not safe to update acpi_gbl_trace_method_name without	 * having the tracer stopped, so we save the original tracer	 * state and disable it.	 */	saved_flags = acpi_gbl_trace_flags;	(void)acpi_debug_trace(NULL,			       acpi_gbl_trace_dbg_level,			       acpi_gbl_trace_dbg_layer,			       0); 	/* This is a hack.  We can't kmalloc in early boot. */	if (is_abs_path)		strcpy(trace_method_name, val);	else {		trace_method_name[0] = '\\';		strcpy(trace_method_name+1, val);	} 	/* Restore the original tracer state */	(void)acpi_debug_trace(trace_method_name,			       acpi_gbl_trace_dbg_level,			       acpi_gbl_trace_dbg_layer,			       saved_flags); 	return 0;} static int param_get_trace_method_name(char *buffer, const struct kernel_param *kp){	return sysfs_emit(buffer, "%s\n", acpi_gbl_trace_method_name);} static const struct kernel_param_ops param_ops_trace_method = {	.set = param_set_trace_method_name,	.get = param_get_trace_method_name,}; static const struct kernel_param_ops param_ops_trace_attrib = {	.set = param_set_uint,	.get = param_get_uint,}; module_param_cb(trace_method_name, &param_ops_trace_method, &trace_method_name, 0644);module_param_cb(trace_debug_layer, &param_ops_trace_attrib, &acpi_gbl_trace_dbg_layer, 0644);module_param_cb(trace_debug_level, &param_ops_trace_attrib, &acpi_gbl_trace_dbg_level, 0644); static int param_set_trace_state(const char *val,				 const struct kernel_param *kp){	acpi_status status;	const char *method = trace_method_name;	u32 flags = 0; /* So "xxx-once" comparison should go prior than "xxx" comparison */#define acpi_compare_param(val, key)	\	strncmp((val), (key), sizeof(key) - 1) 	if (!acpi_compare_param(val, "enable")) {		method = NULL;		flags = ACPI_TRACE_ENABLED;	} else if (!acpi_compare_param(val, "disable"))		method = NULL;	else if (!acpi_compare_param(val, "method-once"))		flags = ACPI_TRACE_ENABLED | ACPI_TRACE_ONESHOT;	else if (!acpi_compare_param(val, "method"))		flags = ACPI_TRACE_ENABLED;	else if (!acpi_compare_param(val, "opcode-once"))		flags = ACPI_TRACE_ENABLED | ACPI_TRACE_ONESHOT | ACPI_TRACE_OPCODE;	else if (!acpi_compare_param(val, "opcode"))		flags = ACPI_TRACE_ENABLED | ACPI_TRACE_OPCODE;	else		return -EINVAL; 	status = acpi_debug_trace(method,				  acpi_gbl_trace_dbg_level,				  acpi_gbl_trace_dbg_layer,				  flags);	if (ACPI_FAILURE(status))		return -EBUSY; 	return 0;} static int param_get_trace_state(char *buffer, const struct kernel_param *kp){	if (!(acpi_gbl_trace_flags & ACPI_TRACE_ENABLED))		return sprintf(buffer, "disable\n");	if (!acpi_gbl_trace_method_name)		return sprintf(buffer, "enable\n");	if (acpi_gbl_trace_flags & ACPI_TRACE_ONESHOT)		return sprintf(buffer, "method-once\n");	else		return sprintf(buffer, "method\n");} module_param_call(trace_state, param_set_trace_state, param_get_trace_state,		  NULL, 0644);#endif /* CONFIG_ACPI_DEBUG */  /* /sys/module/acpi/parameters/aml_debug_output */ module_param_named(aml_debug_output, acpi_gbl_enable_aml_debug_object,		   byte, 0644);MODULE_PARM_DESC(aml_debug_output,		 "To enable/disable the ACPI Debug Object output."); /* /sys/module/acpi/parameters/acpica_version */static int param_get_acpica_version(char *buffer,				    const struct kernel_param *kp){	int result; 	result = sprintf(buffer, "%x\n", ACPI_CA_VERSION); 	return result;} module_param_call(acpica_version, NULL, param_get_acpica_version, NULL, 0444); /* * ACPI table sysfs I/F: * /sys/firmware/acpi/tables/ * /sys/firmware/acpi/tables/data/ * /sys/firmware/acpi/tables/dynamic/ */ static LIST_HEAD(acpi_table_attr_list);static struct kobject *tables_kobj;static struct kobject *tables_data_kobj;static struct kobject *dynamic_tables_kobj;static struct kobject *hotplug_kobj; #define ACPI_MAX_TABLE_INSTANCES	999#define ACPI_INST_SIZE			4 /* including trailing 0 */ struct acpi_table_attr {	struct bin_attribute attr;	char name[ACPI_NAMESEG_SIZE];	int instance;	char filename[ACPI_NAMESEG_SIZE+ACPI_INST_SIZE];	struct list_head node;}; struct acpi_data_attr {	struct bin_attribute attr;	u64	addr;	char filename[ACPI_NAMESEG_SIZE+ACPI_INST_SIZE];}; static ssize_t acpi_table_show(struct file *filp, struct kobject *kobj,			       const struct bin_attribute *bin_attr, char *buf,			       loff_t offset, size_t count){	struct acpi_table_attr *table_attr =	    container_of(bin_attr, struct acpi_table_attr, attr);	struct acpi_table_header *table_header = NULL;	acpi_status status;	ssize_t rc; 	status = acpi_get_table(table_attr->name, table_attr->instance,				&table_header);	if (ACPI_FAILURE(status))		return -ENODEV; 	rc = memory_read_from_buffer(buf, count, &offset, table_header,			table_header->length);	acpi_put_table(table_header);	return rc;} static int acpi_table_attr_init(struct kobject *tables_obj,				struct acpi_table_attr *table_attr,				struct acpi_table_header *table_header){	struct acpi_table_header *header = NULL;	struct acpi_table_attr *attr = NULL;	char instance_str[ACPI_INST_SIZE]; 	sysfs_attr_init(&table_attr->attr.attr);	ACPI_COPY_NAMESEG(table_attr->name, table_header->signature); 	list_for_each_entry(attr, &acpi_table_attr_list, node) {		if (ACPI_COMPARE_NAMESEG(table_attr->name, attr->name))			if (table_attr->instance < attr->instance)				table_attr->instance = attr->instance;	}	table_attr->instance++;	if (table_attr->instance > ACPI_MAX_TABLE_INSTANCES) {		pr_warn("%4.4s: too many table instances\n", table_attr->name);		return -ERANGE;	} 	ACPI_COPY_NAMESEG(table_attr->filename, table_header->signature);	table_attr->filename[ACPI_NAMESEG_SIZE] = '\0';	if (table_attr->instance > 1 || (table_attr->instance == 1 &&					 !acpi_get_table					 (table_header->signature, 2, &header))) {		snprintf(instance_str, sizeof(instance_str), "%u",			 table_attr->instance);		strcat(table_attr->filename, instance_str);	} 	table_attr->attr.size = table_header->length;	table_attr->attr.read = acpi_table_show;	table_attr->attr.attr.name = table_attr->filename;	table_attr->attr.attr.mode = 0400; 	return sysfs_create_bin_file(tables_obj, &table_attr->attr);} acpi_status acpi_sysfs_table_handler(u32 event, void *table, void *context){	struct acpi_table_attr *table_attr; 	switch (event) {	case ACPI_TABLE_EVENT_INSTALL:		table_attr = kzalloc_obj(*table_attr);		if (!table_attr)			return AE_NO_MEMORY; 		if (acpi_table_attr_init(dynamic_tables_kobj,					 table_attr, table)) {			kfree(table_attr);			return AE_ERROR;		}		list_add_tail(&table_attr->node, &acpi_table_attr_list);		break;	case ACPI_TABLE_EVENT_LOAD:	case ACPI_TABLE_EVENT_UNLOAD:	case ACPI_TABLE_EVENT_UNINSTALL:		/*		 * we do not need to do anything right now		 * because the table is not deleted from the		 * global table list when unloading it.		 */		break;	default:		return AE_BAD_PARAMETER;	}	return AE_OK;} static ssize_t acpi_data_show(struct file *filp, struct kobject *kobj,			      const struct bin_attribute *bin_attr, char *buf,			      loff_t offset, size_t count){	struct acpi_data_attr *data_attr;	void __iomem *base;	ssize_t size; 	data_attr = container_of(bin_attr, struct acpi_data_attr, attr);	size = data_attr->attr.size; 	if (offset < 0)		return -EINVAL; 	if (offset >= size)		return 0; 	if (count > size - offset)		count = size - offset; 	base = acpi_os_map_iomem(data_attr->addr, size);	if (!base)		return -ENOMEM; 	memcpy_fromio(buf, base + offset, count); 	acpi_os_unmap_iomem(base, size); 	return count;} static int acpi_bert_data_init(void *th, struct acpi_data_attr *data_attr){	struct acpi_table_bert *bert = th; 	if (bert->header.length < sizeof(struct acpi_table_bert) ||	    bert->region_length < sizeof(struct acpi_bert_region)) {		kfree(data_attr);		return -EINVAL;	}	data_attr->addr = bert->address;	data_attr->attr.size = bert->region_length; 	return sysfs_create_bin_file(tables_data_kobj, &data_attr->attr);} static int acpi_ccel_data_init(void *th, struct acpi_data_attr *data_attr){	struct acpi_table_ccel *ccel = th; 	if (ccel->header.length < sizeof(struct acpi_table_ccel) ||	    !ccel->log_area_start_address || !ccel->log_area_minimum_length) {		kfree(data_attr);		return -EINVAL;	}	data_attr->addr = ccel->log_area_start_address;	data_attr->attr.size = ccel->log_area_minimum_length; 	return sysfs_create_bin_file(tables_data_kobj, &data_attr->attr);} static struct acpi_data_obj {	char *name;	int (*fn)(void *, struct acpi_data_attr *);} acpi_data_objs[] = {	{ ACPI_SIG_BERT, acpi_bert_data_init },	{ ACPI_SIG_CCEL, acpi_ccel_data_init },}; #define NUM_ACPI_DATA_OBJS ARRAY_SIZE(acpi_data_objs) static int acpi_table_data_init(struct acpi_table_header *th, struct acpi_table_attr *table_attr){	struct acpi_data_attr *data_attr;	int i; 	for (i = 0; i < NUM_ACPI_DATA_OBJS; i++) {		if (ACPI_COMPARE_NAMESEG(th->signature, acpi_data_objs[i].name)) {			data_attr = kzalloc_obj(*data_attr);			if (!data_attr)				return -ENOMEM;			sysfs_attr_init(&data_attr->attr.attr);			data_attr->attr.read = acpi_data_show;			data_attr->attr.attr.mode = 0400;			strscpy(data_attr->filename, table_attr->filename);			data_attr->attr.attr.name = data_attr->filename;

Lines 1–500 of 1045.