From df92e695998e1bc6e426a840eb86d6d1ee87e2a5 Mon Sep 17 00:00:00 2001 From: Thomas Renninger Date: Mon, 4 Feb 2008 23:31:22 -0800 Subject: ACPI: track opregion names to avoid driver resource conflicts. Small ACPICA extension to be able to store the name of operation regions in osl.c later In ACPI, AML can define accesses to IO ports and System Memory by Operation Regions. Those are not registered as done by PNPACPI using resource templates (and _CRS/_SRS methods). The IO ports and System Memory regions may get accessed by arbitrary AML code. When native drivers are accessing the same resources bad things can happen (e.g. a critical shutdown temperature of 3000 C every 2 months or so). It is not really possible to register the operation regions via request_resource, as they often overlap with pnp or other resources (e.g. statically setup IO resources below 0x100). This approach stores all Operation Region declarations (IO and System Memory only) at ACPI table parse time. It offers a similar functionality like request_region and let drivers which are known to possibly use the same IO ports and Memory which are also often used by ACPI (hwmon and i2c) check for ACPI interference. A boot parameter acpi_enforce_resources=strict/lax/no is provided, which is default set to lax: - strict: let conflicting drivers fail to load with an error message - lax: let conflicting driver work normal with a warning message - no: no functional change at all Depending on the feedback and the kind of interferences we see, this should be set to strict at later time. Goal of this patch set is: - Identify ACPI interferences in bug reports (very hard to reproduce and to identify) - Find BIOSes for that an ACPI driver should exist for specific HW instead of a native one. - stability in general Provide acpi_check_{mem_}region. Drivers can additionally check against possible ACPI interference by also invoking this shortly before they call request_region. If -EBUSY is returned, the driver must not load. Use acpi_enforce_resources=strict/lax/no options to: - strict: let conflicting drivers fail to load with an error message - lax: let conflicting driver work normal with a warning message - no: no functional change at all Cc: "Mark M. Hoffman" Cc: Jean Delvare Cc: Len Brown Cc: Bjorn Helgaas Signed-off-by: Thomas Renninger Signed-off-by: Andrew Morton Signed-off-by: Len Brown --- drivers/acpi/dispatcher/dsopcode.c | 4 +- drivers/acpi/osl.c | 175 ++++++++++++++++++++++++++++++++++++- include/acpi/acpiosxf.h | 4 +- include/linux/acpi.h | 17 ++++ 4 files changed, 195 insertions(+), 5 deletions(-) diff --git a/drivers/acpi/dispatcher/dsopcode.c b/drivers/acpi/dispatcher/dsopcode.c index fc9da48..f501e08 100644 --- a/drivers/acpi/dispatcher/dsopcode.c +++ b/drivers/acpi/dispatcher/dsopcode.c @@ -359,7 +359,9 @@ acpi_status acpi_ds_get_region_arguments(union acpi_operand_object *obj_desc) status = acpi_os_validate_address(obj_desc->region.space_id, obj_desc->region.address, - (acpi_size) obj_desc->region.length); + (acpi_size) obj_desc->region.length, + acpi_ut_get_node_name(node)); + if (ACPI_FAILURE(status)) { /* * Invalid address/length. We will emit an error message and mark diff --git a/drivers/acpi/osl.c b/drivers/acpi/osl.c index e53fb51..222f7b1 100644 --- a/drivers/acpi/osl.c +++ b/drivers/acpi/osl.c @@ -44,6 +44,8 @@ #include #include +#include +#include #define _COMPONENT ACPI_OS_SERVICES ACPI_MODULE_NAME("osl"); @@ -74,6 +76,18 @@ static void *acpi_irq_context; static struct workqueue_struct *kacpid_wq; static struct workqueue_struct *kacpi_notify_wq; +struct acpi_res_list { + resource_size_t start; + resource_size_t end; + acpi_adr_space_type resource_type; /* IO port, System memory, ...*/ + char name[5]; /* only can have a length of 4 chars, make use of this + one instead of res->name, no need to kalloc then */ + struct list_head resource_list; +}; + +static LIST_HEAD(resource_list_head); +static DEFINE_SPINLOCK(acpi_res_lock); + #define OSI_STRING_LENGTH_MAX 64 /* arbitrary */ static char osi_additional_string[OSI_STRING_LENGTH_MAX]; @@ -1102,6 +1116,127 @@ static int __init acpi_wake_gpes_always_on_setup(char *str) __setup("acpi_wake_gpes_always_on", acpi_wake_gpes_always_on_setup); +/* Check of resource interference between native drivers and ACPI + * OperationRegions (SystemIO and System Memory only). + * IO ports and memory declared in ACPI might be used by the ACPI subsystem + * in arbitrary AML code and can interfere with legacy drivers. + * acpi_enforce_resources= can be set to: + * + * - strict (2) + * -> further driver trying to access the resources will not load + * - lax (default) (1) + * -> further driver trying to access the resources will load, but you + * get a system message that something might go wrong... + * + * - no (0) + * -> ACPI Operation Region resources will not be registered + * + */ +#define ENFORCE_RESOURCES_STRICT 2 +#define ENFORCE_RESOURCES_LAX 1 +#define ENFORCE_RESOURCES_NO 0 + +static unsigned int acpi_enforce_resources = ENFORCE_RESOURCES_LAX; + +static int __init acpi_enforce_resources_setup(char *str) +{ + if (str == NULL || *str == '\0') + return 0; + + if (!strcmp("strict", str)) + acpi_enforce_resources = ENFORCE_RESOURCES_STRICT; + else if (!strcmp("lax", str)) + acpi_enforce_resources = ENFORCE_RESOURCES_LAX; + else if (!strcmp("no", str)) + acpi_enforce_resources = ENFORCE_RESOURCES_NO; + + return 1; +} + +__setup("acpi_enforce_resources=", acpi_enforce_resources_setup); + +/* Check for resource conflicts between ACPI OperationRegions and native + * drivers */ +static int acpi_check_resource_conflict(struct resource *res) +{ + struct acpi_res_list *res_list_elem; + int ioport; + int clash = 0; + + if (acpi_enforce_resources == ENFORCE_RESOURCES_NO) + return 0; + if (!(res->flags & IORESOURCE_IO) && !(res->flags & IORESOURCE_MEM)) + return 0; + + ioport = res->flags & IORESOURCE_IO; + + spin_lock(&acpi_res_lock); + list_for_each_entry(res_list_elem, &resource_list_head, + resource_list) { + if (ioport && (res_list_elem->resource_type + != ACPI_ADR_SPACE_SYSTEM_IO)) + continue; + if (!ioport && (res_list_elem->resource_type + != ACPI_ADR_SPACE_SYSTEM_MEMORY)) + continue; + + if (res->end < res_list_elem->start + || res_list_elem->end < res->start) + continue; + clash = 1; + break; + } + spin_unlock(&acpi_res_lock); + + if (clash) { + if (acpi_enforce_resources != ENFORCE_RESOURCES_NO) { + printk(KERN_INFO "%sACPI: %s resource %s [0x%llx-0x%llx]" + " conflicts with ACPI region %s" + " [0x%llx-0x%llx]\n", + acpi_enforce_resources == ENFORCE_RESOURCES_LAX + ? KERN_WARNING : KERN_ERR, + ioport ? "I/O" : "Memory", res->name, + (long long) res->start, (long long) res->end, + res_list_elem->name, + (long long) res_list_elem->start, + (long long) res_list_elem->end); + printk(KERN_INFO "ACPI: Device needs an ACPI driver\n"); + } + if (acpi_enforce_resources == ENFORCE_RESOURCES_STRICT) + return -EBUSY; + } + return 0; +} + +int acpi_check_region(resource_size_t start, resource_size_t n, + const char *name) +{ + struct resource res = { + .start = start, + .end = start + n - 1, + .name = name, + .flags = IORESOURCE_IO, + }; + + return acpi_check_resource_conflict(&res); +} +EXPORT_SYMBOL(acpi_check_region); + +int acpi_check_mem_region(resource_size_t start, resource_size_t n, + const char *name) +{ + struct resource res = { + .start = start, + .end = start + n - 1, + .name = name, + .flags = IORESOURCE_MEM, + }; + + return acpi_check_resource_conflict(&res); + +} +EXPORT_SYMBOL(acpi_check_mem_region); + /* * Acquire a spinlock. * @@ -1303,10 +1438,46 @@ acpi_status acpi_os_validate_address ( u8 space_id, acpi_physical_address address, - acpi_size length) + acpi_size length, + char *name) { + struct acpi_res_list *res; + if (acpi_enforce_resources == ENFORCE_RESOURCES_NO) + return AE_OK; - return AE_OK; + switch (space_id) { + case ACPI_ADR_SPACE_SYSTEM_IO: + case ACPI_ADR_SPACE_SYSTEM_MEMORY: + /* Only interference checks against SystemIO and SytemMemory + are needed */ + res = kzalloc(sizeof(struct acpi_res_list), GFP_KERNEL); + if (!res) + return AE_OK; + /* ACPI names are fixed to 4 bytes, still better use strlcpy */ + strlcpy(res->name, name, 5); + res->start = address; + res->end = address + length - 1; + res->resource_type = space_id; + spin_lock(&acpi_res_lock); + list_add(&res->resource_list, &resource_list_head); + spin_unlock(&acpi_res_lock); + pr_debug("Added %s resource: start: 0x%llx, end: 0x%llx, " + "name: %s\n", (space_id == ACPI_ADR_SPACE_SYSTEM_IO) + ? "SystemIO" : "System Memory", + (unsigned long long)res->start, + (unsigned long long)res->end, + res->name); + break; + case ACPI_ADR_SPACE_PCI_CONFIG: + case ACPI_ADR_SPACE_EC: + case ACPI_ADR_SPACE_SMBUS: + case ACPI_ADR_SPACE_CMOS: + case ACPI_ADR_SPACE_PCI_BAR_TARGET: + case ACPI_ADR_SPACE_DATA_TABLE: + case ACPI_ADR_SPACE_FIXED_HARDWARE: + break; + } + return AE_OK; } #endif diff --git a/include/acpi/acpiosxf.h b/include/acpi/acpiosxf.h index ca882b8..c082c7d 100644 --- a/include/acpi/acpiosxf.h +++ b/include/acpi/acpiosxf.h @@ -239,8 +239,8 @@ acpi_status acpi_os_validate_interface(char *interface); acpi_status acpi_osi_invalidate(char* interface); acpi_status -acpi_os_validate_address(u8 space_id, - acpi_physical_address address, acpi_size length); +acpi_os_validate_address(u8 space_id, acpi_physical_address address, + acpi_size length, char *name); u64 acpi_os_get_timer(void); diff --git a/include/linux/acpi.h b/include/linux/acpi.h index 63f2e6e..893f90a 100644 --- a/include/linux/acpi.h +++ b/include/linux/acpi.h @@ -217,6 +217,11 @@ extern int pnpacpi_disabled; #define PXM_INVAL (-1) #define NID_INVAL (-1) +int acpi_check_region(resource_size_t start, resource_size_t n, + const char *name); +int acpi_check_mem_region(resource_size_t start, resource_size_t n, + const char *name); + #else /* CONFIG_ACPI */ static inline int acpi_boot_init(void) @@ -229,5 +234,17 @@ static inline int acpi_boot_table_init(void) return 0; } +static inline int acpi_check_region(resource_size_t start, resource_size_t n, + const char *name) +{ + return 0; +} + +static inline int acpi_check_mem_region(resource_size_t start, + resource_size_t n, const char *name) +{ + return 0; +} + #endif /* !CONFIG_ACPI */ #endif /*_LINUX_ACPI_H*/ -- cgit v1.1 From 443dea72d5f428170de6d6e3c4c1a1e2b7632b65 Mon Sep 17 00:00:00 2001 From: Thomas Renninger Date: Mon, 4 Feb 2008 23:31:23 -0800 Subject: ACPI: Export acpi_check_resource_conflict Export acpi_check_resource_conflict(), sometimes drivers already have a struct resource at hand so no need to use the wrappers to build a new one. Signed-off-by: Jean Delvare Cc: "Mark M. Hoffman" Cc: Bjorn Helgaas Signed-off-by: Andrew Morton Signed-off-by: Len Brown --- drivers/acpi/osl.c | 3 ++- include/linux/acpi.h | 8 ++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/drivers/acpi/osl.c b/drivers/acpi/osl.c index 222f7b1..bc1604b 100644 --- a/drivers/acpi/osl.c +++ b/drivers/acpi/osl.c @@ -1157,7 +1157,7 @@ __setup("acpi_enforce_resources=", acpi_enforce_resources_setup); /* Check for resource conflicts between ACPI OperationRegions and native * drivers */ -static int acpi_check_resource_conflict(struct resource *res) +int acpi_check_resource_conflict(struct resource *res) { struct acpi_res_list *res_list_elem; int ioport; @@ -1207,6 +1207,7 @@ static int acpi_check_resource_conflict(struct resource *res) } return 0; } +EXPORT_SYMBOL(acpi_check_resource_conflict); int acpi_check_region(resource_size_t start, resource_size_t n, const char *name) diff --git a/include/linux/acpi.h b/include/linux/acpi.h index 893f90a..a031df8 100644 --- a/include/linux/acpi.h +++ b/include/linux/acpi.h @@ -25,6 +25,7 @@ #ifndef _LINUX_ACPI_H #define _LINUX_ACPI_H +#include /* for struct resource */ #ifdef CONFIG_ACPI @@ -217,6 +218,8 @@ extern int pnpacpi_disabled; #define PXM_INVAL (-1) #define NID_INVAL (-1) +int acpi_check_resource_conflict(struct resource *res); + int acpi_check_region(resource_size_t start, resource_size_t n, const char *name); int acpi_check_mem_region(resource_size_t start, resource_size_t n, @@ -234,6 +237,11 @@ static inline int acpi_boot_table_init(void) return 0; } +static inline int acpi_check_resource_conflict(struct resource *res) +{ + return 0; +} + static inline int acpi_check_region(resource_size_t start, resource_size_t n, const char *name) { -- cgit v1.1