Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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" <mhoffman@lightlink.com>
Cc: Jean Delvare <khali@linux-fr.org>
Cc: Len Brown <lenb@kernel.org>
Cc: Bjorn Helgaas <bjorn.helgaas@hp.com>
Signed-off-by: Thomas Renninger <trenn@suse.de>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Len Brown <len.brown@intel.com>

authored by

Thomas Renninger and committed by
Len Brown
df92e695 488b5ec8

+195 -5
+3 -1
drivers/acpi/dispatcher/dsopcode.c
··· 359 359 360 360 status = acpi_os_validate_address(obj_desc->region.space_id, 361 361 obj_desc->region.address, 362 - (acpi_size) obj_desc->region.length); 362 + (acpi_size) obj_desc->region.length, 363 + acpi_ut_get_node_name(node)); 364 + 363 365 if (ACPI_FAILURE(status)) { 364 366 /* 365 367 * Invalid address/length. We will emit an error message and mark
+173 -2
drivers/acpi/osl.c
··· 44 44 #include <asm/uaccess.h> 45 45 46 46 #include <linux/efi.h> 47 + #include <linux/ioport.h> 48 + #include <linux/list.h> 47 49 48 50 #define _COMPONENT ACPI_OS_SERVICES 49 51 ACPI_MODULE_NAME("osl"); ··· 75 73 static void *acpi_irq_context; 76 74 static struct workqueue_struct *kacpid_wq; 77 75 static struct workqueue_struct *kacpi_notify_wq; 76 + 77 + struct acpi_res_list { 78 + resource_size_t start; 79 + resource_size_t end; 80 + acpi_adr_space_type resource_type; /* IO port, System memory, ...*/ 81 + char name[5]; /* only can have a length of 4 chars, make use of this 82 + one instead of res->name, no need to kalloc then */ 83 + struct list_head resource_list; 84 + }; 85 + 86 + static LIST_HEAD(resource_list_head); 87 + static DEFINE_SPINLOCK(acpi_res_lock); 78 88 79 89 #define OSI_STRING_LENGTH_MAX 64 /* arbitrary */ 80 90 static char osi_additional_string[OSI_STRING_LENGTH_MAX]; ··· 1116 1102 1117 1103 __setup("acpi_wake_gpes_always_on", acpi_wake_gpes_always_on_setup); 1118 1104 1105 + /* Check of resource interference between native drivers and ACPI 1106 + * OperationRegions (SystemIO and System Memory only). 1107 + * IO ports and memory declared in ACPI might be used by the ACPI subsystem 1108 + * in arbitrary AML code and can interfere with legacy drivers. 1109 + * acpi_enforce_resources= can be set to: 1110 + * 1111 + * - strict (2) 1112 + * -> further driver trying to access the resources will not load 1113 + * - lax (default) (1) 1114 + * -> further driver trying to access the resources will load, but you 1115 + * get a system message that something might go wrong... 1116 + * 1117 + * - no (0) 1118 + * -> ACPI Operation Region resources will not be registered 1119 + * 1120 + */ 1121 + #define ENFORCE_RESOURCES_STRICT 2 1122 + #define ENFORCE_RESOURCES_LAX 1 1123 + #define ENFORCE_RESOURCES_NO 0 1124 + 1125 + static unsigned int acpi_enforce_resources = ENFORCE_RESOURCES_LAX; 1126 + 1127 + static int __init acpi_enforce_resources_setup(char *str) 1128 + { 1129 + if (str == NULL || *str == '\0') 1130 + return 0; 1131 + 1132 + if (!strcmp("strict", str)) 1133 + acpi_enforce_resources = ENFORCE_RESOURCES_STRICT; 1134 + else if (!strcmp("lax", str)) 1135 + acpi_enforce_resources = ENFORCE_RESOURCES_LAX; 1136 + else if (!strcmp("no", str)) 1137 + acpi_enforce_resources = ENFORCE_RESOURCES_NO; 1138 + 1139 + return 1; 1140 + } 1141 + 1142 + __setup("acpi_enforce_resources=", acpi_enforce_resources_setup); 1143 + 1144 + /* Check for resource conflicts between ACPI OperationRegions and native 1145 + * drivers */ 1146 + static int acpi_check_resource_conflict(struct resource *res) 1147 + { 1148 + struct acpi_res_list *res_list_elem; 1149 + int ioport; 1150 + int clash = 0; 1151 + 1152 + if (acpi_enforce_resources == ENFORCE_RESOURCES_NO) 1153 + return 0; 1154 + if (!(res->flags & IORESOURCE_IO) && !(res->flags & IORESOURCE_MEM)) 1155 + return 0; 1156 + 1157 + ioport = res->flags & IORESOURCE_IO; 1158 + 1159 + spin_lock(&acpi_res_lock); 1160 + list_for_each_entry(res_list_elem, &resource_list_head, 1161 + resource_list) { 1162 + if (ioport && (res_list_elem->resource_type 1163 + != ACPI_ADR_SPACE_SYSTEM_IO)) 1164 + continue; 1165 + if (!ioport && (res_list_elem->resource_type 1166 + != ACPI_ADR_SPACE_SYSTEM_MEMORY)) 1167 + continue; 1168 + 1169 + if (res->end < res_list_elem->start 1170 + || res_list_elem->end < res->start) 1171 + continue; 1172 + clash = 1; 1173 + break; 1174 + } 1175 + spin_unlock(&acpi_res_lock); 1176 + 1177 + if (clash) { 1178 + if (acpi_enforce_resources != ENFORCE_RESOURCES_NO) { 1179 + printk(KERN_INFO "%sACPI: %s resource %s [0x%llx-0x%llx]" 1180 + " conflicts with ACPI region %s" 1181 + " [0x%llx-0x%llx]\n", 1182 + acpi_enforce_resources == ENFORCE_RESOURCES_LAX 1183 + ? KERN_WARNING : KERN_ERR, 1184 + ioport ? "I/O" : "Memory", res->name, 1185 + (long long) res->start, (long long) res->end, 1186 + res_list_elem->name, 1187 + (long long) res_list_elem->start, 1188 + (long long) res_list_elem->end); 1189 + printk(KERN_INFO "ACPI: Device needs an ACPI driver\n"); 1190 + } 1191 + if (acpi_enforce_resources == ENFORCE_RESOURCES_STRICT) 1192 + return -EBUSY; 1193 + } 1194 + return 0; 1195 + } 1196 + 1197 + int acpi_check_region(resource_size_t start, resource_size_t n, 1198 + const char *name) 1199 + { 1200 + struct resource res = { 1201 + .start = start, 1202 + .end = start + n - 1, 1203 + .name = name, 1204 + .flags = IORESOURCE_IO, 1205 + }; 1206 + 1207 + return acpi_check_resource_conflict(&res); 1208 + } 1209 + EXPORT_SYMBOL(acpi_check_region); 1210 + 1211 + int acpi_check_mem_region(resource_size_t start, resource_size_t n, 1212 + const char *name) 1213 + { 1214 + struct resource res = { 1215 + .start = start, 1216 + .end = start + n - 1, 1217 + .name = name, 1218 + .flags = IORESOURCE_MEM, 1219 + }; 1220 + 1221 + return acpi_check_resource_conflict(&res); 1222 + 1223 + } 1224 + EXPORT_SYMBOL(acpi_check_mem_region); 1225 + 1119 1226 /* 1120 1227 * Acquire a spinlock. 1121 1228 * ··· 1438 1303 acpi_os_validate_address ( 1439 1304 u8 space_id, 1440 1305 acpi_physical_address address, 1441 - acpi_size length) 1306 + acpi_size length, 1307 + char *name) 1442 1308 { 1309 + struct acpi_res_list *res; 1310 + if (acpi_enforce_resources == ENFORCE_RESOURCES_NO) 1311 + return AE_OK; 1443 1312 1444 - return AE_OK; 1313 + switch (space_id) { 1314 + case ACPI_ADR_SPACE_SYSTEM_IO: 1315 + case ACPI_ADR_SPACE_SYSTEM_MEMORY: 1316 + /* Only interference checks against SystemIO and SytemMemory 1317 + are needed */ 1318 + res = kzalloc(sizeof(struct acpi_res_list), GFP_KERNEL); 1319 + if (!res) 1320 + return AE_OK; 1321 + /* ACPI names are fixed to 4 bytes, still better use strlcpy */ 1322 + strlcpy(res->name, name, 5); 1323 + res->start = address; 1324 + res->end = address + length - 1; 1325 + res->resource_type = space_id; 1326 + spin_lock(&acpi_res_lock); 1327 + list_add(&res->resource_list, &resource_list_head); 1328 + spin_unlock(&acpi_res_lock); 1329 + pr_debug("Added %s resource: start: 0x%llx, end: 0x%llx, " 1330 + "name: %s\n", (space_id == ACPI_ADR_SPACE_SYSTEM_IO) 1331 + ? "SystemIO" : "System Memory", 1332 + (unsigned long long)res->start, 1333 + (unsigned long long)res->end, 1334 + res->name); 1335 + break; 1336 + case ACPI_ADR_SPACE_PCI_CONFIG: 1337 + case ACPI_ADR_SPACE_EC: 1338 + case ACPI_ADR_SPACE_SMBUS: 1339 + case ACPI_ADR_SPACE_CMOS: 1340 + case ACPI_ADR_SPACE_PCI_BAR_TARGET: 1341 + case ACPI_ADR_SPACE_DATA_TABLE: 1342 + case ACPI_ADR_SPACE_FIXED_HARDWARE: 1343 + break; 1344 + } 1345 + return AE_OK; 1445 1346 } 1446 1347 1447 1348 #endif
+2 -2
include/acpi/acpiosxf.h
··· 239 239 acpi_status acpi_osi_invalidate(char* interface); 240 240 241 241 acpi_status 242 - acpi_os_validate_address(u8 space_id, 243 - acpi_physical_address address, acpi_size length); 242 + acpi_os_validate_address(u8 space_id, acpi_physical_address address, 243 + acpi_size length, char *name); 244 244 245 245 u64 acpi_os_get_timer(void); 246 246
+17
include/linux/acpi.h
··· 217 217 #define PXM_INVAL (-1) 218 218 #define NID_INVAL (-1) 219 219 220 + int acpi_check_region(resource_size_t start, resource_size_t n, 221 + const char *name); 222 + int acpi_check_mem_region(resource_size_t start, resource_size_t n, 223 + const char *name); 224 + 220 225 #else /* CONFIG_ACPI */ 221 226 222 227 static inline int acpi_boot_init(void) ··· 230 225 } 231 226 232 227 static inline int acpi_boot_table_init(void) 228 + { 229 + return 0; 230 + } 231 + 232 + static inline int acpi_check_region(resource_size_t start, resource_size_t n, 233 + const char *name) 234 + { 235 + return 0; 236 + } 237 + 238 + static inline int acpi_check_mem_region(resource_size_t start, 239 + resource_size_t n, const char *name) 233 240 { 234 241 return 0; 235 242 }