1 /* 2 * osi.c - _OSI implementation 3 * 4 * Copyright (C) 2016 Intel Corporation 5 * Author: Lv Zheng <lv.zheng@intel.com> 6 * 7 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 8 * 9 * This program is free software; you can redistribute it and/or modify 10 * it under the terms of the GNU General Public License as published by 11 * the Free Software Foundation; either version 2 of the License, or (at 12 * your option) any later version. 13 * 14 * This program is distributed in the hope that it will be useful, but 15 * WITHOUT ANY WARRANTY; without even the implied warranty of 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17 * General Public License for more details. 18 * 19 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 20 */ 21 22 /* Uncomment next line to get verbose printout */ 23 /* #define DEBUG */ 24 #define pr_fmt(fmt) "ACPI: " fmt 25 26 #include <linux/module.h> 27 #include <linux/kernel.h> 28 #include <linux/acpi.h> 29 #include <linux/dmi.h> 30 #include <linux/platform_data/x86/apple.h> 31 32 #include "internal.h" 33 34 35 #define OSI_STRING_LENGTH_MAX 64 36 #define OSI_STRING_ENTRIES_MAX 16 37 38 struct acpi_osi_entry { 39 char string[OSI_STRING_LENGTH_MAX]; 40 bool enable; 41 }; 42 43 static struct acpi_osi_config { 44 u8 default_disabling; 45 unsigned int linux_enable:1; 46 unsigned int linux_dmi:1; 47 unsigned int linux_cmdline:1; 48 unsigned int darwin_enable:1; 49 unsigned int darwin_dmi:1; 50 unsigned int darwin_cmdline:1; 51 } osi_config; 52 53 static struct acpi_osi_config osi_config; 54 static struct acpi_osi_entry 55 osi_setup_entries[OSI_STRING_ENTRIES_MAX] __initdata = { 56 {"Module Device", true}, 57 {"Processor Device", true}, 58 {"3.0 _SCP Extensions", true}, 59 {"Processor Aggregator Device", true}, 60 }; 61 62 static u32 acpi_osi_handler(acpi_string interface, u32 supported) 63 { 64 if (!strcmp("Linux", interface)) { 65 pr_notice_once(FW_BUG 66 "BIOS _OSI(Linux) query %s%s\n", 67 osi_config.linux_enable ? "honored" : "ignored", 68 osi_config.linux_cmdline ? " via cmdline" : 69 osi_config.linux_dmi ? " via DMI" : ""); 70 } 71 if (!strcmp("Darwin", interface)) { 72 pr_notice_once( 73 "BIOS _OSI(Darwin) query %s%s\n", 74 osi_config.darwin_enable ? "honored" : "ignored", 75 osi_config.darwin_cmdline ? " via cmdline" : 76 osi_config.darwin_dmi ? " via DMI" : ""); 77 } 78 79 return supported; 80 } 81 82 void __init acpi_osi_setup(char *str) 83 { 84 struct acpi_osi_entry *osi; 85 bool enable = true; 86 int i; 87 88 if (!acpi_gbl_create_osi_method) 89 return; 90 91 if (str == NULL || *str == '\0') { 92 pr_info("_OSI method disabled\n"); 93 acpi_gbl_create_osi_method = FALSE; 94 return; 95 } 96 97 if (*str == '!') { 98 str++; 99 if (*str == '\0') { 100 /* Do not override acpi_osi=!* */ 101 if (!osi_config.default_disabling) 102 osi_config.default_disabling = 103 ACPI_DISABLE_ALL_VENDOR_STRINGS; 104 return; 105 } else if (*str == '*') { 106 osi_config.default_disabling = ACPI_DISABLE_ALL_STRINGS; 107 for (i = 0; i < OSI_STRING_ENTRIES_MAX; i++) { 108 osi = &osi_setup_entries[i]; 109 osi->enable = false; 110 } 111 return; 112 } else if (*str == '!') { 113 osi_config.default_disabling = 0; 114 return; 115 } 116 enable = false; 117 } 118 119 for (i = 0; i < OSI_STRING_ENTRIES_MAX; i++) { 120 osi = &osi_setup_entries[i]; 121 if (!strcmp(osi->string, str)) { 122 osi->enable = enable; 123 break; 124 } else if (osi->string[0] == '\0') { 125 osi->enable = enable; 126 strncpy(osi->string, str, OSI_STRING_LENGTH_MAX); 127 break; 128 } 129 } 130 } 131 132 static void __init __acpi_osi_setup_darwin(bool enable) 133 { 134 osi_config.darwin_enable = !!enable; 135 if (enable) { 136 acpi_osi_setup("!"); 137 acpi_osi_setup("Darwin"); 138 } else { 139 acpi_osi_setup("!!"); 140 acpi_osi_setup("!Darwin"); 141 } 142 } 143 144 static void __init acpi_osi_setup_darwin(bool enable) 145 { 146 /* Override acpi_osi_dmi_blacklisted() */ 147 osi_config.darwin_dmi = 0; 148 osi_config.darwin_cmdline = 1; 149 __acpi_osi_setup_darwin(enable); 150 } 151 152 /* 153 * The story of _OSI(Linux) 154 * 155 * From pre-history through Linux-2.6.22, Linux responded TRUE upon a BIOS 156 * OSI(Linux) query. 157 * 158 * Unfortunately, reference BIOS writers got wind of this and put 159 * OSI(Linux) in their example code, quickly exposing this string as 160 * ill-conceived and opening the door to an un-bounded number of BIOS 161 * incompatibilities. 162 * 163 * For example, OSI(Linux) was used on resume to re-POST a video card on 164 * one system, because Linux at that time could not do a speedy restore in 165 * its native driver. But then upon gaining quick native restore 166 * capability, Linux has no way to tell the BIOS to skip the time-consuming 167 * POST -- putting Linux at a permanent performance disadvantage. On 168 * another system, the BIOS writer used OSI(Linux) to infer native OS 169 * support for IPMI! On other systems, OSI(Linux) simply got in the way of 170 * Linux claiming to be compatible with other operating systems, exposing 171 * BIOS issues such as skipped device initialization. 172 * 173 * So "Linux" turned out to be a really poor chose of OSI string, and from 174 * Linux-2.6.23 onward we respond FALSE. 175 * 176 * BIOS writers should NOT query _OSI(Linux) on future systems. Linux will 177 * complain on the console when it sees it, and return FALSE. To get Linux 178 * to return TRUE for your system will require a kernel source update to 179 * add a DMI entry, or boot with "acpi_osi=Linux" 180 */ 181 static void __init __acpi_osi_setup_linux(bool enable) 182 { 183 osi_config.linux_enable = !!enable; 184 if (enable) 185 acpi_osi_setup("Linux"); 186 else 187 acpi_osi_setup("!Linux"); 188 } 189 190 static void __init acpi_osi_setup_linux(bool enable) 191 { 192 /* Override acpi_osi_dmi_blacklisted() */ 193 osi_config.linux_dmi = 0; 194 osi_config.linux_cmdline = 1; 195 __acpi_osi_setup_linux(enable); 196 } 197 198 /* 199 * Modify the list of "OS Interfaces" reported to BIOS via _OSI 200 * 201 * empty string disables _OSI 202 * string starting with '!' disables that string 203 * otherwise string is added to list, augmenting built-in strings 204 */ 205 static void __init acpi_osi_setup_late(void) 206 { 207 struct acpi_osi_entry *osi; 208 char *str; 209 int i; 210 acpi_status status; 211 212 if (osi_config.default_disabling) { 213 status = acpi_update_interfaces(osi_config.default_disabling); 214 if (ACPI_SUCCESS(status)) 215 pr_info("Disabled all _OSI OS vendors%s\n", 216 osi_config.default_disabling == 217 ACPI_DISABLE_ALL_STRINGS ? 218 " and feature groups" : ""); 219 } 220 221 for (i = 0; i < OSI_STRING_ENTRIES_MAX; i++) { 222 osi = &osi_setup_entries[i]; 223 str = osi->string; 224 if (*str == '\0') 225 break; 226 if (osi->enable) { 227 status = acpi_install_interface(str); 228 if (ACPI_SUCCESS(status)) 229 pr_info("Added _OSI(%s)\n", str); 230 } else { 231 status = acpi_remove_interface(str); 232 if (ACPI_SUCCESS(status)) 233 pr_info("Deleted _OSI(%s)\n", str); 234 } 235 } 236 } 237 238 static int __init osi_setup(char *str) 239 { 240 if (str && !strcmp("Linux", str)) 241 acpi_osi_setup_linux(true); 242 else if (str && !strcmp("!Linux", str)) 243 acpi_osi_setup_linux(false); 244 else if (str && !strcmp("Darwin", str)) 245 acpi_osi_setup_darwin(true); 246 else if (str && !strcmp("!Darwin", str)) 247 acpi_osi_setup_darwin(false); 248 else 249 acpi_osi_setup(str); 250 251 return 1; 252 } 253 __setup("acpi_osi=", osi_setup); 254 255 bool acpi_osi_is_win8(void) 256 { 257 return acpi_gbl_osi_data >= ACPI_OSI_WIN_8; 258 } 259 EXPORT_SYMBOL(acpi_osi_is_win8); 260 261 static void __init acpi_osi_dmi_darwin(void) 262 { 263 pr_notice("DMI detected to setup _OSI(\"Darwin\"): Apple hardware\n"); 264 osi_config.darwin_dmi = 1; 265 __acpi_osi_setup_darwin(true); 266 } 267 268 static void __init acpi_osi_dmi_linux(bool enable, 269 const struct dmi_system_id *d) 270 { 271 pr_notice("DMI detected to setup _OSI(\"Linux\"): %s\n", d->ident); 272 osi_config.linux_dmi = 1; 273 __acpi_osi_setup_linux(enable); 274 } 275 276 static int __init dmi_enable_osi_linux(const struct dmi_system_id *d) 277 { 278 acpi_osi_dmi_linux(true, d); 279 280 return 0; 281 } 282 283 static int __init dmi_disable_osi_vista(const struct dmi_system_id *d) 284 { 285 pr_notice("DMI detected: %s\n", d->ident); 286 acpi_osi_setup("!Windows 2006"); 287 acpi_osi_setup("!Windows 2006 SP1"); 288 acpi_osi_setup("!Windows 2006 SP2"); 289 290 return 0; 291 } 292 293 static int __init dmi_disable_osi_win7(const struct dmi_system_id *d) 294 { 295 pr_notice("DMI detected: %s\n", d->ident); 296 acpi_osi_setup("!Windows 2009"); 297 298 return 0; 299 } 300 301 static int __init dmi_disable_osi_win8(const struct dmi_system_id *d) 302 { 303 pr_notice("DMI detected: %s\n", d->ident); 304 acpi_osi_setup("!Windows 2012"); 305 306 return 0; 307 } 308 309 /* 310 * Linux default _OSI response behavior is determined by this DMI table. 311 * 312 * Note that _OSI("Linux")/_OSI("Darwin") determined here can be overridden 313 * by acpi_osi=!Linux/acpi_osi=!Darwin command line options. 314 */ 315 static const struct dmi_system_id acpi_osi_dmi_table[] __initconst = { 316 { 317 .callback = dmi_disable_osi_vista, 318 .ident = "Fujitsu Siemens", 319 .matches = { 320 DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"), 321 DMI_MATCH(DMI_PRODUCT_NAME, "ESPRIMO Mobile V5505"), 322 }, 323 }, 324 { 325 /* 326 * There have a NVIF method in MSI GX723 DSDT need call by Nvidia 327 * driver (e.g. nouveau) when user press brightness hotkey. 328 * Currently, nouveau driver didn't do the job and it causes there 329 * have a infinite while loop in DSDT when user press hotkey. 330 * We add MSI GX723's dmi information to this table for workaround 331 * this issue. 332 * Will remove MSI GX723 from the table after nouveau grows support. 333 */ 334 .callback = dmi_disable_osi_vista, 335 .ident = "MSI GX723", 336 .matches = { 337 DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International"), 338 DMI_MATCH(DMI_PRODUCT_NAME, "GX723"), 339 }, 340 }, 341 { 342 .callback = dmi_disable_osi_vista, 343 .ident = "Sony VGN-NS10J_S", 344 .matches = { 345 DMI_MATCH(DMI_SYS_VENDOR, "Sony Corporation"), 346 DMI_MATCH(DMI_PRODUCT_NAME, "VGN-NS10J_S"), 347 }, 348 }, 349 { 350 .callback = dmi_disable_osi_vista, 351 .ident = "Sony VGN-SR290J", 352 .matches = { 353 DMI_MATCH(DMI_SYS_VENDOR, "Sony Corporation"), 354 DMI_MATCH(DMI_PRODUCT_NAME, "VGN-SR290J"), 355 }, 356 }, 357 { 358 .callback = dmi_disable_osi_vista, 359 .ident = "VGN-NS50B_L", 360 .matches = { 361 DMI_MATCH(DMI_SYS_VENDOR, "Sony Corporation"), 362 DMI_MATCH(DMI_PRODUCT_NAME, "VGN-NS50B_L"), 363 }, 364 }, 365 { 366 .callback = dmi_disable_osi_vista, 367 .ident = "VGN-SR19XN", 368 .matches = { 369 DMI_MATCH(DMI_SYS_VENDOR, "Sony Corporation"), 370 DMI_MATCH(DMI_PRODUCT_NAME, "VGN-SR19XN"), 371 }, 372 }, 373 { 374 .callback = dmi_disable_osi_vista, 375 .ident = "Toshiba Satellite L355", 376 .matches = { 377 DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"), 378 DMI_MATCH(DMI_PRODUCT_VERSION, "Satellite L355"), 379 }, 380 }, 381 { 382 .callback = dmi_disable_osi_win7, 383 .ident = "ASUS K50IJ", 384 .matches = { 385 DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK Computer Inc."), 386 DMI_MATCH(DMI_PRODUCT_NAME, "K50IJ"), 387 }, 388 }, 389 { 390 .callback = dmi_disable_osi_vista, 391 .ident = "Toshiba P305D", 392 .matches = { 393 DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"), 394 DMI_MATCH(DMI_PRODUCT_NAME, "Satellite P305D"), 395 }, 396 }, 397 { 398 .callback = dmi_disable_osi_vista, 399 .ident = "Toshiba NB100", 400 .matches = { 401 DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"), 402 DMI_MATCH(DMI_PRODUCT_NAME, "NB100"), 403 }, 404 }, 405 406 /* 407 * The wireless hotkey does not work on those machines when 408 * returning true for _OSI("Windows 2012") 409 */ 410 { 411 .callback = dmi_disable_osi_win8, 412 .ident = "Dell Inspiron 7737", 413 .matches = { 414 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 415 DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 7737"), 416 }, 417 }, 418 { 419 .callback = dmi_disable_osi_win8, 420 .ident = "Dell Inspiron 7537", 421 .matches = { 422 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 423 DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 7537"), 424 }, 425 }, 426 { 427 .callback = dmi_disable_osi_win8, 428 .ident = "Dell Inspiron 5437", 429 .matches = { 430 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 431 DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 5437"), 432 }, 433 }, 434 { 435 .callback = dmi_disable_osi_win8, 436 .ident = "Dell Inspiron 3437", 437 .matches = { 438 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 439 DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 3437"), 440 }, 441 }, 442 { 443 .callback = dmi_disable_osi_win8, 444 .ident = "Dell Vostro 3446", 445 .matches = { 446 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 447 DMI_MATCH(DMI_PRODUCT_NAME, "Vostro 3446"), 448 }, 449 }, 450 { 451 .callback = dmi_disable_osi_win8, 452 .ident = "Dell Vostro 3546", 453 .matches = { 454 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 455 DMI_MATCH(DMI_PRODUCT_NAME, "Vostro 3546"), 456 }, 457 }, 458 459 /* 460 * BIOS invocation of _OSI(Linux) is almost always a BIOS bug. 461 * Linux ignores it, except for the machines enumerated below. 462 */ 463 464 /* 465 * Without this this EEEpc exports a non working WMI interface, with 466 * this it exports a working "good old" eeepc_laptop interface, fixing 467 * both brightness control, and rfkill not working. 468 */ 469 { 470 .callback = dmi_enable_osi_linux, 471 .ident = "Asus EEE PC 1015PX", 472 .matches = { 473 DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK Computer INC."), 474 DMI_MATCH(DMI_PRODUCT_NAME, "1015PX"), 475 }, 476 }, 477 {} 478 }; 479 480 static __init void acpi_osi_dmi_blacklisted(void) 481 { 482 dmi_check_system(acpi_osi_dmi_table); 483 484 /* Enable _OSI("Darwin") for Apple platforms. */ 485 if (x86_apple_machine) 486 acpi_osi_dmi_darwin(); 487 } 488 489 int __init early_acpi_osi_init(void) 490 { 491 acpi_osi_dmi_blacklisted(); 492 493 return 0; 494 } 495 496 int __init acpi_osi_init(void) 497 { 498 acpi_install_interface_handler(acpi_osi_handler); 499 acpi_osi_setup_late(); 500 501 return 0; 502 } 503