diff options
-rw-r--r-- | drivers/acpi/dispatcher/dsopcode.c | 4 | ||||
-rw-r--r-- | drivers/acpi/osl.c | 176 | ||||
-rw-r--r-- | include/acpi/acpiosxf.h | 4 | ||||
-rw-r--r-- | include/linux/acpi.h | 25 |
4 files changed, 204 insertions, 5 deletions
diff --git a/drivers/acpi/dispatcher/dsopcode.c b/drivers/acpi/dispatcher/dsopcode.c index fc9da4879cb..f501e083aac 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 3b8aef3aefe..6f49f643728 100644 --- a/drivers/acpi/osl.c +++ b/drivers/acpi/osl.c @@ -44,6 +44,8 @@ #include <asm/uaccess.h> #include <linux/efi.h> +#include <linux/ioport.h> +#include <linux/list.h> #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]; @@ -1091,6 +1105,128 @@ 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 */ +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; +} +EXPORT_SYMBOL(acpi_check_resource_conflict); + +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. * @@ -1292,10 +1428,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 1a16cfbe9e0..022a5fd80c8 100644 --- a/include/acpi/acpiosxf.h +++ b/include/acpi/acpiosxf.h @@ -242,8 +242,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 5a5a13b64d0..25fb4dcc0e6 100644 --- a/include/linux/acpi.h +++ b/include/linux/acpi.h @@ -25,6 +25,7 @@ #ifndef _LINUX_ACPI_H #define _LINUX_ACPI_H +#include <linux/ioport.h> /* for struct resource */ #ifdef CONFIG_ACPI @@ -238,6 +239,13 @@ 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, + const char *name); + #else /* CONFIG_ACPI */ static inline int acpi_boot_init(void) @@ -250,5 +258,22 @@ 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) +{ + 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*/ |