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